Unit тесты

Jul 25, 2013 16:28



tdd - это когда чуваки говорят: «зачем нам думать, лучше напишем ка мы ещё этих юнит тестов»
@hirthwork
  Раньше я никогда не писал unit тестов. Ну, во-первых, я их всегда считал ненужной тратой времени. Во-вторых: я себе слабо представлял в чем их необходимость. В-третьих: я никак не мог понять - как можно тратить время на сраные тесты, когда можно писать код.
И вот буквально пару дней назад я все таки созрел для написания своих первых тестов. Тестировал я функцию, которая вытягивае метаданные из fb2 и epub книжек (Да, я решил написать убийцу calibre). Так как проект написан на qt, то и в качестве средства написания тестов я выбрал QtTest. Ща я покажу немного кода, который необходим был для моих тестов: Вы его всегда сможете адаптировать для себя.
Я сразу приведу весь код, а заодно протестирую еще и работу LJ с github Gist. А она - полное говно. Весь код ломает как хрен знает что. Буду продолжать юзать нормальные хайлатеры.Итак, ключевые моменты написания простого unit теста в Qt использую QtTest:

CMakeLists.txt:

// создаем опцию для включения/выключения тестов
option (TESTS_SHPICK "Enable Shpick tests" OFF)

// инклудим QtTest
if (TESTS_SHPICK)
set (QT_USE_QTTEST TRUE)
endif ()
include (${QT_USE_FILE})

if (TESTS_SHPICK)
// используем файл конфугурации для передачи в код значени ${CMAKE_CURRENT_SOURCE_DIR} - там у нас лежат тестовые книги
configure_file (tests/test.h.in tests/test.h)
// инклудим директорию с тестами
include_directories (${CMAKE_CURRENT_BINARY_DIR}/tests)
QT4_WRAP_CPP (PARSERMANAGERTEST_MOC "tests/parsermanagertest.cpp")

// далее обычные действия по созданию бинарника и линковке его с необходимыми либами
add_executable (bm_shpick_parsermanagertest WIN32
tests/parsermanagertest.cpp
parsermanager.cpp
bookparsers/bookfactory.cpp
bookparsers/epub.cpp
bookparsers/fb2.cpp
${PARSERMANAGERTEST_MOC}
)

target_link_libraries (bm_shpick_parsermanagertest
${QT_LIBRARIES}
${BLUMBERG_LIBRARIES}
${QUAZIP_LIBRARIES}
)
// добавлям тест
add_test (ParserManager bm_shpick_parsermanagertest)
endif ()

parsermanagertest.h

using namespace Blumberg::Shpick;

class TestParserManager : public QObject
{
Q_OBJECT

private slots:
// Метод я называю так же как и реальный метод в проекте
void ParseBook ()
{
// Вот тут как раз используется переменная PLUGIN_SOURCE_DIR, равная ${CMAKE_CURRENT_SOURCE_DIR}
const QString& fb2Path = QString (PLUGIN_SOURCE_DIR) + "/tests/test.fb2";
auto fb2Parser = MakeBookParser (fb2Path);
if (!fb2Parser)
// QFAIL используется там, где вам нужно показать фейл теста.
// Например у меня это происходит, если нету парсера для книги
QFAIL ("Book parsers was not created fro FB2 format");

Blumberg::Book fb2Book = fb2Parser (fb2Path);
if (!fb2Book.isValid ())
QFAIL ("Test book is not valid: fb2 format");

// Дальше идут сами тестыю Проверяем значения, которые возвращает мой парсер со значениями из файлов
QCOMPARE (fb2Book.TitleInfo_.Ganres_.count (), 3);
QCOMPARE (fb2Book.TitleInfo_.Ganres_.at (0), QString ("architecture"));
QCOMPARE (fb2Book.TitleInfo_.Ganres_.at (1), QString ("art"));
QCOMPARE (fb2Book.TitleInfo_.Ganres_.at (2), QString ("art_instr"));
QCOMPARE (fb2Book.TitleInfo_.Authors_.count (), 3);
QCOMPARE (fb2Book.TitleInfo_.Authors_.at (0).LastName_, QString ("last-name1"));
QCOMPARE (fb2Book.TitleInfo_.Authors_.at (0).HomePages_.count (), 3);
QCOMPARE (fb2Book.TitleInfo_.Authors_.at (0).HomePages_.at (2), QUrl ("home-page3"));
QCOMPARE (fb2Book.TitleInfo_.Authors_.at (0).EMails_.count (), 3);
QCOMPARE (fb2Book.TitleInfo_.Authors_.at (0).EMails_.at (1), QString ("email2"));
QCOMPARE (fb2Book.TitleInfo_.Authors_.at (1).MiddleName_, QString ("middle-name2"));
QCOMPARE (fb2Book.TitleInfo_.Authors_.at (2).FirstName_, QString ("first-name3"));
QCOMPARE (fb2Book.TitleInfo_.Authors_.at (2).NickName_, QString ("nickname3"));
QCOMPARE (fb2Book.TitleInfo_.Title_, QString ("book-title1"));
QCOMPARE (fb2Book.TitleInfo_.Tags_, QStringList ("keywords1"));
QCOMPARE (fb2Book.TitleInfo_.WrittenDate_, QString ("1900-01-01"));
QCOMPARE (fb2Book.TitleInfo_.OriginalLanguage_, QString ("en"));
QCOMPARE (fb2Book.TitleInfo_.TextLanguage_, QString ("en"));
QCOMPARE (fb2Book.TitleInfo_.Translators_.count (), 1);
QCOMPARE (fb2Book.TitleInfo_.Translators_.at (0).FirstName_, QString ("first-name4"));
QCOMPARE (fb2Book.TitleInfo_.Seria_, QString ("name1"));
QCOMPARE (fb2Book.TitleInfo_.Episode_, 1);

QCOMPARE (fb2Book.PublishInfo_.BookName_, QString ("book-name1"));
QCOMPARE (fb2Book.PublishInfo_.Publisher_, QString ("publisher1"));
QCOMPARE (fb2Book.PublishInfo_.City_, QString ("city1"));
QCOMPARE (fb2Book.PublishInfo_.PublishedYear_, 1900);
QCOMPARE (fb2Book.PublishInfo_.ISBN_, QString ("isbn1"));

// Тут все тоже самое, только для epub формата.
const QString& epubPath = QString (PLUGIN_SOURCE_DIR) + "/tests/test.epub";
auto epubParser = MakeBookParser (epubPath);
if (!epubParser)
QFAIL ("Book parsers was not created for EPUB format");

Blumberg::Book epubBook = epubParser (epubPath);
if (!epubBook.isValid ())
QFAIL ("Test book is not valid: epub format");

QCOMPARE (epubBook.TitleInfo_.Title_, QString ("book-title1 n-1"));
QCOMPARE (epubBook.TitleInfo_.TextLanguage_, QString ("en"));
QCOMPARE (epubBook.TitleInfo_.Authors_.count (), 3);
QCOMPARE (epubBook.TitleInfo_.Authors_.at (0).FirstName_, QString ("First-Name1 Middle-Name1 Last-Name1 Nickname1"));
QCOMPARE (epubBook.TitleInfo_.Authors_.at (1).FirstName_, QString ("First-Name2 Middle-Name2 Last-Name2 Nickname2"));
QCOMPARE (epubBook.TitleInfo_.Authors_.at (2).FirstName_, QString ("First-Name3 Middle-Name3 Last-Name3 Nickname3"));
QCOMPARE (epubBook.TitleInfo_.Translators_.count (), 1);
QCOMPARE (epubBook.TitleInfo_.Translators_.at (0).FirstName_, QString ("First-Name4 Middle-Name4 Last-Name4 Nickname4"));
QCOMPARE (epubBook.TitleInfo_.Tags_.count (), 3);
QCOMPARE (epubBook.TitleInfo_.Tags_.at (0), QString ("architecture"));
QCOMPARE (epubBook.TitleInfo_.Tags_.at (1), QString ("art"));
QCOMPARE (epubBook.TitleInfo_.Tags_.at (2), QString ("art_instr"));
QCOMPARE (epubBook.PublishInfo_.ISBN_, QString ("isbn1"));
QCOMPARE (epubBook.PublishInfo_.Publisher_, QString ("publisher1"));
QCOMPARE (epubBook.PublishInfo_.PublishedYear_, 1900);
}
};

parsermanagertest.h

// Тут у нас всего две строки

#include "parsermanagertest.h"

QTEST_MAIN (TestParserManager)

Благодаря тестам я нашел с пяток ошибок в своих парсерах. Это очень мне помогло. Плюс это сподвигло меня на генерацию тестовых книг (изначально я использовал реальные). Чем тестовые лучше? Тем, что я их генерировал из fb2.xsd и на выходе получил все возможные варианты параметров. Для тестов лучше некуда.

Для fb2 формата я легко нашел генератор xml из схемы. Сама схема тут. Плюс еще 3 дополнительные схемы для жанров, языка и ссылок в fb2. Когда я начал искать способ генерации из этого xml, то первым делом пытался сделать через online и у меня ничего не получилось. Онлайн парсеры не смогли работать со внешними схемами. Поэтому я начал искать десктопный вариант. И им стала MSVS 2010. Она просто замечательно работает с xml схемами и замечательно генерирует из нее xml. Это я и поспешил сделать. Тестовая книга для fb2 была готова.

Теперь надо было сделать тестовую книгу для EPUB. И тут я столкнулся со сложностью: нормальной схемы я не смог найти. То, что нашел, не позволяло создавать документы. В итоге убив кучу времени я сделалход конем: сгенерил из своего тестового fb2 epub книжку и ее использовал в качестве лакмусовой бумажки. Получилось очень даже неплохо, как мне кажется.

После всех трудов я поделился этой новостью с людом и мне сказали, что на самом деле unit тесты нужны не для поиска ошибок и прочего, а для проверки функций после рефакторинга. Но мы то с Вами знаем, что никто рефакторингом никогда не занимается, потому что на это нету времени/денег/желания...
А я вероятнее всего продолжу использовать тесты. Мне понравилось. Тем более я страдаю желанием переписывать свой старый код, потому что он говно.

.Posted via LeechCraft Blogique.

coding is my life, unit tests, С++, qt

Previous post Next post
Up