基于属性的测试在Go中:比您写代码更快地找出错误

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

当单元测试撒谎,而Bug依然存在时



您写了20个单元测试。全部通过。CI通过。您很满意。然后——生产环境出现了Bug。熟悉吗?经典场景:您只测试了自己想到的那些情况。而现实偏偏会抛出您甚至没考虑过的组合。



在Go中,有一种强大但被低估的方法——基于属性的测试(或属性测试)。与其检查特定的输入和输出,不如描述函数应该满足的属性,而框架会自动生成数百个随机测试数据。如果属性被违反——您就找到了Bug。



在本文中,我们将探讨如何使用gopter库在Go中实施基于属性的测试,编写实际示例,并看看能捕获哪些Bug。开始吧!



什么是基于属性的测试,为什么需要它?



假设您正在测试一个排序函数。普通的单元测试:



func TestSort(t *testing.T) {    input := []int{3, 1, 2}    result := sort(input)    expected := []int{1, 2, 3}    if !reflect.DeepEqual(result, expected) {        t.Errorf("got %v, want %v", result, expected)    }}


好,能工作。但如果数组为空呢?如果所有元素都相同呢?如果有负数呢?您可以再写10个测试,但总会有遗漏。



基于属性的测试从根本上解决了这个问题:您制定不变量——对于任何正确的输入都必须为真的属性。例如,对于排序:



  • 排序后数组的长度等于原始数组的长度。
  • 原始数组中的每个元素都出现在排序后的数组中(反之亦然)。
  • 数组按升序排序。


框架(例如gopter或内置的testing/quick)生成随机输入数据,并检查所有属性是否成立。如果有Bug——即使您不知道在哪里找,也会发现它。



用于基于属性测试的Go库



在Go生态系统中,有几个工具:



  • testing/quick——内置包,极简,但功能有限。
  • gopter——功能强大的库,带有生成器、缩减(最小化输入数据)和灵活配置。我们推荐使用它。
  • rapid——现代替代方案,性能更高。


我们将使用gopter,因为它有更好的文档和社区。安装:



go get github.com/leanovate/gopter


编写第一个基于属性的Go测试



假设我们有一个Reverse函数,用于反转字符串:



func Reverse(s string) string {    runes := []rune(s)    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {        runes[i], runes[j] = runes[j], runes[i]    }    return string(runes)}


可以检查什么属性?如果反转字符串两次,我们应该得到原始字符串:



import (    "testing"    "github.com/leanovate/gopter"    "github.com/leanovate/gopter/gen")

func TestReverseTwice(t *testing.T) { properties := gopter.NewProperties(nil)

properties.Property("reverse(reverse(s)) == s", prop.ForAll( func(s string) bool { return Reverse(Reverse(s)) == s }, gen.AnyString(), ))

properties.TestingRun(t)}


运行测试——gopter会生成100个随机字符串,检查属性。如果一切正常——测试通过。如果不——您将得到一个反例(导致测试失败的最小字符串)。



Bo

博客

书籍推荐