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

    Ниже представлен опыт нагрузочного тестирования базы данных с использованием 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.: Комментарии, нарекания, дополнения, возражения, напутствия приветствуются и очень полезны.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 0

    Only users with full accounts can post comments. Log in, please.