如何对复杂方法进行单元测试

时间:2008-11-02 23:09:01

标签: unit-testing language-agnostic

我有一种方法,给定North的角度和方位角,从8个可能的值(North,NorthEast,East等)返回罗盘点值。我想创建一个单元测试,为这个方法提供合适的覆盖,为North和Bearing提供不同的值,以确保我有足够的覆盖率让我相信我的方法正在工作。

我原来的尝试为-360到360生成了North的所有可能的整数值,并测试了每个Bearing值从-360到360.但是,我的测试代码最终成为我正在测试的代码的另一个实现。这让我想知道最好的测试是什么,这样我的测试代码不会只包含与我的生产代码相同的错误。

我目前的解决方案是花时间编写带有数据点和预期结果的XML文件,我可以在测试期间阅读并使用它来验证方法,但这似乎非常耗时。我不想写一个包含与原始测试包含的相同值范围的文件(这将是很多XML),但我确实想要包含足够的文件以充分测试该方法。

  • 如何在不重新实现方法的情况下测试方法?
  • 如何获得足够的覆盖率,以便对我正在测试的方法充满信心,而无需为所有可能的输入和结果提供测试点?

显然,不要过多地关注我的具体例子,因为这适用于许多需要测试复杂计算和数据范围的情况。

注意:我使用的是Visual Studio和C#,但我相信这个问题与语言无关。

10 个答案:

答案 0 :(得分:30)

首先,你是对的,你希望你的测试代码重现与被测代码相同的计算。其次,你的第二种方法是朝着正确方向迈出的一步。您的测试应该包含一组特定的输入,其中包含这些输入的预先计算的预期输出值。

您的XML文件应该只包含您所描述的输入数据的子集。您的测试应该确保您可以处理输入域的极端范围(-360,360),范围末端的几个数据点以及中间的一些数据点。当给定值超出输入范围时(例如-361和+361),您的测试还应检查您的代码是否正常失败。

最后,在您的特定情况下,您可能需要更多边缘情况,以确保您的函数正确处理有效输入范围内的“切换”点。这些将是输入数据中的点,其中输出将从“北”切换到“西北”,从“西北”切换到“西”等。(不要运行代码来查找这些点,计算它们用手)。

只关注这些边缘情况,边缘之间的一些情况应该会大大减少你必须测试的点数。

答案 1 :(得分:5)

您可以将方法重新分解为更容易进行单元测试的部分,并编写部件的单元测试。然后整个方法的单元测试只需要集中在集成问题上。

答案 2 :(得分:5)

我更愿意做以下事情。

  1. 创建一个包含正确答案的电子表格。无论多么复杂,它都是无关紧要的。您只需要一些带有案例的列和一些带有预期结果的列。

    对于你的例子,这可能很大。但是大可以。你将有一个角度,一个方位和产生的罗盘点值。你可能有一堆中间结果。

  2. 创建一个小程序,读取电子表格并编写简化的底线单元测试用例。您希望将您的案例剥离到

    def testCase215n( self ):
        self.fixture.setCourse( 215 )
        self.fixture.setBearing( 45 )
        self.fixture.calculate()
        self.assertEquals( "N", self.fixture.compass() )
    
  3. [那是Python,同样的想法适用于C#。]

    电子表格包含正确答案的唯一权威列表。您可以从此生成一次或两次代码。当然,除非您在电子表格版本中发现错误并且必须解决此问题。

    我使用xlrd的小型Python程序和Mako模板生成器来执行此操作。你可以用C#产品做类似的事情。

答案 3 :(得分:4)

如果你能想到你的方法的完全不同的实现,使用完全不同的位置来隐藏bug,你可以对此进行测试。我经常做这样的事情,当我有一个有效但复杂的实现时,可以更简单但低效地实现。例如,如果编写一个哈希表实现,我可能会实现一个基于线性搜索的关联数组来对其进行测试,然后使用大量随机生成的输入进行测试。线性搜索AA非常难以搞砸,甚至更难搞砸,因为它与哈希表的相同方式是错误的。因此,如果哈希表具有与线性搜索AA相同的可观察行为,我相信它是正确的。

其他示例包括编写冒泡排序来测试堆排序,或者使用已知的工作排序函数来查找中位数并将其与O(N)中位数查找算法实现的结果进行比较。

答案 4 :(得分:3)

我相信你的解决方案很好,尽管使用了XML文件(我会使用纯文本文件)。但更常用的策略是测试极限情况,例如在你的情况下使用-360,360,-361,361和0的入口值。

答案 5 :(得分:1)

您可以尝试使用orthogonal array testing来实现所有对的覆盖,而不是所有可能的组合。这是一种基于该理论的统计技术,即由于参数对之间的相互作用而发生大多数错误。它可以大大减少您编写的测试用例的数量。

答案 6 :(得分:1)

不确定你的代码有多复杂,如果它取整数并将其分成指南针上的8或16个方向,那么可能是几行代码?

根据您的测试方式,您将很难重新编写代码来测试它。理想情况下,您希望独立方根据相同的要求编写测试代码,但不要查看或借用您的代码。在大多数情况下不太可能发生这种情况。在这种情况下可能有点矫枉过正。

在这种特定情况下,我将按照从-360到+360的顺序为每个数字提供它,并打印数字和结果(以可以作为头文件编译成另一个程序的格式的文本文件)。目视检查方向是否在所需输入处发生变化。这应该易于目视检查和验证。现在您有一个输入表和有效输出表。接下来有一个程序从有效输入中随机选择,将其输入到您正在测试的代码中,并看到正确的答案出来。做几百个随机测试。在某些时候,您需要验证根据您的要求处理小于-360或大于+360的数字,无论是裁剪还是调整我都假设。

答案 7 :(得分:0)

所以我参加了一个软件测试课link text,基本上你想要的是识别输入类......所有实数?所有整数,只有正数,只有负数等...然后对输出动作进行分组。是360与359独特的不同,或者它们最终会对应用做同样的事情。一旦输入与输出组合。

这一切看起来都是抽象和模糊的,但在你提供方法代码之前,很难想出一个完美的策略。

另一种方法是进行分支级别测试或谓词覆盖测试。代码覆盖率不是万无一失,但不覆盖您的所有代码似乎是不负责任的。

答案 8 :(得分:0)

可能与其他测试方法结合使用的一种方法是查看是否可以创建一个反转您正在测试的方法的函数。在这种情况下,它将采用罗盘方向(比如东北),并输出一个轴承(给定北方轴承)。然后,您可以通过将方法应用于一系列输入来测试该方法,然后应用该函数来反转该方法,并查看是否返回原始输入。

有一些复杂情况,特别是如果一个输出对应多个输入,但在这些情况下可能会生成对应于给定输出的输入集,并测试该集的每个成员(或某个样本)集合的元素。)

这种方法的优点是它不依赖于您能够手动模拟方法,或者创建方法的替代实现。如果逆转涉及与原始方法中使用的问题不同的方法,则应该降低在两者中产生等效错误的风险。

答案 9 :(得分:0)

伪码:

array colors = { red, orange, yellow, green, blue, brown, black, white }
for north = -360 to 361
    for bearing = -361 to 361
        theColor = colors[dirFunction(north, bearing)] // dirFunction is the one being tested
        setColor (theColor)
        drawLine (centerX, centerY,
                  centerX + (cos(north + bearing) * radius),
                  centerY + (sin(north + bearing) * radius))
        Verify Resulting Circle against rotated reference diagram.

当North = 0时,您将获得一个8色饼图。当北方变化+或 - 时,饼图看起来会相同,但会旋转很多度。测试验证是确保(a)图像是正确旋转的饼图和(b)橙色区域中没有任何绿点等的简单问题。

顺便提一下,这种技术是世界上最好的调试工具的变体:让计算机为你画出IT =认为它在做什么的图片。 (很多时候,开发人员浪费时间追逐他们认为计算机正在做的事情,却发现它正在做一些完全不同的事情。)