Мне было интересно, как тестировать абстрактные классы и классы, расширяющие абстрактные классы.
Должен ли я тестировать абстрактный класс, расширяя его, заглушая абстрактные методы, а затем тестируя все конкретные методы? Тогда только тестируйте методы, которые я переопределяю, и тестируйте абстрактные методы в модульных тестах для объектов, расширяющих мой абстрактный класс?
Должен ли я иметь абстрактный контрольный пример, который можно использовать для проверки методов абстрактного класса, и расширить этот класс в моем тестовом примере для объектов, расширяющих абстрактный класс?
Обратите внимание, что мой абстрактный класс имеет несколько конкретных методов.
источник
Есть два способа использования абстрактных базовых классов.
Вы специализируете свой абстрактный объект, но все клиенты будут использовать производный класс через его базовый интерфейс.
Вы используете абстрактный базовый класс, чтобы исключить дублирование объектов в вашем проекте, а клиенты используют конкретные реализации через свои собственные интерфейсы.
Решение для 1 - шаблон стратегии
Если у вас первая ситуация, то у вас фактически есть интерфейс, определенный виртуальными методами в абстрактном классе, которые реализуют ваши производные классы.
Вам следует подумать о том, чтобы сделать это реальным интерфейсом, изменить конкретный класс на конкретный, и взять экземпляр этого интерфейса в его конструкторе. Затем ваши производные классы становятся реализациями этого нового интерфейса.
Это означает, что теперь вы можете протестировать свой ранее абстрактный класс, используя фиктивный экземпляр нового интерфейса, а каждую новую реализацию - через общедоступный интерфейс. Все просто и проверяемо.
Решение для 2
Если у вас вторая ситуация, тогда ваш абстрактный класс работает как вспомогательный класс.
Посмотрите на функциональность, которую он содержит. Посмотрите, может ли что-либо из этого быть помещено на объекты, которыми манипулируют, чтобы минимизировать это дублирование. Если у вас все еще есть что-то, посмотрите, как сделать это вспомогательным классом, который ваша конкретная реализация использует в своем конструкторе, и удалите его базовый класс.
Это снова приводит к конкретным классам, которые просты и легко тестируемы.
Как правило
Пользуйтесь сложной сетью простых объектов над простой сетью сложных объектов.
Ключ к расширяемому тестируемому коду - небольшие строительные блоки и независимая проводка.
Обновлено: как обрабатывать смеси обоих?
Можно иметь базовый класс, выполняющий обе эти роли ... т.е. он имеет открытый интерфейс и имеет защищенные вспомогательные методы. Если это так, то вы можете выделить вспомогательные методы в один класс (script2) и преобразовать дерево наследования в шаблон стратегии.
Если вы обнаружите, что у вас есть некоторые методы, которые ваш базовый класс реализует напрямую, а другие являются виртуальными, то вы все равно можете преобразовать дерево наследования в шаблон стратегии, но я бы также использовал его как хороший индикатор того, что обязанности не правильно выровнены, и может нужен рефакторинг.
Обновление 2: Абстрактные классы как трамплин (2014/06/12)
У меня была ситуация, когда я использовал реферат, поэтому я хотел бы выяснить, почему.
У нас есть стандартный формат для наших файлов конфигурации. Этот конкретный инструмент имеет 3 файла конфигурации в этом формате. Я хотел иметь строго типизированный класс для каждого файла настроек, чтобы через внедрение зависимостей класс мог запрашивать настройки, о которых он заботился.
Я реализовал это, имея абстрактный базовый класс, который знает, как анализировать форматы файлов настроек и производные классы, которые предоставляют те же методы, но инкапсулируют местоположение файла настроек.
Я мог бы написать «SettingsFileParser», который обернул 3 класса, а затем делегировать его базовому классу для предоставления методов доступа к данным. Я решил не делать этого пока , как это привело бы к 3 производных классов с более делегирования кода в них , чем все остальное.
Однако ... как этот код развивается, и потребители каждого из этих классов настроек становятся понятнее. Каждую настройку пользователи будут запрашивать некоторые настройки и каким-то образом преобразовывать их (поскольку настройки являются текстовыми, они могут обернуть их в объекты, преобразовать их в числа и т. Д.). Когда это произойдет, я начну извлекать эту логику в методы манипулирования данными и возвращать их обратно в строго типизированные классы настроек. Это приведет к более высокоуровневому интерфейсу для каждого набора настроек, который в конечном итоге перестанет осознавать, что имеет дело с «настройками».
На этом этапе строго типизированные классы настроек больше не будут нуждаться в методах «получения», которые предоставляют базовую реализацию «настроек».
В этот момент я бы больше не хотел, чтобы их общедоступный интерфейс включал методы доступа к настройкам; поэтому я изменю этот класс, чтобы инкапсулировать класс анализатора настроек, а не наследовать от него.
Таким образом, класс Abstract: для меня способ избежать кода делегирования на данный момент и маркер в коде, чтобы напомнить мне о необходимости изменить дизайн позже. Я, возможно, никогда не доберусь до этого, поэтому он может жить долго ... только код может сказать.
Я считаю, что это верно для любого правила ... типа "нет статических методов" или "нет частных методов". Они указывают на запах в коде ... и это хорошо. Это заставляет вас искать абстракцию, которую вы пропустили ... и позволяет в то же время обеспечивать ценность для вашего клиента.
Я представляю себе правила, подобные этому, определяющие ландшафт, в котором обслуживаемый код живет в долинах. Когда вы добавляете новое поведение, это похоже на дождь в вашем коде. Сначала вы помещаете его туда, где он приземляется. Затем вы делаете рефакторинг, чтобы позволить силам хорошего дизайна изменить поведение, пока все не окажется в долинах.
источник
Что я делаю для абстрактных классов и интерфейсов, так это следующее: я пишу тест, который использует объект как конкретный. Но переменная типа X (X является абстрактным классом) не установлена в тесте. Этот тестовый класс не добавляется в набор тестов, но в его подклассы, которые имеют метод установки, устанавливающий переменную для конкретной реализации X. Таким образом, я не дублирую тестовый код. Подклассы неиспользуемого теста могут добавить дополнительные тестовые методы, если это необходимо.
источник
Чтобы выполнить модульный тест специально для абстрактного класса, вы должны получить его для целей тестирования, тестирования base.method () и предполагаемого поведения при наследовании.
Вы тестируете метод, вызывая его, поэтому тестируйте абстрактный класс, реализуя его ...
источник
Если ваш абстрактный класс содержит конкретные функциональные возможности, имеющие деловую ценность, я обычно проверяю его напрямую, создавая двойной тест, который заглушает абстрактные данные, или использую фальшивую среду, чтобы сделать это для меня. Какой из них я выберу, во многом зависит от того, нужно ли мне писать специфичные для теста реализации абстрактных методов или нет.
Наиболее распространенный сценарий, в котором мне нужно это сделать, - это когда я использую шаблонный метод , например, когда я создаю какую-то расширяемую среду, которая будет использоваться сторонней организацией. В этом случае абстрактный класс - это то, что определяет алгоритм, который я хочу протестировать, поэтому имеет смысл проверить абстрактную базу, а не конкретную реализацию.
Однако я думаю, что важно, чтобы эти тесты были сосредоточены только на конкретных реализациях реальной бизнес-логики ; Вы не должны юнит тестировать детали реализации абстрактного класса, потому что в итоге вы получите хрупкие тесты.
источник
Один из способов - написать абстрактный контрольный пример, соответствующий вашему абстрактному классу, а затем написать конкретные контрольные примеры, которые подклассируют ваш абстрактный контрольный пример. сделайте это для каждого конкретного подкласса вашего исходного абстрактного класса (т.е. ваша иерархия тестовых примеров отражает вашу иерархию классов). см. Проверка интерфейса в книге получателей junit: http://safari.informit.com/9781932394238/ch02lev1sec6 .
также см. Суперкласс Testcase в шаблонах xUnit: http://xunitpatterns.com/Testcase%20Superclass.html
источник
Я бы поспорил против "абстрактных" тестов. Я думаю, что тест является конкретной идеей и не имеет абстракции. Если у вас есть общие элементы, поместите их во вспомогательные методы или классы, чтобы все могли их использовать.
Что касается тестирования абстрактного тестового класса, обязательно спросите себя, что именно вы тестируете. Есть несколько подходов, и вы должны выяснить, что работает в вашем сценарии. Вы пытаетесь опробовать новый метод в своем подклассе? Тогда ваши тесты взаимодействуют только с этим методом. Вы тестируете методы в своем базовом классе? Тогда, вероятно, есть отдельное приспособление только для этого класса, и тестируйте каждый метод индивидуально с таким количеством тестов, сколько необходимо.
источник
Это шаблон, который я обычно использую при настройке привязки для тестирования абстрактного класса:
И версия, которую я использую в тесте:
Если абстрактные методы вызываются, когда я этого не ожидаю, тесты не пройдены. При организации тестов я легко могу пометить абстрактные методы лямбдами, которые выполняют утверждения, генерируют исключения, возвращают различные значения и т. Д.
источник
Если конкретные методы вызывают какой-либо из абстрактных методов, эта стратегия не будет работать, и вы захотите протестировать поведение каждого дочернего класса отдельно. В противном случае, его расширение и создание заглушек абстрактных методов, как вы описали, должны быть в порядке, опять же, при условии, что конкретные методы абстрактного класса отделены от дочерних классов.
источник
Я полагаю, вы могли бы захотеть проверить базовую функциональность абстрактного класса ... Но вам, вероятно, лучше всего расширить класс без переопределения каких-либо методов и сделать насмешку с минимальными усилиями для абстрактных методов.
источник
Одним из основных мотивов использования абстрактного класса является включение полиморфизма в вашем приложении - то есть: вы можете заменить другую версию во время выполнения. Фактически, это почти то же самое, что и использование интерфейса, за исключением того, что абстрактный класс обеспечивает некоторые общие условия, часто называемые шаблоном шаблона. .
С точки зрения модульного тестирования необходимо учитывать две вещи:
Взаимодействие вашего абстрактного класса с соответствующими классами . Использование модели фиктивного тестирования идеально подходит для этого сценария, поскольку оно показывает, что ваш абстрактный класс хорошо сочетается с другими.
Функциональность производных классов . Если у вас есть пользовательская логика, которую вы написали для своих производных классов, вам следует тестировать эти классы изолированно.
редактировать: RhinoMocks - это отличная среда для фиктивного тестирования, которая может генерировать фиктивные объекты во время выполнения, динамически наследуя ваш класс. Такой подход может сэкономить вам бесчисленные часы ручного кодирования производных классов.
источник
Во-первых, если абстрактный класс содержит какой-то конкретный метод, я думаю, вы должны сделать это, рассмотрев этот пример
источник
Если абстрактный класс подходит для вашей реализации, протестируйте (как предложено выше) производный конкретный класс. Ваши предположения верны.
Чтобы избежать путаницы в будущем, имейте в виду, что этот конкретный тестовый класс - не подделка , а подделка .
В строгом смысле, макет определяется следующими характеристиками:
источник
После ответа @ patrick-desjardins я реализовал абстрактный и его класс реализации,
@Test
а также:Абстрактный класс - ABC.java
Поскольку абстрактные классы не могут быть созданы, но могут быть разделены на подклассы , конкретный класс DEF.java выглядит следующим образом:
Класс @Test для тестирования как абстрактного, так и неабстрактного метода:
источник