понедельник, 22 мая 2017 г.

Single Statement Unit Tests

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

Посмотрите на этот тестовый метод из RandomStreamTest из OpenJDK 8, созданный Брайаном Гетцем:

@Test
public void testIntStream() {
  final long seed = System.currentTimeMillis();
  final Random r1 = new Random(seed);
  final int[] a = new int[SIZE];
  for (int i=0; i < SIZE; i++) {
    a[i] = r1.nextInt();
  }
  final Random r2 = new Random(seed);
  final int[] b = r2.ints().limit(SIZE).toArray();
  assertEquals(a, b);
}
В этом методе есть две части: алгоритм и утверждение. Алгоритм подготавливает два массива целых чисел, и утверждение сравнивает их и выдает AssertionError, если они не равны.

Я говорю, что первая часть, алгоритм, та, которую мы должны стараться избегать. Единственное, что мы должны иметь - это утверждение. Вот как я хотел бы изменить этот метод тестирования:
@Test
public void testIntStream() {
  final long seed = System.currentTimeMillis();
  assertEquals(
    new ArrayFromRandom(
      new Random(seed)
    ).toArray(SIZE),
    new Random(seed).ints().limit(SIZE).toArray()
  );
}
private static class ArrayFromRandom {
  private final Random random;
  ArrayFromRandom(Random r) {
    this.random = r;
  }
  int[] toArray(int s) {
    final int[] a = new int[s];
    for (int i=0; i < s; i++) {
      a[i] = this.random.nextInt();
    }
    return a;
  }
}
Если бы у Java были прозвища (monikers), этот код выглядел бы еще элегантнее:
@Test
public void testIntStream() {
  assertEquals(
    new ArrayFromRandom(
      new Random(System.currentTimeMillis() as seed)
    ).toArray(SIZE),
    new Random(seed).ints().limit(SIZE).toArray()
  );
}
Как вы можете видеть, в этом методе есть только одно «утверждение»: assertEquals ().

Hamcrest с его assertThat () и его набором основных сопоставлений является идеальным инструментом, чтобы наши методы тестирования с одним утверждением были еще более сплоченными и читаемыми.

Есть ряд практических преимуществ этого принципа, если мы согласны следовать ему:

    Повторное использование. Классы, которые мы будем создавать для тестовых утверждений, могут быть повторно использованы в других тестах и тестовых случаях. Так же, как в приведенном выше примере ArrayFromRandom может использоваться где-то в другом месте. Аналогично, сопоставители Hamcrest могут и будут представлять собой библиотеку компонентов многократно используемых тестов.

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

    Читаемость. С одним утверждением всегда будет очевидно, какова цель метода тестирования. Он начнется с объявления намерения, в то время как все остальные детали нижнего уровня будут с отступом.

    Неизменность. Будет почти невозможно иметь сеттеры в производственном коде, если в методах тестирования нет места для алгоритмического кода. Вы неизбежно создадите неизменяемые объекты, чтобы сделать их тестируемыми с помощью одного утверждения.

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


Комментариев нет:

Отправить комментарий