Глава 7
Поддержка unit-тестинга

7.1 Unit-тестинг для исполняемых двоичных приложений

7.1.1 Определение unit-test приложения

Unit-test исполняемым двоичным приложением называется приложение, в котором находится код unit-тестов и которое необходимо запустить для прохождения unit-тестов. Успешность прохождения unit-тестов определяется по коду завершения приложения. Если приложение возвращает 0, то unit-тесты считаются успешно пройдеными.

7.1.2 Идея

Для поддержки unit-test необходимо создать два проектных файла: один из них отвечает за построение unit-test приложения, второй отвечает за его запуск и анализ кода возврата.

Предполагается, что для проекта существует композитный проектный файл, в котором указываются все проекты. Среди этих проектов так же указываются проекты unit-test-ов. В результате получается, что в процессе построения композита автоматически запускается построение unit-test приложения, после чего unit-test запускается. Если unit-test отрабатывает нормально, то процесс построения композита продолжается. В противном случае построение композита завершается1.

7.1.3 Класс для цели unit-test

В состав Mxx_ru входит класс Mxx_ru::Binary_unittest_target, экземпляр которого нужно создать для того, чтобы включить unit-test в процесс построения проекта. Для использования этого класса необходимо включить файл mxx_ru/binary_unittest:


1  require ’mxx_ru/binary_unittest’
2  
3  Mxx_ru::setup_target(
4   Mxx_ru::Binary_unittest_target.new(
5   "some/project/prj.ut.rb",
6   "some/project/prj.rb" )
7  )

Данный класс получает все параметры в своем конструкторе: собственный псевдоним и имя проекта, который отвечает за построение unit-test приложения.

7.1.4 Пример

Проектный файл unit-test приложения:


1  require ’mxx_ru/cpp’
2  
3  Mxx_ru::setup_target(
4   Mxx_ru::Cpp::Exe_target.new( "test/active_group/prj.rb" ) {
5  
6   required_prj( "threads_1/dll.rb" )
7   required_prj( "so_4/prj.rb" )
8  
9   target_root( "unittest" )
10   target( "test.active_group" )
11  
12   cpp_source( "main.cpp" )
13   }
14  )
15  

Проектный файл для unit-test:


1  require ’mxx_ru/binary_unittest’
2  
3  Mxx_ru::setup_target(
4   Mxx_ru::Binary_unittest_target.new(
5   "test/active_group/prj.ut.rb",
6   "test/active_group/prj.rb" )
7  )

Общий композитный проектный файл:


1  require ’mxx_ru/cpp’
2  
3  Mxx_ru::setup_target(
4   Mxx_ru::Cpp::Composite_target.new( Mxx_ru::BUILD_ROOT ) {
5   global_include_path( "." )
6  
7   required_prj( "so_4/prj.rb" )
8   ...
9   required_prj( "test/active_group/prj.ut.rb" )
10   ...
11   }
12  )

Запускается композитный проект как обычно:


  ruby build.rb

Но в процессе построения проекта будет выдано сообщение:


  running unit test: unittest/test.active_group.exe...

Если же unit-тесты не будут пройдены успешно, то результат построения проекта может выглядеть, например, так:


  main.cpp
  running unit test: unittest/test.active_group.exe...
  thread group #0: a_receiever_1 a_receiever_1::a_1 a_receiever_2::a_1
  thread group #1: a_receiever_2
  [2004.09.24 16:58:29.669152] so_4/ret_code.cpp:40:\
  test/active_group/main.cpp:347: 10000 [Invalid threads groups]
  
  
  unit test ’unittest/test.active_group.exe’ FAILED! 256
  <<<[Mxx_ru::Build_ex] Build error: ’unittest/test.active_group.exe’\
   returns ’256’>>>

7.2 Unit-тестинг в виде сравнения текстовых файлов

В некоторых случая сложно создать двоичное unit-test приложение которое полностью контролирует прохождение тестов и возвращает нулевой код возврата в случае успеха (именно на такие unit-test приложения расчитан класс Mxx_ru::Binary_unittest_target, описанный в 7.1 на стр.  84). Например, таким способом сложно тестировать библиотеки разбора каких-либо входных файлов. В таких случаях удобно создать одно тестовое приложение, которое в командной строке получает имя входного файла в качестве аргумента и помещает результат своей работы в выходной файл. Тогда для каждого из входных файлов можно подготовить эталонный файл результата. И тестирование библиотеки будет заключаться в последовательном запуске тестового приложения с каждым из подготовленных тестовых входных данных с последующим сравнением произведенных выходных файлов с эталонными файлами.

Такой сценарий тестирования можно применять не только в случаях библиотек разбора чего-либо. Но и в случаях, когда результат работы тестируемого кода зависит от входящих данных. И при этом желательно, чтобы была простая процедура добавления и/или замены входных данных, без изменения исходного кода unit-test приложения. Например, для библиотек, выполняющих какие-либо расчеты, шифрование/дешифрование, вычисление/проверку криптографической подписи и т.д. Такое тестирование можно применять даже в случае, когда корректность работы кода проверяется по отладочным печатям, которые тестируемый код осуществляет в log-файл (например, так можно проверять, правильные ли ветви вычислений выбираются в том или ином случае).

Для поддержки такого типа тестирования Mxx_ru предоставляет класс Mxx_ru::Textfile_unittest_target. Идея его использования аналогична идеи использования класса Mxx_ru::Binary_unittest_target2: необходимо создать два проектных файла, первый из которых будет управлять построением unit-test приложения, а второй — запуском тестового приложения с различными параметрами и сравнением результатов работы для каждого тестового случая.

7.2.1 Класс Mxx_ru::Textfile_unittest_target

Класс Mxx_ru::Textfile_unitest_target предназначен для описания целей, которые запускают одно unit-test приложение с разными параметрами и сравнивают результаты работы после каждого запуска с эталонными результатами. Для использования этого класса необходимо подключить в проект файл mxx_ru/textfile_unittest:


1  require ’mxx_ru/textfile_unittest’
2  
3  Mxx_ru::setup_target(
4   Mxx_ru::Textfile_unittest_target.new(
5   "some/project/prj.ut.rb",
6   "some/project/prj.rb" ) {
7  
8   # Описание последовательности запуска тестов.
9   ...
10   }
11  )

Каждый запуск тестового приложения описывается с помощь метода launch. Первым параметром этого метода является строка с параметрами для тестового приложения. Вторым аргументом является вектор описаний подлежащих сравнению файлов. Т.е. допускаются случаи, когда приложение производит более одного выходного файла.

В своем методе build класс Mxx_ru::Textfile_unittest_target сначала выполняет построение тестового приложения. Если тестовое приложение построено успешно, то последовательно выполняются все описанные запуски тестового приложения. Если очередной запуск прошел успешно, т.е. приложение возвратило нулевой код, то производится последовательное сравнение всех описанных пар файлов. Если все пары файлов совпадают, то выполняется следующий запуск приложения и т.д.

7.2.2 Пример

Проектный файл для unit-test:


1  require ’mxx_ru/textfile_unittest’
2  
3  Mxx_ru::setup_target(
4   Mxx_ru::Textfile_unittest_target.new(
5   "prj.ut.rb",
6   "prj.rb" ) {
7  
8   launch( "out_0.txt 0",
9   [ pair( "out_0.txt", "etalons/out_0.txt" ) ] )
10  
11   launch( "out_1.txt 1",
12   [ pair( "out_1.txt", "etalons/out_1.txt" ) ] )
13  
14   launch( "out_128.txt 128",
15   [ pair( "out_0.txt", "etalons/out_0.txt" ),
16   pair( "out_1.txt", "etalons/out_1.txt" ),
17   pair( "out_128.txt", "etalons/out_128.txt" ) ] )
18   }
19  )

Результат запуска unit-test:


  $ ruby prj.ut.rb
  running unit test: ./test.exe...
   launching ’./test.exe out_0.txt 0’...
   comparing ’out_0.txt’ and ’etalons/out_0.txt’
   launching ’./test.exe out_1.txt 1’...
   comparing ’out_1.txt’ and ’etalons/out_1.txt’
   launching ’./test.exe out_128.txt 128’...
   comparing ’out_0.txt’ and ’etalons/out_0.txt’
   comparing ’out_1.txt’ and ’etalons/out_1.txt’
   comparing ’out_128.txt’ and ’etalons/out_128.txt’

Если при сравнении файлов обнаруживаются несовпадения, то результат запуска unit-test может выглядеть, например, так:


  $ ruby prj.ut.rb
  running unit test: ./test.exe...
   launching ’./test.exe out_0.txt 0’...
   comparing ’out_0.txt’ and ’etalons/out_0.txt’
   launching ’./test.exe out_1.txt 1’...
   comparing ’out_1.txt’ and ’etalons/out_1.txt’
   launching ’./test.exe out_128.txt 128’...
   comparing ’out_0.txt’ and ’etalons/out_0.txt’
   comparing ’out_1.txt’ and ’etalons/out_1.txt’
   comparing ’out_128.txt’ and ’etalons/out_128.txt’
  <<<[Mxx_ru::Build_ex] Build error: ’./test.exe out_128.txt 128’ \
  returns ’Error during comparing files ’out_128.txt’, ’etalons/out_128.txt’:\
   Build error: ’./test.exe out_128.txt 128’ returns ’Mismatch found in \
  line 120. Line in ’out_128.txt’ is ’Hello, World!
  ’. Line in ’etalons/out_128.txt’ is ’Hello, world
  ’’’>>>

7.2.3 Особенности

Класс Mxx_ru::Textfile_unittest_target выполняет простое построчное сравнение файлов с учетом регистра символов. Сравнение прерывается при обнаружении первого несовпадения.

В методе clean класс Mxx_ru::Textfile_unittest_target удалят файлы, которые указаны как выходные файлы (т.е. файлы, имена которых задаются первым параметром при обращении к методу pair, удаляются при указании интерпретатору Ruby агрумента --mxx-clean).

Hosted by uCoz