1

我有一个关于单元测试的基本问题。我在我的代码中发现了一个崩溃,我写了一个测试来重现这个错误。然后我修复了这个错误并验证它通过了测试。我的问题是,我是否采取了最好的方法。这是一个简化的示例:

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

在上面的示例中,我有一个名为“all”的方法,它调用方法 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 包括所有模拟有点乏味。但话又说回来,这就是实际方法“all”的工作方式。那么,即使在调用完整方法的情况下,隔离并不完全是它的工作方式,隔离 bug 是否更好?

4

1 回答 1

0

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

当你选择编写哪些测试时,你应该真正关注这个类的契约是什么。你的应用程序的其余部分希望这个类提供什么功能?将您的测试方法基于该功能是什么;不是你碰巧写了哪些方法。

此外,如果 Dependency 类的唯一目的是支持 ClassC 的功能,那么您可以考虑一起编写 SUT 是这个 ClassC 和 Dependency 的测试(有些人称这些为“集成测试”,有些人称它们为“单元测试,其中单元由两个类组成”)。

在您的特定情况下,您的类似乎提供了跨方法划分的功能。所以测试功能 - 并测试可能导致功能以不同方式工作的不同场景。不要测试单个方法。特别是,您将有许多all在各种场景中调用该方法的测试;其中一些会导致错误发生(至少在您修复错误之前),而另一些则不会。

最后,为了帮助您在测试功能和场景方面进行思考,我认为您应该根据正在执行的功能和场景来命名您的测试方法,而不是根据被调用的方法。特别是,我认为testAll(实际上,任何由test后跟方法名称组成的名称)对于测试方法来说都是一个糟糕的名称。但也许这是改天要考虑的事情。

于 2012-09-27T22:25:41.553 回答