Можно ли протестировать "внутренний" класс из dll C++ с помощью MSTest?
В настоящее время мы пытаемся добавить модульное тестирование в наше приложение C++. Приложение состоит из 30 проектов, которые генерируют 29 DLL и 1 EXE. Мы используем MSTest для запуска нашего модульного теста, так как он уже включен в Visual Studio 2010.
Это прекрасно работает для класса, которые объявлены "публичными". Эти классы имеют это в начале:
#ifdef RESEAU_IMPL
#define CLASS_DECL _declspec(dllexport)
#else
#define CLASS_DECL _declspec(dllimport)
#endif
Но для всех остальных классов (90% кода) они не объявлены публичными, поэтому мы не можем использовать их в нашем тесте.
Я читал в Google об атрибуте InternalVisibleTo, но, похоже, он работает только со сборкой C# .NET. Я прав? Я также читаю, чтобы объявить мой класс "as_friend", но я не уверен, где это поставить.
Итак, вкратце: я хочу протестировать класс, который не экспортируется / не публикуется в DLL. Как я могу это сделать?
Спасибо
* РЕДАКТИРОВАТЬ *
Гишу заметил, что модульное тестирование невозможно в неуправляемом коде, но это возможно. Видите, это TestMethode, который тестирует собственный код C++. CVersion находится в C++ MFC.
[TestMethod]
void AssignationCVersion()
{
CVersion version1234(1,2,3,4);
CVersion version4321(4,3,2,1);
Assert::IsTrue(version1234 != version4321);
version1234 = version4321;
Assert::IsTrue(version1234 == version4321);
};
Но то, что кажется невозможным, - это использование специального тега для тестирования внутренней функции. Я первый согласен с тем, что тестирование внутреннего метода не является хорошей практикой, но эти DLL не являются служебными функциями, а являются частью "реального" приложения (может быть, это плохой дизайн но это было сделано 15 лет назад). У кого-нибудь есть идеи на эту тему?
3 ответа
Нет никакого способа, независимо от того, являетесь ли вы модульным тестовым фреймворком или чем-то еще, тестировать код, который вы не видите. DLL в Windows экспортирует только те символы, которые имеют __declspec(dllexport)
определены. Любой другой символ обрабатывается как внутренний, когда DLL компилируется, и не будет виден для кода, использующего DLL.
Это важно, потому что это означает, что компоновщик может оптимизировать, изменять или удалять код, который не экспортируется. Код, который вы хотите проверить, может вообще отсутствовать. Это может быть там, но в другой форме, чем вы ожидаете. DLL компилируется по контракту, что-либо объявлено с dllexport
должен присутствовать и быть видимым, а все остальное просто должно работать. Это не должно быть доступно из внешнего мира.
Это не недостаток MSTest (хотя он имеет множество других недостатков и является довольно ужасным выбором для модульного тестирования кода C++)
Если вы хотите проверить этот код, у вас есть два варианта:
- экспортировать его с
dllexport
, или же - напишите свой код модульного теста как часть самой dll.
Смотрите также вопрос: модульное тестирование неэкспортированных классов в DLL
Три варианта выглядят так:
- Поместите тестовый код в DLL, чтобы он имел доступ к неэкспортированным классам и функциям
- Добавьте все файлы, содержащие тестовый код, в тестовый проект, чтобы они компилировались дважды (не знаю, относится ли это к MSTEst, но было бы так, как бы вы это делали, используя что-то вроде Boost test или CPPunit)
- Соберите весь неэкспортируемый тестируемый код в статическую библиотеку, которая затем связывается с тестовым кодом и с DLL.
Все они имеют разные проблемы.
Поместить тестовый код в DLL не идеально. Либо вы включаете его только в непроизводственные сборки, в этом случае вы не тестируете то, что выпускаете, либо включаете все сборки, в этом случае вы отправляете тестовый код, что может быть нежелательно. Кроме того, тогда должна быть какая-то точка входа для доступа к этим тестам, что вынуждает компилятор включать весь код, не позволяя оптимизатору удалить его, если в противном случае он будет считаться недоступным (возможно, часть кода если к тестированию нельзя получить доступ ни из одного из открытых методов в DLL, поэтому оптимизатор может решить удалить их как мертвый код - тестирование в DLL предотвращает это).
Добавление исходных файлов в оба проекта увеличивает время сборки и сложность обслуживания. Каждый раз, когда вы добавляете новый исходный файл или удаляете исходный файл, его нужно будет добавить в обоих местах. Также, в зависимости от размера кода, это может значительно увеличить время сборки, так как приходится много кода создавать вдвое.
Помещение всего неэкспортируемого тестируемого кода в статическую библиотеку имеет недостаток в создании дополнительного проекта в решении и усложняет организацию. Вы должны быть осторожны со структурой кода (например, один исходный файл должен содержать только экспортированный или неэкспортированный код), а это означает, что вам нужны отдельные тестовые проекты для экспортируемых частей и для неэкспортированных частей. Однако это означает, что код компилируется только один раз, и тесты не являются частью окончательного исполняемого файла, и оптимизатор может выполнять свою полную работу.
В зависимости от размера открытого интерфейса с DLL, то есть количества экспортируемых классов / функций, на мой взгляд, наиболее подходящим является третий вариант. Часто у вас есть только небольшой публичный интерфейс, который является фасадом для большей внутренней структуры. Все, кроме общедоступного фасада, может быть помещено в отдельную статическую библиотеку, которую можно легко связать с исполняемым файлом теста и с DLL.
Ну, не стреляйте в курьера.
- Модульное тестирование Visual Studio (также называемое тестами, запускаемыми с помощью MSTest.exe) проверяет только управляемый код. Вы не можете протестировать неуправляемый C++. В VS11 есть новая встроенная среда модульного тестирования (следующая версия).
- InternalsVisible, как вы сказали, также относится только к управляемому коду.
- ИМХО Обычно вам не нужно тестировать внутренние классы. Как и частные типы или методы, вы тестируете их с помощью открытых / открытых методов, которые их используют. Поэтому, если PublicA.Method1() - это способ, которым ваши клиенты будут использовать InternalHelper.Method2(); затем я полагаюсь на тест для PublicA.Method1(), чтобы сказать мне, если какой-либо из них не работает.
- Если вам необходимо протестировать внутренние классы, попробуйте сделать их общедоступными (если методы достаточно сложны. См. Рефакторинг MethodObject). Или вы можете сделать тестовые классы другом внутренних классов.
,
class ProductionSUT
{
// production code to be tested
friend class TestProductSUT;
}
Отказ от ответственности: не пробовал это... поэтому, возможно, понадобятся некоторые настройки, чтобы успокоить компилятор.