Нагрузочное тестирование базы данных. ContiPerf + DBUnit

в 6:50, , рубрики: dbunit, java, junit, базы данных, тестирование

Ниже представлен опыт нагрузочного тестирования базы данных с использованием 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.
image
Для решения данной задачи вначале была попытка использовать инструмент для тестирования 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js