golang实现Go源代码变异测试的插件库go-mutesting的使用
Golang实现Go源代码变异测试的插件库go-mutesting的使用
什么是go-mutesting?
go-mutesting是一个用于对Go源代码执行变异测试的框架。它的主要目的是发现未被任何测试覆盖的源代码。
快速示例
以下命令使用所有可用的变异器对go-mutesting项目进行变异测试:
go-mutesting github.com/zimmski/go-mutesting/...
该命令会为每个变异打印测试是否成功。如果不成功,会输出源代码补丁以便调查变异。下面是一个变异补丁的示例:
for _, d := range opts.Mutator.DisableMutators {
pattern := strings.HasSuffix(d, "*")
- if (pattern && strings.HasPrefix(name, d[:len(d)-2])) || (!pattern && name == d) {
+ if (pattern && strings.HasPrefix(name, d[:len(d)-2])) || false {
continue MUTATOR
}
}
这个示例显示||
运算符的右侧项(!pattern && name == d)
通过替换为false
使其变得无关紧要。由于测试套件没有检测到这种源代码变化(意味着测试套件没有失败),我们可以将其标记为未测试代码。
安装
go-mutesting包含一个可执行文件,可以通过go get安装:
go get -t -v github.com/zimmski/go-mutesting/...
使用方式
变异测试的目标可以作为参数传递给二进制文件。每个目标可以是Go源文件、目录或包。目录和包也可以包含...
通配符模式,这将递归搜索Go源文件。
以下示例收集由目标定义的所有Go文件,并使用二进制文件的所有可用变异器生成变异:
go-mutesting parse.go example/ github.com/zimmski/go-mutesting/mutator/...
每个变异都必须使用exec命令进行测试。默认情况下使用内置的exec命令,它使用以下步骤测试变异:
- 用变异替换原始文件
- 执行变异文件包的所有测试
- 报告变异是否被杀死
示例代码
下面是一个完整的示例,展示如何使用go-mutesting测试一个示例包:
go-mutesting --exec "$GOPATH/src/github.com/zimmski/go-mutesting/scripts/exec/test-mutated-package.sh" github.com/zimmski/go-mutesting/example
执行后将打印类似以下输出:
PASS "/tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.0" with checksum b705f4c99e6d572de509609eb0a625be
PASS "/tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.1" with checksum eb54efffc5edfc7eba2b276371b29836
PASS "/tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.2" with checksum 011df9567e5fee9bf75cbe5d5dc1c81f
--- /home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go
+++ /tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.3
@@ -16,7 +16,7 @@
}
if n < 0 {
- n = 0
+
}
n++
FAIL "/tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.3" with checksum 82fc14acf7b561598bfce25bf3a162a2
PASS "/tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.4" with checksum 5720f1bf404abea121feb5a50caf672c
PASS "/tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.5" with checksum d6c1b5e25241453128f9f3bf1b9e7741
--- /home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go
+++ /tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.6
@@ -24,7 +24,6 @@
n += bar()
bar()
- bar()
return n
}
FAIL "/tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.6" with checksum 5b1ca0cfedd786d9df136a0e042df23a
PASS "/tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.8" with checksum 6928f4458787c7042c8b4505888300a6
The mutation score is 0.750000 (6 passed, 2 failed, 0 skipped, total is 8)
输出显示找到了8个变异并进行了测试。其中6个通过,意味着测试套件对这些变异失败,因此变异被杀死。然而,2个变异没有使测试套件失败。它们的源代码补丁显示在输出中,可用于调查这些变异。
黑名单假阳性
变异测试可能会产生许多假阳性,因为变异算法不能完全理解给定的源代码。early exits
是一个常见的例子。它们可以作为优化实现,并且几乎总是会触发假阳性,因为将使用未优化的代码路径,这将导致相同的结果。go-mutesting旨在作为自动测试套件的补充。因此有必要将这些变异标记为假阳性。这是通过--blacklist
参数完成的。
如何编写自己的变异exec命令
变异exec命令对每个需要测试的变异调用。命令应至少处理以下阶段:
- 设置源代码以包含变异
- 测试源代码,通过调用测试套件和其他可能的测试功能
- 清理所有更改并删除所有临时资产
- 报告变异是否被杀死
重要的是要注意每个调用应该是隔离的,因此是无状态的。这意味着调用不得干扰其他调用。
已实现的变异器
分支变异器
名称 | 描述 |
---|---|
branch/case | 清空case体 |
branch/if | 清空if 和else if 语句的分支 |
branch/else | 清空else 语句的分支 |
表达式变异器
名称 | 描述 |
---|---|
expression/comparison | 搜索比较运算符,如> 和<= ,并将它们替换为类似的运算符以捕获off-by-one错误,例如> 被>= 替换 |
expression/remove | 搜索&& 和|| 运算符,并通过使用true 或false 作为替换使运算符的每个项无关 |
语句变异器
名称 | 描述 |
---|---|
statement/remove | 删除赋值、递增、递减和表达式语句 |
如何编写自己的变异器
每个变异器必须实现github.com/zimmski/go-mutesting/mutator包的Mutator
接口。接口的方法在源代码文档中有详细描述。
此外,每个变异器必须使用github.com/zimmski/go-mutesting/mutator包的Register
函数注册,以便二进制文件可以使用它。
更多关于golang实现Go源代码变异测试的插件库go-mutesting的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang实现Go源代码变异测试的插件库go-mutesting的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Go源代码变异测试:go-mutesting使用指南
变异测试(Mutation Testing)是一种评估测试用例质量的先进技术,它通过故意在源代码中引入错误(变异),然后检查测试用例是否能发现这些错误。在Go生态中,go-mutesting
是一个流行的变异测试工具。
go-mutesting简介
go-mutesting
是一个用于Go语言的变异测试框架,它可以:
- 自动在代码中注入各种类型的变异
- 运行测试套件检查变异是否被捕获
- 生成变异测试报告
- 支持自定义变异算子
安装go-mutesting
go install github.com/zimmski/go-mutesting/cmd/go-mutesting@latest
基本使用
1. 对单个文件运行变异测试
go-mutesting main.go
2. 对整个包运行变异测试
go-mutesting ./...
3. 查看所有可用变异算子
go-mutesting -list
示例代码
下面是一个完整的示例,展示如何使用go-mutesting进行变异测试:
// main.go
package main
import "fmt"
// Add 两个整数相加
func Add(a, b int) int {
return a + b
}
func main() {
fmt.Println(Add(1, 2))
}
// main_test.go
package main
import "testing"
func TestAdd(t *testing.T) {
tests := []struct {
name string
a int
b int
want int
}{
{"positive", 2, 3, 5},
{"negative", -1, -1, -2},
{"zero", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Add(tt.a, tt.b); got != tt.want {
t.Errorf("Add() = %v, want %v", got, tt.want)
}
})
}
}
运行变异测试:
go-mutesting .
输出解读
输出通常如下格式:
PASS "/var/folders/.../main.go.0"
--- main.go 2023-01-01 12:00:00.000000000 +0800
+++ main.go.0 2023-01-01 12:00:00.000000000 +0800
@@ -5,7 +5,7 @@
// Add 两个整数相加
func Add(a, b int) int {
- return a + b
+ return a - b
}
func main() {
Mutation survived! Your tests failed to detect it.
这表示变异算子将a + b
改为a - b
,而测试用例没有捕获这个变化。
高级用法
1. 指定变异算子
go-mutesting -mutators=math,cond ./...
2. 排除某些变异算子
go-mutesting -exclude=math ./...
3. 设置超时
go-mutesting -timeout=30s ./...
4. 生成HTML报告
go-mutesting -html=report.html ./...
集成到CI/CD
可以在CI流程中添加变异测试:
# .github/workflows/mutation.yml
name: Mutation Testing
on: [push, pull_request]
jobs:
mutation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: '1.20'
- name: Install go-mutesting
run: go install github.com/zimmski/go-mutesting/cmd/go-mutesting@latest
- name: Run mutation tests
run: go-mutesting ./...
自定义变异算子
可以创建自己的变异算子:
package main
import (
"go/ast"
)
func init() {
RegisterMutator("custom_negate_return", MutatorFunc(func(node ast.Node) []Mutation {
if ret, ok := node.(*ast.ReturnStmt); ok {
return []Mutation{
{
Change: func() {
// 对返回值取反
if len(ret.Results) > 0 {
if lit, ok := ret.Results[0].(*ast.BasicLit); ok {
if lit.Kind == token.INT {
if lit.Value == "0" {
lit.Value = "1"
} else {
lit.Value = "0"
}
}
}
}
},
Reset: func() {
// 重置逻辑
},
},
}
}
return nil
}))
}
最佳实践
- 逐步引入:先对关键模块进行变异测试
- 结合覆盖率:变异测试应配合高代码覆盖率使用
- 定期运行:变异测试较耗时,适合在CI中定期运行
- 关注存活变异:优先处理存活时间长的变异
- 团队共识:变异测试是一种高级技术,确保团队理解其价值
性能优化
变异测试可能很耗时,可以通过以下方式优化:
- 使用
-exec
并行运行测试 - 限制变异算子范围
- 只对变更的文件运行
go-mutesting -exec "go test -parallel 4" ./...
go-mutesting是提升Go代码测试质量的强大工具,通过故意引入错误来验证测试用例的有效性,帮助开发者编写更健壮的测试套件。