单元测试:是否有效进行最小化测试?

问题描述:

我有一个关于单元测试的基本问题。我在代码中发现了一个崩溃,并且我编写了一个测试来重现该错误。然后,我修复了这个错误并确认它通过了测试。我的问题是,如果我采取了最好的方法。下面是一个简化的例子:单元测试:是否有效进行最小化测试?

public class ClassC 
{ 
    private int internalFlag; 

    void all(Dependency dep1, Dependency dep2, Dependency dep3, Dependency dep4) 
    { 
     a(dep1); 
     b(dep2) 
     c(dep3) 
     d(dep4) 
     e(); 
     f(); 
    } 

    void e() 
    { 
     if (some logic based on dep3) 
     { 
      internalFlag = 2; 
     } 
    } 

    void f() 
    { 
     if (internalFlag == 2) 
     { 
      Log("All is well"); 
     } 
     else 
     { 
      Log("Crash occurs") 
     } 
    } 
} 

在上面的例子中,我有一个名为 “所有” 的方法调用方法A,B,C,d,E,F。现在,我在“f”中发现了一个崩溃,因为“internalVar”不是预期的值。这个'internalVar'被设置为“e”,但是正如你所看到的,如果条件为假,那么“e”不会将internalFalg设置为任何东西,所以这个错误发生在e。

我能够编写测试。隔离错误然后,我固定的bug,并发现测试过去那是伟大的,但为了做到这一点,我不得不这样做。

void testAll() 
{ 
    mockDep1 = mock(dep1); 
    mockDep2 = mock(dep2); 
    mockDep3 = mock(dep3); 
    mockDep4 = mock(dep4); 

    all(mockDep1, mockDep2, mockDep3, mockDep4); 
} 

现在,我让mockDep1..4部分很简单,在这里在这个例子中,但它实际上是很长的艰苦代码我的问题是,它是有效的,而不是仅仅做最小的调用重现崩溃:

void testCrash() 
{ 
    mockDep3 = mock(dep3); 
    c(mockDep3)  
    e() 
    f(); 
} 

这将是我将被要求做的重现和测试崩溃,除非它不完全如何在实际的方法“all”中调用每个方法的代码。它看起来像编写testAll上面包括所有的嘲笑一遍又一遍每个bug是有点泰迪。但是,再次,那是如何实际的方法“全部”将工作。因此,如果全面的方法被调用,即使隔离不完全如何工作,它是否更好地隔离错误?

从你的例子中不清楚的是哪些方法是公开的,哪些是私有的(或者是保护的或者包私有的)。如果唯一的公共方法是all,那么只要有可能,这就是您的测试应该调用的方法,除非您正在测试真正包含在其中一种方法中的复杂逻辑。

当您选择要编写哪些测试时,您应该真正关注此类合同的内容。您的应用程序的其他应用程序期望此类提供哪些功能?将您的测试方法建立在功能上;而不是你碰巧编写了哪些方法。此外,如果Dependency类的唯一目的是支持ClassC的功能,那么你可以考虑编写将SUT与ClassC和Dependency结合在一起的测试(有些人会称之为“集成测试”,有些人会称它们为“集成测试” “单元测试,单元由两个类组成”)。

在你的特殊情况下,你的类似乎提供了跨越整个分区运行的功能。因此,测试功能 - 并测试可能导致功能以不同方式工作的不同情况。不要测试个别方法。特别是,您将有多种测试在各种情况下调用all方法;其中一些会导致错误发生(至少在修复错误之前),而其中一些则不会。

最后,为了帮助您根据测试功能和场景进行思考,我相信您应该根据正在执行的功能和场景命名您的测试方法,而不是通过所调用的方法命名。特别是,我认为testAll(实际上,任何由test组成的名称后跟一个方法的名称)对于测试方法来说是一个很差的名字。但也许这是另一天需要思考的问题。

+0

我想我明白你在说什么,但也许我不喜欢答案。我猜你是在说我必须对“全部”进行一些调用,提供不同的模拟来触发不同的路径,一个产生崩溃。我想我发现为每次测试重复构建所有这些模拟以避免崩溃,当我可以直接调用e()和f()来触发崩溃并测试修复时,会有点乏味。但在这种情况下,你是对的,e()和f()应该是私有的,所以这不是一个选项。这似乎是一种困难的测试方式。 – SpecialEd

+0

我通常使用[Mockito注释](http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#9)来创建我的嘲笑,这样我就不必重新编辑它,为每个测试编码它们。如果所有模拟都需要特定的存根,我通常会在具有JUnit'@ Before'标签的方法中进行设置。 –

+0

我也使用Mockito,虽然我是新手。当你说“注释”或只是提到像mock()doAnswer()等典型的mockito命令时,你的意思是什么?我认为你是对的,也许我可以简化我的测试,如果我利用了“设置”方法,我认为它类似于你提到的@Before标记。 – SpecialEd