Google testing framework (gtest)

  • Tutorial
Когда вставал вопрос о тестировании кода, я не задумываясь использовал boost::test. Для расширения кругозора попробовал Google Test Framework. Помимо всяких имеющихся в нем плюшек, в отличии от boost::test проект бурно развивается. Хотел бы поделиться приобретенными знаниями. Всем кому интересно прошу

Ключевые понятия



Ключевым понятием в Google test framework является понятие утверждения (assert). Утверждение представляет собой выражение, результатом выполнения которого может быть успех (success), некритический отказ (nonfatal failure) и критический отказ (fatal failure). Критический отказ вызывает завершение выполнения теста, в остальных случаях тест продолжается. Сам тест представляет собой набор утверждений. Кроме того, тесты могут быть сгруппированы в наборы (test case). Если сложно настраиваемая группа объектов должна быть использована в различных тестах, можно использовать фиксации (fixture). Объединенные наборы тестов являются тестовой программой (test program).

Утверждения (assertion)


Утверждения, порождающие в случае их ложности критические отказы начинаются с ASSERT_, некритические — EXPECT_. Следует иметь ввиду, что в случае критического отказа выполняется немедленный возврат из функции, в которой встретилось вызвавшее отказ утверждение. Если за этим утверждением идет какой-то очищающий память код или какие-то другие завершающие процедуры, можете получить утечку памяти.

Имеются следующие утверждения (некритические начинаются не с ASSERT_, а с EXPECT_):

Простейшие логические


  • ASSERT_TRUE(condition);
  • ASSERT_FALSE(condition);


Сравнение


  • ASSERT_EQ(expected, actual); — =
  • ASSERT_NE(val1, val2); — !=
  • ASSERT_LT(val1, val2); — <
  • ASSERT_LE(val1, val2); — <=
  • ASSERT_GT(val1, val2); — >
  • ASSERT_GE(val1, val2); — >=


Сравнение строк


  • ASSERT_STREQ(expected_str, actual_str);
  • ASSERT_STRNE(str1, str2);
  • ASSERT_STRCASEEQ(expected_str, actual_str); — регистронезависимо
  • ASSERT_STRCASENE(str1, str2); — регистронезависимо


Проверка на исключения


  • ASSERT_THROW(statement, exception_type);
  • ASSERT_ANY_THROW(statement);
  • ASSERT_NO_THROW(statement);


Проверка предикатов


  • ASSERT_PREDN(pred, val1, val2, ..., valN); — N <= 5
  • ASSERT_PRED_FORMATN(pred_format, val1, val2, ..., valN); — работает аналогично предыдущей, но позволяет контролировать вывод


Сравнение чисел с плавающей точкой


  • ASSERT_FLOAT_EQ(expected, actual); — неточное сравнение float
  • ASSERT_DOUBLE_EQ(expected, actual); — неточное сравнение double
  • ASSERT_NEAR(val1, val2, abs_error); — разница между val1 и val2 не превышает погрешность abs_error


Вызов отказа или успеха


  • SUCCEED();
  • FAIL();
  • ADD_FAILURE();
  • ADD_FAILURE_AT(«file_path», line_number);


Можно написать собственную функцию, возвращающую AssertionResult

::testing::AssertionResult IsTrue(bool foo)
{
	if (foo)
		return ::testing::AssertionSuccess();
	else
		return ::testing::AssertionFailure() << foo << " is not true";
}

TEST(MyFunCase, TestIsTrue)
{
	EXPECT_TRUE(IsTrue(false));
}


Можно контролировать типы данных с помощью функции ::testing::StaticAssertTypeEq<T1, T2>(). Компиляция пройдет с ошибкой в случае несовпадения типов T1 и T2.

В случае ложности утверждения, выдаются данные, использованные в утверждении. Кроме того, можно задать собственный комментарий:
ASSERT_EQ(1, 0) << "1 is not equal 0";


Можно использовать расширенные наборы символов (wchar_t) как в комментариях, так и в утверждениях, касающихся строк. При этом выдача будет в UTF-8 кодировке.

Тесты (tests)



Для определения теста используется макрос TEST. Он определяет void функцию, в которой можно использовать утверждения. Как отмечалось ранее, критический отказ вызывает немедленный возврат из функции.
TEST(test_case_name, test_name)
{
	ASSERT_EQ(1, 0) << "1 is not equal 0";
}


TEST принимает 2 параметра, уникально идентифицирующие тест, — название тестового набора и название теста. В рамках одного и того же тестового набора названия тестов не должны совпадать. Если название начинается с DISABLED_, это означает, что вы пометили тест (набор тестов) как временно не используемый.

Можно использовать утверждения не только в составе теста, но и вызывать их из любой функции. Имеется лишь одно ограничение — утверждения, порождающие критические отказы не могут быть вызваны из не void функций.

Фиксации (fixtures)



Случается, что объекты, участвующие в тестировании, сложно настраиваются для каждого теста. Можно задать процесс настройки один раз и исполнять его для каждого теста автоматически. В таких ситуациях используются фиксации.

Фиксация представляет собой класс, унаследованный от ::testing::Test, внутри которого объявлены все необходимые для тестирования объекты при этом в конструкторе либо функции SetUp() выполняется их настройка, а в функции TearDown() освобождение ресурсов. Сами тесты, в которых используются фиксации, должны быть объявлены с помощью макроса TEST_F, в качестве первого параметра которого должно быть указано не название набора тестов, а название фиксации.

Для каждого теста будет создана новая фиксация, настроена с помощью SetUp(), запущен тест, освобождены ресурсы с помощью TearDown() и удален объект фиксации. Таким образом каждый тест будет иметь свою копию фиксации «не испорченную» предыдущим тестом.

#include <gtest/gtest.h>
#include <iostream>

class Foo
{
public:
	Foo()
		: i(0)
	{
		std::cout << "CONSTRUCTED" << std::endl;
	}
	~Foo()
	{
		std::cout << "DESTRUCTED" << std::endl;
	}
	int i;
};

class TestFoo : public ::testing::Test
{
protected:
	void SetUp()
	{
		foo = new Foo;
		foo->i = 5;
	}
	void TearDown()
	{
		delete foo;
	}
	Foo *foo;
};

TEST_F(TestFoo, test1)
{
	ASSERT_EQ(foo->i, 5);
	foo->i = 10;
}

TEST_F(TestFoo, test2)
{
	ASSERT_EQ(foo->i, 5);
}

int main(int argc, char *argv[])
{
	::testing::InitGoogleTest(&argc, argv);
	return RUN_ALL_TESTS();
}


В некоторых случаях создание тестируемых объектов является очень дорогой операцией, а тесты не вносят никаких изменений в объекты. В таком случае можно не создавать фиксации заново для каждого теста, а использовать распределенную фиксацию с глобальным SetUp()и TearDown(). Фиксация автоматически становится распределенной, если в классе имеется хотя бы один статический член. Статические функции SetUpTestCase() и TearDownTestCase() будут вызываться для настройки объекта и освобождения ресурсов соответственно. Таким образом, набор тестов перед первым тестом вызовет SetUpTestCase(), а после последнего TearDownTestCase().

Если существует потребность в SetUp() и TearDown() для всей программы тестирования, а не только для набора теста, необходимо создать класс-наследник для ::testing::Environment, переопределить SetUp() и TearDown() и зарегистрировать его с помощью функции AddGlobalTestEnvironment.

Запуск тестов



Объявив все необходимые тесты, мы можем запустить их с помощью функции RUN_ALL_TESTS(). Функцию можно вызывать только один раз. Желательно, чтобы тестовая программа возвращала результат работы функции RUN_ALL_TESTS(), так как некоторые автоматические средства тестирования определяют результат выполнения тестовой программы по тому, что она возвращает.

Флаги



Вызванная перед RUN_ALL_TESTS() функция InitGoogleTest(argc, argv) делает вашу тестовую программу не просто исполняемым файлом, выводящим на экран результаты тестирования. Это целостное приложение, принимающие на вход параметры, меняющие его поведение. Как обычно ключи -h, --help дадут вам список всех поддерживаемых параметров. Перечислю некоторые из них (за полным списком можно обратиться к документации).
  • ./test --gtest_filter=TestCaseName.*-TestCaseName.SomeTest — запустить все тесты набора TestCaseName за исключением SomeTest
  • ./test --gtest_repeat=1000 --gtest_break_on_failure — запустить тестирующую программу 1000 раз и остановиться при первой неудаче
  • ./test --gtest_output=«xml:out.xml» — помимо выдачи в std::out будет создан out.xml — XML отчет с результатами выполнения тестовой программы
  • ./test --gtest_shuffle — запускать тесты в случайном порядке

Если вы используете какие-то параметры постоянно, можете задать соответствующую переменную окружения и запускать исполняемый файл без параметров. Например задание переменной GTEST_ALSO_RUN_DISABLED_TESTS ненулевого значения эквивалентно использованию флага --gtest_also_run_disabled_tests.

Вместо заключения



В данном посте я кратко пробежался по основным функциям Google Test Framework. За более подробными сведениями следует обратиться к документации. Оттуда вы сможете почерпнуть информацию о ASSERT_DEATH используемом при падении программы, о ведении дополнительных журналов, о параметризованных тестах, настройке вывода, тестировании закрытых членов класса и многое другое.

UPD: По справедливому замечанию хабрапользователя nikel добавлена краткая инофрмация по использованию флагов.
UPD 2: Исправление разметки после изменений на Хабре (нативный тег source).

Комментарии 22

    +2
    Всем рекоммендую. Google testing framework позволяет сконцентрироваться непосредственно на тестах — минимум тупой писанины, не относящейся к вашей предметной области. Многим не нравятся макро — это только вначале. На самом деле всё очень рационально, и благодаря им, вывод ошибок вестма приятен
      +3
      Сам по себе Google C++ Testing Framework мало полезен. Если принять во внимание тот факт, что модульные тесты должны быть неинвазивными (т.е. никаких необратимых изменений среды); то его сфера применения окажется очень узкой.

      Я бы посмотрел сразу в сторону Google C++ Mocking Framework, который уже включает в себя googletest.
        0
        А как моки позволяют избавиться от неинвазивности и почему это нельзя сделать без них? Я бы сто раз подумал, прежде чем браться за моки.
          0
          От требования неинвазивности избавиться нельзя в принципе. Если модульный тест не способен обеспечить неизменяемые условия проверки, то зачем он нужен?

          Что касается mock'ов, то я вовсе не призываю использовать их везде и повсюду. Можно конечно и без них, если требуется покрыть тестами только успешные сценарии.

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

          Сам я уже не помню, когда использовал googletest отдельно от googlemock.
            0
            Да, стрессовые ситуации это как раз тот случай, когда без моков нельзя.
      0
      Боже, я никогда не думал что увижу эту картинку (которая мне все глаза уже намозолила) на Хабре! :-)
        0
        Да, за картинку я уже отхватил в карму. =) Ну что поделать, если это, так сказать, официальная картинка фреймворка…
        +4
        hoxnox, а Вы не смотрели средства тестирования, встроенные в qt? Хотелось бы увидеть сравнение подобного инструментария.
          0
          Даже Qt-шные приложения я тестировал boost::test'ом, так что ничего сказать не могу. Но спасибо, что напомнили о наличии в Qt тестов.
          0
          кажется, лучшее решение для скетч-приложений
            +2
            Жалко в C++0x не добавили базовой функциональности для юнит-тестирования. Фреймворк это хорошо, но встроенная поддержка работает лучше и избавляет от несовместимости разных фреймворков. Как в D например:

            class Foo
            {
            private int k;
            public void dosomething();
            }

            unittest
            {
            Foo foo;

            foo.dosomething();

            assert( foo.k < 9 && foo.k > 0 ); // приватная переменная видна
            }


            Здорово же.
              +1
              По-моему дух программирования на C++ предполагает использование конкретных библиотек для решения конкретных задач.
                0
                Дух программирования на C++ слишком силён :). (А я ведь почти фанат этого языка)

                Нет, серьёзно, главная лозунг C++ это «не плати за то, чем не пользуешся». Встроенные юнит-тесты ему не противоречат.

                Минус использования сторонних библиотек думаю очевиден — проблемно перетащить код в другой проект.
                Это как в C — какой бы не был проект в нём обязательно будет определён тип ИМЯ_ПРОЕКТА_BOOL — вроде бы фигня, но этот зоопарк раздражает.
                +1
                В С++0х не вкдючили и более серъёзные вещи, например, концепции за которые ратовал сам Страуструп и просил их включить хотя бы как опшионал. Не включили модули, хотя всем очевидно, что это как раз то, чего не хватает с и с++, и вот это как раз нисколько с/с++ не испортило бы. Первое сильно продвинуло бы вперёд метапрограммирование и параметрический полиморфизм вместо стандартной модели наследования и ад-хок полиморфизма, а второе позволило бы навести порядок с единицами трансляции и заголовочными файлами.
                0
                hoxnox, очень жаль, что обошли вниманием такую не очевидную вещь, как флаги.
                  0
                  Вы совершенно правы, чуть не пропустил одну из самых вкусных плюшек. Спасибо. Обновил.
                    0
                    Вам спасибо за краткую и содержательную статью.
                  0
                  Подборочка постов про Google Test на русском языке:

                  easy-coding.blogspot.com/search/label/gtest

                  Немного переведенной документации по Google Test and Google Mock:

                  code.google.com/p/googletest-translations/
                    0
                    Кстати, для запуска тестов есть уже готовый исходник ${GTEST_DIR}/src/gtest_main.cc, содержащий функцию main, где производится инициализация фреймворка и запуск тестов:

                    #include <iostream>
                    
                    #include "gtest/gtest.h"
                    
                    GTEST_API_ int main(int argc, char **argv) {
                      std::cout << "Running main() from gtest_main.cc\n";
                    
                      testing::InitGoogleTest(&argc, argv);
                      return RUN_ALL_TESTS();
                    }

                    Очень удобно. Просто пишешь тесты, а при сборке указываешь компилятору этот файл. Мелочь, а приятно.
                      0
                      Добавлю, что флаги можно указывать непосредственно в теле программы, что может быть удобно. Например, так:

                      int main(int argc, char *argv[])
                      {
                      // For Android ADB
                      std::ios_base::sync_with_stdio(false);

                      testing::InitGoogleTest(&argc, argv);

                      // Force print tests times
                      testing::GTEST_FLAG(print_time) = true;

                      // Filter tests
                      testing::GTEST_FLAG(filter) = "LoggerTst.*";

                      return RUN_ALL_TESTS();
                      }
                        +1
                        Некоторое время назад перешел на unittest++
                        Он очень хороше подходит для случаев когда дополнительный функционал не нужен а хочется простоты.

                        TEST(UnitTestName) {
                        //Any code
                        }

                        это все что нужно для написания теста. Это позволяет очень быстро их строчить, что у меня очень положительно сказывается на их количестве и вообще использованиями — быстрее написать тест чем запустить.
                        Также очень хороший вывод результатов и ошибок.

                        для проверок доступны простые макросы
                        CHECK(condition), condition == true
                        CHECK_EQUAL(expected, actual) — любые типы данных
                        CHECK_CLOSE(expected, actual, tolerance) — для проверки double/float
                        CHECK_ARRAY_EQUAL(expected, actual, count)
                        CHECK_ARRAY_CLOSE(expected, actual, count, tolerance)
                        CHECK_THROW(expression, ExpectedExceptionType)

                        Советую посмотреть тем кто хочеть простоты и не нуждается в больших возможностях.
                        Что еще приятно в линукс системах подключается просто добавление библиотеки.
                          0
                          А чем сложнее линковаться с gtest вместо unittest++'овской библиотечки и использовать их gtest assert'ы и expect'ы вместо check'ов? По-моему сложность такая же, а в выхлопе куча потенциальных (если вы их не используете) возможностей.

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                        Самое читаемое