Как выполнить модульное тестирование абстрактных классов: расширить с помощью заглушек?

Мне было интересно, как проводить модульное тестирование абстрактных классов и классов, расширяющих абстрактные классы.

Должен ли я тестировать абстрактный класс, расширяя его, убирая абстрактные методы, а затем тестируя все конкретные методы? Затем проверяйте только те методы, которые я переопределяю, и проверяйте абстрактные методы в модульных тестах для объектов, расширяющих мой абстрактный класс?

Должен ли я иметь абстрактный тестовый пример, который можно использовать для тестирования методов абстрактного класса, и расширить этот класс в моем тестовом примере для объектов, расширяющих абстрактный класс?

Обратите внимание, что у моего абстрактного класса есть несколько конкретных методов.

Лучше не проводить модульное тестирование абстрактных классов напрямую: enterprisecraftsmanship.com/posts/…

Vladimir 25.06.2020 14:50
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
466
1
162 665
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

Я бы возражал против «абстрактных» тестов. Я считаю, что тест - это конкретная идея, не имеющая абстракции. Если у вас есть общие элементы, поместите их в вспомогательные методы или классы, чтобы каждый мог их использовать.

Что касается тестирования абстрактного тестового класса, не забудьте спросить себя, что именно вы тестируете. Есть несколько подходов, и вы должны выяснить, что работает в вашем сценарии. Вы пытаетесь протестировать новый метод в своем подклассе? Тогда пусть ваши тесты будут взаимодействовать только с этим методом. Вы тестируете методы в своем базовом классе? Тогда, вероятно, создайте отдельное приспособление только для этого класса и протестируйте каждый метод индивидуально с помощью необходимого количества тестов.

Я не хотел повторно тестировать код, который я уже тестировал, поэтому я пошел по пути абстрактных тестовых примеров. Я пытаюсь протестировать все конкретные методы в моем абстрактном классе в одном месте.

Paul Whelan 28.10.2008 16:41

Я не согласен с извлечением общих элементов во вспомогательные классы, по крайней мере, в некоторых (многих?) Случаях. Если абстрактный класс содержит некоторую конкретную функциональность, я думаю, вполне приемлемо провести модульное тестирование этой функциональности напрямую.

Seth Petry-Johnson 28.10.2008 17:12

Если конкретные методы вызывают какой-либо из абстрактных методов, эта стратегия не будет работать, и вы захотите протестировать поведение каждого дочернего класса отдельно. В противном случае его расширение и заглушка абстрактных методов, как вы описали, должно быть нормально, опять же при условии, что конкретные методы абстрактного класса отделены от дочерних классов.

Я полагаю, вы могли бы захотеть протестировать базовую функциональность абстрактного класса ... Но вам, вероятно, было бы лучше, если бы вы расширили класс без переопределения каких-либо методов и с минимальными усилиями имитировали абстрактные методы.

Чтобы выполнить модульный тест специально для абстрактного класса, вы должны получить его для целей тестирования, тестирования результатов base.method () и предполагаемого поведения при наследовании.

Вы тестируете метод, вызывая его, поэтому тестируйте абстрактный класс, реализуя его ...

Для абстрактных классов и интерфейсов я делаю следующее: я пишу тест, который использует конкретный объект. Но переменная типа X (X - абстрактный класс) в тесте не устанавливается. Этот тестовый класс не добавляется в набор тестов, а его подклассы, у которых есть метод установки, который устанавливает переменную в конкретную реализацию X. Таким образом я не дублирую тестовый код. Подклассы неиспользуемого теста могут при необходимости добавлять дополнительные методы тестирования.

не вызывает ли это проблем с приведением в подклассе? если X имеет метод a, а Y наследует X, но также имеет метод b. Когда вы подклассифицируете свой тестовый класс, разве вам не нужно приводить абстрактную переменную к Y для выполнения тестов на b?

Johnno Nolan 23.04.2011 15:05
Ответ принят как подходящий

Напишите объект Mock и используйте его только для тестирования. Обычно они очень-очень-очень минимальны (наследуются от абстрактного класса) и не более того. Затем в своем модульном тесте вы можете вызвать абстрактный метод, который хотите протестировать.

Вы должны протестировать абстрактный класс, который содержит некоторую логику, как и все другие классы, которые у вас есть.

Черт возьми, я должен сказать, что впервые согласился с идеей использования мока.

Jonathan Allen 04.12.2008 10:50

Даок: Не могли бы вы привести простой пример? Я не знаю, почему я должен использовать Mock вместо минимального класса. Или я вас неправильно понял? Заранее спасибо!

guerda 07.02.2009 19:36

Моки просто позволяют избежать необходимости писать заглушку реализации абстрактного базового класса, в этом случае они, безусловно, являются наиболее разумным выбором, если вы счастливы принять зависимость от имитирующего фреймворка.

ShuggyCoUk 14.02.2009 17:21

Просто пытаюсь решить это ... поэтому вы создаете макет как подкласс. Генерируется ли макет на лету? Если да, то как проникнуть внутрь, чтобы проверить все внутренности абстрактного? Если макет жестко запрограммирован, входит ли какой-либо тестовый код в объявление макета, чтобы заставить абстрактное работать. Просто интересуюсь. Я делаю то же, что и Мнемент.

JW. 08.10.2010 03:29

Вам нужно два класса, макет и тест. Мок-класс расширяет только абстрактные методы тестируемого абстрактного класса. Эти методы могут быть бездействующими, возвращать null и т. д., Поскольку они не будут проверяться. Тестовый класс тестирует только неабстрактный общедоступный API (то есть интерфейс, реализованный абстрактным классом). Для любого класса, расширяющего класс Abstract, вам потребуются дополнительные тестовые классы, потому что абстрактные методы не были охвачены.

cyber-monk 10.08.2011 22:00

Это действительно хороший пример того, как это сделать с mockito stackoverflow.com/questions/1087339/…

Jose Muanis 12.11.2011 00:03

Очевидно, что это можно сделать ... но чтобы по-настоящему протестировать любой производный класс, вы собираетесь тестировать эту базовую функциональность снова и снова ... что приводит к тому, что у вас есть абстрактный тестовый инструмент, чтобы вы могли исключить это дублирование при тестировании. Это все пахнет! Я настоятельно рекомендую еще раз взглянуть на то, почему вы вообще используете абстрактные классы, и посмотреть, будет ли что-то еще работать лучше.

Nigel Thorne 07.01.2012 15:32

переоцененный следующий ответ намного лучше.

Martin Spamer 31.05.2012 12:32

@MartiSpamer: Я бы не сказал, что этот ответ переоценен, потому что он был написан намного раньше (на 2 года), чем ответ, который вы считаете лучшим ниже. Давайте просто поддержим Патрика, потому что в контексте того, когда он опубликовал этот ответ, это было здорово. Будем поощрять друг друга. Ваше здоровье

Marvin Thobejane 09.07.2013 15:27

@Nigel Thorne, не говорю, что вы неправы или что-то в этом роде, просто я обижен вашим комментарием. При этом, если бы я использовал абстрактный класс для исключения дублирования внутри объектов в вашем дизайне, а клиенты использовали бы конкретные реализации через свои собственные интерфейсы, как это плохой дизайн? в моем использовании у меня есть около 20 классов, которые расширяют мою аннотацию, в которой есть конкретные методы, которые используются всеми расширяющимися классами. как пахнет этот код? какой способ сделать это лучше?

liltitus27 09.01.2014 19:35

@ liltitus27 Есть много способов написать код, понятный компилятору. Будучи опытным программистом, вы уже отфильтровываете набор возможных дизайнов, чтобы отбросить проекты, которые имеют высокую степень связи, запутанные или неподдерживаемые. Дизайн, прошедший ваши изученные фильтры, классифицируется как «хороший дизайн». У меня есть еще один критерий, по которому я сужу дизайн. Могу я легко это проверить?

Nigel Thorne 11.01.2014 01:29

Это шаблон, которым я обычно следую при настройке жгута для тестирования абстрактного класса:

public abstract class MyBase{
  /*...*/
  public abstract void VoidMethod(object param1);
  public abstract object MethodWithReturn(object param1);
  /*,,,*/
}

И версия, которую я использую при тестировании:

public class MyBaseHarness : MyBase{
  /*...*/
  public Action<object> VoidMethodFunction;
  public override void VoidMethod(object param1){
    VoidMethodFunction(param1);
  }
  public Func<object, object> MethodWithReturnFunction;
  public override object MethodWithReturn(object param1){
    return MethodWihtReturnFunction(param1);
  }
  /*,,,*/
}

Если абстрактные методы вызываются, когда я этого не ожидаю, тесты терпят неудачу. При организации тестов я могу легко заменить абстрактные методы лямбдами, которые выполняют утверждения, генерируют исключения, возвращают разные значения и т. д.

Одним из основных мотивов использования абстрактного класса является включение полиморфизма в вашем приложении, то есть вы можете заменить другую версию во время выполнения. Фактически, это почти то же самое, что и использование интерфейса, за исключением того, что абстрактный класс предоставляет некоторую общую сантехнику, часто называемую Шаблон шаблона.

С точки зрения модульного тестирования необходимо учитывать две вещи:

  1. Взаимодействие вашего абстрактного класса с родственными ему классами. Использование фреймворка фиктивного тестирования идеально подходит для этого сценария, поскольку он показывает, что ваш абстрактный класс хорошо взаимодействует с другими.

  2. Функциональность производных классов. Если у вас есть собственная логика, написанная для производных классов, вы должны тестировать эти классы изолированно.

edit: RhinoMocks - это потрясающая среда тестирования макетов, которая может генерировать фиктивные объекты во время выполнения, динамически наследуя от вашего класса. Такой подход может сэкономить вам бесчисленные часы ручного кодирования производных классов.

Если ваш абстрактный класс содержит конкретную функциональность, имеющую ценность для бизнеса, я обычно тестирую его напрямую, создавая тестовый двойник, который заглушает абстрактные данные, или используя для этого фреймворк mocking. Какой из них я выберу, во многом зависит от того, нужно ли мне писать специфичные для тестов реализации абстрактных методов или нет.

Наиболее распространенный сценарий, в котором мне нужно это сделать, - это когда я использую Шаблон метода шаблона, например, когда я создаю какую-то расширяемую структуру, которая будет использоваться третьей стороной. В этом случае абстрактный класс - это то, что определяет алгоритм, который я хочу протестировать, поэтому имеет больше смысла тестировать абстрактную базу, чем конкретную реализацию.

Однако я считаю важным, чтобы эти тесты были сосредоточены на только конкретные реализации реальной бизнес-логики; вам не следует проводить модульное тестирование детали реализации абстрактного класса, потому что вы получите хрупкие тесты.

один из способов - написать абстрактный тестовый пример, соответствующий вашему абстрактному классу, а затем написать конкретные тестовые примеры, которые подклассифицируют ваш абстрактный тестовый пример. сделайте это для каждого конкретного подкласса исходного абстрактного класса (т.е. иерархия тестовых примеров отражает иерархию классов). см. Тестирование интерфейса в книге получателей junit: http://safari.informit.com/9781932394238/ch02lev1sec6. https://www.manning.com/books/junit-recipes или https://www.amazon.com/JUnit-Recipes-Practical-Methods-Programmer/dp/1932394230, если у вас нет учетной записи сафари.

также см. Testcase Superclass в шаблонах xUnit: http://xunitpatterns.com/Testcase%20Superclass.html

Во-первых, если абстрактный класс содержал какой-то конкретный метод, я думаю, вам стоит это сделать, учитывая этот пример.

 public abstract class A 

 {

    public boolean method 1
    {
        // concrete method which we have to test.

    }


 }


 class B extends class A

 {

      @override
      public boolean method 1
      {
        // override same method as above.

      }


 } 


  class Test_A 

  {

    private static B b;  // reference object of the class B

    @Before
    public void init()

      {

      b = new B ();    

      }

     @Test
     public void Test_method 1

       {

       b.method 1; // use some assertion statements.

       }

   }

Следуя ответу @ patrick-desjardins, я реализовал абстрактный и его класс реализации вместе с @Test следующим образом:

Абстрактный класс - ABC.java

import java.util.ArrayList;
import java.util.List;

public abstract class ABC {

    abstract String sayHello();

    public List<String> getList() {
        final List<String> defaultList = new ArrayList<>();
        defaultList.add("abstract class");
        return defaultList;
    }
}

Как и Абстрактные классы не могут быть созданы, но могут быть подклассами, конкретный класс DEF.java выглядит следующим образом:

public class DEF extends ABC {

    @Override
    public String sayHello() {
        return "Hello!";
    }
}

Класс @Тестовое задание для тестирования как абстрактного, так и не абстрактного метода:

import org.junit.Before;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.contains;
import java.util.Collection;
import java.util.List;
import static org.hamcrest.Matchers.equalTo;

import org.junit.Test;

public class DEFTest {

    private DEF def;

    @Before
    public void setup() {
        def = new DEF();
    }

    @Test
    public void add(){
        String result = def.sayHello();
        assertThat(result, is(equalTo("Hello!")));
    }

    @Test
    public void getList(){
        List<String> result = def.getList();
        assertThat((Collection<String>) result, is(not(empty())));
        assertThat(result, contains("abstract class"));
    }
}

Если абстрактный класс подходит для вашей реализации, протестируйте (как было предложено выше) производный конкретный класс. Ваши предположения верны.

Чтобы избежать путаницы в будущем, имейте в виду, что этот конкретный тестовый класс не фиктивный, а фальшивый.

Строго говоря, насмехаться определяется следующими характеристиками:

  • Вместо каждой зависимости тестируемого предметного класса используется макет.
  • Мок - это псевдо-реализация интерфейса (вы можете вспомнить, что, как правило, зависимости следует объявлять как интерфейсы; возможность тестирования - одна из основных причин этого)
  • Поведение членов интерфейса макета - методы или свойства - поставляются во время тестирования (опять же, с использованием фиктивного фреймворка). Таким образом, вы избегаете связывания тестируемой реализации с реализацией ее зависимостей (каждая из которых должна иметь свои собственные дискретные тесты).

Другие вопросы по теме