一个测试工程师走进一家酒吧,要了一杯啤酒;
一个测试工程师走进一家酒吧,要了一杯咖啡;
一个测试工程师走进一家酒吧,要了 0.7 杯啤酒;
一个测试工程师走进一家酒吧,要了-1 杯啤酒;
一个测试工程师走进一家酒吧,要了 2^32 杯啤酒;
一个测试工程师走进一家酒吧,要了一杯洗脚水;
一个测试工程师走进一家酒吧,要了一杯蜥蜴;
一个测试工程师走进一家酒吧,要了一份 asdfQwer@24dg!&*(@;
一个测试工程师走进一家酒吧,什么也没要;
一个测试工程师走进一家酒吧,又走出去又从窗户进来又从后门出去从下水道钻进来;
一个测试工程师走进一家酒吧,又走出去又进来又出去又进来又出去,最后在外面把老板打了一顿;
一个测试工程师走进一家酒吧,要了一杯烫烫烫的锟斤拷;
一个测试工程师走进一家酒吧,要了 NaN 杯 Null;
一个测试工程师冲进一家酒吧,要了 500T 啤酒咖啡洗脚水野猫狼牙棒奶茶;
一个测试工程师把酒吧拆了;
一个测试工程师化装成老板走进一家酒吧,要了 500 杯啤酒并且不付钱;
一万个测试工程师在酒吧门外呼啸而过;
一个测试工程师走进一家酒吧,要了一杯啤酒';DROP TABLE 酒吧;
测试工程师们满意地离开了酒吧。
然后一名顾客点了一份炒饭,酒吧炸了。
上面是网上流行的一个关于测试的笑话,其主要核心思想是——你永远无法把所有问题都充分测试。
在软件工程中,测试是极其重要的一环,比重通常可以与编码相同,甚至大大超过。那么在 Golang 里,怎么样把测试写好,写正确?本文将对这个问题做一些简单的介绍。 当前文章将主要分两个部分:
- Golang 测试的一些基本写法和工具
- 如何写“正确”的测试,这个部分虽然代码是用 golang 编写,但是其核心思想不限语言
由于篇幅问题,本文将不涉及性能测试,之后会另起一篇来谈。
测试路上不迷茫:关注微信公众号【程序员小濠】(主要分享软件测试的学习资源,帮助想转行、进阶、小白成为高级测试工程师…软件测试交流群:175317069)
为什么要写测试
我们举个不太恰当的例子,测试也是代码,我们假定写代码时出现 bug 的概率是 p(0<p<1),那么我们同时写测试的话,两边同时出现 bug 的概率就是(我们认为两个事件相互独立)
P(代码出现 bug) * P(测试出现 Bug) = p^2 < p
例如 p 是 1%的话,那么同时写出现 bug 的概率就只有 0.01%了。
测试同样也是代码,有可能也写出 bug,那么怎么保证测试的正确性呢?给测试也写测试?给测试的测试继续写测试?
我们定义 t(0)为原始的代码,任意的 i,i > 0,t(i+1)为对于 t(i)的测试,t(i+1)正确为 t(i)正确的必要条件,那么对所有的 i,i>0,t(i)正确都是 t(0)正确的必要条件。。。
测试的种类
测试的种类有非常多,我们这里只挑几个对一般开发者来说比较重要的测试,做简略的说明。
白盒测试、黑盒测试
首先是从测试方法上可以分为白盒测试和黑盒测试(当然还存在所谓的灰盒测试,这里不讨论)
- 白盒测试 (White-box testing):白盒测试又称透明盒测试、结构测试等,软件测试的主要方法之一,也称结构测试、逻辑驱动测试或基于程序本身的测试。测试应用程序的内部结构或运作,而不是测试应用程序的功能。在白盒测试时,以编程语言的角度来设计测试案例。测试者输入数据验证数据流在程序中的流动路径,并确定适当的输出,类似测试电路中的节点。
- 黑盒测试 (Black-box testing):黑盒测试,软件测试的主要方法之一,也可以称为功能测试、数据驱动测试或基于规格说明的测试。测试者不了解程序的内部情况,不需具备应用程序的代码、内部结构和编程语言的专门知识。只知道程序的输入、输出和系统的功能,这是从用户的角度针对软件界面、功能及外部结构进行测试,而不考虑程序内部逻辑结构。
我们写的单元测试一般属于白盒测试,因为我们对测试对象的内部逻辑有着充分了解。
单元测试、集成测试
从测试的维度上,又可以分为单元测试和集成测试:
- 在计算机编程中,单元测试又称为模块测试,是针对程序模块来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类、抽象类、或者派生类中的方法。
- 整合测试又称组装测试,即对程序模块采用一次性或增值方式组装起来,对系统的接口进行正确性检验的测试工作。整合测试一般在单元测试之后、系统测试之前进行。实践表明,有时模块虽然可以单独工作,但是并不能保证组装起来也可以同时工作。
单元测试可以是黑盒测试,集成测试亦可以是白盒测试
回归测试
- 回归测试是软件测试的一种,旨在检验软件原有功能在修改后是否保持完整。
回归测试主要是希望维持软件的不变性,我们举一个例子来说明。例如我们发现软件在运行的过程中出现了问题,在 gitlab 上开启了一个 issue。之后我们并且定位到了问题,我们可以先写一个测试(测试的名称可以带上 issue 的 ID)来复现问题(该版本代码运行此测试结果失败)。之后我们修复问题后,再次运行测试,测试的结果应当成功。那么我们之后每次运行测试的时候,通过运行这个测试,可以保证同样的问题不会复现。
一个基本的测试
我们先来看一个 Golang 的代码:
// add.go
package add
func Add(a, b int) int {
return a + b
}
复制代码
一个测试用例可以写成:
// add_test.go
package add
import (
"testing"
)
func TestAdd(t *testing.T) {
res := Add(1, 2)
if res != 3 {
t.Errorf("the result is %d instead of 3", res)
}
}
复制代码
在命令行我们使用 go test
go test
复制代码
这个时候 go 会执行该目录下所有的以_test.go 为后缀中的测试,测试成功的话会有如下输出:
% go test
PASS
ok code.byted.org/ek/demo_test/t01_basic/correct 0.015s
复制代码
假设这个时候我们把 Add 函数修改成错误的实现
// add.go
package add
func Add(a, b int) int {
return a - b
}
复制代码
再次执行测试命令
% go test
--- FAIL: TestAddWrong (0.00s)
add_test.go:11: the result is -1 instead of 3
FAIL
exit status 1
FAIL code.byted.org/ek/demo_test/t01_basic/wrong 0.006s
复制代码
会发现测试失败。
只执行一个测试文件
那么如果我们想只测试这一个文件,输入
go test add_test.go
复制代码
会发现命令行输出
% go test add_test.go
# command-line-arguments [command-line-arguments.test]
./add_test.go:9:9: undefined: Add
FAIL command-line-arguments [build failed]
FAIL
复制代码
这是因为我们没有附带测试对象的代码,修改测试后可以获得正确的输出:
% go test add_test.go add.go
ok