В данный момент я пишу тестовый проект и застрял в тестировании следующего поведения.
Я получил интерфейс под названием Menu
к которому можно динамически добавлять записи через addEntry
-Метод. Есть еще один класс, который содержит Menu
объект. Давайте назовем это MenuPresenter
, Когда конкретный метод (например, someAction(string title)
) называется MenuPresenter
следует создать экземпляр Entry
объект с полученным названием и добавить его в Menu
объект. Создание экземпляра Entry
объект происходит в заводском методе внутри MenuPresenter
,
Проверенное поведение должно быть WhenSomeActionIsCalledShouldAddAnEntryContainingTitleToMenu
Но я не нахожу правильный способ написать тест. Я выяснил две основные возможности его тестирования, но на самом деле они мне не нравятся из-за (позже) упомянутых недостатков.
MenuSpy
наследование от Menu
, который имеет getAddedEntry
-Метод. Таким образом, вы можете извлечь добавленное Entry
и проверить состояние объектаEXPECT_TRUE(entry->getTitle() == title);
Недостатки: Чтобы проверить состояние Entry
У меня есть объект, чтобы либо расширить API интерфейса интерфейсными методами, которые используются только для целей тестирования, либо использовать открытые переменные-члены. Это позволяет каждому клиенту получить доступ к внутренней структуре каждой реализации Entry
, Таким образом, клиенты связаны с внутренней структурой.
EntryFactory
интерфейс, который имеетmakeEntry(std::string title)
-метод. Как это можно реализовать EntryFactorySpy
и можете проверить, если makeEntry
-метод вызывается с правильными параметрами (заголовок). В другом тесте мы можем реализовать EntryFactoryStub
который возвращает конкретный Entry
объект. Тогда мы можем проверить (через MenuSpy
) если MenuPresenter
добавил полученную запись с завода в меню. Создание экземпляра Entry
Затем объект тестируется в модульном тесте на заводе.Недостатки: Потому что я проверяю вызов фабрики makeEntry
— Исправлен алгоритм использования фабрики для создания записей. Испытание тесно связано с внутренней структурой MenuPresenter
, Изменение алгоритма (например, использование теперь фабричного метода нарушило бы тест, без ожидаемого поведения приложения.
Для поведения приложения не важно, если MenuPresenter
создает Entry
само по себе, если он использует EntryFactory
, Вот почему я предпочел бы первый путь. Но я не хочу, чтобы клиент Entry
быть связанным с внутренней структурой Entry
только по причинам тестирования.
Это только пример моей проблемы. На самом деле запись не только создается со строкой. Он получает другие сложные объекты как shared_ptr. Это еще одна причина, почему я не хочу использовать общедоступные методы получения. Таким образом, можно извлечь сложный объект из Entry
и измените его (да, я мог бы выдать const shared_ptr, но мне кажется, что это не очень хорошая практика)
Вопрос в том, знает ли кто-нибудь шаблон тестирования или решение, которое решает мою проблему? Значение тестирования, если правильный Entry
объект добавлен в Menu
объект, не будучи связанным с жестким алгоритмом или внутренней структурой Entry
?
Я Java-разработчик, я никогда не использую TDD в C ++, но, возможно, я могу описать то, что вы хотите протестировать, как вы упоминали выше.
В классическом TDD, MenuPresenter
должно быть больше похоже на мини-интеграционный тест, как сказал Мартин Фаулер в Испытание изоляции.
По сути, классические тесты xunit — это не только модульные тесты, но и мини-интеграционные тесты. В результате многим людям нравится тот факт, что клиентские тесты могут отлавливать ошибки, которые, возможно, пропущены основными тестами для объекта, особенно в областях исследования, где взаимодействуют классы.
В Классическом TDD Используйте реальные объекты, если это возможно, и Test Double если неловко использовать реальную вещь при тестировании MenuPresenter
, Так что спросите себя, какой ожидаемый эффект происходит, когда Entry
добавлено в Menu
, тестирование Menu
будет добавлять под названием Entry
только бессмысленно, вы должны проверить, что такое поведение после Entry
добавлено Entry
добавлено будет отображаться как под названием MenuItem
так ты просто отстаиваешь реальный Menu
объект, который отображается MenuItem
с ожидаемым названием.
В самом деле, WhenSomeActionIsCalledShouldAddAnEntryContainingTitleToMenu
тест уже разоблачает someAction
детали реализации по его названию и привязка теста к реализации someAction
С другой стороны, когда вы меняете алгоритм someAction
тест не пройден. Вы можете добавить тест как displaysAnTitltedMenuItemInMenuWhenSomeActionIsCalledWithinATitle
,
Как вы можете видеть, тест в любом случае будет связан с реализацией. мы только можем сделать, это сделать тестовую связь с реализацией как ниже насколько это возможно, так что мы просто меняем немного тестов, когда меняется реализация.
если вы нашли Menu
это неудобно, вы можете использовать MenuSpy
вместо этого, а затем написать некоторые IntegrationContractTest в MenuSpy
& MenuImpl
, как вы можете видеть, что тест с использованием MenuSpy
немного выше связь с реализацией, чем реальная Menu
потому что мы ожидаем титулованного Entry
был добавлен в MenuSpy
не последствия Menu
после титулованного Entry
добавлено.
пусть MenuSpy
чтобы проверить, есть ли титул Entry
был добавлен, например:
someAction(title);
menuSpy->hasReceivedATitledEntry(title);
ИЛИ ЖЕ: вы можете проверить добавленный Entry
является ли CheckableEntry
в getAddedEntryTitle
Метод перед возвратом заголовка.
someAction(title);
EXPECT_TRUE(menuSpy->getAddedEntryTitle() == title);
Других решений пока нет …