Статьи

Модульное тестирование с FizzBuzz и Mockito

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

FizzBuzz Kata:

« Напишите программу, которая печатает числа от 1 до 100. Но для кратных трех выведите« Fizz »вместо числа, а для кратных пяти выведите« Buzz ». Для чисел, кратных трем и пяти, выведите «FizzBuzz» «.

Возможное решение алгоритма FizzBuzz:

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
public class FizzBuzz {
 
    private static final int FIVE = 5;
    private static final int THREE = 3;
 
    public String calculate(int number) {
 
        if (isDivisibleBy(number, THREE) && isDivisibleBy(number, FIVE)) {
            return "FizzBuzz";
        }
 
        if (isDivisibleBy(number, THREE)) {
            return "Fizz";
        }
 
        if (isDivisibleBy(number, FIVE)) {
            return "Buzz";
        }
 
        return "" + number;
    }
 
    private boolean isDivisibleBy(int dividend, int divisor) {
        return dividend % divisor == 0;
    }
}

Поскольку приведенный выше код решает алгоритм FizzBuzz, он не решает проблему FizzBuzz. Чтобы закончить, нам нужен код для печати чисел от 1 до 100 с использованием алгоритма. И эта часть кода может быть использована для демонстрации идеи насмешек в JUnit с Mockito.

В результате этого упражнения я получил NumberPrinter который принимает два аргумента: Printer и NumberCalculator и имеет один открытый метод для печати чисел:

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
public class NumberPrinter {
 
    private NumberCalculator numberCalculator;
    private Printer printer;
 
    public NumberPrinter(NumberCalculator numberCalculator, Printer printer) {
        this.numberCalculator = numberCalculator;
        this.printer = printer;
    }
 
    public void printNumbers(int limit) {
        if (limit < 1) {
            throw new RuntimeException("limit must be >= 1");
        }
        for (int i = 1; i <= limit; i++) {
            try {
                printer.print(numberCalculator.calculate(i));
            } catch (Exception e) {
                // noop
            }
        }
    }
}
 
public interface NumberCalculator {
    String calculate(int number);
}
 
public interface Printer {
    void print(String s);
}

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

Особенности Mockito продемонстрировали:

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

Использованные аннотации:

  • @RunWith(MockitoJUnitRunner.class) — инициализирует @Mock перед каждым методом тестирования
  • @Mock — помечает поле как фиктивное
  • @InjectMocks — помечает поле, в которое должно быть выполнено внедрение
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@RunWith(MockitoJUnitRunner.class)
public class NumberPrinterTest {
 
    @Mock
    private Printer printer;
 
    @Mock
    private NumberCalculator numberCalculator;
 
    @InjectMocks
    private NumberPrinter numberPrinter;
 
    @Test
    public void printsCalculatorResultsHundredTimes() {
        // arrange
        int limit = 100;
        when(numberCalculator.calculate(anyInt()))
                .thenReturn("0"// first invocation returns "0"
                .thenReturn("1"); // other invocations return "1"
        // act
        numberPrinter.printNumbers(limit);
        // assert
        verify(numberCalculator, times(limit)).calculate(anyInt());
        verify(printer, times(1)).print("0");
        verify(printer, times(limit - 1)).print("1");
        verifyNoMoreInteractions(numberCalculator, printer);
    }
 
    @Test
    public void continuesOnCalculatorOrPrinterError() {
        // arrange
        when(numberCalculator.calculate(anyInt()))
                .thenReturn("1")
                .thenThrow(new RuntimeException())
                .thenReturn("3");
        // stub the void method with an exception
        doThrow(new RuntimeException()).when(printer).print("3");
        // act
        numberPrinter.printNumbers(3);
        // assert
        verify(numberCalculator, times(3)).calculate(anyInt());
        verify(printer).print("1");
        verify(printer).print("3");
 
        verifyNoMoreInteractions(numberCalculator, printer);
    }
}

Наслаждайтесь Мокито !

  • Хотите узнать больше об аннотациях Mockito? Взгляните на «Mockito — @Mock, @Spy, @Captor и @InjectMocks» Евгения Парашива: http://www.baeldung.com/mockito-annotations
  • Ищете примеры кода? Взгляните на демонстрационный проект юнит-тестирования, представляющий различные аспекты юнит-тестирования, включая макетирование: https://github.com/kolorobot/unit-testing-demo