如何对此进行单元测试(使用模拟或经典单元测试?)

时间:2011-02-28 22:21:18

标签: java unit-testing mocking

我需要为以下方法编写一个JUnit测试(简化):

/** Return name of previous entry or name of given entry if no previous entry. */
public String getPreviousName(TreeEntry entry) {
    if (entry.getPrevious() != null) {
        return entry.getPrevious().getName();
    } else {
        return entry.getName();
    }
}

我只是不知道应该怎么测试这个。我可以

a)使用Mocks模拟参数及其先前的条目或

b)创建一个真正的TreeEntry,它是真正的前一个条目

使用模拟的问题是,我需要在测试用例中说明实际实现需要使用哪种方法,例如:在这里我说正确的名称是通过TreeEntry的getPrevious()然后通过前一个条目的getName()获得的(不考虑if null情况,因为这基本上不是问题):

TreeEntry mockEntry = mock(TreeEntry.class);
TreeEntry mockPreviousEntry = mock(TreeEntry.class);
when(mockEntry.getPrevious()).thenReturn(mockPreviousEntry);
when(mockPreviousEntry.getName()).thenReturn("testName");

assertEquals("testName", classUnderTest.getPreviousName(mockEntry));

但是实现者可以像这样实现它:

return NameService.getName(entry.getPrevious());

这对测试无关紧要。

但是,如果我使用经典的单元测试而没有模拟,我还有另外一个问题。 TreeEntry的构造函数非常复杂。它有5个参数需要有效的对象。所以创建类似的东西非常复杂:

TreeEntry entry = new TreeEntry(...);
TreeEntry previousEntry = new TreeEntry(...);
entry.setPrevious(previousEntry);
previousEntry.setName("testName");

assertEquals("testName", classUnderTest.getPreviousName(entry));

代码会比这长得多,因为构造函数需要复杂的参数。另一件事是构造函数访问文件系统以从那里加载它的内容,因此它增加了使单元测试更慢。

使用经典方法,它还将测试与TreeEntry的实现联系起来。因为如果在TreeEntry中使用构造函数或前一个条目的设置更改了某些内容,则还需要在测试中更改它。如果它被遗忘会导致测试失败,即使是严格的测试类也不是TreeEntry。

您认为对此进行单元测试的正确方法是什么?

我个人倾向于更喜欢嘲弄的方法。是否可以说在测试用例中我已经指定了哪个是被测试类的合作者,因为它在某种程度上也更多地属于设计而不是实现?

祝你好运, 亚历

4 个答案:

答案 0 :(得分:1)

我认为你的嘲弄方法看起来很好(我同意如果TreeEntry和你说的一样复杂,你不应该将它们混合到这些测试中(但我希望你在其他地方测试TreeEntry))。如果您正在编写这样的大量测试,那么您应该考虑使用一些Builder模式(如果您有耐心的话,可以使用流利的语法),这样您就可以构建模拟,例如像这样:

BuildANew.TreeEntry().WithPreviousEntryName("testName");

或者您可以创建一个ITreeEntry接口并根据该接口编写树操作代码,并创建一个简单的存根实现以传递到您的测试中,这样您就不需要乱用了模拟设置代码。或者,为了更清晰的方法,创建界面模拟它。

答案 1 :(得分:1)

我认为您最好的选择是从TreeEntry中提取接口,在测试代码中使用s基本存根实现它,然后对其进行测试。

答案 2 :(得分:0)

如果您只想测试

public String getPreviousName(TreeEntry entry)

方法我会嘲笑它,因为你只是对这个方法进行单元测试。最好保持您的单元测试快速。

但是如果你有兴趣测试整个文件访问和TreeEntry的创建,我会创建它们。

在我的日常工作中,我首先尝试编写单元测试,然后设计函数,因为它有助于设计易于使用/测试的界面。

http://en.wikipedia.org/wiki/Test-driven_development

答案 3 :(得分:0)

我认为模拟没问题。

另一方面:JUnit测试不仅仅是一个简单的测试,它还是驱动器设计。

如果您发现难以编写测试用例,例如构造函数需要复杂的参数,您可能会重新考虑您的设计。

顺便说一下,我认为方法getPreviousName()不是必需的,getPreviousEntry()就足够了。