如果使用依赖注入的方式来实现 TDD,怎么避免注入冗余的参数呢?

2021-03-18 14:47:59 +08:00
 pkoukk

最近想学习一下 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 以进行充分的测试了

1725 次点击
所在节点    Go 编程语言
9 条回复
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
}
```
pkoukk
2021-03-18 15:06:51 +08:00
@anonydmer
这还是不能解决问题啊,QueryModel 和 UpdateModel 在哪里初始化?初始化时候所需的资源从哪里来?
我上面的示例只是一个简化的模式,实际上 QueryModel 和 UpdateModel 也可能包含的还有子模块,子模块也需要初始化
anonydmer
2021-03-18 15:15:13 +08:00
1. 对于这个单测来说,他依赖的只是 QueryModel 的 Query 方法;如果 QueryModel 是个接口的话,你随便注入一个实现他的就可以
2. 你这个测试如果还 QueryModel 的子模块就不对了;
3. QueryModel 如何初始化那是另外的事情,一般是在 Executor 实例化时初始化注入( Golang 中推荐的实际是一个 struct 使用一个工厂方法来实例化);单测时候直接注入 mock 的
anonydmer
2021-03-18 15:16:06 +08:00
上述 2, “你这个测试如果还 QueryModel 的子模块就不对了” -> "你这个测试如果还依赖 QueryModel 的子模块就不对了"
carlclone
2021-03-18 15:21:20 +08:00
你需要一个 Service 层或者 Repository 层, 然后把他们注入进来
zjsxwc
2021-03-18 15:31:07 +08:00
参数注入本来就会碰到楼主说的这种问题。

所以我们都用依赖注入容器,让容器来管理。

楼主这种“如果子模块也有资源需要初始化,那也要从最顶层传进去么?”问题,
只需要在容器里搞个原资源类似别名的新资源代替原资源就行。


推荐个刚刚看到的 golang 依赖注入容器
https://github.com/bassbeaver/gioc
pkoukk
2021-03-18 16:05:36 +08:00
@anonydmer
这只是一个简化模型...我知道你的方式可以完成测试,但是如果面对更复杂的状况呢?
假设在第一个分支里,query 返回 a,这个 a 的返回是依赖于 query 子模块进行的一些 http 操作而来的,a 会被用作 exec 函数下面的分支里进行判断,那么如果不能对 query 的子模块进行 mock,就无法控制 a 的返回,那么 exec 的测试就无法全覆盖。

我想表达的是冲突就在于如果需要注入依赖,那么是不是就得全注进去?如果不是全部注入,就意味着子模块有独立的数据源,不受上层模块的控制。如果是全部注入,那么问题就是提前无法确认哪些资源会被用到。

5,6 楼的方法应该可行..但是就感觉本来简单的结构变得太复杂了
anonydmer
2021-03-18 16:19:57 +08:00
“假设在第一个分支里,query 返回 a,这个 a 的返回是依赖于 query 子模块进行的一些 http 操作而来的,a 会被用作 exec 函数下面的分支里进行判断,那么如果不能对 query 的子模块进行 mock,就无法控制 a 的返回,那么 exec 的测试就无法全覆盖”

这时候你应该在多个测试用例中直接 mock query 返回不同的 a ;
pkoukk
2021-03-18 16:28:00 +08:00
@anonydmer
你说的对...我把自己绕进去了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/762831

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX