Последнее время старательно осваиваю 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)... Успешно. Что-то я наверное делаю не так? :)
9 коммент.:
Не уверен, что понял проблему правильно, но традиционно решаю проблему с перенаправлением вывода на плюсах примерно так
class CerrRedirector
{
public:
CerrRedirector():out_("log.txt")
{
if(out_)
oldBuffer_ = out_ ? std::cerr.rdbuf(out_.rdbuf()) : 0;
}
~CerrRedirector()
{
if(oldBuffer_) {
std::cerr.rdbuf(oldBuffer_);
out_.close();
}
}
private:
std::ofstream out_;
std::streambuf* oldBuffer_;
};
очень много буков...
Хотя если по коду жестко используется std::cerr - никуда от этого не денешься.
но это плохо... жесткая связанность с cerr получается.
Гораздо удобнее оперировать абстрактным потоком вывода, для перехвата в файл можно подсунуть ofstream, для последующего анализа - ostringstream...
Проблема была в том, что содержание потока вообще не интересно. Можно использовать тот же ostringstream, но он всетаки память расходует, в то время как это совершенно не нужно. Может быть там мегабайты вывода, который безразличен?
Все букавы тута.
oldBuffer_ = std::cerr.rdbuf(out_.rdbuf());
Остальное для красоты - пока живет объект - всё из cerr валится куда надо. Хочешь туда же cout - не проблема - расширяй, параметризуй объектом потока и т.д. Просто не везде можно докинуть ссылку на поток. А глобальная какашка в виде cout, cerr всё равно уже существует - пускай для дела послужит :)
Это да, если код унаследованный, то тут вариантов особо нету...
Кстати буфер можно делать и абстрактный...
Типа boost::iostreams::stream_buffer засунуть... Его же можно параметризовать boost::iostreams::null_sink'ом, чтобы получился поток без вывода...
Но если код разрабатывается с нуля, с учетом TDD - то можно подходить к потокам и гибче, нежели перенаправлять cout.
Одного только не понимаю, почему я не могу создавать его непосредственно при вызове?
int foo(ostream &out);
BOOST_CHECK_EQUAL(foo(onullstream()), 1)
... возвращает ошибку, о том, что нет метода foo, в который можно было бы передать onullstream...
думаю, int foo(const ostream &out); заработает.
Не, const здесь ну никак не подходит... для const ostream не заработают opertator <<...
Не понимаю что ему мешает передаться как неконстантная ссылка? Просто временный объект... что-то тут с типами происходит... типа она не воспринимает onullstream() как ostream, почему-то...
Может быть он не наследник, а просто имеет оператор преобразования в ostream? надо проверить...
Не понимаю что ему мешает передаться как неконстантная ссылка?
Просто насколько я помню временные объекты (ваш foo(onullstream())) всегда константы. Поэтому и не может. Можно попробовать const_cast :-)
Ничего подобного... их можно даже менять, если хочется...
Только все пропадет сразу после завершения вызова функции...
А-а-а, я сообразил... все дело в ссылке. Компилятор запрещает инициализировать ссылки временными объектами - ибо это может привести к ссылке на уже удаленный объект.
А передавать ostream по значению тоже нельзя, он не копируется.
Значит просто инициализировать объект onullstream заранее.
Отправить комментарий