*** ВНИМАНИЕ: Блог переехал на другой адрес - demin.ws ***

четверг, 29 октября 2009 г.

codepad.org

Открыл для себя codepad.org, и начал пользоваться с завидной регулярностью, когда надо быстренько что-нибудь попробовать - откомпилировать и запустить, а потом еще и показать кому-нибудь.

Искусственная типизация однородных параметров в C++

Допустим есть вот такой класс:
class Date {
public:
Date(int year, int month, int day) {
...
}
};
К сожалению, не весь мир пользуется логичной нотацией Год/Месяц/День или День/Месяц/Год. Иногда люди пишут Месяц/День/Год. Хотя и первые два легко перепутать. Вот к чему я веду: где-то в далеком от описания класса коде кто-то пишет:
Date d(2009, 4, 5);
Что он этим хотел сказать? 4-е Мая или 5-е Апреля? Сложно быть уверенным, что пользователь такого класса когда-нибудь не перепутает порядок аргументов.

Можно улучшить дизайн? Да.

Например, так:
class Year {
public:
explicit Year(int year) : year_(year) {}
operator int() const { return year_; }
private:
int year_;
};
И аналогично:
class Month { ... };
class Day { ... };
Интерфейс самого класса Date может быть таким:
class Date {
public:
Date(Year year, Month month, Day day);
Date(Month month, Day day, Year year);
Date(Day day, Month month, Year year);
}
И использовать класс надо так:
Date d(Year(2010), Month(4), Day(5));
или
Date d(Month(4), Day(5), Year(2010));
Результат будет всегда предсказуем и виден в вызывающем коде. Тут все inline'овое, так что эти три "лишние" класса никакого замедления не дадут.

Согласен, писанины немного больше, но зато полная гарантия от опечаток и, как следствие, глупых, но коварных ошибок.

Возражения есть?

четверг, 22 октября 2009 г.

Коварный printf()

Вчера подорвался на ерунде как ребенок.

Сижу, отлаживаю новый онлайновый ассемблер в своем эмуляторе Радио-86РК. Под отладкой понимается ёрзанье с применением html'я.

Для сборки финального html-файла из кучи мелких у меня написана примитивная программа. Вот ее фрагмент:
...
while (!feof(f)) {
char line[1024];
*line = 0;
fgets(line, sizeof(line), f);
printf(line);
}
...
Подразумевается, что данный код прострочно копирует данные из файла f на стандарный вывод.

Даже если отставить в сторону использование буфера с константной длиной и прочих "штучек" языка С, этот код имеет одну проблему, которая стоила мне сомнений в наличии сознания. До каких-то пор все работало отлично, но как только я начал использовать процентные значения для широт и высот в html, начались странности. Получалось, что вместо, например:
<table width="100%">
на выходе было:
<table width="100">
Вы, наверное, уже догадались, в чем тут дело. Но, признаюсь, я искал проблему минут тридцать.

Вместо:
printf(line);
надо писать:
printf("%s", line);
А иначе все процентные символы будут расцены как указатели форматов, ибо первый параметр printf() - это не просто строка, а формат, и в случае их неэкранирования будут уделены, что и происходило в моем случае.

Вывод (который следует после начального "сам дурак"): Лучше писать на С++ и использовать потоки для форматного вывода.

Лирическое отступление. Кстати, онлайновый ассемблер очень огранично вписался в эмулятор. Спасибо Вячеславу Славинскому за оригинальный код этого ассемблера. Особенно меня радует возможность автоматической фоновой компиляции. Теперь можно, прямо не отходя от эмулятора, переключиться в ассемблер, написать что-нибудь на диалекте Intel 8080 (КР580), скомпилировать и загнать прямо в эмулятор.

четверг, 15 октября 2009 г.

Введение в Google Mock (видео)

Данное видео будет хорошим дополнением к предыдущему посту про Google Mock для желающих освоить эту библиотеку.

вторник, 13 октября 2009 г.

Google C++ Mocking Framework для начинающих

В рамках проекта популяризации культуры разработки софта с активным использованием тестирования выложил перевод Google C++ Mocking for Dummies на русский язык - Google C++ Mocking Framework для начинающих.

Использование Mock-объектов является очень интересной темой. И владение ей позволяет перевести unit-тестирование на принципиально иной уровень.

Как рассказано в статье, языки программирования типа Python или Java благодаря встроенному механизму Reflection позволяют строить Mock-объекты почти автоматически. С++ не дает такой роскоши, но гугловцы проделали отличную работу, создав Google Mock. Практически все, что можно как-то упростить или автоматизировать в плане mock-дел в С++, сделано и сделано добротно.

Соглашусь, что поначалу вся эта тема с Mock-объектами выглядит несколько громоздко и сложновато, но тут как с эргономичной клавиатурой - надо сначала привыкнуть, а потом все окупится сполна.

Посты по теме:

пятница, 9 октября 2009 г.

Клонирование проектов в Google Code

Около суток Mercurial на Google Code был в read-only режиме. Проводили какую-то профилактику.

Захожу сегодня и что вижу:



Появился новый пункт в закладке Source — Clone (обведено красным). Это то, чего так долго ждали большевики — клонирование проектов.

Если администратор проекта не хочет добавлять вас в контрибуторы, а вы таки хотите показать людям свою лепту в данном проекте, то теперь вы можете сделать клон проекта и продолжить работу над своей веткой независимо. История изменений полностью наследуется от оригинала.

Клон может быть в свою очередь тоже склонирован.

Больше открытых проектов, хороших и разных!

Посты по теме:

среда, 7 октября 2009 г.

Презентация от авторов Google C++ Testing Framework

В догонку к посту про новую версию Google Test, желающие могут заценить небольшую презентацию от ее авторов:



В презентации описывается не только сама библиотека, ее возможности и примеры использования, но и сам подход к разработке через тестирование (test driven development), рассмотрены несколько базовых советов по написанию удобного для тестирования кода и т.д.

В целом, не самые плохие десять минут, чтобы поглядеть и немного поразмыслить.

вторник, 6 октября 2009 г.

Закомментированные куски кода и TODO

Когда я вижу в production коде, в котором мне надо по какой-то причине разобраться, что-то типа:
...
int x = (i << 8) | (i >> 2) | ((i & 0x06) ^ 0xAA);

// if (x >= 0x12345)
// x = x >> 3;

calc_something_complicated(x);
...
мне хочется рвать и метать. Расчехлить blame, найти автора и заглянуть ему в глаза.

Иначе, что мне остается: только думать, что автор этих строк, видимо, бился с формулой, пытался подогнать результат под что-то (возможно, какой-то тест). Достиг ли он результата? Или может оно так и продолжает глючить... Кто знает. Единственное, о чем этот код однозначно говорит, что автор не был уверен в том, что пишет. Потому, что если он был уверен, то удалил бы этот фрагмент или раскомментировал бы его навсегда.

На втором месте у меня стоит отладочная печать, навеки оставленная в коде:
...
int x = (i << 8) | (i >> 2) | ((i & 0x06) ^ 0xAA);

// std::cerr << "На этот раз x = " << x << std::endl;

calc_something_complicated(x);
...
Снова получается, что автор сомневался и так и не отладил все до конца.

Конечно, подобные куски могут появляться просто по невнимательности и забывчивости, но как может помочь это оправдание? Никак.

Ну на третьем месте нашего хит-парада - блоки TODO.
...
int x = (i << 8) | (i >> 2) | ((i & 0x06) ^ 0xAA);

// TODO: Заменить тип int на int64, так как на носу эра 64-разрядных машин.
// Программист Вася, 06.10.2009. Если что, вы знаете где меня искать.

calc_something_complicated(x);
...
Тут еще куда ни шло. Куски TODO можно найти автоматическим поиском при подготовке релиза и перед ответственным слиянием. Но каждый TODO должен быть подписан и датирован, а лучше еще и детально объяснен. Ни что так не помогает оценить "нужность" куска кода, как дата его последней модификации.

Итак, вывод тут только один: в production коде никогда не должно быть закомментированных кусков. А если они есть, то они должны сопровождаться четкими комментариями, поясняющими их суть.

суббота, 3 октября 2009 г.

Google C++ Testing Framework 1.4.0

Вчера гугловцы анонсировали новую версию моей любимой библиотеки тестирования Google C++ Testing Framework - 1.4.0.

Что новенького?

Одна из главных новых возможностей - это "The event listener API". А попросту говоря, возможность полностью контролировать процесс печати результатов тестирования. Это позволяет формировать отчеты по тестированию в нужном формате без изменения кода библиотеки.

Например, стандартный вывод при выполнении элементарного теста (файл runner.cpp):
#include "gtest/gtest.h"

TEST(One, Simple) {
EXPECT_EQ(1, 2);
}

int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
будет таким:
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from One
[ RUN ] One.Simple
runner.cpp(4): error: Value of: 2
Expected: 1
[ FAILED ] One.Simple (15 ms)
[----------] 1 test from One (15 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (31 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] One.Simple

1 FAILED TEST
Для задания иного формата вывода нужно реализовать свой event listener (назовем его сервис печати). Например (файл runner.cpp):
#include "gtest/gtest.h"

using namespace ::testing;

// Данный класс заменит стандартный сервис печати.
class LaconicPrinter : public ::testing::EmptyTestEventListener {
// Вызывается до начала работы теста.
virtual void OnTestStart(const TestInfo& test_info) {
printf("*** Test %s.%s starting.\n",
test_info.test_case_name(), test_info.name());
}

// Вызывается при срабатывании какого-либо утверждения или явного вызова
// функции SUCCESS().
virtual void OnTestPartResult(const TestPartResult& test_part_result) {
printf("%s in %s:%d\n%s\n",
test_part_result.failed() ? "*** Failure" : "Success",
test_part_result.file_name(),
test_part_result.line_number(),
test_part_result.summary());
}

// Вызывается после выполнения теста.
virtual void OnTestEnd(const TestInfo& test_info) {
printf("*** Test %s.%s ending.\n",
test_info.test_case_name(), test_info.name());
}
};

TEST(One, Simple) {
EXPECT_EQ(1, 2);
}

int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);

// Получаем ссылку на список сервисов печати.
::testing::TestEventListeners& listeners =
::testing::UnitTest::GetInstance()->listeners();

// Удаляем стандартный сервис печати.
delete listeners.Release(listeners.default_result_printer());
// Добавляем наш сервис в список. Google Test самостоятельно удалит этот объект.
listeners.Append(new LaconicPrinter);

return RUN_ALL_TESTS();
}
Теперь отчет по работе тестов будет выглядеть так:
*** Test One.Simple starting.
*** Failure in runner.cpp:31
Value of: 2
Expected: 1
*** Test One.Simple ending.
Необходимо отметить, что одновременно может быть зарегистрировано несколько сервисов печати. Но в этом случае их выводы могут смешиваться и превращаться в кашу. Для избежания этого мы принудительно удаляем стандартный сервис печати, чтобы его вывод не мешал нашему.

Полностью интерфейс сервиса печати выглядит так:
class EmptyTestEventListener : public TestEventListener {
public:
// Вызывается при начале прогона всех тестов.
virtual void OnTestProgramStart(const UnitTest& unit_test);
// Вызывается при начале очередной итерации тестирования. Google Test
// позволяет управлять количеством итерации при прогоне тестов.
virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration);
// Вызывается до функции Environment::SetUp(), устанавливающей необходимое
// окружение для работы всех тестов.
virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test);
// Вызывается после функции Environment::SetUp(), устанавливающей необходимое
// окружение для работы всех тестов.
virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test);
// Вызывается при начале прогона группы тестов (у которых первый параметр
// макроса TEST/TEST_F одинаковый).
virtual void OnTestCaseStart(const TestCase& test_case);
// Вызывается при начале работы теста.
virtual void OnTestStart(const TestInfo& test_info);
// Вызывается при срабатывании утверждения в тесте или явного вызова
// функции SUCCESS().
virtual void OnTestPartResult(const TestPartResult& test_part_result);
// Вызывается после завершения работы теста.
virtual void OnTestEnd(const TestInfo& test_info);
// Вызывается после прогона группы тестов.
virtual void OnTestCaseEnd(const TestCase& test_case);
// Вызывается до функции Environment::TearDown(), производящей освобождение
// ресурсов, занятых Environment::StartUp().
virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test);
// Вызывается после функции Environment::TearDown().
virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test);
// Вызывается после очередной итерации тестирования.
virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration);
// Вызывается после прогона всех тестов.
virtual void OnTestProgramEnd(const UnitTest& unit_test);
};
Также из значимого можно отметить новый ключ командной строки --gtest_shuffle, позволяющий запускать тесты в случайном порядке. Ключом --gtest_random_seed=SEED можно "управлять" случайностью этого порядка. Если SEED равен 0, то случайность будет действительно случайной, так как случайная последовательность будет определяться текущим временем.

Что приятно, теперь формат XML файлов, генерируемых при использовании ключа --gtest_output, полностью совместим с форматом JUnit. Это значит, что, например, система автоматической сборки, тестирования и интеграции Hudson теперь понимает отчеты Google Test без дополнительный конвертации.

Также теперь при работе в Visual Studio сообщения о сбойных тестах выводятся прямо в окно "Output", что позволяет, кликая на них, сразу находить строки, где сбоят тесты. Здорово, что данная фича основана на моем коде.

Еще, теперь время работы тестов печатается всегда, по умолчанию, то есть опция --gtest_print_time будто бы всегда включена.

Есть еще несколько незначительных улучшений:
  • поддержка CodeGear Studio
  • собственная реализация tuple для независимости от boost при использовании Combine()
  • множество улучшений для совместимости с Solaris, Windows Mobile и некоторыми другими платформами.
Радость, одно слово.

Я перестал что-то писать что-либо на C++ без тестов, а Google Test делает этот процесс простым и легким.

Я уже обновился до версии 1.4.0, а вы?

Ссылки по теме: