Использование toString() для модульного тестирования в Java

В модульном тесте, как правило, рекомендуется проверять возвращаемые значения в соответствии со строкой, которую возвращает их toString()?

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

 assertEquals(someExpression.toString() ,"[a, b, c]");

Мне кажется, что соображения таковы:

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

Минусы: тест зависит от toString(), который формально не определен в документе, и, следовательно, может измениться в любой будущей версии.

5 ответов

Решение

Единственный раз, когда я буду тестировать на toString() объекта, когда у меня есть не редактируемый класс, который не реализовал hashcode или же equals, но вместо этого реализовали toString() выводить содержимое своего поля. Даже тогда я не буду использовать жестко закодированную строку в качестве теста на равенство, а вместо этого сделаю что-то вроде

SomeObject b = new SomeObject(expected, values, here);
assertEquals(a.toString(), b.toString());

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

Изменить 1: Естественно, если вы тестируете функцию / процесс, который выводит строку, это будет один из случаев, когда вы должны использовать жестко закодированную строку в качестве ожидаемого результата.

String input = "abcde";
String result = removeVowels(input);
assertEquals(result, "bcd");

Использование toString() для модульного тестирования в Java

Если в вашем модульном тесте вы намереваетесь утверждать, что все поля объекта равны, toString() Метод вашего тестируемого объекта должен возвращать строку, отображающую значения ключей для всех полей.
Но, как вы подчеркнули, со временем поля класса могут измениться, и поэтому ваши toString() метод может не отражать фактические поля и toString() метод не был разработан для этого.

Тест зависит от toString(), который формально не определен в документе, и, следовательно, может измениться в любой будущей версии.

Есть альтернативы toString() что позволяет писать хороший код без
заставляет вас поддерживать toString() метод со всеми возвращаемыми или смешанными полями ключ-значение toString() первоначальное намерение для отладки с целью утверждения.
Кроме того, модульные тесты должны документировать поведение тестируемого метода.
Следующий код не говорит о предполагаемом поведении:

 assertEquals(someExpression.toString() ,"[a, b, c]");

1) Библиотека отражений

Идея состоит в том, чтобы смешать механизмы отражения Java с механизмами утверждения JUnit (или любым API Test Unit, который вы используете).
С помощью отражения вы можете сравнить, равны ли каждое поле из двух объектов одного типа. Идея заключается в следующем: вы создаете текстовое сообщение об ошибке с указанием полей, где не соблюдается равенство между двумя полями и по каким причинам.
Когда все поля были сопоставлены с помощью отражения, если сообщение об ошибке не является нулевым, вы используете механизм модульного теста, чтобы вызвать исключение сбоя с соответствующим сообщением об ошибке, которое вы создали ранее.
В противном случае утверждение является успешным. Я регулярно использую его в своем проекте, когда это оказывается актуальным.
Вы можете сделать это самостоятельно или использовать API, например, Unitils, который может сделать эту работу за вас.

User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
ReflectionAssert.assertReflectionEquals(user1, user2);

Лично я создал свою собственную библиотеку, чтобы выполнить работу с возможностью добавления нескольких настроек в утверждение. Это не очень сложно сделать, и вы можете вдохновлять от Unitils.

2) Библиотека тестовых модулей

Я думаю, что это лучшая альтернатива.
Вы можете использовать Harmcrest или AssertJ.
Это более многословно, чем чистое отражение, но оно также имеет большие преимущества:

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

С AssertJ вы можете заменить этот код:

 assertEquals(foo.toString() ,"[value1=a, value1=b, value1=c]");

где foo - это экземпляр Foo класса, определенного как:

public class Foo{

  private String value1;
  private String value2;
  private String value3;

  // getters
}

чем-то вроде:

Assertions.assertThat(someExpression)
          .extracting(Foo::getValue1, Foo::getValue2, Foo::getValue3)
          .containsExactly(a, b, c);

Assert.assertThat() Метод имеет специальные соответствия для коллекций и многое другое.

Например, contains() работы для коллекций:

List<String> someExpression = asList("a", "b", "c");
assertThat(someExpression.toString(), contains("a", "b", "c"));

Преимущества:

  • Вам не нужно реализовывать .toString();
  • когда тест не пройден, сообщение об ошибке довольно описательно, в нем будет указано "Ожидаемая коллекция содержит" b "и не нашла его".

Пытаться:

import static org.junit.Assert.assertThat;
import static org.hamcrest.Matchers.contains;
import static java.util.Arrays.asList;

List<String> someExpression = asList("a", "c");
assertThat(someExpression.toString(), contains("a", "b", "c"));

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

Для моего примера здесь https://vanilla-java.github.io/2016/03/23/Microservices-in-the-Chronicle-world-Part-1.html

TopOfBookPrice tobp = new TopOfBookPrice("Symbol", 123456789000L, 1.2345, 1_000_000, 1.235, 2_000_000);
assertEquals("!TopOfBookPrice {\n" +
        "  symbol: Symbol,\n" +
        "  timestamp: 123456789000,\n" +
        "  buyPrice: 1.2345,\n" +
        "  buyQuantity: 1000000.0,\n" +
        "  sellPrice: 1.235,\n" +
        "  sellQuantity: 2000000.0\n" +
        "}\n", tobp.toString());

Этот тест не проходит, почему? Вы можете легко увидеть в своей IDE, как она производит

Сравнение дисплея

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

assertEquals("--- !!meta-data #binary\n" +
        "header: !SCQStore {\n" +
        "  wireType: !WireType BINARY,\n" +
        "  writePosition: 0,\n" +
        "  roll: !SCQSRoll {\n" +
        "    length: !int 86400000,\n" +
        "    format: yyyyMMdd,\n" +
        "    epoch: 0\n" +
        "  },\n" +
        "  indexing: !SCQSIndexing {\n" +
        "    indexCount: !short 16384,\n" +
        "    indexSpacing: 16,\n" +
        "    index2Index: 0,\n" +
        "    lastIndex: 0\n" +
        "  },\n" +
        "  lastAcknowledgedIndexReplicated: -1,\n" +
        "  recovery: !TimedStoreRecovery {\n" +
        "    timeStamp: 0\n" +
        "  }\n" +
        "}\n" +
        "# position: 344, header: 0\n" +
        "--- !!data #binary\n" +
        "msg: Hello world\n" +
        "# position: 365, header: 1\n" +
        "--- !!data #binary\n" +
        "msg: Also hello world\n", Wires.fromSizePrefixedBlobs(mappedBytes.readPosition(0)));

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

Примечание: я всегда ставлю ожидаемое значение первым.

Лучше переопределить equals а также hashCode (самостоятельно или, например, с помощью ломбокса @EqualsAndHashCode для этого) и сделать сравнение, используя их, чем используя toString, Таким образом, вы делаете тест явно о равенстве, и позже вы можете повторно использовать эти методы.

Затем вы можете сделать (следуя вашему примеру со списком):

assertEquals(
    someExpression,
    Arrays.asList(new Foo("a"), new Foo("b"), new Foo("c"))
);
Другие вопросы по тегам