Golang Go语言中如何使用依赖注入的方式实现TDD并避免注入冗余参数
Golang Go语言中如何使用依赖注入的方式实现TDD并避免注入冗余参数
最近想学习一下 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 以进行充分的测试了
更多关于Golang Go语言中如何使用依赖注入的方式实现TDD并避免注入冗余参数的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
<br>type Executor struct {<br> q QueryModel<br> d UpdateModel<br>}<br><br>func (e Executor) Exec(v1 int, v2 string) int{<br> count := 0<br> if v1 > 10 {<br> e.q.Query(v1)<br> count += 1<br> }<br> if strings.HasPrefix(v2, "a") {<br> e.d.Update(v2)<br> count += 2<br> }<br> return count<br>}<br>
更多关于Golang Go语言中如何使用依赖注入的方式实现TDD并避免注入冗余参数的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
这还是不能解决问题啊,QueryModel 和 UpdateModel 在哪里初始化?初始化时候所需的资源从哪里来?
我上面的示例只是一个简化的模式,实际上 QueryModel 和 UpdateModel 也可能包含的还有子模块,子模块也需要初始化
- 对于这个单测来说,他依赖的只是 QueryModel 的 Query 方法;如果 QueryModel 是个接口的话,你随便注入一个实现他的就可以
2. 你这个测试如果还 QueryModel 的子模块就不对了;
3. QueryModel 如何初始化那是另外的事情,一般是在 Executor 实例化时初始化注入( Golang 中推荐的实际是一个 struct 使用一个工厂方法来实例化);单测时候直接注入 mock 的
上述 2, “你这个测试如果还 QueryModel 的子模块就不对了” -> “你这个测试如果还依赖 QueryModel 的子模块就不对了”
你需要一个 Service 层或者 Repository 层, 然后把他们注入进来
参数注入本来就会碰到楼主说的这种问题。
所以我们都用依赖注入容器,让容器来管理。
楼主这种“如果子模块也有资源需要初始化,那也要从最顶层传进去么?”问题,
只需要在容器里搞个原资源类似别名的新资源代替原资源就行。
推荐个刚刚看到的 golang 依赖注入容器
https://github.com/bassbeaver/gioc
这只是一个简化模型…我知道你的方式可以完成测试,但是如果面对更复杂的状况呢?
假设在第一个分支里,query 返回 a,这个 a 的返回是依赖于 query 子模块进行的一些 http 操作而来的,a 会被用作 exec 函数下面的分支里进行判断,那么如果不能对 query 的子模块进行 mock,就无法控制 a 的返回,那么 exec 的测试就无法全覆盖。
我想表达的是冲突就在于如果需要注入依赖,那么是不是就得全注进去?如果不是全部注入,就意味着子模块有独立的数据源,不受上层模块的控制。如果是全部注入,那么问题就是提前无法确认哪些资源会被用到。
5,6 楼的方法应该可行…但是就感觉本来简单的结构变得太复杂了
“假设在第一个分支里,query 返回 a,这个 a 的返回是依赖于 query 子模块进行的一些 http 操作而来的,a 会被用作 exec 函数下面的分支里进行判断,那么如果不能对 query 的子模块进行 mock,就无法控制 a 的返回,那么 exec 的测试就无法全覆盖”
这时候你应该在多个测试用例中直接 mock query 返回不同的 a ;
在Golang中,实现依赖注入(DI)以促进测试驱动开发(TDD)并避免冗余参数,通常可以通过接口和结构体组合来实现。以下是一些关键步骤和策略:
-
定义接口:首先,为需要注入的依赖定义接口。接口定义了依赖的行为,使得测试时可以用模拟(mock)对象替换实际实现。
-
使用结构体和方法接收者:在结构体中,通过接口类型的字段来持有依赖。这样,可以在实例化结构体时注入具体的依赖实现。
-
构造函数注入:通过构造函数将依赖注入到结构体中。这避免了在方法调用时传递大量参数,同时也使代码更清晰。
-
利用依赖注入框架(可选):虽然Go没有内置的DI框架,但可以使用像
Wire
或Uber's dig
这样的第三方库来自动化依赖注入过程,减少手动编码工作量。 -
编写单元测试:使用Go的测试框架(
testing
包)编写单元测试。在测试中,可以使用接口的模拟对象来替换实际依赖,从而专注于测试业务逻辑。 -
避免冗余参数:如果多个方法共享相同的依赖,确保这些依赖在结构体的层级上被注入,而不是在每个方法中都作为参数传递。这减少了方法的签名长度,提高了代码的可读性。
通过上述方法,可以在Go中实现有效的依赖注入,促进TDD实践,同时保持代码的整洁和可维护性。