Как получить доступ к приватным полям класса из теста с помощью 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, полагаясь на этот новый класс.