单元测试重点是对代码逻辑进行测试,也就是证明:为什么你的代码是正确的。Mock测试是单元测试中常用的一种手段,特别是对于代码运行时对环境有严重依赖的,可以在不具备相应环境的情况下运行。例如:数据库、中间件、或者第三方接口等情况。
利用接口可以很容易的构造Mock环境,方便对代码进行单元测试。
原有实现
在这个实现中,我们定义了一个Obj的结构体,并且定义了getData方法。这里我们假定getData是从第三方服务中获取数据并进行处理。另外我们有一个showTotal的函数用于调用结构体的方法,并且显示出来,相当于后续的程序处理。
如果我们对showTotal进行单元测试的时候,我们会发现需要构建getData的依赖环境才能进行单元测试,所以此时我们可以使用interface构建一个MockObj来伪造getData返回数据,进行不同的场景测试。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| package original
import "fmt"
type Obj struct {
name string
}
// This is a real function to connect third party service, like database
func (o Obj) getData() int {
fmt.Printf("This is [%s] environment\n", o.name)
// if we fetch data here
data := map[string]int{
"total": 10,
}
return data["total"]
}
func showTotal(o Obj) int {
total := o.getData()
fmt.Printf("Total count: %v\n", total)
}
func main() {
o := Obj{"Production"}
showTotal(o)
}
|
代码改造
定义接口
首先我们先定义一个接口,定义接口的目的是能让我们的Mock结构体生效
1
2
3
| type objInterface interface {
getData() int
}
|
改造函数
我们对showTotal的函数入参进行调整,原有入参只接收了Obj这个结构体,我们改成接收接口参数。
1
2
3
4
5
| func showTotal(o objInterface) int {
total := o.getData()
fmt.Printf("Total count: %v\n", total)
return total
}
|
Mock结构体
我们新建一个main_test.go的单元测试文件,我们新增一个Mock结构体及Mock的getData方法。
1
2
3
4
5
6
| type MockObj struct {}
func (o MockObj) getData() int {
fmt.Printf("This is a mock function using interface\n")
return 100
}
|
Mock测试
我们新增一个测试用例,如果showTotal能够返回100,则测试成功,否则抛出异常。在测试用例中,我们将定义好的MockObj传给了showTotal,此时showTotal调用了我们定义的Mock方法,所以返回100,单元测试构建成功。
1
2
3
4
5
6
7
8
| func TestShowTotal(t *testing.T) {
mo := MockObj{}
total := showTotal(mo)
if total != 100 {
t.Log("Mock show total failed!")
t.Fail()
}
}
|
完整代码
主体函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| package main
import "fmt"
type objInterface interface {
getData() int
}
type Obj struct {
name string
}
// This is a real function to connect third party service, like database
func (o Obj) getData() int {
fmt.Printf("This is [%s] environment\n", o.name)
// if we fetch data here
data := map[string]int{
"total": 10,
}
return data["total"]
}
func showTotal(o objInterface) int {
total := o.getData()
fmt.Printf("Total count: %v\n", total)
return total
}
func main() {
o := Obj{"Production"}
showTotal(o)
}
|
测试用例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| package main
import (
"fmt"
"testing"
)
type MockObj struct {}
func (o MockObj) getData() int {
fmt.Printf("This is a mock function using interface\n")
return 100
}
func TestShowTotal(t *testing.T) {
mo := MockObj{}
total := showTotal(mo)
if total != 100 {
t.Log("Mock show total failed!")
t.Fail()
}
}
|
运行及结果
运行go test之前,记得要设定环境变量
1
2
| export GO111MODULE=off
go test
|
返回如下,我们很清楚的看到了测试中调用了我们的Mock方法
1
2
3
4
| This is a mock function using interface
Total count: 100
PASS
ok _/root/workspace/go/test_interface_unittest 0.003s
|