我有一个课我正在添加单元测试。该类有几个构造函数,它们采用不同的类型并将它们转换为规范形式,然后可以将其转换为其他类型。
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);
}
这是一个遗留类,我无法更改原始类。
答案 0 :(得分:15)
看起来你已经获得了获取“原始”值的规范方法(在这种情况下为toLong
) - 所以只需在获取时测试所有构造函数是否正确价值。然后,您可以基于单个构造函数测试其他方法(例如getString()
),因为您知道一旦各种构造函数完成,它们都会使对象处于相同的状态。
这假设有点白盒测试 - 即你知道 toLong
实际上是内部状态的简单反映,所以在测试中测试+构造函数是可以的
答案 1 :(得分:12)
构造函数测试的预期结果是:已创建实例
遵循这个想法,你可以将构造函数测试中的工作限制为纯实例化:
@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) {
}
}
验证转化是否有效的测试可以分配给吸气剂。
答案 2 :(得分:11)
每个构造函数的测试似乎对我来说最合适。不要害怕为你的测试方法使用冗长,精细和冗长的名称,这使它们变得明显和具有描述性。
@Test
public void moneyConstructorThatTakesALong {
答案 3 :(得分:3)
我认为你花了太多时间思考它。你所有的选择都很好,所以只需选择你最喜欢的选项。不要忘记,目的是让您确信代码将按设计/预期工作。这些方案中的每一个都将提供这一点。
就个人而言,在这种简单的情况下,我会使用一个验证构造函数的测试用例。它消除了对过多且相当笨拙的方法名称的需要。
答案 4 :(得分:2)
测试构造函数只是为了确保根据需要获取数据并不是一个坏主意,但是如果你真的不喜欢多个断言,请将它们拆分并用它们正在做的命名方法 示例:CanContructorAcceptString 或:CanConstructorAcceptNonLongStringValue
这样的事情。
答案 5 :(得分:0)
您有多个输入(通过构造函数)和多个输出(通过不同的getX()方法)。但是它内部的成员数量似乎更少(在您的示例中,1个长值)。通过使用 x 不同的构造函数创建 x 不同的对象,首先测试不同的输入会不会更容易。然后,您可以使用实现的equals()方法检查这些是否相等。这可以通过一种测试方法完成。
然后,您可以逐个检查可能的getter方法,而不使用所有不同的构造函数。
当然,这需要你实现(单独测试)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... */ }
答案 6 :(得分:-1)
构造函数是一个对象初始化程序。构造函数测试是对初始化事实的测试。也就是说,验证是否通过传递给构造函数的值来初始化对象的字段。这只能通过反思来验证。