有比一系列“ AssertEquals”更好的编写单元测试的方法吗?


12

这是使用qunit进行单元测试的基本示例:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>

<link rel="stylesheet" href="qunit/qunit-1.13.0.css">
<script src = "qunit/qunit-1.13.0.js"></script>
<script src = "../js/fuzzQuery.js"></script>

<script>

test("Fuzz Query Basics", function()
        {
            equal(fuzzQuery("name:(John Smith)"), "name:(John~ Smith~)");
            equal(fuzzQuery("name:Jon~0.1"), "name:Jon~0.1");
            equal(fuzzQuery("Jon"), "Jon~");
            //etc

        }
    );

</script>
</head>
<body>
    <div id="qunit"></div>
</body>
</html>

现在我认为这有点重复。

可以将所有输入/输出放入一个数组,然后遍历它。

test("Fuzz Query Basics", function()
        {
            var equals = [
                           ["name:(John Smith)", "name:(John~ Smith~)"],
                           ["name:Jon~0.1", "name:Jon~0.1"],
                           ["Jon", "Jon~"]
                           ];

            for (var i = 0; i<equals.length; i++)
                {
                    equal(fuzzQuery(equals[i][0]), equals[i][1]);               
                }

        }
    );

这很好。

对于第二种方法,我可以想到的唯一优点是,如果事实证明您实际上并不想要使用equal它,则可以在一处进行更改。

就可读性而言,尽管我可能更喜欢第二种方法,但我认为这都不是决定性的。

进一步抽象它,您可以将输入/输出案例放入单独的CSV文件中,这可能使修改变得更容易。

问题是-编写此类单元测试的一般约定是什么?

您是否有理由不应该将它们放入数组中?


这些都会告诉您哪个值失败了吗?
JeffO 2014年

1
@JeffO-是-使用QUnit-如果测试失败,则输出将显示期望值和实际值。
dwjohnston 2014年

Answers:


8

重构的测试有一种气味:条件测试逻辑

您应该避免在测试中编写条件逻辑的原因有两个。首先,如链接的xUnit Patterns文章中所述,它破坏了您确定测试代码正确的能力。

第二是它掩盖了测试的含义。我们之所以编写“ 测试方法”,是因为它们将用于测试给定行为的逻辑放在一个地方,并允许我们给它起一个描述性的名称(有关测试好名声的价值,请参见Dan North的原始BDD文章)。当您的测试通过for循环隐藏在单个函数中时,它会模糊代码的含义,使读者无法理解。读者不仅必须理解循环,还必须从精神上阐明循环中正在测试的所有不同行为。

与往常一样,解决方案是将抽象层次上移。使用一个可以为您提供参数化测试的测试框架,例如xUnit.NETContexts(免责声明:我编写了Contexts)。这使您能够以自然的方式将针对相同行为的三角测试组合在一起,同时将单独行为的测试分开。


顺便问好问题
本杰明·霍奇森

1
1)如果您将抽象层次上移,您是否隐藏了for循环所掩盖的那些细节?2)不确定参数化测试是否适用于此。似乎在某处有相似之处,但是我有很多类似于OP的情况,其中我有一个10-20个值的数据集,只想通过SUT运行所有这些值。是的,每个值都是不同的,并且可能测试不同的Boodary,但似乎实际上为每个值“发明”测试名称将是一个过大的杀伤力。我在使用类似的文件时发现了最佳的值/代码大小比...
DXM 2014年

...循环。只要测试失败,则断言将准确打印出失败的内容,开发人员将获得足够的反馈以准确查明问题。
DXM 2014年

@DXM 1)测试框架提供了参数化的测试功能。我们暗中信任测试框架,因此我们不会为其编写测试。2)参数化测试正是出于此目的:您每次都执行完全相同的步骤,但是输入/输出值不同。通过使用相同的测试方法运行不同的输入,该测试框架使您无需为每个名称编写名称。
本杰明·霍奇森

5

看来您确实想要数据驱动的单元测试。既然您提到使用QUnit,我发现了一个启用参数化测试的插件:

https://github.com/AStepaniuk/qunit-parameterize

只要测试代码本身不是有条件的,数据驱动测试在思想上就没有错。查看您的测试代码,它似乎是进行数据驱动测试的很好的选择。

GitHub README的示例代码:

QUnit
    .cases([
        { a : 2, b : 2, expectedSum : 4 },
        { a : 5, b : 5, expectedSum : 10 },
        { a : 40, b : 2, expectedSum : 42 }
    ])
    .test("Sum test", function(params) {
        var actualSum = sum(params.a, params.b);
        equal(actualSum, params.expectedSum);
    });

1
同意,它看起来像是一个数据驱动的测试。但是似乎这就是他在第二个代码示例中已经拥有的。
罗伯特·哈维

1
@RobertHarvey-正确。对于他要完成的工作,有一个公认的术语,并且存在一个用于测试框架的插件,该插件使编写此类测试更加容易。我认为值得一提的是未来的答案,仅此而已。
Greg Burghardt

1

通过使用更易于维护的数组,您可以减少重复的次数。我喜欢使用的一种方法是拥有一个单独的方法来安排,执行和声明测试,但是接受我正在测试的输入参数,因此每个输入集只有一种测试方法。

这使我可以立即判断出哪些测试/输入失败。


0

我喜欢您的第二种方法,但我会加2分

  • 不要使用数组来存储测试数据,因为使用索引是一种不干净的方法
  • 不要使用for循环

`

[
    {
        process: "name:(John Smith)",
        result: "name:(John~ Smith~)"
    },
    {
        process: "name:Jon~0.1", 
        result: "name:Jon~0.1"
    },
    {
        process: "Jon", 
        result: "Jon~"
    }
]
.forEach(function(data){

    var result = fuzzQuery(data.process);
    equal(result, data.result);
});

我不确定qunit,但是一个好的测试运行者会告诉您什么输入字符串失败,以及预期的结果是什么

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.