Статьи

Беги, Юнит!

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

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

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

Например, если у нас есть следующий объект значения, представляющий рациональные числа:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class RationalNumber {
 
  private final long numerator;
  private final long denominator;
 
  public RationalNumber(long numerator, long denominator) {
    this.numerator = numerator;
    this.denominator = denominator;
  }
 
  public long getNumerator() {
    return numerator;
  }
 
  public long getDenominator() {
    return denominator;
  }
 
  @Override
  public String toString() {
    return String.format("%d/%d", numerator, denominator);
  }
}

И у нас есть сервисный класс под названием App с методом convert, который делит число на округленное значение до десятичного числа:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public class App {
 
  /**
   * THE Logic
   *
   * @param number some rational number
   * @return BigDecimal rounded to 5 decimal points
   */
  public static BigDecimal convert(RationalNumber number) {
    BigDecimal numerator = new BigDecimal(number.getNumerator()).
        setScale(5, RoundingMode.HALF_UP);
 
    BigDecimal result = numerator.divide(
        new BigDecimal(number.getDenominator()),
        RoundingMode.HALF_UP);
 
    return result;
  }
}

И для фактического класса AppTest у нас есть

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@RunWith(Parameterized.class)
public class AppTest {
 
  private RationalNumber input;
  private BigDecimal expected;
 
  public AppTest(RationalNumber input, BigDecimal expected) {
    this.input = input;
    this.expected = expected;
  }
 
  @Parameterized.Parameters(name = "{index}: number[{0}]= {1}")
  public static Collection<Object> data() {
    return Arrays.asList(new Object[][]{
      {new RationalNumber(1, 2), new BigDecimal("0.50000")},
      {new RationalNumber(1, 1), new BigDecimal("1.00000")},
      {new RationalNumber(1, 3), new BigDecimal("0.33333")},
      {new RationalNumber(1, 5), new BigDecimal("0.20000")},
      {new RationalNumber(10000, 3), new BigDecimal("3333.33333")}
    });
  }
 
  @Test
  public void testApp() {
    //given the test data
    //when
    BigDecimal out = App.convert(input);
    //then
    Assert.assertThat(out, is(equalTo(expected)));
  }
 
}

Параметризованный бегун или @RunWith (Parameterized.class) включает «параметризацию» или, другими словами, внедрение коллекции значений, аннотированных @ Parameterized.Parameters, в конструктор Test, где каждый из подсписков является списком параметров. Это означает, что каждый из объектов RationalNumber в методе data () будет введен во входную переменную, и каждое из значений BigDecimal будет ожидаемым значением, поэтому в нашем примере у нас есть 5 тестов.

Существует также необязательное произвольное именование сгенерированного теста, добавленное в аннотацию, поэтому « {index}: number [{0}] = {1} » будет заменено соответствующими параметрами, определенными в методе data () и « {index} « заполнитель будет индексом тестового примера, как показано на следующем рисунке

service1
Выполнение параметризованных тестов в IntelliJ Idea

Юнит правила

Простейшее определение правил JUnit состоит в том, что они в некотором смысле являются перехватчиками и очень похожи на аспектно-ориентированное программирование Spring или API-перехватчики Java EE. В основном вы можете делать полезные вещи до и после выполнения теста.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public class TheRuleTest {
  @Rule
  public TemporaryFolder folder = new TemporaryFolder();
 
  @Test
  public void someTest() throws IOException {
    //given
    final File tempFile = folder.newFile("thefile.txt");
    //when
    tempFile.setExecutable(true)  ;
    //then
    assertThat(tempFile.canExecute(), is(true));
  }
}

Мы могли бы сделать это в блоках @Before и @After и использовать временные файлы java, но легко забыть что-то и оставить некоторые файлы незамеченными в некоторых сценариях, когда тест не пройден.

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

1
2
@Rule
public MethodRule globalTimeout = new Timeout(20);

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

Простой сценарий, объясняющий поведение, заключается в добавлении правила, которое печатает что-то до и после теста.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
 
public class MyTestRule implements TestRule {
 
  public class MyStatement extends Statement {
 
    private final Statement statement;
 
    public MyStatement(Statement statement) {
      this.statement = statement;
    }
 
    @Override
    public void evaluate() throws Throwable {
      System.out.println("before statement" );
      statement.evaluate();
      System.out.println("after statement");
    }
 
  }
 
  @Override
  public Statement apply(Statement statement,
                         Description description) {
 
    System.out.println("apply rule");
    return new MyStatement(statement);
  }
 
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public class SomeTest {
 
  @Rule
  public MyTestRule folder = new MyTestRule();
 
  @Test
  public void testA()  {
    System.out.println("A");
  }
 
  @Test
  public void testB()  {
    System.out.println("B");
  }
}

Когда мы запустим тест, на выходе консоли будет создан следующий вывод:

1
2
3
4
5
6
7
8
apply rule
before statement
A
after statement
apply rule
before statement
B
after statement

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

Подводить итоги

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

Но вы знаете, что? У нас не всегда есть те на нашем пути класса, и есть вероятность, что JUnit там и уже используется повсеместно. Чем не использовать его полный потенциал?

Ссылка: Беги, Юнит! Бег!!! от нашего партнера JCG Аттилы Михаила Балаза в блоге Java Advent Calendar .