Операторы If в тестах

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

@Test
public void testRecipientsCount() {
    assertEquals(3, recipientsCount);
}

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

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

@Test
public void testRecipientsCount(){
    if(something) {
        assertEquals(3, recipientsCount);
    }
    else {
        assertEquals(4, recipientsCount);
    }
}
...

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

Я думал, что разделение теста на двух тестовых классах может работать, но это приведет к избыточному коду в обоих классах (I еще нужно проверить, были ли созданы не-внутренние сообщения, их размер, содержимое и т. д.), и несколько строк для одного из них.

мой вопрос: как это сделать, чтобы я не использовал if или нагрузки избыточного кода (не используя параметризованный класс будет производить еще более избыточный код)?

4 ответов


на мой взгляд, Junit следует читать как протокол. Это означает, что вы можете писать избыточный код, чтобы сделать тест более читабельным. Напишите testcase для каждого возможного оператора if в вашей бизнес-логике, а также для отрицательных случаев. Это единственный способ получить 100% тестовое покрытие. Я использую структуру:

- testdata preparation
- executing logic
- check results
- clear data

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

abstract class YourBusinessObjectAssert{
  public static void assertYourBussinessObjectIsValid(YourBusinessObject pYourBusinessObject,       Collection<YourBusinessObject> pAllYourBusinessObject) {
for (YourBusinessObject lYourBusinessObject : pAllYourBusinessObject) {
  if (lYourBusinessObject.isTechnicalEqual(pYourBusinessObject)) {
    return;
  }
}
assertFalse("Could not find requested YourBusinessObject in List<YourBusinessObject>!", true);
}
}

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


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

Если возможно, я бы сказал, что тест должен читать как рассказ - мой предпочтительный макет (и это не моя идея: -) - его довольно широко используется):

- given:  do setup etc
- when:  the place you actually execute/call the thing under test
- expect: verify the result

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


почему бы не иметь собственный метод, который проверяет вещи, которые являются общими для каждого метода? Что-то вроде (но, вероятно, с некоторым входным параметром для testCommonStuff() способ):

@Test
public void testRecipientsCountA(){
   testCommonStuff();
   // Assert stuff for test A
}

@Test
public void testRecipientsCountB(){
   testCommonStuff();
   // Assert stuff for test B
}

private void testCommonStuff() {
   // Assert common stuff
}

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


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

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

@Test
public void testRecipientsCount(){
    assertEquals(expectedCount(something), recipientsCount)
}

private int expectedCount(boolean which) {
    if (something){
       return 3;
    }
    else {
       return 4;
    }
}