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



Мне было интересно, как тестировать абстрактные классы и классы, которые расширяют абстрактные классы.



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



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



обратите внимание, что мой абстрактный класс имеет какие-то конкретные методы.

766   14  

14 ответов:

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

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

существует два способа использования абстрактных базовых классов.

  1. вы специализируете свой абстрактный объект, но все клиенты будут использовать производный класс через его базовый интерфейс.

  2. вы используете абстрактный базовый класс для дублирования объектов в вашем дизайне, и клиенты используют конкретные реализации через собственные интерфейсы.!


Решение Для 1-Стратегии Шаблон

Option1

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

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

IMotor

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


Решение Для 2

Если у вас вторая ситуация, то ваш абстрактный класс работает как вспомогательный класс.

AbstractHelper

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

Motor Helper

Это снова приводит к конкретным классам, которые просты и легко проверяемы.


как правило

благоприятная сложная сеть простых объекты по простой сети сложных объектов.

ключ к расширяемому тестируемому коду-это небольшие строительные блоки и независимая проводка.


Обновлено : как обрабатывать смесью?

можно иметь базовый класс, выполняющий обе эти роли... ie: он имеет открытый интерфейс и имеет защищенные вспомогательные методы. Если это так, то вы можете разложить вспомогательные методы на один класс (scenario2) и преобразовать дерево наследования в шаблон стратегии.

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


обновление 2: абстрактные классы как ступенька (2014/06/12)

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

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

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

Я мог бы написать "SettingsFileParser", что 3 класса обернуты, а затем делегированы в базовый класс для предоставления методов доступа к данным. Я решил не делать этого пока как это приведет к 3 производных классов с более делегация код в них, чем что-либо еще.

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

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

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

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

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

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

один из способов-написать абстрактный тестовый случай, соответствует абстрактный класс, то пишите конкретные тесты, подкласс абстрактного теста. сделайте это для каждого конкретного подкласса исходного абстрактного класса (т. е. ваш тест иерархия отражает иерархию классов). см. раздел тестирование интерфейса в книге рецептов junit:http://safari.informit.com/9781932394238/ch02lev1sec6.

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

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

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

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

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);
  }
  /*,,,*/
}

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

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

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

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

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

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

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

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

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

 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 следующим образом:

класс Abstarct-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!";
    }
}

@Test класс для проверки как абстрактных, так и неабстрактных метод:

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"));
    }
}

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

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

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

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

Comments

    Ничего не найдено.