go test 笔记

本文详细介绍了Go语言的gotest工具,包括常用命令、测试类型(单元测试和基准测试)、并行测试、内存统计、覆盖率报告以及如何解决golangtestcached问题。涵盖了测试用例编写、执行、控制和性能优化等内容。
摘要由CSDN通过智能技术生成

1. go test 笔记

现代编程语言中, 很多都集成了测试工具, 例如 golang 中, 就有 testing 包提供一系列测试工具。

通过 go test 命令就可以实现测试用例的执行, 通过不同的参数还可以进行例如压测、并发测试等测试功能。

大多数的 Go 程序员都知道和喜欢用 go test, 这个测试工具来自于 Go 官方的 gc 工具链。(想要执行测试代码)这个命令可能是最简单的了, 而且还能做得很漂亮。

大家都知道, 运行 go test 命令将执行当前目录下的包的测试代码, 它会寻找 *_test.go 文件, 并在这些文件中, 寻找符合 TestXxx(*testing.T){} 命名的函数和参数(即, 接收 *testing.T 参数的函数, 命名为 TestXxx, Xxx 可以是任何不以小写字符开头的名字)。这个测试代码不会影响正常的编译过程, 只在执行 go test 时被使用。

在你写自己的测试代码前, 建议看一下标准库中的 testing/iotest, testing/quicknet/http/httptest 软件包。

1.1. 常用命令

go test -timeout -v 30s -run ^Test_calculateBandwidthUtilization$ gitlab.jiagouyun.com/cloudcare-tools/datakit/plugins/inputs/snmp/snmputil
alias gtcovershow='go test -v -cover . -coverprofile=coverage.out && go tool cover -html=coverage.out'

1.2. 介绍

go test 是 go 语言自带的测试工具, 其中包含的是两类:

  • 单元测试
  • 性能测试

通过 go help test 可以看到 go test 的使用说明:

格式:

go test [-c] [-i] [build flags] [packages] [flags for test binary]

参数解读:

  • -c: 编译 go test 成为可执行的二进制文件, 但是不运行测试。
  • -i: 安装测试包依赖的 package, 但是不运行测试。

关于 build flags:
调用 go help build, 这些是编译运行过程中需要使用到的参数, 一般设置为空

关于 packages:
调用 go help packages, 这些是关于包的管理, 一般设置为空

关于 flags for test binary:
调用 go help testflag, 这些是 go test 过程中经常使用到的参数

  • -test.v: 是否输出全部的单元测试用例(不管成功或者失败), 默认没有加上, 所以只输出失败的单元测试用例。
  • -test.run pattern: 只跑哪些单元测试用例
  • -test.bench patten: 只跑那些性能测试用例
  • -test.benchmem: 是否在性能测试的时候输出内存情况
  • -test.benchtime t: 性能测试运行的时间, 默认是 1s
  • -test.cpuprofile cpu.out: 是否输出 cpu 性能分析文件
  • -test.memprofile mem.out: 是否输出内存性能分析文件
  • -test.blockprofile block.out: 是否输出内部 goroutine 阻塞的性能分析文件
  • -test.memprofilerate n: 内存性能分析的时候有一个分配了多少的时候才打点记录的问题。这个参数就是设置打点的内存分配间隔, 也就是 profile 中一个 sample 代表的内存大小。默认是设置为 512 * 1024 的。如果你将它设置为 1, 则每分配一个内存块就会在 profile 中有个打点, 那么生成的 profile 的 sample 就会非常多。如果你设置为 0, 那就是不做打点了。你可以通过设置 memprofilerate=1GOGC=off 来关闭内存回收, 并且对每个内存块的分配进行观察。
  • -test.blockprofilerate n: 基本同上, 控制的是 goroutine 阻塞时候打点的纳秒数。默认不设置就相当于 -test.blockprofilerate=1, 每一纳秒都打点记录一下
  • -test.parallel n: 性能测试的程序并行 cpu 数, 默认等于 GOMAXPROCS
  • -test.timeout t: 如果测试用例运行时间超过 t, 则抛出 panic
  • -test.cpu 1,2,4: 程序运行在哪些 CPU 上面, 使用二进制的 1 所在位代表, 和 nginx 的 nginx_worker_cpu_affinity 是一个道理
  • -test.short: 将那些运行时间较长的测试用例运行时间缩短

可以通过使用 -count=1 关闭缓存;

可以加上 -p 1 参数, 让 go test 命令并行执行每个 package;

-cover 参数的描述: 开启覆盖分析;

1.2.1. esting 的测试用例形式

测试用例有四种形式:

TestXxxx(t *testing.T)      // 基本测试用例
BenchmarkXxxx(b *testing.B) // 压力测试的测试用例
Example_Xxx()               // 测试控制台输出的例子
TestMain(m *testing.M)      // 测试 Main 函数

给个 Example 的例子: (Example 需要在最后用注释的方式确认控制台输出和预期是不是一致的)

func Example_GetScore() {
    score := getScore(100, 100, 100, 2.1)
    fmt.Println(score)
    // Output:
    // 31.1
}

1.2.2. testing 包内的结构

  • B: 压力测试
  • BenchmarkResult: 压力测试结果
  • Cover: 代码覆盖率相关结构体
  • CoverBlock: 代码覆盖率相关结构体
  • InternalBenchmark: 内部使用的结构
  • InternalExample: 内部使用的结构
  • InternalTest: 内部使用的结构
  • M: main 测试使用的结构
  • PB: Parallel benchmarks 并行测试使用结果
  • T: 普通测试用例
  • TB: 测试用例的接口

1.2.3. testing 的通用方法

T 结构内部是继承自 common 结构, common 结构提供集中方法, 是我们经常会用到的:

当我们遇到一个断言错误的时候, 我们就会判断这个测试用例失败, 就会使用到:

  • Fail: case 失败, 测试用例继续
  • FailedNow: case 失败, 测试用例中断

当我们遇到一个断言错误, 只希望跳过这个错误, 但是不希望标示测试用例失败, 会使用到:

  • SkipNow: case 跳过, 测试用例不继续

当我们只希望在一个地方打印出信息, 我们会用到:

  • Log: 输出信息
  • Logf: 输出有 format 的信息

当我们希望跳过这个用例, 并且打印出信息:

  • Skip: Log + SkipNow
  • Skipf: Logf + SkipNow

当我们希望断言失败的时候, 测试用例失败, 打印出必要的信息, 但是测试用例继续:

  • Error: Log + Fail
  • Errorf: Logf + Fail

当我们希望断言失败的时候, 测试用例失败, 打印出必要的信息, 测试用例中断:

  • Fatal: Log + FailNow
  • Fatalf: Logf + FailNow

1.3. testing.T 中的报告方法

上面的例子中, 我们使用到了 testing.T 中的 Errorf 方法, 他打印出了错误信息, 但事实上, 他并不会中断程序的执行

testing.T 类提供了几个十分常用的报告方法

1.3.1. testing.T

testing.T 的结构定义如下:

type T struct {
    common
    isParallel bool
    context    *testContext // For running tests and subtests.
}

common 是一个 struct, 他为 T 类型提供了所有的报告方法

1.3.2. common 提供的报告方法

testing.T 的报告方法

方法名声明说明
LogLog(args …interface{})输出信息
LogfLogf(format string, args …interface{})格式化输出信息
FailFail()提示用户测试失败并继续
FailNowFailNow()提示用户测试失败并中止测试(通过调用 runtime.Goexit())
ErrorError(args …interface{})提示用户测试错误并打印信息, 通过调用 Log + Fail 实现
ErrorfErrorf(format string, args …interface{})Error 方法的格式化输出版本
SkipNowSkipNow()跳出测试(通过调用 runtime.Goexit())
SkipSkip(args …interface{})打印信息并退出测试, 通过调用 Log 与 SkipNow 实现
SkipfSkipf(format string, args …interface{})Skip 的格式化输出版本
FatalFatal(args …interface{})输出日志、提示用户测试失败并退出, 通过调用 Log 与 FailNow 实现
FatalfFatalf(format string, args …interface{})Fatal 的格式化输出版本

1.4. 单元测试

单元测试是最为常见和常用的测试方法

只要在项目文件中写入下面的方法:

func TestXxx(*testing.T) {
// 测试函数体
}

然后执行:

go test .

就可以看到编译、运行后的测试结果了

1.4.1. 示例

测试通过

我们编写一个斐波那契数列运算的函数:

func Fib(n int) int {
        if n < 2 {
                return n
        }
        return Fib(n-1) + Fib(n-2)
}

编写单元测试代码:

func TestFib(t *testing.T) {
    var (
        in       = 7
        expected = 13
    )
    actual := Fib(in)
    if actual != expected {
        t.Errorf("Fib(%d) = %d; expected %d", in, actual, expected)
    }
}

执行 go test . 输出了:

ok      chapter09/testing    0.007s

测试通过

测试失败

我们稍稍修改一下代码:

func Fib(n int) int {
        if n < 2 {
                return n
        }
        return Fib(n-1) + Fib(n-1)
}

执行 go test . 可以看到:

--- FAIL: TestSum (0.00s)

    t_test.go:16: Fib(10) = 64; expected 13

FAIL

FAIL    chapter09/testing    0.009s

显然, 测试失败了。

1.4.2. 测试指定用例

go test -v --run '(Some|Another)'

=== RUN   TestDoSomeOperation
Doing some operation...
--- PASS: TestDoSomeOperation (0.00s)
=== RUN   TestDoAnotherOperation
Doing another operation...
--- PASS: TestDoAnotherOperation (0.00s)
PASS
ok      example.com/jonasbn/twotests    0.147s

测试当前目录及其子目录特定名的函数:

go test -v -run TestIntegrate ./...

1.4.3. 执行当前项目所有 test

  • 运行当前目录及所有子目录下的测试用例: go test ./...
  • 运行指定目录及所有子目录下的测试用例: go test foo/...
  • 运行指定前缀的测试用例: go test foo...
  • 运行 GOPATH 下的所有测试用例: go test ...
go [command] ./...

Here ./ tells to start from the current folder, ... tells to go down recursively.

For Example:

go list ...

In any folder lists all the packages, including packages of the standard library first followed by external libraries in your go workspace.

ahhh That is why go build ... take some time (about a minute or more… don’t recall), it was compiling every package, right?

1.4.4. 排除指定目录

$(go list ./... | grep -v /vendor/)

The grep -v /vendor/ part is to exclude the /vendor/ directory. So just do the same for your Store directory:

go test $(go list ./... | grep -v /Store/) -coverprofile .testCoverage.txt

Note that excluding /vendor/ this way is not necessary (unless you’re using a really old version of Go). If you are using an old version of Go, you can combine them:

go test $(go list ./... | grep -v /vendor/ | grep -v /Store/) -coverprofile .testCoverage.txt

1.5. 子测试

掌握了上面的内容, 你就可以为你的代码编写合适的测试用例了

但是, 有的时候你想要像函数调用一样嵌套多个单元测试, 或者想在若干个测试开始前或结束后做一些事情, 这在 go 语言中有着很好的支持

golang 1.7 版本开始, 引入了一个新特性: 子测试

1.5.1. 示例

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>
}

1.5.2. 执行

go test -run ''      # Run 所有测试。
go test -run Foo     # Run 匹配 "Foo" 的顶层测试, 例如 "TestFoo"、"TestFooBar"。
go test -run Foo/A=  # 匹配顶层测试 "Foo", 运行其匹配 "A=" 的子测试。
go test -run /A=1    # 运行所有匹配 "A=1" 的子测试。

1.5.3. 子测试并发执行 t.Parallel()

很多情况下, 我们并不想等着若干个子测试一个个顺次执行, 而是希望能够让他们相互并发执行, 这时 t.Parallel() 就派上用场了

当然, t.Parallel() 并不仅仅能够应用在子测试中, 任何几个测试函数中, 只要调用了 t.Parallel(), 他们之间都会并发执行

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

1.6. TestMain

子测试让我们能够嵌套测试函数, 在若干个测试函数之前、之后或之间进行一些操作

但我们是否可以定义, 无论在什么情况下, 只要测试函数执行, 他前后就必须执行一些操作呢?

golang 用 TestMain 可以实现这样的特性

func TestMain(m *testing.M)

只要测试文件中包含该函数, 那么, 无论执行测试文件中的哪个函数, 都会先去运行 TestMain 函数

TestMain 函数中, 通过 m.Run() 就可以调用本次预期将会执行的测试函数

不难看出, 这是一个面向切面编程思想的应用

1.6.1. 示例

func TestMain(m *testing.M) {
    // do someting setup
    exitCode := m.Run()
    os.Exit(exitCode)
    // do something teardow
}

1.7. 基准测试 benchmark test

很多时候, 我们不仅需要测试程序执行的正确性, 对于程序执行的性能消耗我们往往更加看重, 毕竟在项目上线前, 究竟需要多少资源来部署项目, 项目能够承受多大的流量, 不对这些了然于胸, 就无法保证线上业务的安全, 后果将会是灾难性的

go test 工具同样也提供了压力测试等功能的支持 benchmark test

1.7.1. 基准测试的编写与执行

go test 的基准测试提供了将目标代码段执行 N 次统计运行时间, 从而实现压测的功能

与单元测试类似, 只要在项目的 xxx_test.go 文件中写入下面的方法即可实现基准测试函数的编写:

func BenchmarkXxx(*testing.B) {
// 测试函数体
}

例如:

func BenchmarkFib10(b *testing.B) {
        for n := 0; n < b.N; n++ {
                Fib(10)
        }
}

执行:

go test -bench=.

就会展示:

$ go test -bench=.

BenchmarkFib10-4        3000000           424 ns/op

PASS

ok      chapter09/testing    1.724s

表示执行了 3000000 次, 耗时 1.724 秒, 每次调用 424 纳秒

1.7.2. 设定执行时间

我们也可以通过 -benchtime 来指定测试的执行时间:

$ go test -bench=Fib40 -benchtime=20s

BenchmarkFib40-4             30     838675800 ns/op

1.8. 并行测试

既然是性能压测, 串行的执行统计运行耗时常常并不是我们想要的测试手段, 通过并发执行来观察资源的消耗情况是更好的测试方法

go test 提供了 b.RunParallel 方法用来实现让多个基准测试方法并行执行的功能

func BenchmarkTemplateParallel(b *testing.B) {
    templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
    b.RunParallel(func(pb *testing.PB) {
         // 每个 goroutine 有属于自己的 bytes.Buffer.
        var buf bytes.Buffer
        for pb.Next() {
              // 所有 goroutine 一起, 循环一共执行 b.N 次
            buf.Reset()
            templ.Execute(&buf, "World")
        }
    })
}

调用了 b.RunParallel 方法的测试函数将会在单独的 goroutine 中启动

需要注意的是, b.StartTimerb.StopTimeb.ResetTimer 三个方法会影响到所有 goroutine, 因此不要在并行测试中调用

1.8.1. 调节并发度

并行基准测试其并发度受环境变量 GOMAXPROCS 控制, 默认情况下是 CPU 核心数

可以在测试开始前, 通过 b.SetParallelism 方法实现对并发度的控制, 例如执行 b.SetParallelism(2) 则意味着并发度为 2*GOMAXPROCS

在执行 go test 命令时, 增加 -cpu 参数, 可以打印 cpu 资源消耗的详情信息

1.8.2. t.Parallel() example

for _, tc := range cases {
    func(tc *caseSpec) {
        t.Run(tc.name, func(t *testing.T) {
            // Parallel signals that this test is to be run in parallel with (and only with)
            // other parallel tests. When a test is run multiple times due to use of
            // -test.count or -test.cpu, multiple instances of a single test never run in
            // parallel with each other.
            t.Parallel()
            
            // ...

            t.Cleanup(func() {
                // ...
            })
        })
    }(tc)
}

默认情况下, 指定包的测试是按照顺序执行的, 但也可以通过在测试的函数内部使用 t.Parallel() 来标志某些测试也可以被安全的并发执行(和默认的一样, 假设参数名为 t)。在并行执行的情况下, 只有当那些被标记为并行的测试才会被并行执行, 所以只有一个测试函数时是没意义的。它应该在测试函数体中第一个被调用(在任何需要跳过的条件之后), 因为它会重置测试时间:

func TestParallel(t *testing.T) {
    t.Parallel()
    // actual test...
}

在并发情况下, 同时运行的测试的数量默认取决于 GOMAXPROCS。它可以通过 -parallel n 被指定 (go test -parallel 4)

另外一个可以实现并行的方法, 尽管不是函数级粒度, 但却是包级粒度, 就是类似这样执行 go test p1 p2 p3(也就是说, 同时调用多个测试包)。在这种情况下, 包会被先编译, 并同时被执行。当然, 这对于总的时间来说是有好处的, 但它也可能会导致错误变得具有不可预测性, 比如一些资源被多个包同时使用时(例如, 一些测试需要访问数据库, 并删除一些行, 而这些行又刚好被其他的测试包使用的话)。

为了保持可控性, -p 标志可以用来指定编译和测试的并发数。当仓库中有多个测试包, 并且每个包在不同的子目录中, 一个可以执行所有包的命令是 go test ./..., 这包含当前目录和所有子目录。没有带 -p 标志执行时, 总的运行时间应该接近于运行时间最长的包的时间(加上编译时间)。运行 go test -p 1 ./..., 使编译和测试工具只能在一个包中执行时, 总的时间应该接近于所有独立的包测试的时间加上编译的时间的总和。你可以自己试试, 执行 go test -p 3 ./..., 看一下对运行时间的影响。

还有, 另外一个可以并行化的地方(你应该测试一下)是在包的代码里面。多亏了 Go 非常棒的并行原语, 实际上, 除非 GOMAXPROCS 通过环境变量或者在代码中显式设置为 GOMAXPROCS=1, 否则, 包中一个 goroutines 都没有用是不太常见的。想要使用 2 个 CPU, 可以执行 GOMAXPROCS=2 go test, 想要使用 4 个 CPU, 可以执行 GOMAXPROCS=4 go test, 但还有更好的方法: go test -cpu=1,2,4 将会执行 3 次, 其中 GOMAXPROCS 值分别为 1, 2, 和 4。

-cpu 标志, 搭配数据竞争的探测标志 -race, 简直进入天堂(或者下地狱, 取决于它具体怎么运行)。

1.8.3. 并行安全测试 -race

只要在 go test 命令中加入 -race 参数, 就可以在测试阶段发现可能的并发安全问题

下面是一个典型的非并发安全的例子:

func TestParallelSafe(t *testing.T) {
    a := 1
    go func(){
        a = 2
    }()
    a = 3
    t.Logf("a is ", a)

    time.Sleep(2 * time.Second)
}

执行 go test -race . 会打印出:

runtime  go test -race .

a is  3

==================

WARNING: DATA RACE

Write by goroutine 5:

  main.func·001()

      /data/test/race1.go:11 +0x3a

Previous write by main goroutine:

  main.main()

      /data/test/race1.go:13 +0xe7

Goroutine 5 (running) created at:

  main.main()

      /data/test/race1.go:12 +0xd7

==================

Found 1 data race(s)

exit status 66

从而可以发现竞争条件的存在

但需要注意的是, 只有测试用例覆盖到的代码才可以顺利检测出竞争, 因此保证测试用例的覆盖率是一个很重要的事。

More: Introducing the Go Race Detector

1.9. 跳过测试 (Skipping tests)

一些测试可能要求要有特定的上下文环境。例如, 一些测试可能需要调用一个外部的命令, 使用一个特殊的文件, 或者需要一个可以被设置的环境变量。当条件无法满足时, (如果)不想让那些测试失败, 可以简单地跳过那些测试:

func TestSomeProtectedResource(t *testing.T) {
    if os.Getenv("SOME_ACCESS_TOKEN") == "" {
        t.Skip("skipping test; $SOME_ACCESS_TOKEN not set")
    }
    // ... the actual test
}

如果 go test -v 被调用(注意那个冗余 (“-v”) 标志), 输出将会提醒已跳过的测试:

=== RUN TestSomeProtectedResource
--- SKIP: TestSomeProtectedResource (0.00 seconds)
        example_test.go:17: skipping test; $SOME_ACCESS_TOKEN not set

通常是用 -short 命令行标志来实现这个跳过的特性, 如果标志被设置的话, 反映到代码中, testing.Short() 将简单地返回 true(就像是 -v 标志一样, 如果它被设置, 通过判断 testing.Verbose(), 你可以打印出额外的调试日志)。

当测试需要运行较长时间时, 而你又很着急的话, 你可以执行 go test -short, (如果)提供这个包的开发者又刚好实现了这个功能, 运行时间长的测试将会被跳过。这就是从源码安装时, (通常情况下)Go 测试被执行的样子, 这里有 stdlib 库中运行时间较长的测试被跳过的例子:

func TestCountMallocs(t *testing.T) {
    if testing.Short() {
        t.Skip("skipping malloc count in short mode")
    }
    // rest of test...
}

跳过只是一个可选项, -short 标志只是一个标示, 具体还依赖于开发者, 他们可以选择(这种标示生效时是否)运行的测试, 来避免一些运行比较慢的断言被执行。

这里还有 -timeout 标志, 它能够被用来强制退出限定时间内没有运行完的测试。例如, 运行这个命令 go test -timeout 1s 以执行下面的测试:

func TestWillTimeout(t *testing.T) {
    time.Sleep(2 * time.Second)
    // pass if timeout > 2s
}

会有如下输出(截断):

=== RUN TestWillTimeout
panic: test timed out after 1s

如果想执行特定的测试函数, 而不是执行全部的测试集, 只需要运行 go test -run TestNameRegexp

1.10. 内存统计

除了观察 CPU 的调用情况, 我们也常常需要去观察内存的使用情况

通过 go test 命令添加 -benchmem 参数可以打开基准测试的内存统计功能

也可以通过在测试用例执行开始前, 调用 b.ReportAllocs 函数, 这样做的好处是只会影响你需要的函数:

func BenchmarkTmplExucte(b *testing.B) {
    b.ReportAllocs()
    templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
    b.RunParallel(func(pb *testing.PB) {
        // Each goroutine has its own bytes.Buffer.
        var buf bytes.Buffer
        for pb.Next() {
            // The loop body is executed b.N times total across all goroutines.
            buf.Reset()
            templ.Execute(&buf, "World")
        }
    })
}

打印出了:

BenchmarkTmplExucte-4        2000000           898 ns/op         368 B/op           9 allocs/op

表示总计执行 2000000 次, 平均每次耗时 898 纳秒, 每次执行消耗内存 368 Bytes, 共计分配内存 9 次。

1.11. 打印测试用例覆盖率报告

go test 命令增加 -coverprofile 参数, 指定输出文件, 就可以输出测试的覆盖率报告。

1.12. 问题解决

1.12.1. 问题: golang test cached

现象:

go test .
ok  	gitlab.jiagouyun.com/cloudcare-tools/datakit/plugins/inputs/pythond	(cached)

解决

  • 方法一: 添加 --count=1 参数
$ go test . -count=1
ok  	gitlab.jiagouyun.com/cloudcare-tools/datakit/plugins/inputs/pythond	4.740s
  • 方法二: Go 官方提供 clean 工具, 来删除对象文件和缓存文件
go clean -testcache // Delete all cached test results
  • 方法三: 设置 GOCACHE 环境变量。GOCACHE 指定了 go 命令执行时缓存的路径, 以便之后被复用。 设置 GOCACHE=off 即可禁用缓存

1.13. inside testing

1.13.1. testing.B

type B struct {
    common
    importPath       string // import path of the package containing the benchmark
    context          *benchContext
    N                int
    previousN        int           // number of iterations in the previous run
    previousDuration time.Duration // total duration of the previous run
    benchFunc        func(b *B)
    benchTime        benchTimeFlag
    bytes            int64
    missingBytes     bool // one of the subbenchmarks does not have bytes set.
    timerOn          bool
    showAllocResult  bool
    result           BenchmarkResult
    parallelism      int // RunParallel creates parallelism*GOMAXPROCS goroutines
    // The initial states of memStats.Mallocs and memStats.TotalAlloc.
    startAllocs uint64
    startBytes  uint64
    // The net total of this test after being run.
    netAllocs uint64
    netBytes  uint64
    // Extra metrics collected by ReportMetric.
    extra map[string]float64
}

可以看到, testing.B 的首个元素也是 common 结构, 因此他也拥有 testing.T 中所有的报告方法。

1.14. 测试驱动开发

软件开发界泰斗 Kent Beck 先生甚至在《Test Driven Development: By Example》一书中提出了著名的测试驱动开发理论 TDD

TDD

众所周知, 在盖房子前, 先拉起基准线, 再比照着线来砌砖是一个好习惯, 而在软件开发中, TDD 就是这个基准线, 他要求在开发工作开始前, 先根据用户需求编写测试用例, 再在开发的过程中不断用测试用例校验代码, 直到完全通过即意味着开发完成

同时, 历史的所有测试用例都持续保留, 可以保证新增需求对老功能影响的可控性

1.14.1. 优点

  1. 提升工程质量: 丰富的测试用例让开发者的开发更加专注, 能够做到有的放矢, 从而减轻压力与程序设计过程中的不可控因素
  2. 提升开发效率: 敏捷开发变得可行
  3. 更容易重构: 完整的测试用例十分便于回归测试, 在重构过程中, 丰富的回归测试让重构过程更加可控

1.14.2. 缺点

  1. 可能造成开发人员将注意力过度集中于单元测试用例, 而忽略更加长期的规划
  2. 开发过程需要额外维护所有单元测试用例与回归测试用例的正确性, 增大开发成本, 尤其是在实际工程开发中, 需求总是会发生变化, 这会造成测试用例的频繁更改, 更加令人难以维护
  3. GUI、web 页面等难以编写测试用例
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云满笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值