вторник, 1 сентября 2009 г.

null ostream


Последнее время старательно осваиваю TDD. На работе это пока не используется, но для себя все пишу через тесты. Привычку вырабатываю. :) Купил вот недавно самую дорогую книгу в своей жизни: Шаблоны тестирования xUnit. Рефакторинг кода тестов. Очень познавательная книга. Юнит тестинг без фанатизма. Там в частности написано, что использовать базы данных в тестах можно! Но есть способы лучше. Вообще главная мораль этой книги - что наличие тестов, каких бы то ни было значительно лучше, чем их отсутствие.

Но собственно написать я хотел о другом. Изобретаю консольное приложение. Консольное приложение должно что-то выводить на консоль. Для этого традиционно используется cout. И собственно приложению ничего больше не нужно.

Но когда речь заходит о тестах - cout становится серьезной помехой.

Я выбрал следующий путь решения. Методы, которые что-то выводят, получают в качестве параметра ostream &. Это позволяет в тестах подсунуть вместо cout что-то другое, например ostringstream. И проверить что же собственно вывелось на консоль после вызова метода.

Но иногда тестам и этого не нужно. Бывает нужно передать что нибудь, только чтобы метод им удовлетворился и проглотил. И встает вопрос dummy ostream - потока-заглушки.

Вариант #1 с заглушиванием cout:
std::cout.exceptions(std::ios::goodbit); 
std::cout.setstate(std::ios::failbit);
std::cout << 1; // ignore
... мне совершенно не нравится. Да и для тестов он не очень удобен. Каждый раз глушить и разглушать обратно? Нет, нам нужен объект, наследник ostream, который просто не делает ничего. Сделать так можно. Решение приводить не буду, сошлюсь. Но давайте посмотрим что есть на эту тему в boost. Решение #3, найденное в интернете:
struct null_sink : boost::io::sink {
void write(const char*, std::streamsize) { }
};
typedef boost::io::stream_facade<null_sink> null_ostream;
... сейчас не работает. Не скажу точно когда работало, не важно. Решение #4:
typedef boost::iostreams::stream<boost::iostreams::null_sink> null_ostream;
... работает, но для того, чтобы все было гладко, перед использованием надо вызвать out.open(boost::iostreams::null_sink());, что не совсем удобно. И последнее... onullstream описан в файле boost/test/utils/nullstream.hpp. Одного только не понимаю, почему я не могу создавать его непосредственно при вызове?
int foo(ostream &out);
BOOST_CHECK_EQUAL(foo(onullstream()), 1)
... возвращает ошибку, о том, что нет метода foo, в который можно было бы передать onullstream... но если создать onullstream заранее, то никаких возражений у компилятора не возникает.
onullstream out;
BOOST_CHECK_EQUAL(foo(oot), 1)
... Успешно. Что-то я наверное делаю не так? :)