Как получить доступ к приватным полям класса из теста с помощью UnitTest ++?
Мне приходится сталкиваться с неприятностями при кодировании моих модульных тестов, используя UnitTest ++. Мне интересно, как получить доступ к частным классам классов чистым способом (или, возможно, любым способом...)
В настоящее время у меня есть решение для доступа к защищенным элементам, используя элемент класса, полученный из тестируемого класса. Следующий код показывает идею:
struct MyFixture : ClassUnderTest { };
TEST_FIXTURE(MyFixture, OneTest)
{
do_something();
CHECK(protected_field == true);
}
Тем не менее, я думаю, что это не очень чисто, потому что проблемы, связанные с наследованием, могут возникать в некоторых конфигурациях, и, во всяком случае, только защищенные члены могут быть доступны и протестированы.
Я попытался объявить тестовые классы как friends, но поскольку они были созданы специальным способом с помощью UnitTest ++, я еще не успел это сделать.
Кто-нибудь знает, как сделать тестовые классы друзьями из тестируемых классов?
Есть ли другой способ приблизиться к этой проблеме по-другому?
Спасибо всем заблаговременно.
Ответы
Ответ 1
Модульное тестирование - это тестирование ваших объектов через их общий интерфейс. Тот факт, что это может быть трудно, - это то, почему писать тестируемый код иногда называют искусством. Не все могут сразу написать тестовый код, поэтому люди придумали подход XP к написанию тестов в первую очередь. Звучит нереально, работает на самом деле.
Однако, если вам абсолютно необходимо проверить частные функции, вот список методов, которые я бы рассмотрел в порядке моих собственных предпочтений:
-
Доступ к частным переменным-членам должен осуществляться через публичные сеттеры и получатели.
-
Я бы рекомендовал сделать вашу частную функцию-член нестатической не-членной функцией в пространстве имен, которое можно вызвать, например. details
или internal
. Не объявляйте его в файле заголовка, просто определите его в том же файле, где определены функции класса. Добавьте его объявление в заголовочный файл myClass_internal.h
в проект unit test и протестируйте его. Трудности, связанные с этим, во многом зависят от сложности вашей архитектуры.
-
Сделайте свой тестовый класс наследуемым от вашего тестируемого класса. Это не требует значительного изменения кода, но может потребовать использования множественного наследования, которое в некоторых местах даже запрещено.
-
Сделайте свой тест другом вашего тестируемого класса. Трудность зависит от используемой вами структуры тестирования. Скажем, , который я использую, это довольно сложно:)
-
Взлом с переопределением публичных и частных должен быть вашим абсолютно последним средством, если все остальное не удастся. Хотя я предпочел бы вместо этого изменить дизайн на более тестируемый.
Ответ 2
Там один действительно уродливый, но удивительно полезный хак, который я обычно использую для модульного тестирования:
#define private public
#define protected public
#include "class_to_be_tested.h"
// and here, all class methods and fields are public :P
НЕ используйте его ни для чего другого, кроме модульного тестирования!
Кроме того, этот метод имеет некоторые ограничения - во-первых, не все личные с префиксом private
. Во-вторых, один популярный компилятор управляет спецификатором доступа в символ компоновщика.
Другой, также не очень хороший метод - это кастинг. Вы создаете структуру, которая имеет те же поля, но все общедоступные, и передает ее по указателю на вашу частную структуру. Это имеет недостаток, однако, что классы должны точно соответствовать:
class my_class
{
private:
char name[40];
char grade;
int age;
public:
//
}
struct my_class_hack
{
public:
char name[40];
char grade;
int age;
}
struct hack_it* my_class_hacked = (my_class_hack*)ptr;
... вы можете получить неприятные сюрпризы, если компилятор играет с вашим кодом, поэтому никогда не используйте это для производственного кода.
Ответ 3
Это было предметом горячих дебатов несколько лет назад, но общепринятый результат состоял в том, что вы должны только проверять внешне видимое поведение вашего класса, и поэтому доступ к его внутренним данным и поведению не требуется.
В то время как это изначально не может быть полезно, вы можете извлечь частные части своего класса, которые хотите протестировать, в новые классы, внешнее поведение которых - поведение, которое вы хотите проверить. Затем вы можете проверить эти классы обычным способом, и вы улучшите свой общий дизайн.
Ответ 4
Я бы тоже пошел на th #define Hack,
но также кладет класс класса #define для получения неявных данных частного класса.
Я не согласен с Дмитрием:
1.) это добавит интерфейсы к моему производственному коду только для тестирования и нарушит мою инкапсуляцию. Я не хочу, чтобы у клиентов был доступ к моим личным данным.
2.) снова, как видно из 1.) → хорошая идея, если эти интерфейсы действительно доступны только для тестирования
3.) работает только в том случае, если доступ защищен, что как-то также компрометирует инкапсуляцию
4.) также означает модификацию моего производственного кода только для тестирования и даже создает связь между производственным кодом к моему тестовому коду!!!
5.) с вашей точки зрения это правильно, у меня скорее есть уродливый код тестирования, чем уродливый код производства:)
Ответ 5
Пока я согласен со всеми, говорящими "не делай этого", иногда ты работаешь в большом проекте, где у тебя нет свободы делать все изменения в тестируемом классе, который тебе нужен. В таких ситуациях я бы предпочел объявление друга для публичного/частного переопределения хакеров.
Я понял, как это сделать для UnitTest ++
Вот пример для класса "Deck" с защищенным/приватным методом "dealInternal", который вы хотели бы использовать из теста "Внутренние".
//------------- Deck.h -------------
namespace SuiteDeck { class TestInternals; }
class Deck {
// etc...
private:
friend class SuiteDeck::TestInternals;
bool dealInternal();
};
//------------ TestDeck.cpp ----------
#include "UnitTest++.h"
#include "Deck.h"
SUITE(Deck) {
TEST(Internals) {
Deck d;
CHECK(d.dealInternal() == true); // or whatever
}
}
Ответ 6
Лучше избегать тестирования личных вещей. Почему вы хотите проверить private_field? Что происходит с ошибками, когда для private_field установлено недопустимое значение? Можно ли проверить это неправильное поведение вместо того, чтобы утверждать неверное значение?
Другие варианты включают
-
играть с препроцессором, чтобы сделать его общедоступным, когда код скомпилирован для модульного тестирования
-
извлеките частное поле и связанную с ним логику в новый класс, где он будет общедоступным и сделает ClassUnderTest, полагаясь на этот новый класс.