[TOC] # Mock依赖 有的时候,由于业务逻辑的复杂性,功能代码并不会就这么直接,往往还会掺杂很多其他组件,这就给我们的测试工作带来很大的麻烦,我这里列举几个常见的依赖: * 组件依赖 * 函数依赖 组件依赖和函数依赖是两种比较常见的依赖,但是,这两种依赖也是可以扩展开来说的,既可能来自于我们自己编写的组件/函数,也可能是引入其他人写的。但是,无妨,对于这些情况,我们都会做一些分析 ## 组件依赖处理 传一个 Stub 组件进入,从而达到控制依赖组件行为的效果 举一个例子先,例如我们比较常见的 Service 层和 DAO 层的操作,Service 处理完逻辑之后,交给 DAO 层进行持久化,或者需要调用 DAO 层从持久化中获取一些必要的数据;在测试的时候,我们很多时候不希望真的持久化或者从持久化中获取数据,那么就会对 DAO 层进行一些 Mock ~~~ import "fmt" type Data struct { Field string } type Dao interface { ReadAll() []Data SaveData(d *Data) } type MongoDao struct { } func (d MongoDao) ReadAll() []Data { return []Data{} } func (d MongoDao) SaveData(data *Data) { //... } type Service struct { Dao *Dao } func (s *Service) Login (username string) bool { users := (*s.Dao).ReadAll() for _, user := range users { if username == user.Field { return true } } return false } func Newservice(d Dao) *Service { srv := Service{Dao: &d} return &srv } func main() { d := MongoDao{} srv := Newservice(d) fmt.Println(srv.Login("abc")) } ~~~ 这里我们想要测试**Service**的正确性,但是又不想要真的持久化 DAO,所以,这个时候我们会自己创建一个 Stub,然后提供给 Service,同时,我们还能操作 DAO 的行为,达到运行得效果 ~~~ //用StubDao代替Mongodb type StubDao struct { } func (d StubDao) ReadAll() []Data { return []Data{Data{"abc"}} } func (d StubDao) SaveData(data *Data) { } func TestLogin(t *testing.T) { d := StubDao{} srv := NewService(d) rst := srv.Login("abc") if !rst { t.Error("login error") } } ~~~ 这里对测试代码稍微改了一下,可以发现,我们可以通过修改一个变量来控制 Stub 的输出,从而达到测试不同功能的效果,这就解决了组件依赖的问题 ## 函数依赖 函数依赖相比于组件依赖会更麻烦一点,因为我们在前面可以看到,组件依赖的话我们可以传递 Stub 进行,这样我们可以随意得控制 Stub 的行为,但是函数不行呀,这里我们又不能传函数进去,因为函数是被 import 进去的啊。问题就在这了,因为函数是被 import 进去的,所以可以理解为函数是全局的了,既然这样,那么我们为什么不修改一下函数呢?什么意思?我们先来看着正常的业务例子 ~~~ var Login = func(username, password string) bool { if username == password { return false } return true } func Reply(username, password, msg string) bool { if Login(username, password) { fmt.Println(msg) return true } return false } func stu() { Reply("a", "b", "aa") Reply("a", "b", "bb") } ~~~ 要先登录,然后登录完之后我们才能回复消息,这里我们的登录逻辑是简单的,但是,在实际业务中可能这里的登录逻辑就设计到 DB 访问等等,我们希望不走真实的逻辑,而是自己来控制`Login`的行为 先分析一下我们的 UT 目的,我们的目的是测试`Reply`函数,我们期望是`Login`成功,那么`Reply`也应该是成功的;如果`Login`失败,那么`Reply`也应该是失败的。这个测试结论不应该被`Login`所影响,及时以后`Login`逻辑修改了,我们也应该是这个逻辑,不会受到影响,那么我们可以这么编写 UT ~~~ func TestSuccReply(t *testing.T) { origLogin := Login defer func() { Login = origLogin }() Login = func(username, password string) bool { return true } if !Reply("a", "a", "aaa") { t.Errorf("reply false for login success") } } func TestLogin(t *testing.T) { origLogin := Login defer func() { Login = origLogin }() Login = func(username, password string) bool { return false } if Reply("a", "a", "aa") { t.Errorf("reply true for login fail") } } ~~~ 这里可以发现,我们是修改了`Login`这个函数的代码,从而控制`Login`函数的返回值,这样我们就可以测试我们写的代码的逻辑是否正确了