Разработка
Некоторое время назад я задался вопросом, как организовать корректное тестирование серверной части своего приложения. Входные данные для решения этой задачи были следующими. Приложение работает на основе БД postgres 8.4, создания скелета приложения используется Spring 3, приложение собирается при помощи maven 2.1. Для тестирования была выбрана связка JUnit и DBUnit.
Можно спорить об удобстве тех или иных инструментов. Просто, мне показалось, что проще реализовать именно связку JUnit и DBUnit.
Разобъём задачу на шаги. Перед тем, как выполнить все тесты, мы должны быть уверены, что схема БД соответствует структуре объектов приложения. Для решения этой задачи перед тестированием мы будем обновлять схему БД при помощи hibernate3-maven-plugin. Здесь надо брать во внимание, что при обновлении схемы могут возникнуть сложности, если одно из свойств объекта изменило тип или параметры (длина и прочее). Перед каждым тестом мы будем наполнять БД данными, а после тестов таблицы будем очищать. Все тесты мы будем конфигурировать с классами проекта через spring.
В данном проекте используются доступные в публичных репозиториях библиотеки, а архив с исходным кодо проекта приложен в самом низу. Скопировав к себе архив и запустив команду mvn test, вы должны увидеть следующие результаты выполнения:
Running ru.gubber.hbnsprgtest.controller.EntityControllerLoaddAllTest
Hibernate: select this_.ID as ID0_0_, this_.ENTITY_VALUE as ENTITY2_0_0_, this_.ENTITY_DECRIPTION as ENTITY3_0_0_, this_.ENTITY_NAME as ENTITY4_0_0_ from TEST_ENTITY_TBL this_
entity = TestEntity{id=1, name='entity_one', count=1}
entity = TestEntity{id=2, name='entity_two', count=2}
entity = TestEntity{id=3, name='entity_three', count=3}
tearDown
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.36 sec
Running ru.gubber.hbnsprgtest.controller.EntityControllerFindByNameTest
Hibernate: select testentity0_.ID as ID0_, testentity0_.ENTITY_VALUE as ENTITY2_0_, testentity0_.ENTITY_DECRIPTION as ENTITY3_0_, testentity0_.ENTITY_NAME as ENTITY4_0_ from TEST_ENTITY_TBL testentity0_ where testentity0_.ENTITY_NAME like ?
entity = TestEntity{id=2, name='entity_two', count=2}
entity = TestEntity{id=3, name='entity_three', count=3}
tearDown
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.24 sec
Results : Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

Справа приведена структура проекта. Папка src/main/java - содержит классы проекта. В данном случае проект совсем маленький. Но для тестового проекта этого будет достаточно. Папка src/main/resources - пуста, т.к. это иммитация сервной части, которая потом внедряется в основной проект, где уже будут указаны все настройки проекта.
Папка src/test/java - лежат классы тестов, серверной части всего проекта.
Папка src/test/resources - содержит файлы, необходимые для конфигурации тестового приложения, а так же "идеальные" данные для теста.
Первое на что я нарвался, когда занялся этим вопросом, так это то, что DBUnit отказывался подключаться к БД Postgres. Эта ошибка возникала при использовании нескольких версий 2.4.х, но в версии 2.4.7 эта ошибка не возникает. Так что надо иметь это в виду, если вы используете эту СУБД.
Дальше, рассмотрим структуру базового класса тестирования.
public abstract class AbstractTest extends TestCase {
@Autowired
private HibernateTemplate hibernateTemplate;
private FileInputStream is;
private IDatabaseConnection conn;
private IDataSet dataSet;
private static boolean setuped = false;
private Transaction tr;
public void before() throws Exception {
Locale.setDefault(new Locale("en", "EN"));
if (!setuped)
try {
hibernateTemplate.execute(new HibernateCallback<Session>() {
@Override
public Session doInHibernate(Session session) throws HibernateException, SQLException {
tr = session.beginTransaction();
try {
is = new FileInputStream("./src/test/resources/dbunit-test-data/MyEntityDaoImpl.xml");
conn = new DatabaseConnection(session.connection());
dataSet = new FlatXmlDataSet(is);
DatabaseOperation.INSERT.execute(conn, dataSet);
tr.commit();
tr = session.beginTransaction();
} catch (DatabaseUnitException e) {
e.printStackTrace();
tr.rollback();
} catch (IOException e) {
e.printStackTrace();
tr.rollback();
} catch (SQLException e) {
e.printStackTrace();
tr.rollback();
}
return null;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
System.out.println("tearDown");
try {
hibernateTemplate.execute(new HibernateCallback<Session>() {
@Override
public Session doInHibernate(Session session) throws HibernateException, SQLException {
Transaction tr = session.beginTransaction();
try {
is = new FileInputStream("./src/test/resources/dbunit-test-data/MyEntityDaoImpl.xml");
conn = new DatabaseConnection(session.connection());
dataSet = new FlatXmlDataSet(is);
DatabaseOperation.TRUNCATE_TABLE.execute(conn, dataSet);
tr.commit();
} catch (DatabaseUnitException e) {
e.printStackTrace();
tr.rollback();
} catch (IOException e) {
e.printStackTrace();
tr.rollback();
}
return null;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
Здесь стоит обратить внимание, что создаётся один объект для обеспечения одного объекта транзакции на протяжении всего теста, в противном случае возникала ошибка при удалении данных из БД. На строчках 19 и 53 указано из какого файла надо брать данные для наполнения БД "идеальными" данными, на основании которых должны проводиться тесты. В следующем примере приведён пример получения доступа к данным, которые перед выплнением теста были вставлены в БД перед выполнением самого теста.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-testing.xml"})
public class EntityControllerFindByNameTest extends AbstractTest {
@Autowired
private TestEntityController controller;
@Before
@Override
public void before() throws Exception {
super.before();
}
@Test
public void testLoadAll() {
List<TestEntity> entities = controller.findEntitiesByName("ty_t");
for (TestEntity entity : entities) {
System.out.println("entity = " + entity);
}
}
@After
public void after() {
try {
super.tearDown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
На первой строке указан способ тестирования. На второй строчке этого примера указано какой файл необходимо использовать для конфигурации Spring при тестировании через JUnit.
Ниже приведена настройка плагина, который отвечает за обновление БД, перед тем, как начнётся тестирование системы, чтобы синхронизировать схему БД со структурой объектов проекта.
<plugin>
<!-- HIBERNATE 3 PLUGIN - Данный плагин создаёт таблицы в базе данных перед проведением тестов-->
<groupId>org.codehaus.mojo</groupId>
<artifactId>hibernate3-maven-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<phase>process-classes</phase>
<goals>
<goal>hbm2ddl</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>8.4-701.jdbc3</version>
</dependency>
</dependencies>
<configuration>
<components>
<component>
<name>hbm2ddl</name>
<implementation>annotationconfiguration</implementation>
</component>
</components>
<componentProperties>
<configurationfile>src/test/resources/hibernate.cfg.xml</configurationfile>
<propertyfile>src/test/resources/hibernate.properties</propertyfile>
<outputfilename>myschema.ddl</outputfilename>
<export>true</export>
<update>true</update>
<format>true</format>
<scan-classes>true</scan-classes>
</componentProperties>
</configuration>
</plugin>
По нижеприведённой ссылке вы можете скачать исходный код проекта.