|
GoogleTestPrimerRussian
Введение в Google C++ Testing Framework
Featured Дата последнего обновления документа: 05.11.2010.
Введение: Что такое Google C++ Testing Framework?Google C++ Testing Framework (далее по тексту Google Test) призвана помочь вам в создании качественных тестов для программ на C++. Неважно работаете ли вы в Linux, Windows или Mac -- если вы пишете программы на С++, то Google Test поможет вам в этом. Что значит хороший тест, и как именно Google Test помогает писать хорошие тесты?
Замечание: Как упоминалось раньше, неформально мы можем ссылаться на Google C++ Testing Framework как на Google Test. Создание нового проектаДля создания тестовой программы с использованием Goole Test, вам необходимо скомпилировать Google Test в формат библиотеки и прилинковать к вашим тестам. Мы создали необходимые скрипты и файлы конфигурации для некоторых популярных системы сборки (msvc/ для Visual Studio, xcode/ для Mac Xcode, make/ для GNU make, codegear/ для Borland C++ Builder, scons/ для Scons, и скрипт для autotools в корневом каталоге Google Test). Если вы используете другую систему сборки, возьмите за образец файл make/Makefile чтобы понять, как собрать Google Test (по сути вам надо скомпилировать src/gtest-all.cc со значениями GTEST_ROOT и GTEST_ROOT/include в списке путей для заголовочных файлов, где GTEST_ROOT является корневым каталогом директории Google Test). Скомпилировав Google Test как библиотеку, мы можете создать новый проект. Убедитесь, что путь GTEST_ROOT/include включен в список поиска заголовочных файлов, чтобы компилятор смог найти <gtest/gtest.h>. Проект должен быть слинкован с библиотекой Google Test (например, для Visual Studio, вы можете добавить зависимость от gtest.vcproj). Если какие-то моменты остались неясными, посмотрите, посмотрие, как устроены тесты самой библиотеки Google Test, и используйте их в качестве примера. Основные понятияРаботая с Google Test, вы пишете утверждения, которые являются операторами проверки истинности некоторого условия. Проверка утверждения может закончится успехом, фатальной ошибкой (fatal error) или нефатальной ошибкой (non-fatal error). При фатальной ошибке выполнение текущего теста прерывается. В противном случае программа просто продолжает работу дальше. Тесты используют утверждения для проверки логики работы программного кода. Если тест аварийно завершается или какое-либо утверждение в нем не срабатывает, то считается, что тест дал сбой, иначе тест считается успешным. Вы можете группировать тесты в наборы (test cases), сообразно структуре тестируемой программы. Если несколько тестов используют общие объекты и подпрограммы, вы может объединить их в тестовый класс (test fixture). Тестовая программа может состоять из нескольких наборов тестов. Мы покажем, как писать тестовую программу, начиная с уровня отдельных утверждений, затем поднимаясь на уровень тестовых наборов и тестовых классов. УтвержденияУтверждения в Google Test являются макросами, схожими с вызовами функций. Вы тестируете класс или функцию, делая логические постулаты (утверждения) об их ожидаемом поведении. Когда такой постулат нарушается, Google Test печатает имя файла и номер строки, где произошел сбой. Также выводится диагностическое сообщение об ошибке. Вы можете задавать свои собственные дополнительные сообщения, которые будут добавлены к стандартному. Утверждения бывают двух типов, схожих по названию, но по разному влияющих на выполнение текущего теста. ASSERT_* генерируют фатальные ошибки, при возникновении которых выполнение текущей функции прерывается. Другой тип, EXPECT_*, генерирует "мягкие", нефатальные ошибки, которые не прерывают текущую функцию. Такие утверждения являются более предпочтительными, так как дают возможность обнаружить сразу несколько проблем в тесте. Однако вам следует использовать ASSERT_*, если уже не имеет смысла продолжать тест после сбоя. Когда показавший ошибку ASSERT_* немедленно прерывает выполнение текущей функции, возможно, что часть кода, ответственного за чистку мусора и освобождение ресурсов после теста, не будет выполнена, что может привести к утечкам памяти. В зависимости от типа утечки может и не стоит заботиться о них в данном случае, но надо быть готовым к возможным сообщениям об ошибках от систем проверки динамически распределенной памяти. Чтобы вывести свое собственное сообщение об ошибке, просто используйте макрос утверждения как стандартный поток и оператор <<. Например: ASSERT_EQ(x.size(), y.size()) << "Длина вектора x не равна длине вектора y";
for (int i = 0; i < x.size(); ++i) {
EXPECT_EQ(x[i], y[i]) << "Вектора x и y отличаются по индексу " << i;
}Все, что может быть выведено в поток ostream можно послать подобным же образом в макрос утверждения. В частности, строки языка С и объекты string. Если печатается широкая строка (wchar_t*, TCHAR* в режиме UNICODE в Windows, или std::wstring), то она будет преобразована в кодировку UTF-8. Основные типы утвержденийДанные утверждения проверяют условие на истинность или ложность.
Обратите внимание, что когда утверждение дает сбой, ASSERT_* генерирует фатальную ошибку и прекращает выполнение текущей функции, тогда как EXPECT_* генерирует "мягкую", нефатальную ошибку, и текущая функция продолжает выполнение. В любом случае считается, что данный тест в целом дал сбой. Доступно на: Linux, Windows, Mac. СравненияЭтот раздел описывает утверждения для сравнение друх значений.
В случае ошибки Google Test печатает оба значение знач1 и знач2. В ASSERT_EQ* и EXPECT_EQ* (все остальные варианты проверки на равенство будут рассмотрены чуть позже) вы задаете выражение, которое хотите проверить, в параметре действительное, а ожидаемое значение -- в параметре ожидаемое. Следование такому соглашению удобно тем, что Google Test печатает сообщения об ошибках, исходя именного из такого использования параметров утверждения. Значения аргументов должны иметь возможность сравнения. Иначе вы получите сообщение компилятора об ошибке. Значения также должны поддерживать оператор << для вывода в поток ostream. Все встроенные типы данных удовлетворяют этим условиям. Утверждения могут работать с пользовательскими типами, но только если вы зададите соответственные операторы сравнения (например, ==, < и т.д.). Если соответственный оператор определен, то предпочтительнее использовать макросы ASSERT_*(), так как они печатают не только результат сравнения, но сами операнды. Аргументы всегда вычисляются только один раз, поэтому можно спокойно использовать вызовы с побочными эффектами. Однако, в языках С и С++ порядок вычисления аргументов функций не определен, и компилятор может их вычислить в любом порядке, поэтому нельзя полагаться на определенный порядок вычисления аргументов утверждения. ASSERT_EQ() поддерживает сравнение указателей. Если это две строки С, будет проверено, указывают ли они на одну и ту же область памяти, а не значения самих строк. Если же вы хотите сравнить значения двух строк С (например, const char*), то используйте макрос ASSERT_STREQ(), которое будет описано ниже. В частности, для проверки строки С на NULL используйте ASSERT_STREQ(NULL, c_string), однако для сравнения двух объектов типа string надо использовать ASSERT_EQ. Макросы, описанные в данном разделе, работают с обычными и многобайтовыми строковыми объектами (string и wstring) и строками С. Доступно на: Linux, Windows, Mac. Сравнение строкУтверждения в данной группе все сравнивают строки С. Если вы хотите сравнить два объекта типа string, то используйте вместо них EXPECT_EQ, EXPECT_NE и т.д.
Обратите внимание, что "CASE" в имени утверждения означает, то регистр будет проигнорирован. *STREQ* и *STRNE* также могут работать с многобайтовыми строками С (wchar_t*). В случае неудачного сравнения двух многобайтовых строк, их значения буду напечатаны в виде однобайтовых строк в формате UTF-8. Значение указателя NULL и пустая строка являются разными значениям. Доступно на: Linux, Windows, Mac. См. также: Более подробная информация о приемах сравнения (например, подстроки, префиксы и регулярные выражения), см. Advanced Google Test Guide. Элементарные тестыДля создания теста:
TEST(имя_набора_тестов, имя_теста) {
... тело_теста ...
}Аргуметы макроса TEST() идут от общего к частному. Первый аргумент является именем набора тестов, а второй -- именем теста в данном наборе. Набор тестов может содержать любое количество отдельных тестов. Полное имя теста состоит из имени набора, которому этот тест принадлежит, и его собственного имени. Тесты из разных наборов могут иметь одинаковые собственные имена. Например, имеется функция, возвращающая целое: int Factorial(int n); // Вернуть факториал n Тест для этой функции может быть таким: // Проверить факториал от 0.
TEST(FactorialTest, HandlesZeroInput) {
EXPECT_EQ(1, Factorial(0));
}
// Проверить факториал некоторых положительных значений.
TEST(FactorialTest, HandlesPositiveInput) {
EXPECT_EQ(1, Factorial(1));
EXPECT_EQ(2, Factorial(2));
EXPECT_EQ(6, Factorial(3));
EXPECT_EQ(40320, Factorial(8));
}Google Test группирует результаты тестов по наборам, так что связанные по смыслу тесты должны быть в одном наборе; другими словами, первый аргумент их TEST() должен быть одинаковым. В приведенном выше примере вы имеем два теста, HandlesZeroInput and HandlesPositiveInput, принадлежащих одному набору с именем FactorialTest. Доступно на: Linux, Windows, Mac. Тестовые классы: использования единой конфигурации для нескольких тестовВ какой-то момент вы может обнаружить, что пишите несколько тестов, использующих одинаковые данные. В этом случае можно задействовать тестовые классы, что позволит повторно использовать одну и ту же конфигурацию объектов для нескольких различных тестов. Для создания тестового класса:
Когда используете тестовый класс, пишите TEST_F() вместе TEST(), что даст тесту доступ к объектам и подпрограммам тестового класса. TEST_F(имя_набора_тестов, имя_теста) {
... тело_теста ...
}Как и у TEST() первый аргумент -- это имя набора тестов, но для TEST_F() он должен совпадать с именем тестового класса. Возможно вы догадались: _F от английского fixture. С сожалению система макросов в С++ не позволяет нам создать единый макрос, который бы поддерживал оба типа тестов. Использование неправильного макроса приведет к ошибке компиляции. Также вы должны объявить тестовый класс до использования его имени в TEST_F(), иначе вы получите ошибку компиляции "virtual outside class declaration". Для теста, объявленного с помощью TEST_F(), Google Test:
Google Test не использует повторно один и тот же экземпляр тестового класса для разных тестов. Любые изменения, которые тест может сделать в очередном экземпляре тестового класса, не затрагивают остальные тесты. Как пример, давайте напишем тест для очереди типа FIFO с именем Queue, имеющей следующий интерфейс: template <typename E> // E - типа элемента.
class Queue {
public:
Queue();
void Enqueue(const E& element);
E* Dequeue(); // Возвращает NULL, если очередь пуста.
size_t size() const;
...
};Сначала определяем тестовый класс. По соглашению вам стоит назвать его FooTest, где Foo - имя тестируемого класса. class QueueTest : public ::testing::Test {
protected:
virtual void SetUp() {
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}
// virtual void TearDown() {}
Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};В этом случае TearDown() не требуется, так как не нужно ничего освобождать после теста в дополнение к тому, что делается в деструкторе. Теперь напишем тесты, используя TEST_F() и тестовый класс. TEST_F(QueueTest, IsEmptyInitially) {
EXPECT_EQ(0, q0_.size());
}
TEST_F(QueueTest, DequeueWorks) {
int* n = q0_.Dequeue();
EXPECT_EQ(NULL, n);
n = q1_.Dequeue();
ASSERT_TRUE(n != NULL);
EXPECT_EQ(1, *n);
EXPECT_EQ(0, q1_.size());
delete n;
n = q2_.Dequeue();
ASSERT_TRUE(n != NULL);
EXPECT_EQ(2, *n);
EXPECT_EQ(1, q2_.size());
delete n;
}Мы использовали оба типа утверждений: ASSERT_* и EXPECT_*. Используйте EXPECT_*, если хотите, чтобы тест продолжил работу после регистрации ошибки. Если продолжение теста бессмысленно, то используйте ASSERT_*. Например, второе утверждение в тесте Dequeue -- ASSERT_TRUE(n != NULL), так как мы собираемся разыменовывать указатель n ниже, а это может закончиться нарушением защиты памяти, если n равен NULL. Когда все это запускается, происходит следующее:
Доступно на: Linux, Windows, Mac. Замечание: Google Test автоматически сохраняет все свои настройки, когда тестовый объект создается и восстанавливает их, когда он уже уничтожен. Запуск тестовTEST() и TEST_F() автоматически регистрируют ваши тест в Google Test. Так что в отличие от многих других тестовых библиотек для С++ вам не надо вручную регистрировать тест в отдельном списке для запуска. После объявления тестов вы можете просто вызвать функцию RUN_ALL_TESTS(), которая вернет 0, если все тесты прошли успешно, и 1 в противном случае. RUN_ALL_TESTS() запускает все тесты в вашем исполняемом модуле -- тесты могут находиться в разных тестовых наборах или разных исходных модулях. При старте макрос RUN_ALL_TESTS():
Дополнительно, если конструктор тестового класса завершился с ошибкой на шаге 2, шаги с 3 до 5 пропускаются. Аналогично если на шаге 3 возникает ошибка, то шаг 4 пропускается. Важно: Вы не должны игнорировать возвращаемое функцией RUN_ALL_TESTS() значение, иначе gcc сообщит вам об ошибке. Смысл этого в том, что автоматизированная система тестирования определяет успешность прохождения тестов по коду возврата, а не по данным, выведенным в стандартные потоки stdout/stderr; поэтому ваша функция main() должна возвращать значение, полученное от RUN_ALL_TESTS(). Также стоит помнить, что вы можете вызывать RUN_ALL_TESTS() только один раз. Повторный ее вызов может конфликтовать с дополнительными возможностями Google Test (например, "смертельные" тесты), и данная возможность не поддерживается. Доступно на: Linux, Windows, Mac. Пишем функцию main()Можете начать вот с такой заготовки: #include "this/package/foo.h"
#include <gtest/gtest.h>
namespace {
// Тестовый класс для тестирования класса Foo.
class FooTest : public ::testing::Test {
protected:
// Можете удалить любую или все из функций ниже, если они пустые.
FooTest() {
// Здесь можно подготовить тестовые данные для каждого теста.
}
virtual ~FooTest() {
// Здесь производится чистка мусора. Данная функция не должна
// генерировать исключений.
}
// Если конструктор или деструктор не подходят вам для настройки
// тестовых данных и чистки мусора, то можете использовать следующие
// методы:
virtual void SetUp() {
// Данная функция вызывается сразу после конструктора (до теста).
}
virtual void TearDown() {
// Данная функция вызывается сразу после теста (до деструктора).
}
// Объекты, объявленные тут, могут быть использованы во всем тестовом
// классе Foo.
};
// Проверяем, что метод Foo::Bar() правильно выполняет задачу Abc.
TEST_F(FooTest, MethodBarDoesAbc) {
const string input_filepath = "this/package/testdata/myinputfile.dat";
const string output_filepath = "this/package/testdata/myoutputfile.dat";
Foo f;
EXPECT_EQ(0, f.Bar(input_filepath, output_filepath));
}
// Проверяем, что класс Foo правильно выполняет задачу Xyz.
TEST_F(FooTest, DoesXyz) {
// Убеждаемся, что Xyz работает правильно в Foo.
}
} // namespace
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}Функция ::testing::InitGoogleTest() производит разбор параметров командной строки для Google Test и удаляет все неизвестные флаги. Это позволяет пользователю управлять тестовой программой через различные флаги, описанные в Advanced Google Test Guide. Вы должны вызвать эту функцию до RUN_ALL_TESTS(). Иначе настройки Google Test не будут должным образом проинициализированы. В Windows InitGoogleTest() поддерживает многобайтовые строки, и может быть скомпилирована в режиме UNICODE. Резонно предположить, что написание функции main() занятие скучное. Поэтому Google Test предоставляет уже готовую реализацию фунции main(). Если она вас устраивает, то просто прилинкуйте библиотеку gtest_main и все. Важное замечание для пользователей Visual C++Если вы помещаете тесты в библиотеку, а ваша функция main() находится в другой библиотеке или .exe файле, то такие тесты не будут работать. Причина в ошибке в Visual C++. Когда вы определяете тесты, Google Test создает соответствующие статические объекты для их регистрации. На эти объекты никто не ссылается, но их конструкторы все равно работают. Когда линкер Visual C++ обнаруживает, что ни на один объект в библиотеке никто не ссылается, он исключает такую библиотеку из линковки. Вам надо как-то сослаться на вашу библиотеку, чтобы линкер ее не выкинул. И вот как это делается. Где-нибудь в коде библиотеки объявите функцию: __declspec(dllimport) int PullInMyLibrary() { return 0; }Если вы помещаете тесты в статическую библиотеку (не DLL), тогда __declspec(dllexport) не нужно. Затем в главной программе напишите код, который будет вызывать данную функцию: int PullInMyLibrary(); static int dummy = PullInMyLibrary(); Это создаст видимость явного использования вашей библиотеки тестами, и позволит им быть зарегистрированными при старте. Также, если вы помещаете тесты в статическую библиотеку, то добавьте /OPT:NOREF в настройки линкера главной программы. Если вы используете графическую среду MSVC++, то зайдите в настройки проекта .exe файла Properties/Configuration Properties/Linker/Optimization и установите опцию References в Keep Unreferenced Data (/OPT:NOREF). Это не даст линкеру исключить отдельные имена функций, генерируемых вашими тестами, из конечного исполняемого файла. Есть еще одна проблема. Если вы используете Google Test как статическую библиотеку (как это задано в gtest.vcproj), ваши тесты также должны находиться в статической библиотеке. Если они у вас вынуждено находятся в DLL, вы должны скомпилировать Google Test тоже в форме DLL. Иначе ваши тесты не будут правильно запускаться или запускаться вообще. Из этого можно сделать вывод: для упрощения жизни -- не используйте библиотеки для ваших тестов! Что дальше?Поздравляем! Вы освоили основы Google Test. Вы можете начать писать и запускать тесты c Google Test, посмотреть примеры из Samples, или продолжить читать Advanced Google Test Guide, где описано множество других полезных возможностей Google Test. ОграниченияGoogle Test разработана быть безопасной для многопотокового выполнения. Однако у нас пока нет достаточно времени для реализации механизмов синхронизации для различных платформ. Поэтому, пока небезопасно использовать утверждения Google Test параллельно в двух потоках. Для большинства тестов это не является проблемой, когда утверждения проверяются в главном потоке. Если хотите, то вы можете самостоятельно разработать примитивы синхронизации в gtest-port.h. | |||||||||||||||||||||||||||||||||||||||||||||
Вызов RUN_ALL_TESTS запускает все тесты созданные с помощью макросов TEST или TEST_F. А как можно запустить отдельный тест?
RUN_ALL_TESTS запускает все тесты. Для выборочного запуска воспользуйтесь опцией командной строки --gtest_filter.
Замечание для Visual Studio, которого мне не хватало: в свойствах проекта с тестами в Build Event\Post-Build Event\Command Line можно прописать “$(TargetDir?)$(TargetFileName?)” для Release и Debug конфигураций. Тогда результаты теста будут отображаться в output после построения проекта. http://leefrancis.org/2010/11/17/google-test-gtest-setup-with-microsoft-visual-studio-2008-c/
Последнее актуально и для Boost тестов