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命令,它使用以下步骤测试变异:

  1. 用变异替换原始文件
  2. 执行变异文件包的所有测试
  3. 报告变异是否被杀死

示例代码

下面是一个完整的示例,展示如何使用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命令对每个需要测试的变异调用。命令应至少处理以下阶段:

  1. 设置源代码以包含变异
  2. 测试源代码,通过调用测试套件和其他可能的测试功能
  3. 清理所有更改并删除所有临时资产
  4. 报告变异是否被杀死

重要的是要注意每个调用应该是隔离的,因此是无状态的。这意味着调用不得干扰其他调用。

已实现的变异器

分支变异器

名称 描述
branch/case 清空case体
branch/if 清空ifelse if语句的分支
branch/else 清空else语句的分支

表达式变异器

名称 描述
expression/comparison 搜索比较运算符,如><=,并将它们替换为类似的运算符以捕获off-by-one错误,例如>>=替换
expression/remove 搜索&&||运算符,并通过使用truefalse作为替换使运算符的每个项无关

语句变异器

名称 描述
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

1 回复

更多关于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
	}))
}

最佳实践

  1. 逐步引入:先对关键模块进行变异测试
  2. 结合覆盖率:变异测试应配合高代码覆盖率使用
  3. 定期运行:变异测试较耗时,适合在CI中定期运行
  4. 关注存活变异:优先处理存活时间长的变异
  5. 团队共识:变异测试是一种高级技术,确保团队理解其价值

性能优化

变异测试可能很耗时,可以通过以下方式优化:

  • 使用-exec并行运行测试
  • 限制变异算子范围
  • 只对变更的文件运行
go-mutesting -exec "go test -parallel 4" ./...

go-mutesting是提升Go代码测试质量的强大工具,通过故意引入错误来验证测试用例的有效性,帮助开发者编写更健壮的测试套件。

回到顶部