Тесты TEST_P в библиотеке не запускаются, когда INSTANTIATE_TEST_CASE_P вызывается в отдельном исполняемом файле - PullRequest
0 голосов
/ 05 марта 2019

Я пытаюсь следовать инструкциям для Создание параметризованных по значению абстрактных тестов в googletest README.Я создал каталог проекта, в котором у меня есть CMakeLists.txt, fixture.hh, fixture.cc, test.cc, и весь репозиторий Google Test извлечен в подкаталоге googletest.Моя цель состоит в том, чтобы просто создать библиотеку с классом текстовых фикстур и набором TEST_P тестов, которые могут быть связаны отдельными исполняемыми файлами модульных тестов, чтобы минимизировать дублирование кода.

CMakeLists.txt:

cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

enable_testing()
add_subdirectory(googletest)
include(GoogleTest)

add_library(test_utils STATIC fixture.cc)
target_link_libraries(test_utils PUBLIC gtest)

add_executable(unit_tests test.cc)
target_link_libraries(unit_tests PRIVATE test_utils gtest_main)
gtest_discover_tests(unit_tests)
// fixture.hh

#pragma once

#include <gtest/gtest.h>

namespace test_utils { /* *************************************************** */

class Fixture : public ::testing::TestWithParam<size_t> {};

} /* namespace test_utils *************************************************** */
// fixture.cc

#include "fixture.hh"

using namespace test_utils;

TEST_P( Fixture, foo )
{
  ASSERT_EQ( GetParam()%2, 0 );
}
// test.cc

#include "fixture.hh"

namespace { /* ************************************************************** */

using namespace test_utils;

INSTANTIATE_TEST_CASE_P( BarInstantiation, Fixture, ::testing::Values( 18 ) );

} /* namespace ************************************************************** */

Когда я запускаю ./unit_tests, он выдает:

Running main() from ../googletest/googletest/src/gtest_main.cc
[==========] Running 0 tests from 0 test cases.
[==========] 0 tests from 0 test cases ran. (0 ms total)
[  PASSED  ] 0 tests.

ctestвыходы No tests were found!!!.Что я делаю не так?

Ответы [ 3 ]

0 голосов
/ 05 марта 2019

TEST_P - это макрос, который при раскрытии объявляет полный класс C ++, к которому INSTANTIATE_TEST_CASE_P добавляет функциональность.

Поскольку ваш TEST_P находится в отдельном файле .cc, ваш вызов INSTANTIATE_TEST_CASE_P (который находится в его собственном файле .cc) не может его увидеть, поэтому он по сути определяет функцию без класса-владельца.

С другой стороны, класс, определенный в TEST_P, не оснащен методами, для которых INSTANTIATE_TEST_CASE_P определил бы его.Поэтому он не находит никаких тестовых экземпляров для запуска.

Вам нужно переместить объявление TEST_P туда, где ваш вызов INSTANTIATE_TEST_CASE_P может увидеть его, чтобы оно заработало.

Предупреждение, хотя - TEST_Pне только объявляет класс, но определяет несколько функций.Поэтому, если вы попытаетесь поместить его в файл заголовка, вы можете получить некоторые ошибки компоновщика, относящиеся к этим методам класса (как уже определено, например).Таким образом, лучшее место для этого может быть рядом с вызовом INSTANTIATE_TEST_CASE_P.

TEST_P действительно предназначен для того, чтобы быть его собственным независимым тестовым примером, и кажется, что он был разработан, чтобы войти в его собственный исходный файл, инедоступен как компонент библиотеки, как вы, похоже, пытаетесь это сделать.

0 голосов
/ 07 марта 2019

Как вы знаете, стандартная процедура юнит-тестирования с помощью googletest:

  1. Создание библиотеки, содержащей тестируемый код.
  2. Напишите свои юнит-тесты googletestв одном или нескольких исходных файлах.
  3. При желании напишите исходный файл, который определяет main в стандартным способом googletest для запуска модульных тестов.Кроме того, вы можете не определять main, а вместо этого добавлять libgtest_main к вашей связи.
  4. Скомпилировать все источники googletest из 2 и 3 в объектные файлы.
  5. Свяжите тест-гугл-тестера, введя все объектные файлы вместе с библиотекой из 1 плюс libgtest_main (если вам это нужно), плюс libgtest

Вы хотели бы связать libgtest_main вместо определения main, и , вы хотели бы изменять процедуру неортодоксальным способом, архивируя все объектные файлы из 2 в статической библиотеке и добавление ее к вашей связи вместо прямой привязки объектных файлов.

И в вашем опубликованном примере есть еще один тривиальный вариант.У вас нет библиотеки тестируемого кода, потому что она не нужна для иллюстрации вашей проблемы.Вы только что получили модульный тест заполнителя, который обязательно пройдет, если он будет запущен.Проблема в том, что он даже не запускается!

Давайте пока отложим CMake, так как это не имеет значения для объяснения вашей головоломки.Здесь у меня есть ваши файлы, которые были отправлены:

$ ls -R
.:
fixture.cc  fixture.hh  test.cc

Сначала я собираюсь собрать бегуна googletest по стандартной процедуре (за исключением того факта, что нет тестируемого кода).

Скомпилируйте источники:

$ g++ -Wall -Wextra -c test.cc fixture.cc

Ссылка:

$ g++ -o tester test.o fixture.o -lgtest_main -lgtest -pthread

Запуск:

$ ./tester
Running main() from /home/imk/Downloads/googletest-master/googletest/src/gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from BarInstantiation/Fixture
[ RUN      ] BarInstantiation/Fixture.foo/0
[       OK ] BarInstantiation/Fixture.foo/0 (0 ms)
[----------] 1 test from BarInstantiation/Fixture (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (0 ms total)
[  PASSED  ] 1 test.

Ничего плохого в этом нет.

Теперь ясделаю тест-бегун снова так, как вы хотите.Не нужно перекомпилировать какие-либо источники.Я просто создаю статическую библиотеку, включающую fixture.o:

$ ar rcs libutest.a fixture.o

и перекомпоновываю, используя ее вместо fixture.o:

$ g++ -o tester test.o -L. -lutest -lgtest_main -lgtest -pthread

И снова запускаю:

$ ./tester
Running main() from /home/imk/Downloads/googletest-master/googletest/src/gtest_main.cc
[==========] Running 0 tests from 0 test cases.
[==========] 0 tests from 0 test cases ran. (0 ms total)
[  PASSED  ] 0 tests.

Вот твоя загадка.Там нет тестов сейчас.Как получилось?

Наконец, я собираюсь связать тестового бегуна третьим способом:

$ g++ -o tester test.o -lgtest_main -lgtest -pthread

Не связывает либо fixture.o или libutest.a.И тот работает:

$ ./tester
Running main() from /home/imk/Downloads/googletest-master/googletest/src/gtest_main.cc
[==========] Running 0 tests from 0 test cases.
[==========] 0 tests from 0 test cases ran. (0 ms total)
[  PASSED  ] 0 tests.

точно так же, как второй.В этом случае это не удивительно.Мы знаем , что бегун googletest может быть связан без каких-либо тестов, и это то, что он будет делать.

Давайте немного изучим символы, которые определены в fixture.o.

$ nm -C --defined-only fixture.o | egrep -w '(W|V)' | wc -l
463

Там 463 слабые символы определены.И определены только 2 сильных символа:

$ nm -C --defined-only fixture.o | egrep -w '(A|B|C|D|G|R|S|T)'
0000000000000000 B Fixture_foo_Test::gtest_registering_dummy_
0000000000000000 T Fixture_foo_Test::TestBody()

Линкер имеет важное значение для различения слабых и сильных символов.

компоновщик не будет связывать программу, которая содержит какую-либо сильную символьную ссылку, которую он не может разрешить, т. е. для которой он не может найти определения в связи.Не удастся связать с ошибками undefined reference .Но он не должен разрешать слабые ссылки для ссылки на программу.Неразрешенная ссылка на слабый символ просто остается нулевой (= 0) в программе.

Компоновщик не будет связывать программу (или совместно используемую библиотеку), чья связь создает несколько строгих определений одного и того же символа.Он не сможет установить связь с ошибками множественное определение .Но он будет допускать несколько слабых определений, а также одно строгое определение одного и того же символа, и в этом случае он выбирает строгое определение для разрешения ссылок и отбрасывает все слабые.И он будет терпеть несколько слабых определений символа без какого-либо сильного.В этом случае он выберет одно из слабых определений произвольно - на практике, только первое, которое видит - и отбросит все остальное.

Компилятор C ++ обычно генерирует смеси определений слабых и сильных символов в порядкеперевести C ++ в успешно скомпонованный объектный код.Встроенные методы классаи экземпляры шаблонов функций должны быть скомпилированы со слабыми определениями.

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

Когда вы предлагаете объектному файлу foo.o компоновщику, он безоговорочно свяжет его с программой .

A статическая библиотека - это архив объектных файлов, которые вы предлагаете компоновщику , из которого можно извлечь только те, которые ему нужны , и связать их впрограмма.По умолчанию он не будет извлекать и связывать любой из них безоговорочно.Только те, которые разрешают символы, уже упомянутые в связи , для которых нужно найти определения .

Но ссылка на слабый является той, которую компоновщик не нужно разрешать , чтобы связать программу.Таким образом, по умолчанию никогда не извлекает и не связывает объектный файл из статической библиотеки, чтобы разрешить слабую ссылку на символ.Ссылка может быть неопределенной в программе, поэтому компоновщик не будет тратить усилия на поиск библиотек для ее определения.

Собирая факты, которые мы только что просмотрели, мы знаем, что связь:

$ g++ -o tester test.o fixture.o -lgtest_main -lgtest -pthread

, который выполнил TEST_P, определенный в fixture.cc, - это тот, который вставит ссылку fixture.o в программу, потому что это объектный файл.

И мыТакже известно, что связь:

$ g++ -o tester test.o -L. -lutest -lgtest_main -lgtest -pthread

, которая выполнила 0 тестов, извлечет и свяжет libutest.a(fixture.o) с программой только , если test.o содержит хотя бы одну ссылку на сильный символэто определено в fixture.o, то есть ссылка на один из:

Fixture_foo_Test::gtest_registering_dummy_
Fixture_foo_Test::TestBody()

, поскольку все 463 других символа, определенных в fixture.o, являются слабыми.

Но test.o не делает никакихссылки на любые символы, содержащие Fixture_foo_Test:

$ nm -C test.o | grep Fixture_foo_Test; echo Done
Done

Таким образом, мы можем заключить, что связь:

$ g++ -o tester test.o -L. -lutest -lgtest_main -lgtest -pthread

будет не извлекать и ссылаться libutest.a(fixture.o).Это точно такая же связь, как:

$ g++ -o tester test.o -lgtest_main -lgtest -pthread

, которая, как мы знаем, содержит 0 тестов.

Помещение fixture.o в статическую библиотеку делает его ненужным длясвязь tester, и она не связана.

В этом свете ваш файл CMakeLists.txt легко исправить, упростив его до:

cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

enable_testing()
add_subdirectory(googletest)
include(GoogleTest)

add_executable(unit_tests test.cc fixture.cc)
target_link_libraries(unit_tests PRIVATE gtest_main gtest)
gtest_discover_tests(unit_tests)

, чтов результате fixture.o будет безоговорочно связан с unit_tests, и программа выдаст:

[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from BarInstantiation/Fixture
[ RUN      ] BarInstantiation/Fixture.foo/0
[       OK ] BarInstantiation/Fixture.foo/0 (0 ms)
[----------] 1 test from BarInstantiation/Fixture (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 1 test.

, как вы и надеялись.

Очевидно, что это решение не удовлетворяет вашей заявленной мотивации для создания libtest_utils.a в первую очередь:

Моя цель состоит в том, чтобы просто создать библиотеку с классом текстовых фикстур и набором тестов TEST_P, с которыми могут быть связаны отдельные исполняемые модули модульных тестов, чтобы минимизировать дублирование кода.

Но эта цель неверна.Нет смысла создавать статическую библиотеку из набора TEST_P тестовых источников (или любых источников), если вы собираетесь перестроить ее из тех же источников для связи с каждымисполняемый файл, которому нужны некоторые объектные файлы, которые вы будете архивировать в статической библиотеке.Если вы собираетесь перестраивать его из исходного кода для каждого исполняемого файла, который в этом нуждается, вы минимизируете дублирование кода, просто сохраняя все TEST_P тестовые источники в одном общем месте или хранилище, и все еще не будетТочка в перестройке статической библиотеки для каждого исполняемого файла.Каждый из этих исполняемых проектов может также включать некоторые или все TEST_P источники библиотеки из своих собственных источников и не беспокоить перестройку библиотеки или создание ссылок на нее.

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

Вы хотите, чтобы googletest создавался вашим проектом модульного тестирования, а ваши TEST_P исходники компилировались и связывались с тем же googletest в том же проекте.Поэтому нет смысла архивировать объектные файлы TEST_P, которые вы встраиваете в промежуточную статическую библиотеку, в отличие от простого связывания их непосредственно в исполняемом модульном тесте.И, если вы do поместите их в промежуточную статическую библиотеку, по умолчанию компоновщик будет их игнорировать.

Я сказал по умолчанию , компоновщик будет их игнорировать, потому чтоможно заставить компоновщик связать объектный файл из статической библиотеки, даже если это не нужно.Вы можете сделать это, заставив его извлечь и связать все объектные файлы в статической библиотеке, используя параметр --whole-archive компоновщика .Если вызывать компоновщик как обычно через gcc / g ++ (или clang / clang ++), параметры связывания, которые вам понадобятся для статической библиотеки libbar.a, будут следующими:

... -Wl,--whole-archive -lbar -Wl,--no-whole-archive ...

Вам необходимо включить--whole-archive до -lbar и отключите его впоследствии, потому что в противном случае он будет применяться не только к -lbar, но и ко всем последующим библиотекам, включая даже те, которые вы даже не упоминаете в командной строке и которые связаны по умолчанию.

Если вы принудительно связываете все объектные файлы в libbar.a, то, конечно, любые слабые определения, которые они содержат , будут использоваться для разрешения слабых ссылок на символы в программе.

Тем не менее, не так-то просто получить опции связывания -Wl,--whole-archive -lbar -Wl,--no-whole-archive в проекте CMake, который строит libbar.a.См. этот вопрос и ответ.И опять же, в этом нет никакого смысла, так зачем бороться с рамками?Googletest ожидает, что вы создадите свои юнит-тесты в своем проекте юнит-тестов и сделаете очевидную вещь: свяжите объектные файлы с программой.Если вы сделаете это, ваши юнит-тесты будут связаны и запущены, как вы ожидаете.

0 голосов
/ 05 марта 2019

Можете ли вы проверить, имеет ли это значение, если вы связываете fixture.cc напрямую (без предварительного создания статической библиотеки).

В официальном googletest репозитории есть тестовый пример, который явно тестирует, что тестовые примеры могут быть определены и созданы в разных единицах перевода.Я не совсем уверен, но добавление статической библиотеки около fixture.cc может привести к проблеме статического порядка инициализации .

...