累,每次写完代码还需要再祈祷
不受控,代码运行结果主要看运气,大仙忙的时候可能保佑不了
单元测试太费时间了,对于编写单元测试不熟练的新手来说,编写单元测试可能比写代码的还费时间
单元测试运行时间太长(这通常是单元测试设计不合理或者代码可测试性较差造成的
祖传代码,看都看不懂怎么写单元测试(这个确实有点棘手。。可以考虑先给新代码加单元测试
不会写单元测试
Arrange--准备数据
Act--运行代码
Assert--判断结果是否符合预期
func Add(x, y int) int {
return x + y
}
import "testing"
func TestAdd(t *testing.T) {
// arrange 准备数据
x, y := 1, 2
// act 运行
got := Add(x, y)
//assert 断言
if got != 3 {
t.Errorf("Add() = %v, want %v", got, 3)
}
}
package ut
import (
"fmt"
"strconv"
"strings"
)
func isNumber(num string) (int, error) {
num = strings.TrimSpace(num)
n, err := strconv.Atoi(num)
return n, err
}
func multiply(x string, y int) string {
// 如果x 去除前后的空格后是数字,返回 数字的乘积
// 比如 x="2" y=3 return "6"
// 如果x 去除前后的空格后不是数字,则返回字符串的x的y倍
// 比如 x="a" y=2 return "aa"
num, err := isNumber(x)
if err == nil {
return fmt.Sprintf("%d", num*y)
}
result := ""
for i := 0; i < y; i++ {
result = fmt.Sprintf("%s%s", result, x)
}
return result
}
// 测试方法的名字不直观,并不能看出具体要测试什么
func Test_multiply(t *testing.T) {
type args struct {
x string
y int
}
// 一个测试方法中有太多的测试用例
tests := []struct {
name string
args args
want string
}{
{
"return nil",
args{
"",
2,
},
"",
},
{
"return 2",
args{
"1",
2,
},
"2",
},
{// 测试数据有点奇葩,不直观
"return aaa",
args{
"aaaaaaaaaa",
6,
},
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := multiply(tt.args.x, tt.args.y); got != tt.want {
// 数据错误的时候有明确标明测试数据,期望结果和实际结果,这一点还是有用的
t.Errorf("multiply() = %v, want %v", got, tt.want)
}
})
}
}
代码比较长(这里只列出来了三个用例,实际上并没有完整覆盖全部结果)
测试方法如果出错了并不容易定位位置(三个测试数据都在一个方法,任何一个错误都会指向到同一个位置
有个测试的数据比较长,不太能直观判断测试数据是否正确
输入值并不完整,比如包含空格的数字字符串" 1" 、" 1 "、 "1 "并没有测试。
单元测试越简单越好,一个单元测试只做一件事
对错误易于追踪,如果测试失败,错误提示应该容易帮我我们定位问题
测试函数的命名符合特定的规则 Test_{被测方法}_{输入}_{期望输出}
有用的失败消息
输入简单且能够完整运用代码的输入(包含边界值、特殊情况
// 测试特殊值 “空字符串”
func Test_multiply_empty_returnEmpty(t *testing.T) {
// 用例简单,只包含输入、执行和判断
x, y, want := "", 1, ""
got := multiply(x, y)
if got != want {
// 有效的失败消息
t.Errorf("multiply() = %v, want %v", got, want)
}
}
// 测试包含空格的数字 边界值
func Test_multiply_numberWithSpace_returnNumber(t *testing.T) {
x, y, want := " 2", 3, "6"
got := multiply(x, y)
if got != want {
t.Errorf("multiply() = %v, want %v", got, want)
}
}
// 测试正常数据
func Test_multiply_number_returnNumber(t *testing.T) {
x, y, want := "2", 3, "6"
got := multiply(x, y)
if got != want {
t.Errorf("multiply() = %v, want %v", got, want)
}
}
// 测试非数字字符
func Test_multiply_String_returnString(t *testing.T) {
// 输入简单的字符串就可以测试,没必要用太奇怪或者太长或者太大的数据数据
x, y, want := "a", 3, "aaa"
got := multiply(x, y)
if got != want {
t.Errorf("multiply() = %v, want %v", got, want)
}
}
// 测试空格 边界值
func Test_multiply_space_returnSpace(t *testing.T) {
x, y, want := " ", 3, " "
got := multiply(x, y)
if got != want {
t.Errorf("multiply() = %v, want %v", got, want)
}
}
包含空格的非数字字符
数字右侧包含空格的字符串
数字两侧都有空格的字符串
左边有空格
右边有空格
两边都有空格
年收入0-36000部分
年收入36000-144000 部分
年收入144000-300000部分
年收入300000-420000部分
...
Mock是在测试代码中创建一个模拟对象,模拟被测方法的执行。测试使用模拟对象来验证结果是否正确
Stub是在测试包中创建一个模拟方法,用于替换被测代码中的方法,断言针对被测类执行。
//auth.go
//假设我们有一个依赖http请求的鉴权接口
type AuthService interface{
Login(username string,password string) (token string,e error)
Logout(token string) error
}
//auth_test.go
type authService struct {}
func (auth *authService) Login (username string,password string) (string,error){
return "token", nil
}
func (auth *authService) Logout(token string) error{
return nil
}
package ut
func notifyUser(username string){
// 如果是管理员,发送登录提醒邮件
}
type AuthService struct{}
func (auth *AuthService) Login(username string, password string) (string, error) {
notifyUser(username)
return "token", nil
}
func (auth *AuthService) Logout(token string) error {
return nil
}
一个方式是使用Mock的形式,定义authService 接口,然后实现接口 TestAuthService,在 TestAuthService Login中 替换掉notifyUser。这种做法改动比较大,同时重复代码也比较多(当然如果是python java等支持重载的语言可以只重载Login接口即可。
还有一种方法就是重构Login方法,把notifyUser 作为参数传入其中,这样,我们只需在测试代码中重新定义notifyUser,然后作为参数传入到Login即可模拟发送邮件提醒的功能。
在构造函数或成员变量中出现new关键字
在构造函数或成员变量中使用static方法
在构造函数中有除了字段赋值外的其它操作
在构造函数中使用条件语句或者循环
在构造函数中没有使用builder或factory方法,而是使用object graph来构造
增加或使用初始化代码
搞定Go单元测试(一)——基础原理
Guide Writing Testable Code
Selective Unit Testing – Costs and Benefits
版本上线拜哪个神仙比较灵验?
[1]
搞定Go单元测试(一)——基础原理: https://juejin.im/post/5ce93447e51d45775746b8b0[2]
Guide Writing Testable Code: http://misko.hevery.com/attachments/Guide-Writing%20Testable%20Code.pdf[3]
Selective Unit Testing – Costs and Benefits: http://blog.stevensanderson.com/2009/11/04/selective-unit-testing-costs-and-benefits/[4]
版本上线拜哪个神仙比较灵验?: https://www.zhihu.com/question/52985744