[ 高质量编程简介及编码规范 | 青训营笔记 ]

101 阅读10分钟

文档

呈现于                             Linux/AMD64                            视窗/AMD 64                            达尔文/AMD 64                            JS/WASM                        

概述

包测试支持 Go 包的自动测试。 它旨在与“go test”命令一起使用,该命令可自动执行 执行表单的任何函数

func TestXxx(*testing.T)

其中 Xxx 不以小写字母开头。函数名称 用于识别测试例程。

在这些函数中,使用错误、失败或相关方法来发出故障信号。

若要编写新的测试套件,请创建一个文件 包含此处所述的 TestXxx 函数, 并为该文件指定一个以“_test.go”结尾的名称。 该文件将从常规文件中排除 包构建,但在运行“go test”命令时将包含在内。

测试文件可以与被测试文件位于同一包中, 或在带有后缀“_test”的相应包中。

如果测试文件在同一个包中,则可能引用未导出 包中的标识符,如以下示例所示:

package abs

import "testing"

func TestAbs(t *testing.T) {
    got := Abs(-1)
    if got != 1 {
        t.Errorf("Abs(-1) = %d; want 1", got)
    }
}

如果文件位于单独的“_test”包中,则表示正在测试的包 必须显式导入,并且只能使用其导出的标识符。 这被称为“黑匣子”测试。

package abs_test

import (
	"testing"

	"path_to_pkg/abs"
)

func TestAbs(t *testing.T) {
    got := abs.Abs(-1)
    if got != 1 {
        t.Errorf("Abs(-1) = %d; want 1", got)
    }
}

有关更多详细信息,请运行“go help test”和“go help testflag”。

基准

表单的功能

func BenchmarkXxx(*testing.B)

被视为基准测试,并在以下情况下由“go test”命令执行 提供了其 -bench 标志。基准测试按顺序运行。

有关测试标志的说明,请参阅 golang.org/cmd/go/#hdr…

示例基准测试函数如下所示:

func BenchmarkRandInt(b *testing.B) {
    for i := 0; i < b.N; i++ {
        rand.Int()
    }
}

基准测试函数必须运行目标代码 b.N 次。 在基准测试执行期间,b.N 被调整,直到基准测试函数持续 足够长,可以可靠地计时。输出

BenchmarkRandInt-8   	68453040	        17.8 ns/op

意味着环路以每个环路 68453040.17 ns 的速度运行 8 次。

如果基准测试在运行前需要一些昂贵的设置,计时器 可以重置:

func BenchmarkBigLen(b *testing.B) {
    big := NewBig()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        big.Len()
    }
}

如果基准测试需要在并行设置中测试性能,则可以使用 运行并行帮助程序函数;此类基准旨在与 Go 测试 -CPU 标志:

func BenchmarkTemplateParallel(b *testing.B) {
    templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
    b.RunParallel(func(pb *testing.PB) {
        var buf bytes.Buffer
        for pb.Next() {
            buf.Reset()
            templ.Execute(&buf, "World")
        }
    })
}

给出了基准测试结果格式的详细规范 在 golang.org/design/1431…

有一些标准工具可用于处理 golang.org/x/perf/cmd 的基准测试结果。 特别是,golang.org/x/perf/cmd/… 执行 统计稳健的 A/B 比较。

例子

该包还运行并验证示例代码。示例函数可以 包括以“输出:”开头的结束行注释,并与 运行测试时函数的标准输出。(比较 忽略前导空格和尾随空格。这些是示例的示例:

func ExampleHello() {
    fmt.Println("hello")
    // Output: hello
}

func ExampleSalutations() {
    fmt.Println("hello, and")
    fmt.Println("goodbye")
    // Output:
    // hello, and
    // goodbye
}

注释前缀“无序输出:”类似于“输出:”,但匹配任何 行顺序:

func ExamplePerm() {
    for _, value := range Perm(5) {
        fmt.Println(value)
    }
    // Unordered output: 4
    // 2
    // 1
    // 3
    // 0
}

没有输出注释的示例函数将被编译但不执行。

用于声明包、函数 F、类型 T 和示例的命名约定 T型的方法M是:

func Example() { ... }
func ExampleF() { ... }
func ExampleT() { ... }
func ExampleT_M() { ... }

包/类型/函数/方法的多个示例函数可能由 向名称追加不同的后缀。后缀必须以 小写字母。

func Example_suffix() { ... }
func ExampleF_suffix() { ... }
func ExampleT_suffix() { ... }
func ExampleT_M_suffix() { ... }

当整个测试文件包含单个时,整个测试文件将作为示例呈现 示例函数,至少一个其他函数、类型、变量或常量 声明,并且没有测试或基准测试函数。

模糊

“Go Test”和测试包支持模糊测试,这是一种测试技术,其中 使用随机生成的输入调用函数以查找错误而不是 由单元测试预期。

表单的功能

func FuzzXxx(*testing.F)

被视为模糊测试。

例如:

func FuzzHex(f *testing.F) {
  for _, seed := range [][]byte{{}, {0}, {9}, {0xa}, {0xf}, {1, 2, 3, 4}} {
    f.Add(seed)
  }
  f.Fuzz(func(t *testing.T, in []byte) {
    enc := hex.EncodeToString(in)
    out, err := hex.DecodeString(enc)
    if err != nil {
      t.Fatalf("%v: decode: %v", in, err)
    }
    if !bytes.Equal(in, out) {
      t.Fatalf("%v: not equal after round trip: %v", in, out)
    }
  })
}

模糊测试维护一个种子语料库,或一组由 默认,并且可以生成种子输入。种子输入可以通过以下方式注册 呼叫 (*F)。添加或通过将文件存储在目录中 testdata/fuzz/ (其中<名称>是模糊测试的名称)在包含以下内容的包装内 模糊测试。种子输入是可选的,但模糊测试引擎可能会发现 当提供一组具有良好 代码覆盖率。这些种子输入也可以用作错误的回归测试 通过模糊测试识别。

传递给 (*F) 的函数。模糊测试中的模糊被认为是模糊 目标。模糊目标必须接受 *T 参数,后跟一个或多个参数 随机输入的参数。传递给 (*F) 的参数类型。添加必须 与这些参数的类型相同。模糊目标可能发出信号 它发现问题的方式与测试相同:通过调用 T.Fail(或任何 方法称其为T.Error或T.Fatal)或恐慌。

启用模糊测试时(通过将 -fuzz 标志设置为正则表达式 与特定的模糊测试匹配),使用参数调用模糊目标 通过反复对种子输入进行随机更改而生成。上 支持的平台,“Go Test”使用模糊测试编译测试可执行文件 覆盖率仪器。模糊测试引擎使用该检测来 查找和缓存可扩大覆盖范围的输入,从而增加 查找错误。如果给定输入的模糊目标失败,则模糊测试引擎 将导致失败的输入写入目录中的文件 testdata/fuzz/ 在包目录中。此文件稍后用作 种子输入。如果文件无法在该位置写入(例如, 因为目录是只读的),模糊测试引擎将文件写入 而是生成缓存中的模糊缓存目录。

禁用模糊测试时,将使用种子输入调用模糊目标 注册了F.Add和来自testdata/fuzz/的种子输入。在此 模式,模糊测试的行为与常规测试非常相似,子测试已开始 用F.Fuzz而不是T.Run。

有关模糊测试的文档,请参阅 go.dev/doc/fuzz

可以在运行时跳过测试或基准测试,调用 跳过 *T 或 *B 的方法:

func TestTimeConsuming(t *testing.T) {
    if testing.Short() {
        t.Skip("skipping test in short mode.")
    }
    ...
}

如果输入无效,则可以在模糊目标中使用 *T 的 Skip 方法, 但不应被视为失败的输入。例如:

func FuzzJSONMarshaling(f *testing.F) {
    f.Fuzz(func(t *testing.T, b []byte) {
        var v interface{}
        if err := json.Unmarshal(b, &v); err != nil {
            t.Skip()
        }
        if _, err := json.Marshal(v); err != nil {
            t.Errorf("Marshal: %v", err)
        }
    })
}

子测试和子基准

T 和 B 的运行方法允许定义子测试和子基准, 无需为每个函数定义单独的函数。这使得可以使用 比如表驱动的基准测试和创建分层测试。 它还提供了一种共享常见设置和拆卸代码的方法:

func TestFoo(t *testing.T) {
    // <setup code>
    t.Run("A=1", func(t *testing.T) { ... })
    t.Run("A=2", func(t *testing.T) { ... })
    t.Run("B=1", func(t *testing.T) { ... })
    // <tear-down code>
}

每个子测试和子基准都有一个唯一的名称:名称的组合 顶级测试和传递给 Run 的名称序列,分隔为 斜杠,带有可选的尾随序列号以消除歧义。

-run、-bench 和 -fuzz 命令行标志的参数是未锚定的常规 与测试名称匹配的表达式。对于具有多个斜杠分隔的测试 元素,例如子测试,参数本身以斜杠分隔,带有 依次匹配每个 name 元素的表达式。因为它是未锚定的,所以 空表达式匹配任何字符串。 例如,使用“匹配”表示“其名称包含”:

go test -run ''        # Run all tests.
go test -run Foo       # Run top-level tests matching "Foo", such as "TestFooBar".
go test -run Foo/A=    # For top-level tests matching "Foo", run subtests matching "A=".
go test -run /A=1      # For all top-level tests, run subtests matching "A=1".
go test -fuzz FuzzFoo  # Fuzz the target matching "FuzzFoo"

-run 参数还可用于运行种子中的特定值 语料库,用于调试。例如:

go test -run=FuzzFoo/9ddb952d9814

可以设置 -fuzz 和 -run 标志,以便模糊目标,但 跳过所有其他测试的执行。

子测试也可用于控制并行度。父测试将仅 在其所有子测试完成后完成。在此示例中,所有测试都是 彼此并行运行,并且仅彼此运行,无论 可以定义的其他顶级测试:

func TestGroupedParallel(t *testing.T) {
    for _, tc := range tests {
        tc := tc // capture range variable
        t.Run(tc.Name, func(t *testing.T) {
            t.Parallel()
            ...
        })
    }
}

在并行子测试完成之前,Run 不会返回,从而提供了一种方法 要在一组并行测试后进行清理,请执行以下操作:

func TestTeardownParallel(t *testing.T) {
    // This Run will not return until the parallel tests finish.
    t.Run("group", func(t *testing.T) {
        t.Run("Test1", parallelTest1)
        t.Run("Test2", parallelTest2)
        t.Run("Test3", parallelTest3)
    })
    // <tear-down code>
}

主要

测试或基准测试程序有时需要进行额外的设置或拆卸 在执行之前或之后。有时还需要控制 哪些代码在主线程上运行。为了支持这些和其他情况, 如果测试文件包含函数:

func TestMain(m *testing.M)

然后生成的测试将调用TestMain(m),而不是运行测试或基准测试 径直。TestMain 在主 goroutine 中运行,可以执行任何设置 并且需要围绕 m.Run 的调用进行拆解。m.Run 将返回一个出口 可以传递给操作系统的代码。退出。如果 TestMain 返回,则测试包装器 会将 m.Run 的结果传递给操作系统。退出自身。

当调用 TestMain 时,标志。尚未运行解析。如果测试主要取决于 命令行标志,包括测试包的标志,它应该调用 旗。显式解析。命令行标志始终由时间测试解析 或基准测试函数运行。

TestMain 的一个简单实现是:

func TestMain(m *testing.M) {
	// call flag.Parse() here if TestMain uses flags
	os.Exit(m.Run())
}

TestMain 是一个低级原语,对于休闲来说应该是必需的 测试需求,其中普通测试功能就足够了。

指数