четверг, ноября 13, 2008

Unit test framework для языка Perl


Хотя на работе я пишу в основном на 2-х языках, на C++ и Perl, идея того, что юнит тесты можно писать и для скриптов на Perl’е, а не только для C++ программ, пришла мне в голову относительно недавно. Perl не относится к числу простых в изучении языков, да и синтаксис у него такой, что можно голову сломать порою, так что автоматизированная  проверка скриптов на то, что они делают то, для чего они были написаны, в общем, не плохая идея.

С относительно простыми скриптами можно конечно и без автоматизированного тестирования обойтись, но по мере того, как они начинают усложняться и увеличиваться в длину, да ещё, по идее, требовать рефакторинга с целью выделения общего кода для нескольких скриптов в отдельный модуль, юнит тесты становятся уже жизненной необходимостью. Да что там модули, даже для отдельных регулярных выражениий совсем неплохо бы иметь по нескольку юнит тестов, чтобы понимать, что они правильно обрабатывают разные входные данные: поди пойми без бутылки, что делает нечто подобное:

s/((?:(?! [-_] )[\w-]+\.)+[A-Za-z][\w-]+)/”$1 “( ($addr = gethostbyname($1))?”[" . inet_ntoa($addr) . "]” : “???”)/gex;


Полез искать юнит тест фреймворк для Perl’а - нашёл несколько, но реально попробовал только один:Test::More.
Чтобы его установить на Windows, нужно проделать несколько шагов:

  1. Скачать его со CPAN - Test-Simple-0.86.tar.gz.
  2. Разархивировать
  3. Выполнить:  perl Makefile.pl
  4. Найти утититу nmake.exe (она, к примеру, в Visual Studio есть).
  5. Выполнить:
    nmake
    nmake test
    nmake install
Всё, после этого этот модуль установлен, можно пользоваться, вот, скажем, пример из туториала к нему:

#!/usr/bin/perl -w

use Test::Simple tests => 8;

use Date::ICal;

$ical = Date::ICal->new( year => 1964,
month => 10,  day => 16,
hour => 16, min => 12, sec => 47,
tz => '0530' );

ok( defined $ical, 'new() returned something' );
ok( $ical->isa('Date::ICal'), "  it's the right class" );
ok( $ical->sec   == 47,       '  sec()'   );
ok( $ical->min   == 12,       '  min()'   );
ok( $ical->hour  == 16,       '  hour()'  );
ok( $ical->day   == 17,       '  day()'   );
ok( $ical->month == 10,       '  month()' );
ok( $ical->year  == 1964,     '  year()'  );

А вот что это выдаёт в результате:

1..8
ok 1 - new() returned something
ok 2 -   it's the right class
ok 3 -   sec()
ok 4 -   min()
ok 5 -   hour()
not ok 6 -   day()
#     Failed test (- at line 16)
ok 7 -   month()
ok 8 -   year()
# Looks like you failed 1 tests of 8.


Тут тестируется стандартный модуль Date::ICal - проверяется, что этот объект создаётся и правильно инициализируется. Для тестирования тут используется Test::Simple - упрощенная версия Test::More, которая также входит в поставку. Test::More содержит порядка 15 тестовых примитивов, типа is, ok, like и др, при помощи которых можно проверять в основном равенства/неравенства, При помощи like, в частности, можно тестировать регулярные выражения.

воскресенье, ноября 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

пятница, октября 31, 2008

Энциклопедия Google knol начала поддеживать другие языки

Наконец-то google начала переход от моноязычной беты к чему-то более менее полезному. Поддержки русского языка, правда, официально до сих пор нет, добавили только немецкий, французский и итальянский. Но, думаю, и до остальных языков дело скоро дойдёт. Не официально писать knol’ы на русском никто не мешает. В данный момент по моим прикидкам их написано в районе нескольких сотен. Статистики там я не нашёл, приходится изворачиваться чтобы её узнать - поиск по букве "в", которая встречается исключительно в текстах на кирилице, даёт что-то в районе 900 статей, правда сюда попадают украинские и  болгарские статьи, но их не так много.

Моя экспериментальная статья там про бесплатные игровые движки наконец-то начала находится в обычном google’овском поиске, а не только во внутреннем. Как и ожидалось, google отдаёт предпочтения страницам в Knol перед прочими, использует своё конкурентное преимущество. По крайней мере гугление по словам "бесплатные игровые движки" на первых 2-х местах выдаёт мою knol’овскую страницу и её источник - статью о движках с моего блога. Википедия только на 6-м. Кстати, переиндексация - мгновенная, не успел я в knol’овской статье переправить ссылку со старого блогспотовского на этот блог, как в результатах гугловского поиска старый блог сменился новым.

Мораль, в общем, такая - knol скоро активно засрут seo-шники уже можно пользоваться этим, правда пока не с целью поиска информации (для этого пока пригодна только википедия), а с целью её предоставить и получить от этого какую-то выгоду в виде трафика на свои сайты или напрямую через Adsense на странице.

четверг, октября 23, 2008

Задачки по программированию

Просто чтобы не потерять. Читал ru_cpp, в комментах наткнулся на сайты с простыми и не очень задачками на разработку алгоритмов. После того, как задачка решена, можно с помощью сайта проверить ответ. Сайты предназначены в основном для студентов и школьников, натаскиваемых на участие в соревнованиях по спортивному программированию, но в принципе пользоваться может конечно любой. На английском.

  • www.spoj.pl. По ссылке - список “классических проблем”, количество человек, которые пытались это решить и количество решений, признанных правильными.
  • projecteuler.net. Тоже список алгоритмических задачек. Задачки попроще, чем на первом сайте.
  • www.topcoder.com. Online соревнования программистов в области алгоритмов, проектирования, тестирования. Кроме соревнований там лежат неплохие туториалы по алгоритмическим аспектам программирования.
  • acm.timus.ru, acm.sgu.ru, acm.mipt.ru - русские сайты аналогичного содержания, архивы задач с различных соревнований по спортивному программированию. Правда основное содержание всё равно на английском, поскольку людей натаскивают на участие в международных соревнованиях.

вторник, октября 21, 2008

Programming skills которые вы хотели бы изучить

Программист для того, чтобы оставаться программистом, должен постоянно что-то изучать - языки, технологии и т.п. Говорят, что очень хорошая практика - каждый год изучать новый язык программирования. Но часто человек, работающий в одной конторе длительное время зацикливается на своей текущей работе и просто не знает куда же кинуть взгляд, что в программистском мире появилось нового и перспективного, что неплохо было бы изучить.

На англоязычном сайте вопросов и ответов для программистов stackoverlow.com один человек задал вопрос: “What is the one programming skill you have always wanted to master but haven’t had time?” То есть, в вольном переводе - что в области программирования вы всегда хотели изучить, но у вас никогда на это не хватало времени?

Самые популярные ответы:

  • Функциональное программирование.
  • Как перейти наконец на полностью автоматизированное юнит-тестирование.
  • Программирование игр, в частности 3D графики
  • Как писать драйвера устройств
  • Мультипоточное программирование
  • Изучить скриптовые языки типа Ruby и Python.
  • Создание компиляторов/интерпретаторов  для языка программирования
  • Изучение ассемблера
  • Нейронные сети
  • Регулярные выражения
  • Lisp / Scheme
  • Научиться пользоваться отладчиком WinDbg
  • Изучить язык функционального программирования Haskell
  • C++
  • Распределённые вычисления. Научиться проектировать и разрабатывать системы подобные world community grid or folding@home.

В общем, над некоторыми пунктами списка полезное помедитировать на досуге.