V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
pkoukk
V2EX  ›  Go 编程语言

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

  •  
  •   pkoukk · 2021-03-18 14:47:59 +08:00 · 1581 次点击
    这是一个创建于 1107 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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


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

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

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

    这时候你应该在多个测试用例中直接 mock query 返回不同的 a ;
    pkoukk
        9
    pkoukk  
    OP
       2021-03-18 16:28:00 +08:00
    @anonydmer
    你说的对...我把自己绕进去了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2769 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 12:19 · PVG 20:19 · LAX 05:19 · JFK 08:19
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.