#java #unit-testing
#java #модульное тестирование
Вопрос:
У меня есть класс, в который я добавляю модульные тесты. Класс имеет несколько конструкторов, которые принимают разные типы и преобразуют их в каноническую форму, которая затем может быть преобразована в другие типы.
public class Money {
public Money(long l) {
this.value = l;
}
public Money(String s) {
this.value = toLong(s);
}
public long getLong() {
return this.value;
}
public String getString() {
return toString(this.value);
}
}
На самом деле есть пара других типов, которые он принимает и преобразует.
Я пытаюсь определить, какой наиболее подходящий способ тестирования этих конструкторов.
Должен ли быть тест для каждого конструктора и типа вывода:
@Test
public void longConstructor_getLong_MatchesValuePassedToConstructor() {
final long value = 1.00l;
Money m = new Money(value);
long result = m.getLong();
assertEquals(value, result);
}
Это приводит к множеству различных тестов. Как вы можете видеть, я затрудняюсь дать им названия.
Должно ли быть несколько утверждений:
@Test
public void longConstructor_outputsMatchValuePassedToConstructor() {
final long longValue = 1.00l;
final String stringResult = "1.00";
Money m = new Money(longValue);
assertEquals(longValue, m.getLong());
assertEquals(stringResult, m.getString());
}
Это содержит несколько утверждений, что вызывает у меня дискомфорт. Он также тестирует getString (и через прокси toString), но не указывает это в названии теста. Присвоить им имена еще сложнее.
Я делаю это совершенно неправильно, сосредотачиваясь на конструкторах. Должен ли я просто протестировать методы преобразования? Но тогда в следующем тесте будет отсутствовать toLong
метод.
@Test
public void getString_MatchesValuePassedToConstructor() {
final long value = 1.00;
final String expectedResult = "1.00";
Money m = new Money(value);
String result = m.getLong();
assertEquals(expectedResult, result);
}
Это устаревший класс, и я не могу изменить исходный класс.
Комментарии:
1. Наличие нескольких утверждений не должно вызывать у вас дискомфорта — это все академично. Если на практике это работает для вас и доступно в обслуживании, дерзайте.
2. Или рассмотрите возможность параметризации теста — один тест может проверять несколько входных значений, параметры могут включать как входные, так и ожидаемые выходные данные.
Ответ №1:
Похоже, у вас есть канонический способ получения «необработанного» значения ( toLong
в данном случае) — так что просто проверьте правильность всех конструкторов при извлечении этого значения. Затем вы можете протестировать другие методы (такие как getString()
) на основе одного конструктора, поскольку вы знаете, что после завершения работы различных конструкторов все они оставляют объект в том же состоянии.
Это предполагает тестирование в виде «белого ящика», то есть вы знаете, что toLong
на самом деле это простое отражение внутреннего состояния, поэтому можно протестировать это конструктор в тесте.
Комментарии:
1. Я думаю, что я пытался быть слишком скрытным в этом, чтобы быть практичным.
2. @ICR: Я обнаружил, что существует много несколько догматичных рассуждений о тестировании в Интернете. Я бы предпочел иметь большое количество прагматичных, но несовершенных тестов, чем просто один идеальный тест, который на самом деле не очень помог 🙂
Ответ №2:
Ожидаемый результат от теста конструктора таков: создан экземпляр
Следуя этой идее, вы могли бы ограничить работу в конструкторных тестах чистым созданием экземпляров:
@Test public void testMoneyString() {
try {
new Money("0");
new Money("10.0");
new Money("-10.0");
} catch (Exception e) {
fail(e.getMessage());
}
}
@Test public void testMoneyStringIllegalValue() {
try {
new Money(null);
fail("Exception was expected for null input");
} catch (IllegalArgumentException e) {
}
try {
new Money("");
fail("Exception was expected for empty input");
} catch (IllegalArgumentException e) {
}
try {
new Money("abc");
fail("Exception was expected for non-number input");
} catch (IllegalArgumentException e) {
}
}
Тест для проверки того, работают ли преобразования, может быть назначен получателям.
Ответ №3:
Тест для каждого конструктора кажется мне наиболее подходящим. Не бойтесь использовать длинные, сложные и многословные названия для ваших методов тестирования, это делает их очевидными и описательными.
@Test
public void moneyConstructorThatTakesALong {
Ответ №4:
Я думаю, вы тратите слишком много времени на размышления об этом. Все ваши варианты работают просто отлично, поэтому просто выберите тот, который вам больше нравится. Не забывайте, что цель состоит в том, чтобы дать вам уверенность в том, что код будет работать так, как задумано / ожидаемо. Каждый из этих сценариев обеспечит это.
Лично в таком простом случае я бы выбрал один тестовый пример, который проверяет конструкторы. Это устраняет необходимость в чрезмерных и довольно громоздких именах методов.
Ответ №5:
Неплохая идея протестировать конструкторы, просто чтобы убедиться, что они принимают данные по мере необходимости, но если вам действительно не нравятся несколько утверждений, разделите их и назовите метод, с помощью которого они выполняют пример: CanContructorAcceptString или: CanConstructorAcceptNonLongStringValue
Что-то вроде этого.
Ответ №6:
У вас есть несколько входных данных (через конструкторы) и несколько выходных данных (через разные методы getX ()). Но количество элементов, которые у него есть внутри, кажется, меньше (в вашем примере 1 длинное значение). Не было бы проще сначала протестировать различные входные данные, создав x разных объектов, используя x разных конструкторов. Затем вы можете проверить, все ли они равны, используя реализованный метод equals(). Это можно сделать с помощью одного метода тестирования.
Затем вы можете проверять возможные методы получения один за другим, не используя все разные конструкторы.
Конечно, для этого требуется, чтобы вы реализовали (отдельно протестировали) метод equals.
В вашем примере я бы создал следующие тестовые наборы:
@Test
public void testEquals() {
Money m1 = new Money(1);
Money m2 = new Money(1);
Money m3 = new Money(2);
assertEquals(m1, m2);
assertEquals(m2, m1);
assertNotEquals(m1, m3);
assertNotEquals(m3, m1);
assertNotEquals(m1, null);
}
private void testConstructors(long lValue, String sValue) {
Money m1 = new Money(lValue);
Money m2 = new Money(sValue);
assertEquals(m1, m2);
}
@Test
public void testConstructorsPositive() {
testConstructors(1, "1");
}
@Test
public void testConstructorsNegative() {
testConstructors(-1, "-1");
}
@Test
public void testConstructorsZero() {
testConstructors(0, "0");
}
@Test
public void testGet...() { /* etc... */ }
Комментарии:
1. Если конструктор ничего не делает, все эти тесты проходят, потому что money = 0.
2. Тесты testGet …() завершатся неудачей, если конструктор ничего не сделает.
Ответ №7:
Конструктор — это инициализатор объекта. Тестирование конструктора — это проверка факта инициализации. То есть проверка того, что поля объекта были инициализированы значениями, переданными конструктору. И это может быть проверено только отражением.