Ниже представлен опыт нагрузочного тестирования базы данных с использованием JUnit и ассоциированных с ним DBUnit и ContiPerf.
ContiPerf
ContiPerf — утилита, которая позволяет использовать JUnit4 для нагрузочных тестов. Проста в использовании, легко и разнообразно настраивается. Использует Java аннотации для задания настроек теста и требований выполнения, создает подробный отчет в виде html файла с графиком распределения времени выполнения. Требует использование Java не ниже 5 версии и JUnit не ниже версии 4.7.
DBUnit
DBUnit — расширение для JUnit, упрощающее тестирование программ, работающих с бд. Впрочем, вполне популярен и в представлении не нуждается, поэтому ограничусь ссылками: знакомство для начинающих, упоминания на хабре.
Тест
Для начала создадим файл с параметрами подключения к базе данных. Здесь используются Spring JavaConfig и для нагрузочного тестирования необходим пул соединений, в качестве которого выбран Tomcat JDBC Connection Pool. О последнем и способах его использования можно прочитать здесь и здесь. Пул выбирался по принципу первый с примерами в списке google запроса «connection pool».
@Configuration
@Profile("db1")
public class DBConfig {
@Bean
public javax.sql.DataSource.DataSource dataSource(){
org.apache.tomcat.jdbc.pool.DataSource ds = new org.apache.tomcat.jdbc.pool.DataSource();
ds.setDriverClassName("dbDriver");
ds.setUrl("dbUrl");
ds.setUsername("dbUser");
ds.setPassword("");
ds.setInitialSize(5);
ds.setMaxActive(10);
ds.setMaxIdle(5);
ds.setMinIdle(2);
return ds;
}
}
Далее настроим DBUnit. В общей сложности получится два конфигурационных класса. Первый — JavaConf — служит для описания настроек соединения при желании и к нескольким базам данных и далее позволяет легко подключать нужную бд к тесту. Ниже будет создан класс, при наследовании от которого появится возможность использовать уже настроенный функционал DBUnit, и не нужно будет настраивать его каждый раз. Эти классы можно использовать и при другом виде, не только при нагрузочном тестировании.
@ContextConfiguration(classes = DBConfig.class)
@ActiveProfiles("db1")
public class DBUnitConfig extends DBTestCase{
@Autowired
javax.sql.DataSource dataSource;
protected IDatabaseTester tester;
protected IDataSet beforeData;
private TestContextManager testContextManager;
@Before
public void setUp() throws Exception {
this.testContextManager = new TestContextManager(getClass());
this.testContextManager.prepareTestInstance(this);
tester = new DataSourceDatabaseTester(dataSource);
}
@Override
protected IDataSet getDataSet() throws Exception {
return beforeData;
}
@Override
protected DatabaseOperation getTearDownOperation() throws Exception {
return DatabaseOperation.NONE;
}
}
Здесь используется небольшой трюк. Дело в том, что JUnit позволяет только один раз использовать аннотацию @RunWith, которая будет использована позже, что в свою очередь не позволяет воспользоваться классическим @RunWith(SpringJUnit4ClassRunner.class). Поэтому нужно вручную создавать экземпляр контекста и подтягивать его вот этим участком кода(с):
// танец с бубном
this.testContextManager = new TestContextManager(getClass());
this.testContextManager.prepareTestInstance(this);
Чтобы понять вид используемой здесь магии следует обратиться к документации соответствующего класса.
Наконец, переходим непосредственно к тестовому классу:
// как и обещано RunWith использован, здесь для параллельного выполнения тестовых методов
@RunWith(ParallelRunner.class)
// настройки нагрузочного теста: продолжительность в мс, количество потоков,
// время ожидания между тестами в мс (случайно выбирается из указанного интервала)
@PerfTest(duration = 10000, threads = 50, timer = RandomTimer.class, timerParams = { 5, 15 })
// требования к тесту: максимальное и среднее время выполнения, не является обязательным полем
@Required(max = 500, average = 100)
public class PerfTest extends DBUnitConfig {
// активация ContiPerf
@Rule
public ContiPerfRule i = new ContiPerfRule();
// метод для инициализации данных необходимых для теста
@Before
public void setUp() throws Exception
{
super.setUp();
// тривиальный пример xml файла набора данных: "<dataset></dataset>"
beforeData = new FlatXmlDataSetBuilder().build(
Thread.currentThread().getContextClassLoader()
.getResourceAsStream("initData.xml"));
tester.setDataSet(beforeData);
tester.onSetup();
}
// тестовый метод
@Test
public void test1()
{
Connection sqlConnection = null;
CallableStatement statement = null;
ResultSet set = null;
try {
sqlConnection = tester.getConnection().getConnection();
sqlConnection.setAutoCommit(true);
statement = sqlConnection.prepareCall("select smth from tbl where param=?");
statement.setString(1, "prm");
set = statement.executeQuery(); set.next();
int smth1 = set.getInt(1);
statement.close();
statement = sqlConnection.prepareCall("{ ? = call pkg.function(?) }");
statement.registerOutParameter(1, (Types.INTEGER));
statement.setString(2, "prm");
statement.execute();
int smth2 = statement.getInt(1);
statement.close();
}
catch(Exception ex){
System.out.print(ex.getMessage()); }
finally {
try {
set.close();
statement.close();
sqlConnection.close();}
catch (Exception e){
System.out.print(e.getMessage());}
}
Assert.assertEquals(smth1, smth2);
}
}
В catch, как идея к дополнениям, можно добавить счетчик ошибок и выводить их количество в методе с аннотацией after.
Больше настроек для ContiPerf можно найти на официальной странице: можно, например, в аннотации @PerfTest задавать не длительность теста, а количество его выполнения с помощью параметра invocations.
Краткий отчет для каждого тестового метода пишется в консоли после выполнения, более детальный отчет (пример приведен ниже) с подробной статистикой находится в файле target/contiperf-report/index.html.
Для решения данной задачи вначале была попытка использовать инструмент для тестирования SoapUI и JDBC запросы, однако возникла проблема — частое, методичное выпадение ошибки: «java.sql.SQLException: Listener refused the connection with the following error:ORA-12519, TNS:no appropriate service handler found» — не нашедшая решения. Позже нашлось другое решение с использованием другого инструмента — JMeter, но слишком поздно…
P.S.: Комментарии, нарекания, дополнения, возражения, напутствия приветствуются и очень полезны.
Автор: chochu