Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Go

与Go同行:测试Golang程序

by
Difficulty:IntermediateLength:MediumLanguages:

Chinese (Simplified) (中文(简体)) translation by Qiang Ji (you can also view the original English article)

在本教程中我将使用被Go语言设计者和社区总结出的最佳实践教会你们在Go语言中所有惯用测试的基础。 Go语言测试的主要武器是标准测试包。 测试对象是一个能解决Euler项目中一个简单问题的示例程序。

平方和差

平方和差问题很简单:“找出第一百个自然数的平方和与和的平方的差值。”

这个特别的问题可以被很简单地解决,特别是如果你知道高斯。 比如,第N个自然数的和是(1 + N) * N / 2,第N个整数的平方和是:(1 + N) * (N * 2 + 1) * N / 6。 所以整个问题可以通过以下公式解决,将100分配给N:

(1 + N) * (N * 2 + 1) * N / 6 - ((1 + N) * N / 2) * ((1 + N) * N / 2)

是的,这非常的具体,没有太多的东西可以测试。 相反地,我创建了一些比这个问题所需要的更多的功能,这可以在将来为其它程序提供服务(项目欧拉现在有559个问题)。

我把代码放在了GitHub上。

以下是四个函数的签名:

现在,通过我们的目标程序(请原谅我,TDD狂热分子),让我们看看如何为这个程序编写测试。

测试包

测试包是与go test命令结合使用的。 你的包测试应该在一个以"_test.go"结尾的文件里。 你可以将测试拆分为遵循此约定的多个文件。 例如:"whatever1_test.go"和"whatever2_test.go"。 你应该在这些测试文件中编写你的测试函数。

每个测试函数是一个公开导出的名字以"Test"开始的函数,它接受一个指向testing.T对象的指针,并且什么都不返回。 它看上去像这样:

T对象提供了各种可用于指示故障或记录错误的方法。

记住:只有定义在测试文件中的测试函数将被go test命令执行。

编写测试

每个测试遵循同样的流程:设置测试环境(可选),提供测试输入的代码,捕获测试结果,并且将其与预期输出进行比较。 请注意,输入和结果不一定是函数的参数。

如果被测数据是从数据库中获取数据,那么输入将确保数据库包含适当的测试数据(可能涉及到各种级别的模拟)。 但是,对于我们的应用程序,常用情况是将输入参数传递给函数并将结果与函数输出进行比较,这就足够了。

让我们以SumList()函数开始。 此函数以一组整数数组作为输入并返回它们的和。 以下是一个可鉴别SumList()函数应该有的行为的测试函数。

它有两个测试用例,如果预期输出与结果不匹配,则调用test.T对象的Error()方法。

这都是很直观的,但它看上去有点啰嗦。 惯用的Go测试使用表驱动测试,你可以在其中定义一组输入和预期输出的结构,然后将这些一对对的列表在一个循环中提供给相同的逻辑。 以下是测试SumList()函数的方法。

这样更好。 这很容易去加入更多的测试用例。 这很容易在一个地方拥有全面的测试用例,如果你决定更改测试逻辑,则无需更改多个实例。

以下是测试SquareList()函数的另一个例子。 在这种情况下,输入和输出均为整数数组,因此测试组对结构是不同,但流程是相同的。 一个有趣的事情是,Go不提供内置的数组比对的方法,所以我使用reflect.DeepEqual()将输出数组与预期数组进行比较。

运行测试

运行测试与在你的包目录中输入go test一样简单。 Go会找到带有“_test.go”后缀的所有文件以及带有“Test”前缀的所有函数,并将其作为测试运行。 以下是当一切测试正常时的样子:

没有很戏剧性。 让我故意破坏一个测试。 我将更改SumList()的测试用例,以使求1与2和的预期输出为7。

现在当你运行go test,你能得到:

它很好地陈述了发生了什么,它也应该给了你解决问题所需要的所有信息。 在这种情况下,问题是测试自己本身是错误的,期望值应该是3。这是一个重要的教训。 不要自动假设如果测试失败,那测试中的代码就是有问题的。 考虑整个系统,其中包括被测代码,测试本身和测试环境。

测试覆盖率

确保你的代码能工作,仅仅通过测试是不够的。 另一个重要方面是测试覆盖率。 你的测试覆盖了代码中的每个语句? 有时即使你做到了这也是不够的。 例如,如果你的代码中有个循环一直运行直到一个条件被满足,你可以成功地用一个条件去测试它,但没有注意到在某些情况下该条件可能总是假的,这将导致无限循环 。

单元测试

单位测试就像刷牙和使用牙线。 你不应该忽视它们。 它们是防御问题的第一到防线,并让你有信心重构代码。 当尝试重现问题并且能够编写一个失败的测试来证明问题在修正后已经不存在,它们也是一种好事儿。

集成测试

集成测试也是必要的。 把它们想象成去看牙医。 有段时间不需要它们你也许也OK,但是如果你忽略它们太长时间,那就不好了。

大多数复杂的程序是由多个相互关联的模块或组件组成的。 问题常常会在一起编写这些组件时发生。 集成测试可以让你确信整个系统按预期运行。 还有许多其它类型的测试,如验收测试,性能测试,压力/负载测试和全面的整个系统测试,但单元测试和集成测试是测试软件的两个基本方法。

结论

Go具有内置的测试支持,一种优良的测试方式,以及以表驱动测试形式被推荐的指导方针。

对每个输入和输出组合编写特殊struct的需求是有点烦人,但这是你对Go语言简单的设计方法支付的代价。

关注我们的公众号
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.