Статьи

Управляемое данными модульное тестирование в Java

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

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

уровень

минимальные статусные очки

уровень результата

бронза

0

бронза

бронза

300

Серебряный

бронза

700

Золото

бронза

1500

платиновый

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

Тестирование на основе данных хорошо поддерживается в современных библиотеках модульного тестирования JVM, таких как Spock и Spec2. Однако некоторые команды не имеют возможности использовать язык, отличный от Java, или ограничены использованием JUnit. В этой статье мы рассмотрим несколько вариантов тестирования на основе данных в простом старом JUnit.

Параметризованные тесты в JUnit

JUnit обеспечивает некоторую поддержку тестов, управляемых данными, через  Parameterized  test runner. Простой управляемый данными тест в JUnit с использованием этого подхода может выглядеть следующим образом:

@RunWith(Parameterized.class)
public class WhenEarningStatus {

    @Parameters(name = "{index}: {0} initially had {1} points, earns {2} points, should become {3} ")
    public static Iterable<Object[]> data() {
        return Arrays.asList(new Object[][]{
                {Bronze, 0,    100,  Bronze},
                {Bronze, 0,    300,  Silver},
                {Bronze, 100,  200,  Silver},
                {Bronze, 0,    700,  Gold},
                {Bronze, 0,    1500, Platinum},
        });
    }

    private Status initialStatus;
    private int initialPoints;
    private int earnedPoints;
    private Status finalStatus;

    public WhenEarningStatus(Status initialStatus, int initialPoints, int earnedPoints, Status finalStatus) {
        this.initialStatus = initialStatus;
        this.initialPoints = initialPoints;
        this.earnedPoints = earnedPoints;
        this.finalStatus = finalStatus;
    }

    @Test
    public void shouldUpgradeStatusBasedOnPointsEarned() {
        FrequentFlyer member = FrequentFlyer.withFrequentFlyerNumber("12345678")
                                            .named("Joe", "Jones")
                                            .withStatusPoints(initialPoints)
                                            .withStatus(initialStatus);

        member.earns(earnedPoints).statusPoints();

        assertThat(member.getStatus()).isEqualTo(finalStatus);
    }
}

Вы предоставляете тестовые данные в виде списка массивов объектов, обозначенных  аннотацией _ @ Parameterized @. Эти массивы объектов содержат строки тестовых данных, которые вы используете для теста, управляемого данными. Каждая строка используется для создания переменных-членов класса через конструктор.

Когда вы запустите тест, JUnit создаст экземпляр и запустит тест для каждой строки данных. Вы можете использовать  имя  атрибут  @Parameterized  аннотации , чтобы обеспечить более значимое название для каждого теста.

У параметризованных тестов JUnit есть несколько ограничений. Наиболее важным является то, что, поскольку тестовые данные определены на уровне класса, а не на тестовом уровне, вы можете иметь только один набор тестовых данных для каждого тестового класса. Не говоря уже о том, что код несколько загроможден — вам нужно определить переменные-члены, конструктор и так далее.

К счастью, есть лучший вариант.

Использование JUnitParams

Более элегантный способ провести управляемое данными тестирование в JUnit — использовать [ https://code.google.com/p/junitparams/|JUnitParams ]. JUnitParams (см [ http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22JUnitParams%22|Maven  Central] , чтобы найти самую последнюю версию) является открытым исходным кодом библиотека , которая делает данные тестирование в Юнит проще и понятнее.

Простой управляемый данными тест с использованием JUnitParam выглядит следующим образом:

@RunWith(JUnitParamsRunner.class)
public class WhenEarningStatusWithJUnitParams {

    @Test
    @Parameters({
            "Bronze, 0,   100,  Bronze",
            "Bronze, 0,   300,  Silver",
            "Bronze, 100, 200,  Silver",
            "Bronze, 0,   700,  Gold",
            "Bronze, 0,   1500, Platinum"

    })
    public void shouldUpgradeStatusBasedOnPointsEarned(Status initialStatus, int initialPoints, 
                                                       int earnedPoints, Status finalStatus) {
        FrequentFlyer member = FrequentFlyer.withFrequentFlyerNumber("12345678")
                                            .named("Joe", "Jones")
                                            .withStatusPoints(initialPoints)
                                            .withStatus(initialStatus);

        member.earns(earnedPoints).statusPoints();

        assertThat(member.getStatus()).isEqualTo(finalStatus);
    }
}

Тестовые данные определяются в   аннотации @Parameters , которая связана с самим тестом, а не с классом, и передается в тест через параметры метода. Это позволяет иметь разные наборы тестовых данных для разных тестов в одном классе или смешивать управляемые данными тесты с обычными тестами в одном классе, что является гораздо более логичным способом организации ваших классов.

JUnitParam также позволяет получать тестовые данные из других методов, как показано здесь:

 @Test
    @Parameters(method = "sampleData")
    public void shouldUpgradeStatusFromEarnedPoints(Status initialStatus, int initialPoints, 
                                                    int earnedPoints, Status finalStatus) {
        FrequentFlyer member = FrequentFlyer.withFrequentFlyerNumber("12345678")
                .named("Joe", "Jones")
                .withStatusPoints(initialPoints)
                .withStatus(initialStatus);

        member.earns(earnedPoints).statusPoints();

        assertThat(member.getStatus()).isEqualTo(finalStatus);
    }

    private Object[] sampleData() {
        return $(
                $(Bronze, 0,   100, Bronze),
                $(Bronze, 0,   300, Silver),
                $(Bronze, 100, 200, Silver)
        );
    }

Метод  $  предоставляет удобный способ преобразования тестовых данных в массивы объектов, которые необходимо вернуть.

Вы также можете вывести

  @Test
  @Parameters(source=StatusTestData.class)
  public void shouldUpgradeStatusFromEarnedPoints(Status initialStatus,int initialPoints,
  int earnedPoints,Status finalStatus){
  ...
  }

Тестовые данные здесь получены из метода в   классе StatusTestData :

  public class StatusTestData{
  public static Object[] provideEarnedPointsTable(){
  return $(
  $(Bronze,0,  100,Bronze),
  $(Bronze,0,  300,Silver),
  $(Bronze,100,200,Silver)
  );
  }
  }

Этот метод должен быть статическим, возвращать массив объектов и начинаться со слова «обеспечить».

Таким образом, получение тестовых данных из внешних методов или классов открывает путь к получению тестовых данных из внешних источников, таких как файлы CSV или Excel.

JUnitParam предоставляет простой и понятный способ реализации управляемых данными тестов в JUnit без лишних затрат и ограничений традиционных параметризованных тестов JUnit.

Тестирование с не-Java языками

Если вы не ограничены Java и / или JUnit, более современные инструменты, такие как Spock ( https://code.google.com/p/spock/ ) и Spec2, предоставляют отличные способы написания чистых, выразительных модульных тестов в Groovy и Scala. соответственно. Например, в Groovy вы можете написать тест, подобный следующему:

class WhenEarningStatus extends Specification{

  def"should earn status based on the number of points earned"(){
  given:
  def member =FrequentFlyer.withFrequentFlyerNumber("12345678")
  .named("Joe","Jones")
  .withStatusPoints(initialPoints)
  .withStatus(initialStatus);

  when:
  member.earns(earnedPoints).statusPoints()

  then:
  member.status == finalStatus

  where:
  initialStatus | initialPoints | earnedPoints | finalStatus
  Bronze  |0  |100  |Bronze
  Bronze  |0  |300  |Silver
  Bronze  |100  |200  |Silver
  Silver  |0  |700  |Gold
  Gold  |0  |1500  |Platinum
  }
}

 — специалист по BDD, автоматизированному тестированию и оптимизации жизненного цикла программного обеспечения, а также автор книги  BDD in Action  и других книг. Джон работает регулярные курсы  в Австралии, Лондоне и Европе по темам , связанным с такими , как  Agile сбор требованийповедение Driven DevelopmentTest Driven Development и автоматизированного тестирования приема .


Ссылки на блог >>