如何对构造函数进行单元测试

44

我正在为一个类添加单元测试。这个类有几个构造函数,它们接受不同的类型并将它们转换成一个规范形式,然后可以将其转换成其他类型。

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);
}

这是一个遗留的类,我无法更改原始类。


拥有多个断言不应该让你感到不舒服 - 这只是学术上的问题。如果在实践中它对你有用并且易于维护,请去尝试。 - Scott Langham
或者考虑参数化测试 - 一个测试可以测试多个输入值,参数可以包括输入和预期输出。 - cptully
7个回答

18

看起来你已经有了获取“原始”值(在这种情况下是 toLong )的规范方式-因此只需在提取值时测试所有构造函数是否正确。然后,您可以基于单个构造函数测试其他方法(例如 getString()),因为您知道一旦各种构造函数完成,它们都将使对象处于相同的状态。

这假设进行了某种白盒测试-即你知道 toLong 实际上是内部状态的简单反映,所以测试那个+一个构造函数就可以了。


我认为我一直试图过于黑盒化,以至于不太实用。 - ICR
6
我会尽力进行翻译,以下是需要翻译的内容:@ICR: 我发现在网上有很多关于测试的教条性文章。我宁愿拥有大量实用但不完美的测试,也不要只有一个完美的测试,但其实并没有什么帮助 :) - Jon Skeet

16

构造函数测试的期望结果是:已创建实例

按照这个思路,您可以将构造函数测试限制为纯实例化:

@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) {          
    }

}

可以将一个测试分配给getter来验证转换是否有效。


12

我认为每个构造函数都需要进行测试。不要害怕在测试方法中使用冗长、详细和言辞华丽的名称,这使得它们更加明显和描述性。

@Test
public void moneyConstructorThatTakesALong {

3

我觉得你花了太多时间思考这个问题。所有的选项都可以正常工作,所以只需选择你最喜欢的选项即可。不要忘记目的是为了让你有信心代码将按设计/预期工作。每种情况都能做到这一点。

就我个人而言,在这种简单的情况下,我会使用一个测试用例来验证构造函数。这样就消除了过多且不好处理的方法名称的需要。


2

测试构造函数确保它们按要求接收数据并不是一个坏主意,但如果您真的不喜欢多个断言,可以将它们分开并根据其功能命名方法。

例如:CanContructorAcceptString 或 CanConstructorAcceptNonLongStringValue

就像这样。


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... */ }

如果构造函数什么都不做,那么所有的测试都会通过,因为 money = 0。 - Piotr Perak
如果构造函数没有执行任何操作,那么testGet...()测试将失败。 - Marc

-1

构造函数是一个对象初始化程序。构造函数测试是初始化的事实的测试。也就是说,验证对象的字段是否已通过传递给构造函数的值进行了初始化。这只能通过反射来验证。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接