最近想学习一下 TDD,在网上找到了一篇文章
learn-go-with-tests
中间使用了依赖注入方式来预留给测试进行 MOCK,这部分我怎么看怎么感觉别扭,如果我需要根据 if else 初始化不同的资源呢?
func Exec(v1 int, v2 string) int {
count := 0
if v1 > 10 {
q := NewQuery()
q.Query(v1)
count += 1
}
if strings.HasPrefix(v2, "a") {
d := NewUpdate()
d.Update(v2)
count += 2
}
return count
}
如果要使用依赖注入,是要改成下面的样子么?
func ExecDI(v1 int, v2 string, q QueryModule, d UpdateModule) int {
count := 0
if v1 > 10 {
q.Query(v1)
count += 1
}
if strings.HasPrefix(v2, "a") {
d.Update(v2)
count += 2
}
return count
}
那如果子模块也有资源需要初始化,那也要从最顶层传进去么?
func ExecDIDI(v1 int, v2 string, q QueryModule, d UpdateModule, configs config.Config, db *sql.DB) int {
count := 0
if v1 > 10 {
q.QueryDI(v1, configs)
count += 1
}
if strings.HasPrefix(v2, "a") {
d.UpdateDI(v2, db)
count += 2
}
return count
}
假设一个偏远的代码分支需要从 Http 获取一些数据,本来只要执行到分支这里的时候,再让它自行去获取就好。
那改成依赖注入会变成从一开始就要获取么?
如果还是保留这部分不注入,那么这部分代码块就没法 mock 以进行充分的测试了
1
anonydmer 2021-03-18 14:56:04 +08:00
```
type Executor struct { q QueryModel d UpdateModel } func (e Executor) Exec(v1 int, v2 string) int{ count := 0 if v1 > 10 { e.q.Query(v1) count += 1 } if strings.HasPrefix(v2, "a") { e.d.Update(v2) count += 2 } return count } ``` |
2
pkoukk OP @anonydmer
这还是不能解决问题啊,QueryModel 和 UpdateModel 在哪里初始化?初始化时候所需的资源从哪里来? 我上面的示例只是一个简化的模式,实际上 QueryModel 和 UpdateModel 也可能包含的还有子模块,子模块也需要初始化 |
3
anonydmer 2021-03-18 15:15:13 +08:00
1. 对于这个单测来说,他依赖的只是 QueryModel 的 Query 方法;如果 QueryModel 是个接口的话,你随便注入一个实现他的就可以
2. 你这个测试如果还 QueryModel 的子模块就不对了; 3. QueryModel 如何初始化那是另外的事情,一般是在 Executor 实例化时初始化注入( Golang 中推荐的实际是一个 struct 使用一个工厂方法来实例化);单测时候直接注入 mock 的 |
4
anonydmer 2021-03-18 15:16:06 +08:00
上述 2, “你这个测试如果还 QueryModel 的子模块就不对了” -> "你这个测试如果还依赖 QueryModel 的子模块就不对了"
|
5
carlclone 2021-03-18 15:21:20 +08:00
你需要一个 Service 层或者 Repository 层, 然后把他们注入进来
|
6
zjsxwc 2021-03-18 15:31:07 +08:00
参数注入本来就会碰到楼主说的这种问题。
所以我们都用依赖注入容器,让容器来管理。 楼主这种“如果子模块也有资源需要初始化,那也要从最顶层传进去么?”问题, 只需要在容器里搞个原资源类似别名的新资源代替原资源就行。 推荐个刚刚看到的 golang 依赖注入容器 https://github.com/bassbeaver/gioc |
7
pkoukk OP @anonydmer
这只是一个简化模型...我知道你的方式可以完成测试,但是如果面对更复杂的状况呢? 假设在第一个分支里,query 返回 a,这个 a 的返回是依赖于 query 子模块进行的一些 http 操作而来的,a 会被用作 exec 函数下面的分支里进行判断,那么如果不能对 query 的子模块进行 mock,就无法控制 a 的返回,那么 exec 的测试就无法全覆盖。 我想表达的是冲突就在于如果需要注入依赖,那么是不是就得全注进去?如果不是全部注入,就意味着子模块有独立的数据源,不受上层模块的控制。如果是全部注入,那么问题就是提前无法确认哪些资源会被用到。 5,6 楼的方法应该可行..但是就感觉本来简单的结构变得太复杂了 |
8
anonydmer 2021-03-18 16:19:57 +08:00
“假设在第一个分支里,query 返回 a,这个 a 的返回是依赖于 query 子模块进行的一些 http 操作而来的,a 会被用作 exec 函数下面的分支里进行判断,那么如果不能对 query 的子模块进行 mock,就无法控制 a 的返回,那么 exec 的测试就无法全覆盖”
这时候你应该在多个测试用例中直接 mock query 返回不同的 a ; |