Go语言单元测试和基准测试实例代码分析

发布时间:2023-02-07 09:26:28 作者:iii
阅读:154
GO开发者专用服务器,限时0元免费领! 查看>>

Go语言单元测试和基准测试实例代码分析

引言

在软件开发过程中,测试是确保代码质量和功能正确性的重要环节。Go语言作为一门现代化的编程语言,提供了强大的测试工具和框架,使得开发者能够轻松地编写单元测试和基准测试。本文将深入探讨Go语言中的单元测试和基准测试,并通过实例代码进行分析,帮助读者更好地理解和应用这些测试技术。

1. Go语言中的测试框架

Go语言内置了一个轻量级的测试框架,开发者可以通过testing包来编写测试代码。测试代码通常放在与被测试代码相同的包中,并以_test.go为后缀的文件中。测试函数需要以Test开头,并接受一个*testing.T类型的参数。

1.1 单元测试

单元测试是对代码中最小的可测试单元进行测试,通常是函数或方法。通过单元测试,开发者可以验证每个函数或方法的行为是否符合预期。

1.1.1 编写单元测试

假设我们有一个简单的函数Add,用于计算两个整数的和:

// add.go
package main

func Add(a, b int) int {
    return a + b
}

我们可以编写一个单元测试来验证Add函数的正确性:

// add_test.go
package main

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}

在这个测试中,我们调用Add函数并检查返回值是否等于预期值。如果返回值不符合预期,我们使用t.Errorf来报告错误。

1.1.2 运行单元测试

要运行单元测试,可以使用go test命令:

go test

如果测试通过,输出将类似于:

PASS
ok      your/package    0.001s

如果测试失败,输出将显示错误信息:

--- FL: TestAdd (0.00s)
    add_test.go:8: Add(2, 3) = 6; want 5
FL
exit status 1
FL    your/package    0.001s

1.2 基准测试

基准测试用于测量代码的性能,通常用于评估函数或方法的执行时间。基准测试函数需要以Benchmark开头,并接受一个*testing.B类型的参数。

1.2.1 编写基准测试

我们可以为Add函数编写一个基准测试:

// add_test.go
package main

import "testing"

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(2, 3)
    }
}

在这个基准测试中,我们使用b.N来控制循环次数,b.N的值由测试框架自动调整,以确保测试运行足够长的时间。

1.2.2 运行基准测试

要运行基准测试,可以使用go test命令并加上-bench参数:

go test -bench=.

输出将类似于:

goos: darwin
goarch: amd64
pkg: your/package
BenchmarkAdd-8         1000000000               0.319 ns/op
PASS
ok      your/package    0.350s

在这个输出中,BenchmarkAdd-8表示基准测试的名称和并发数,1000000000表示循环次数,0.319 ns/op表示每次操作的平均时间。

2. 测试覆盖率

测试覆盖率是衡量测试代码覆盖被测试代码的程度。Go语言提供了内置的工具来生成和查看测试覆盖率报告。

2.1 生成测试覆盖率报告

要生成测试覆盖率报告,可以使用go test命令并加上-cover参数:

go test -cover

输出将类似于:

PASS
coverage: 100.0% of statements
ok      your/package    0.001s

这个输出表示测试覆盖了100%的代码。

2.2 生成HTML格式的覆盖率报告

要生成更详细的HTML格式的覆盖率报告,可以使用以下命令:

go test -coverprofile=coverage.out
go tool cover -html=coverage.out

这将生成一个HTML文件,并在浏览器中打开,显示详细的覆盖率信息。

3. 表格驱动测试

表格驱动测试是一种常见的测试模式,它通过将测试用例组织成表格的形式,使得测试代码更加简洁和易于维护。

3.1 编写表格驱动测试

我们可以为Add函数编写一个表格驱动测试:

// add_test.go
package main

import "testing"

func TestAdd(t *testing.T) {
    tests := []struct {
        a, b, expected int
    }{
        {2, 3, 5},
        {0, 0, 0},
        {-1, 1, 0},
        {-1, -1, -2},
    }

    for _, tt := range tests {
        result := Add(tt.a, tt.b)
        if result != tt.expected {
            t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
        }
    }
}

在这个测试中,我们定义了一个包含多个测试用例的表格,每个测试用例包含输入参数和预期结果。然后我们遍历表格中的每个测试用例,调用Add函数并检查返回值是否符合预期。

3.2 运行表格驱动测试

运行表格驱动测试的方式与普通单元测试相同:

go test

如果所有测试用例都通过,输出将类似于:

PASS
ok      your/package    0.001s

如果有测试用例失败,输出将显示错误信息:

--- FL: TestAdd (0.00s)
    add_test.go:14: Add(-1, -1) = 0; want -2
FL
exit status 1
FL    your/package    0.001s

4. 子测试

子测试是Go语言1.7引入的一个新特性,它允许在一个测试函数中定义多个子测试。子测试可以独立运行,并且可以并行执行。

4.1 编写子测试

我们可以为Add函数编写一个包含子测试的测试函数:

// add_test.go
package main

import "testing"

func TestAdd(t *testing.T) {
    tests := []struct {
        name           string
        a, b, expected int
    }{
        {"positive numbers", 2, 3, 5},
        {"zeros", 0, 0, 0},
        {"negative and positive", -1, 1, 0},
        {"negative numbers", -1, -1, -2},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

在这个测试中,我们为每个测试用例定义了一个子测试,并使用t.Run来运行子测试。每个子测试都有一个名称,可以在测试输出中看到。

4.2 运行子测试

运行子测试的方式与普通单元测试相同:

go test

输出将类似于:

=== RUN   TestAdd
=== RUN   TestAdd/positive_numbers
=== RUN   TestAdd/zeros
=== RUN   TestAdd/negative_and_positive
=== RUN   TestAdd/negative_numbers
--- PASS: TestAdd (0.00s)
    --- PASS: TestAdd/positive_numbers (0.00s)
    --- PASS: TestAdd/zeros (0.00s)
    --- PASS: TestAdd/negative_and_positive (0.00s)
    --- PASS: TestAdd/negative_numbers (0.00s)
PASS
ok      your/package    0.001s

如果有子测试失败,输出将显示具体的子测试名称和错误信息。

5. 并行测试

Go语言的测试框架支持并行测试,可以通过t.Parallel()来标记一个测试函数或子测试为并行执行。

5.1 编写并行测试

我们可以为Add函数编写一个并行测试:

// add_test.go
package main

import "testing"

func TestAddParallel(t *testing.T) {
    tests := []struct {
        name           string
        a, b, expected int
    }{
        {"positive numbers", 2, 3, 5},
        {"zeros", 0, 0, 0},
        {"negative and positive", -1, 1, 0},
        {"negative numbers", -1, -1, -2},
    }

    for _, tt := range tests {
        tt := tt // 避免闭包捕获循环变量
        t.Run(tt.name, func(t *testing.T) {
            t.Parallel()
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

在这个测试中,我们使用t.Parallel()来标记每个子测试为并行执行。注意,为了避免闭包捕获循环变量的问题,我们需要在循环内部重新定义tt变量。

5.2 运行并行测试

运行并行测试的方式与普通单元测试相同:

go test

输出将类似于:

=== RUN   TestAddParallel
=== RUN   TestAddParallel/positive_numbers
=== RUN   TestAddParallel/zeros
=== RUN   TestAddParallel/negative_and_positive
=== RUN   TestAddParallel/negative_numbers
--- PASS: TestAddParallel (0.00s)
    --- PASS: TestAddParallel/positive_numbers (0.00s)
    --- PASS: TestAddParallel/zeros (0.00s)
    --- PASS: TestAddParallel/negative_and_positive (0.00s)
    --- PASS: TestAddParallel/negative_numbers (0.00s)
PASS
ok      your/package    0.001s

6. 测试辅助函数

在编写测试时,有时需要编写一些辅助函数来简化测试代码。Go语言提供了testing.THelper方法,用于标记辅助函数,以便在测试失败时能够正确显示调用栈。

6.1 编写测试辅助函数

我们可以为Add函数编写一个测试辅助函数:

// add_test.go
package main

import "testing"

func testAdd(t *testing.T, a, b, expected int) {
    t.Helper()
    result := Add(a, b)
    if result != expected {
        t.Errorf("Add(%d, %d) = %d; want %d", a, b, result, expected)
    }
}

func TestAdd(t *testing.T) {
    testAdd(t, 2, 3, 5)
    testAdd(t, 0, 0, 0)
    testAdd(t, -1, 1, 0)
    testAdd(t, -1, -1, -2)
}

在这个测试中,我们定义了一个testAdd辅助函数,并使用t.Helper()来标记它为辅助函数。这样,当测试失败时,调用栈将显示testAdd函数的调用位置,而不是TestAdd函数。

6.2 运行测试辅助函数

运行测试辅助函数的方式与普通单元测试相同:

go test

输出将类似于:

PASS
ok      your/package    0.001s

如果有测试失败,输出将显示错误信息,并正确显示调用栈。

7. 测试中的错误处理

在测试中,有时需要处理错误情况。Go语言提供了testing.TFatalError方法来报告错误。

7.1 使用Fatal方法

Fatal方法用于报告致命错误,并立即终止测试。我们可以为Add函数编写一个测试,使用Fatal方法来处理错误:

// add_test.go
package main

import "testing"

func TestAddFatal(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Fatalf("Add(2, 3) = %d; want 5", result)
    }
}

在这个测试中,如果Add函数的返回值不符合预期,我们将使用t.Fatalf来报告致命错误,并立即终止测试。

7.2 使用Error方法

Error方法用于报告非致命错误,并继续执行测试。我们可以为Add函数编写一个测试,使用Error方法来处理错误:

// add_test.go
package main

import "testing"

func TestAddError(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}

在这个测试中,如果Add函数的返回值不符合预期,我们将使用t.Errorf来报告错误,并继续执行测试。

8. 测试中的日志输出

在测试中,有时需要输出一些日志信息来帮助调试。Go语言提供了testing.TLogLogf方法来输出日志信息。

8.1 使用Log方法

Log方法用于输出日志信息。我们可以为Add函数编写一个测试,使用Log方法来输出日志信息:

// add_test.go
package main

import "testing"

func TestAddLog(t *testing.T) {
    result := Add(2, 3)
    t.Logf("Add(2, 3) = %d", result)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}

在这个测试中,我们使用t.Logf来输出Add函数的返回值。

8.2 查看日志输出

要查看日志输出,可以使用go test命令并加上-v参数:

go test -v

输出将类似于:

=== RUN   TestAddLog
--- PASS: TestAddLog (0.00s)
    add_test.go:7: Add(2, 3) = 5
PASS
ok      your/package    0.001s

在这个输出中,我们可以看到t.Logf输出的日志信息。

9. 测试中的性能分析

在基准测试中,有时需要进行性能分析,以找出代码中的性能瓶颈。Go语言提供了testing.BReportAllocsReportMetric方法来报告内存分配和自定义指标。

9.1 使用ReportAllocs方法

ReportAllocs方法用于报告内存分配情况。我们可以为Add函数编写一个基准测试,使用ReportAllocs方法来报告内存分配:

// add_test.go
package main

import "testing"

func BenchmarkAddAllocs(b *testing.B) {
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        Add(2, 3)
    }
}

在这个基准测试中,我们使用b.ReportAllocs来报告内存分配情况。

9.2 查看内存分配报告

要查看内存分配报告,可以使用go test命令并加上-bench-benchmem参数:

go test -bench=. -benchmem

输出将类似于:

goos: darwin
goarch: amd64
pkg: your/package
BenchmarkAddAllocs-8         1000000000               0.319 ns/op           0 B/op          0 allocs/op
PASS
ok      your/package    0.350s

在这个输出中,0 B/op表示每次操作的内存分配量,0 allocs/op表示每次操作的内存分配次数。

9.3 使用ReportMetric方法

ReportMetric方法用于报告自定义指标。我们可以为Add函数编写一个基准测试,使用ReportMetric方法来报告自定义指标:

// add_test.go
package main

import "testing"

func BenchmarkAddMetric(b *testing.B) {
    for i := 0; i < b.N; i++ {
        result := Add(2, 3)
        b.ReportMetric(float64(result), "result")
    }
}

在这个基准测试中,我们使用b.ReportMetric来报告Add函数的返回值。

9.4 查看自定义指标报告

要查看自定义指标报告,可以使用go test命令并加上-bench-benchmem参数:

go test -bench=. -benchmem

输出将类似于:

goos: darwin
goarch: amd64
pkg: your/package
BenchmarkAddMetric-8         1000000000               0.319 ns/op           0 B/op          0 allocs/op           5 result/op
PASS
ok      your/package    0.350s

在这个输出中,5 result/op表示每次操作的自定义指标值。

10. 测试中的并发控制

在测试中,有时需要控制并发执行的测试数量。Go语言提供了testing.BSetParallelism方法来设置并发执行的测试数量。

10.1 使用SetParallelism方法

SetParallelism方法用于设置并发执行的测试数量。我们可以为Add函数编写一个基准测试,使用SetParallelism方法来设置并发执行的测试数量:

// add_test.go
package main

import "testing"

func BenchmarkAddParallel(b *testing.B) {
    b.SetParallelism(4)
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            Add(2, 3)
        }
    })
}

在这个基准测试中,我们使用b.SetParallelism来设置并发执行的测试数量为4。

10.2 查看并发执行报告

要查看并发执行报告,可以使用go test命令并加上-bench-benchmem参数:

go test -bench=. -benchmem

输出将类似于:

”` goos: darwin goarch: amd64 pkg

亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>

推荐阅读:
  1. 腾讯Go开发岗位面试问的问题有哪些
  2. 利用go开发时如何引用静态库中的.a文件

开发者交流群:

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

go

上一篇:C++之list容器模拟怎么实现

下一篇:polyfills怎么按需加载

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》
开发者交流群×