DevCN
菜单
全部文章快讯开发科技深度热点

Go 单元测试:用表驱动用例检查输入和边界

为折扣计算定义明确规则,并用正常、边界、舍入和无效输入验证实现。练习从业务约定推导测试,而不是照抄实现。


本节目标

为折扣计算定义明确规则,并用正常、边界、舍入和无效输入验证实现。练习从业务约定推导测试,而不是照抄实现。

前置知识

掌握函数、error、整数计算及模块运行。测试文件与main.go放在同一目录,执行go test而不是go run测试文件。

准备与运行

在一个新的练习目录执行以下命令。目录名可以修改;若该目录已经初始化过模块,不要重复执行go mod init。

mkdir go-lesson-12
cd go-lesson-12
go mod init example.com/devcn/lesson12

将下方完整代码保存为当前目录的main.go,然后执行:

go fmt ./...
go run .

先用go version确认工具链可用。本例在Go 1.25.7、macOS ARM64环境验证,只使用标准库;没有测试所有操作系统和Go版本。

核心概念

先规定接口:cents是整数分,percent是实际支付比例,80表示支付原价的80%,不是减掉80%。金额不得为负,支付比例限定在0到100之间。结果不足一分的部分向下截断。

整数分能避免直接用二进制浮点表示小数金额带来的部分精度问题,但不是完整的财务计算方案。金额上限、货币单位和舍入政策仍应由业务明确,本例只处理能够安全完成整数乘法的练习数据。

表驱动测试把案例集中为输入、预期数值与是否报错。每个子测试有名称,失败时能快速定位是哪条约定没有满足。代码覆盖率可以辅助检查,但不能代替合理的预期结果。

完整示例

package main

import (
	"errors"
	"fmt"
)

func discounted(cents, percent int) (int, error) {
	if cents < 0 || percent < 0 || percent > 100 {
		return 0, errors.New("invalid input")
	}
	return cents * percent / 100, nil
}
func main() {
	value, err := discounted(2000, 80)
	if err != nil {
		fmt.Println("计算失败:", err)
		return
	}
	fmt.Println(value)
}

运行后应得到:

1600

代码如何工作

discounted先检查无效范围,再计算价格。main演示2000分按80%支付得到1600分。测试则额外覆盖0金额、0%支付、100%支付以及不合法的负数和超范围比例。

测试文件名以_test.go结尾,函数名以Test开头并接收*testing.T。go test识别测试文件,运行各子测试。每条用例同时比较数值与错误状态,防止函数返回正确数值却带错误,或错误路径被误当成成功。

运行后应看到所有子测试PASS。再故意把实现中的/100改为/10,测试应失败;恢复代码后重新通过。这个实验比只看到绿色结果更能说明测试是否真的保护了约定。

测试文件与执行

将以下代码保存为同目录的main_test.go,再执行go test -v ./...。预期8个子测试全部通过;命令输出的耗时和包路径因环境而异。

package main

import "testing"

func TestDiscounted(t *testing.T) {
	cases := []struct {
		name                 string
		cents, percent, want int
		bad                  bool
	}{
		{"normal", 2000, 80, 1600, false},
		{"zero_amount", 0, 80, 0, false},
		{"free", 100, 0, 0, false},
		{"full", 100, 100, 100, false},
		{"negative_amount", -1, 80, 0, true},
		{"negative_percent", 100, -1, 0, true},
		{"too_large_percent", 100, 101, 0, true},
		{"rounding", 101, 50, 50, false},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got, err := discounted(tc.cents, tc.percent)
			if (err != nil) != tc.bad || got != tc.want {
				t.Fatalf("got %d, %v; want %d, bad=%v", got, err, tc.want, tc.bad)
			}
		})
	}
}

常见错误与边界

  • 测试期望值不要直接调用被测函数计算,否则错误实现可能同时生成错误答案。
  • 0%是合法边界,负比例才是无效输入,两者应分别测试。
  • cents*percent对超大整数可能溢出。本例明确限制为练习数据,不能直接用于无上限的金额输入。
  • “101分按50%支付得到50分”是本例的截断规则,其他产品可能选择四舍五入,需另定约定。

练习

新增1999分按75%支付的测试用例。先手算结果,再加入用例,不要修改已有的101分舍入案例。

参考答案

1999 × 75 = 149925,整数除以100得到1499。因此新增案例为:

{"three_quarters", 1999, 75, 1499, false},

把它加入测试表后重新执行go test -v ./...。若结果为1500,先核对是否有人把业务规则改成了四舍五入,而不是立即修改测试预期。

下载推广海报

文章推广海报

《Go 单元测试:用表驱动用例检查输入和边界》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。