воскресенье, ноября 09, 2008

Русский перевод Boost::test документации


Я посмотрел на статистику Google Analytics моего блога и обнаружил, что одними из самых популярных страниц в нём являются статьи про библиотеку Boost (про unit testing и сборник ссылок на русские переводы документации по Boost). Так что раз люди это ищут, они это получат :) Ниже мой перевод куска документации с введением в юнит тестирование при помощи Boost::test.

Hello the testing world. Введение в тестирование с использованием Unit Test Framework.

Как программа должна сообщать об ошибках? Обычный способ - это показ сообщения об ошибке:

if( something_bad_detected )
std::cout << "something bad has been detected"
<< std::endl;
Однако, чтобы узнать, случилась ли ошибка, такой способ требует проверки вывода программы после каждого запуска. Поскольку юнит-тестовые программы запускаются обычно часто (как часть процесса регрессионного тестирования), то ручная проверка на наличие сообщений об ошибках - это слишком неэффективное решение. Фреймворки для тестирования, такие как GNU/expect, могут делать эти проверки автоматически, но они слишком сложны для простых тестов.


Лучше и проще для тестирующей программы использовать следующий метод сообщения об ошибках - возвращать EXIT_SUCCESS (как правило 0) в случае отсутствия ошибки и EXIT_FAILURE в случае её обнаружения. Это позволит скрипту для регрессионных тестов автоматически определять успех/провал теста. В дальнейшем скрипт может создать HTML таблицу с результатами тестов, послать email или сделать что-то ещё без необходимости изменять сам C++ код юнит тестов.


Протокол тестирования, основанный на этом подходе, не требует для своей реализации  никаких дополнительных средств, библиотек и т.п, для этого вполне достаточно языка C++ и стандартной библиотеки STL. Программист должен, однако, помнить о необходимости ловить все возможные исключения и конвертировать их в выходы из программы с ненулевым кодом возврата. А также программист не должен использовать макро assert() из стандартной библиотеки, так как на некоторых системах это приводит к нежелательным сторонним эффектам - к появлению окошка, требующего реакции пользователя.


Unit Test Framework из библиотеки Boost создан для автоматизации таких задач. Функция main() из библиотеки сделана для того, чтобы освободить пользователя от заботы об обнаружении ошибок и генерации отчётов. У пользователей есть возможность использовать средства библиотеки для выполнения сложных проверок. Давайте взглянем на следующую простую программу юнит тестирования:


#include <my_class.hpp>
int main( int, char* [] )
{
  my_class test_object( "qwerty" );
  return test_object.is_valid() ?
    EXIT_SUCCESS : EXIT_FAILURE;
}


С этим юнит тестом есть несколько проблем.


Вам нужно конвертировать результат вызова is_valid() в нужный код возврата. Если при конструировании test_object или при вызове is_valid() возникнет исключение, программа завершится аварийно. Вы не увидите никаких сообщений, если запустите тест вручную. Unit Test Framework решает все эти проблемы. Для его использования эта программа должна быть изменена следующим образом:


#include <my_class.hpp>
#define BOOST_TEST_MODULE MyTest
#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_CASE( my_test )
{
  my_class test_object( "qwerty" );
  BOOST_CHECK( test_object.is_valid() );
}


При этом вы не только получите унифицированный код возврата даже в случае появления исключения, но и увидите отформатированное сообщение о результате выполнения теста из BOOST_CHECK, на случай если вы запустите этот тест вручную. Существуют ли другие способы выполнить проверку? Пример ниже демонстрирует несколько разных способов обнаружить и сообщить об ошибке в функции add():


#define BOOST_TEST_MODULE MyTest
#include <boost/test/unit_test.hpp>

int add( int i, int j ) { return i+j; }

BOOST_AUTO_TEST_CASE( my_test )
{
  // seven ways to detect and report the same error:
  BOOST_CHECK( add( 2,2 ) == 4 ); // #1 continues on error
  BOOST_REQUIRE( add( 2,2 ) == 4 );// #2 throws on error
  if( add( 2,2 ) != 4 )
  BOOST_ERROR( "Ouch..." ); // #3 continues on error
  if( add( 2,2 ) != 4 )
  BOOST_FAIL( "Ouch..." );  // #4 throws on error
  if( add( 2,2 ) != 4 ) throw "Ouch...";// #5 throws 
  // on error 
  BOOST_CHECK_MESSAGE( add( 2,2 ) == 4, // #6 continues
  // on error
  "add(..) result: " << add( 2,2 ) );
  BOOST_CHECK_EQUAL( add( 2,2 ), 4 ); // #7 continues
  // on error
}


(1)

В этом варианте используется BOOST_CHECK, который показывает сообщение об ошибке (по умолчанию он его выводит в std::cout), которое включает в себя выражение, не прошедшее проверку, имя файла с исходниками, и строку в нём. А также он увеличивает счётчик ошибок. При завершении программы счётчик ошибок будет автоматически показан средствами Unit Test Framework.

(2)

Этот вариант использует BOOST_REQUIRE, средство, во всём подобное #1, за исключением того, что после вывода сообщения об ошибке генерируется исключение, отлавливаемое затем Unit Test Framework. Это используется тогда, когда возникшая ошибка настолько серьёзна, что делает дальнейшее выполнение программы бессмысленным. BOOST_REQUIRE отличается от макроса assert() стандартной библиотеки тем, что перенаправляет обнаруженную ошибку в унифицированную процедуру генерации отчётов из Unit Test Framework.

(3)

Вариант использования, похожий на #1, за исключением того, что обнаружение ошибки и сообщение об этом кодируются по отдельности. Это может быть полезно при сложном условном операторе с несколькими условиями, о провале каждого из которых можно сообщить отдельно.

(4)

Вариант использования, похожий на #2, за исключением того, что обнаружение ошибки и сообщение об этом кодируются по отдельности. Это может быть полезно при сложном условном операторе с несколькими условиями, о провале каждого из которых можно сообщить отдельно.

(5)

Этот вариант бросает исключение, отлавливаемое затем Unit Test Framework. Показываемое сообщение об ошибке будет более информативным, если исключение было унаследовано от std::exception, или является char* или std::string.

(6)

Этот вариант использует средство BOOST_CHECK_MESSAGE, похожее на #1, за исключением того, что подобно варианту #3 показывает альтернативное сообщение об ошибке, описанное во втором аргументе.

(7)

Этот вариант использует средство BOOST_CHECK_EQUAL, похожее на #1. Лучше всего это подходит для проверки на равенство двух переменных, так как в случае несовпадения показывает оба значения.


P.S. Для того, чтобы увидеть, что этот Unit Test Framework выводит в случае обнаружения ошибки я исправил первую проверку на заведомо неправильную:
BOOST_CHECK( add( 2,2 ) == 5 );

В этом случае программа выдала следующее:
Running 1 test case… 

c:/boosttest/unittests.cpp(12): error in “my_test”: check add( 2,2 ) == 5 failed

Комментариев нет: