Hatred's Log Place

DON'T PANIC!

Apr 9, 2021 - 2 minute read - programming

GTest: запуск теста только вручную

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

Тестовый фреймворк GTest.

GTest, как и многие другие имеет возможность фильтровать тесты, иными словами, выбирать только те, которые нужно запустить в данный момент времени:

./tests --gtest_filter=Categoty.some_test

Если фильтр не указывается, то запускаются все тесты. При этом, у Google Test есть макрос, который в рантайме позволяет исключить тест из прогона:

GTEST_SKIP()

Остаётся придумать, как передать условие, по которому этот макрос вызовется.

Дополнительный параметр командной строки заводить не хотелось, тем более, что уже есть фильтр… Оказалось, что достучаться к тому, что передано для --gtest_filter= в качестве параметра проще простого:

std::string str = ::testing::GTEST_FLAG(filter);

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

TEST(Category, some_test) {
    std::string str = ::testing::GTEST_FLAG(filter);
	auto enable_pos = str.find("Category.some_test");
	if (enable_pos == std::string::npos) {
    	GTEST_SKIP();
	}
}

Теперь, тест запустится, только если явно указать имя теста при запуске:

./tests --gtest_filter=Category.some_test

или все тесты, включая наш:

./tests --gtest_filter=Category.some_test:*

Под катом небольшой бонус :)

Бонус

Пытливый взгляд, мог заметить, что наш запустится если в командную строку передать что-то вроде:

./tests --gtest_filter=Category.some_test_super_postfix:*

Ещё, можно заметить, что не обрабатывается - перед тестом.

По последнему: в этом нет необходимости, потому как это будет обработано самим фреймворком и нашему тесту даже не будет передано управление.

По первому: да. Нужно добавить граничные условия и оформить в виде отдельной функции:

static bool skip_test(std::string_view test_name) {
    std::string filter = ::testing::GTEST_FLAG(filter);
    auto const pos = filter.find(test_name);

    if (pos == std::string::npos)
        return true;

    auto const end = pos + test_name.length();
    if (end < filter.length() && filter[end] != ':')
        return true;

    if (pos > 0 && filter[pos - 1] != ':')
        return true;

    return false;
}

TEST(Category, some_test) {
	if (skip_test("Category.some_test")) {
    	GTEST_SKIP();
	}
}

Ну и что бы не писать имя теста, добавляем ещё сахара:

static bool skip_this_test() {
    std::string full_name =
            std::string(::testing::UnitTest::GetInstance()->current_test_info()->test_suite_name()) + '.' +
            ::testing::UnitTest::GetInstance()->current_test_info()->name();
    return skip_test(full_name);
}

TEST(Category, some_test) {
	if (skip_this_test()) {
    	GTEST_SKIP();
	}
}

Сдобрить макросами (SKIP_TEST_IF() или вроде того) по вкусу.

Для чего?

У меня есть один тест, который замеряет производительность. По сути, все тейст кейсы, которые в нём есть, уже проверены, но нужно выполнить какую-то операцию заданное число раз и просто замерить время. Естественно такой тест замедляет выполнение всего набора, да и выполнять его нужно, по сути, один раз (ну или время от времени, что бы проверить отсутствие деградации по скорости).

Как-то так.