Вопрос: Правильная методика единичного тестирования


При использовании TDD я обнаружил, что мне нужно проверить постоянный (окончательный) hashmap, который содержит значения поиска ( ПОЖАЛУЙСТА, ПОСМОТРЕТЬ ПРИЧИНУ, ПОЧЕМУ ЭТО БЫЛО ДЕЛО ОБНОВЛЕНИЕ )

Смотри ниже

private static final Map<Integer,String> singleDigitLookup = new HashMap<Integer, String>(){{
        put(0,"Zero");put(1,"One");put(2,"Two");put(3,"Three");put(4,"Four");put(5,"Five");put(6,"Six");put(7,"Seven");
        put(8,"Eight");put(9,"Nine");
}}; 

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

ИСПЫТАТЕЛЬНЫЙ СТИЛЬ 1

@Test
public void whenWordIsOneThenReturn1(){
   assertEquals(1, WordToIntegerConverter.toInteger("One"));
}

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

ИСПЫТАТЕЛЬНЫЙ СТИЛЬ 2

@Test
    public void whenWordIsZeroThroughNineReturnIntegerConversion(){
        HashMap<Integer, String> lookup = new HashMap<Integer, String>(){{
            put(0,"Zero");put(1,"One");put(2,"Two");put(3,"Three");put(4,"Four");put(5,"Five");
            put(6,"Six");put(7,"Seven");put(8,"Eight");put(9,"Nine");
        }};
        for(int i = 0; i < 10; i++) {
            assertEquals(i, WordToIntegerConverter.toInteger(lookup.get(i)));
        }
    } 

Мой вопрос таков; лучше ли использовать стиль 1 для модульного тестирования или лучше использовать стиль 2.  

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

ОБНОВИТЬ Я получил приличную отдачу от этого вопроса, поэтому позвольте мне объяснить далее. Это не постоянная забота о себе, а проверка различных случаев моего кода. Это была проблема практики (практика TDD Via Katas), а не производственный код. Проблема заключалась в преобразовании чисел в слова, так что меня волнует в моем модульном тестировании, так что я могу правильно обрабатывать разные возможные числа. Были и другие константы, к которым я не включал, например, постоянное запоминание числа подростков (11, 12, 13 ...) и tensDigits (20, 30, 40 ...). Его довольно легко сделать опечатку здесь.


5


источник


Ответы:


Подход №1 выполняет свою работу, просто с отвратительным количеством вырезания-вставки. Подход № 2 исправляет это, но за счет того, что тесты не являются независимыми: если один тест не прошел, следующие из них не запускаются. Исправить один тест, чтобы найти кучу новых, которые сейчас терпят неудачу, довольно раздражает. Вы можете улучшить это, сделав параметризованный тест, вот пример из wiki junit:

@RunWith(Parameterized.class)
public class FibonacciTest {
    @Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][] {     
                 { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 }  
           });
    }

    private int fInput;

    private int fExpected;

    public FibonacciTest(int input, int expected) {
        fInput= input;
        fExpected= expected;
    }

    @Test
    public void test() {
        assertEquals(fExpected, Fibonacci.compute(fInput));
    }
}

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


10