Ответ 1
Из настроек сцены собрано, что библиотека, чей тестовый пример gtest
пропадает, статически связана в сборке приложения. Кроме того,
Используется инструментальная цепочка GNU.
Причина проблемного поведения проста. Тест
программа не содержит ссылок на что-либо в библиотеке, которая содержит
TEST(X, just_a_passing_test)
. Поэтому компоновщику не нужно связывать какие-либо
объектный файл из этой библиотеки, чтобы связать программу. Так оно и есть. Итак
gtest
runtime не находит этот тест в исполняемом файле, потому что его нет.
Это помогает понять, что статическая библиотека в формате GNU является архивом объектных файлов, украшенных блоком заголовков домохозяйства и глобальной таблицей символов.
OP обнаружил, что путем кодирования в программе специальной ссылки на любой публичный символ в библиотеке проблем, он мог "магически" заставить его тестового примера в программу.
Никакой магии. Чтобы удовлетворить ссылку на этот общедоступный символ, компоновщик
теперь обязано связать объектный файл с библиотекой - тот, который содержит
определение символа. И ОП сообщает, что библиотека создана
от a .cpp
. Таким образом, в библиотеке есть только один объектный файл, и он
содержит также определение тестового примера. С этим объектным файлом в
связь, тестовый пример находится в программе.
OP трясет тщетно с параметрами компилятора, переключаясь с GCC на clang,
в поисках более респектабельного способа достижения той же цели. Компилятор
не имеет значения. GCC или clang, он получает связь с системным компоновщиком, ld
(если не будут приняты необычные меры для его замены).
Есть ли более респектабельный способ получить ld
для связывания объектного файла с
статическая библиотека, даже если программа ссылается на отсутствие символов в этом объектном файле?
Есть. Скажем, что проблема в программе app
, и проблема статической библиотеки
libcool.a
Тогда обычная командная строка GCC, которая связывает app
, напоминает это, в соответствующем
указывает:
g++ -o app -L/path/to/the/libcool/archive -lcool
Это делегирует командную строку ld
, с дополнительными параметрами компоновщика и
библиотеки, которые g++
считает по умолчанию для системы, в которой он находится.
Когда компоновщик приходит к рассмотрению -lcool
, он выяснит, что это запрос
для архива /path/to/the/libcool/archive/libcool.a
. Тогда это будет фигурировать
вне зависимости от того, имеет ли он в данный момент какие-либо неразрешенные ссылки на символы в руке
определения которых скомпилированы в объектных файлах в libcool.a
. Если есть
любой, то он свяжет эти объектные файлы с app
. Если нет, то ссылки
ничего от libcool.a
и не переходит.
Но мы знаем, что в libcool.a
есть определения символов, которые мы хотим
link, хотя app
не ссылается на них. В этом случае мы можем сказать
компоновщик, чтобы связать объектные файлы с libcool.a
, даже если они
не упоминается. Точнее, мы можем сказать g++
сказать компоновщику, чтобы это сделать,
так:
g++ -o app -L/path/to/the/libcool/archive -Wl,--whole-archive -lcool -Wl,-no-whole-archive
Те опции -Wl,...
указывают g++
передать параметры ...
в ld
. --whole-archive
option сообщает ld
связать все объектные файлы с последующими архивами, независимо от того,
ссылаются или нет, до дальнейшего уведомления. -no-whole-archive
указывает
ld
, чтобы прекратить это делать и возобновить работу, как обычно.
Может показаться, что -Wl,-no-whole-archive
является избыточным, так как это последнее, что на
g++
командной строки. Но это не так. Помните, что g++
добавляет системные библиотеки по умолчанию
до командной строки, за кулисами, прежде чем передать его в ld
. Вы определенно
не хотите, чтобы --whole-archive
действовал, когда эти библиотеки по умолчанию связаны.
(Связь не с ошибками определения).
Примените это решение к задаче и TEST(X, just_a_passing_test)
будет выполняться без взлома, чтобы заставить программу сделать некоторые операции no-op
ссылку в объектный файл, который определяет этот тест.
В этом случае очевидный недостаток этого решения. Если произойдет, что библиотека из
которые мы хотим принудительно связать с каким-либо объектным файлом без ссылок, содержит
кучу других файлов с объектами без ссылок, которые нам действительно не нужны.
--whole-archive
связывает их все они тоже, и они просто раздуваются в программе.
Решение --whole-archive
может быть более респектабельным, чем ссылка no-op
взломать, но это не респектабельно. Он даже не выглядит респектабельным.
Реальное решение здесь - это сделать разумную вещь. Если вы хотите компоновщик, чтобы связать определение чего-то в вашей программе, тогда не держите это в секрете от компоновщик. По крайней мере, объявите вещь в каждом блоке компиляции, где вы ожидайте, что его определение будет использовано.
Выполнение разумной вещи при тестировании gtest
предполагает понимание того, что
Макрос gtest
, подобный TEST(X, just_a_passing_test)
, расширяется до определения класса,
в этом случае:
class X_just_a_passing_test_Test : public ::testing::Test {
public:
X_just_a_passing_test_Test() {}
private:
virtual void TestBody();
static ::testing::TestInfo* const test_info_ __attribute__ ((unused));
X_just_a_passing_test_Test(X_just_a_passing_test_Test const &);
void operator=(X_just_a_passing_test_Test const &);
};
(плюс статический инициализатор для test_info_
и определение для TestBody()
).
Аналогично для вариантов TEST_F
, TEST_P
. Следовательно, вы можете развернуть эти
макросов в вашем коде с теми же ограничениями и ожиданиями, которые
применяются к определениям классов.
В этом свете, если у вас есть библиотека libcool
, определенная в cool.h
, реализована в cool.cpp
и хотите gtest
модульные тесты для него, выполняемые тестовой программой tests
который реализован в tests.cpp
, разумная вещь: -
- Введите заголовочный файл,
cool_test.h
-
#include "cool.h"
в нем -
#include <gtest/gtest.h>
. - Затем определите в нем
libcool
тестовые примеры -
#include "cool_test.h"
вtests.cpp
, - Скомпилировать и связать
tests.cpp
сlibcool
иlibgtest
И это очевидно, почему вы не сделали бы то, что сделал OP. Вы бы не определили
классы, которые необходимы tests.cpp
и не нужны cool.cpp
, в пределах cool.cpp
а не в tests.cpp
.
ОП не просил совета по определению тестовых случаев в библиотеке потому что:
как еще вы могли бы протестировать библиотеки, не имея исполняемого файла для каждого, что быстро ускоряет их.
Как правило, я бы рекомендовал практиковать сохранение gtest
исполняемого файла
на каждую библиотеку, подлежащую тестированию на единицу: быстро запускать их безболезненно с помощью обычных инструментов автоматизации
такой make
, и гораздо лучше получить приемочный/ошибочный вердикт на каждую библиотеку, чем
просто вердикт для кучки библиотек. Но если вы не хотите этого делать,
возражение:
// tests.cpp
#include "cool_test.h"
#include "cooler_test.h"
#include "coolest_test.h"
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Скомпилировать и связать с libcool
, libcooler
, libcoolest
и libgtest