接收方法相互呼叫时的单元测试

时间:2017-11-18 04:47:08

标签: unit-testing go

想象一下,User包只包含两个简单的方法

  1. Hello,上面写着“你好”
  2. Say实现用户说话的方式
  3. 原始

    package user
    
    import "fmt"
    
    type user struct {}
    
    func (u user) Hello() {
        u.Say("Hello")
    }
    
    func (u user) Say(sentence string) {
        fmt.Println(sentence)
    }
    

    但是,我们无法对Hello进行单元测试,因为它取决于Say不可模仿。

    在使用StackOverflow和Goole之后,我总结了两种解决问题的方法,但没有一种方法是完美的。

    方法1 - 使用lambda func

    user.go

    package user
    
    import "fmt"
    
    type user struct{}
    
    func (u user) Hello() {
        say("Hello")
    }
    
    func (u user) Say(sentence string) {
        say(sentence)
    }
    
    var say = func(sentence string) {
        fmt.Println(sentence)
    }
    

    user_test.go

    package user
    
    import (
        "testing"
    )
    
    func TestHello(t *testing.T) {
        sayCalled := 0
        sayCallArg := ""
    
        mockSay := func(sentence string) {
            sayCalled++
            sayCallArg = sentence
        }
        say = mockSay
    
        u := user{}
        u.Hello()
    
        if sayCalled != 1 {
            t.Fatalf("not called")
        }
        if sayCallArg != "Hello" {
            t.Fatalf("wrong arg")
        }
    }
    

    方法2 - 使用界面

    user.go

    package user
    
    import "fmt"
    
    type user struct {
        sayer Sayer
    }
    
    func (u user) Hello() {
        u.sayer.Say("Hello")
    }
    
    func (u user) Say(sentence string) {
        u.sayer.Say(sentence)
    }
    
    type Sayer interface {
        Say(string)
    }
    
    type sayer struct{}
    
    func (s sayer) Say(sentence string) {
        fmt.Println(sentence)
    }
    

    user_test.go

    package user
    
    import (
        "testing"
    )
    
    type mockSayer struct {
        called    int
        calledArg string
    }
    
    func (s *mockSayer) Say(sentence string) {
        s.called++
        s.calledArg = sentence
    }
    
    func TestHello(t *testing.T) {
        mockSayer := &mockSayer{}
        u := user{sayer: mockSayer}
        u.Hello()
    
        if mockSayer.called != 1 {
            t.Fatalf("not called")
        }
        if mockSayer.calledArg != "Hello" {
            t.Fatalf("wrong arg")
        }
    }
    

    我理解大多数情况,人们会建议使用方法2,因为这就是依赖注入在Go中的工作方式。

    但是,在这个例子中,将Say的实现提取到另一层(我认为不必要的复杂性)是很奇怪的。

    有没有更好的解决方案来解决这种依赖? 或者您更喜欢哪种方法?为什么?

1 个答案:

答案 0 :(得分:3)

以上都不是。我不知道你在哪里证明Hello方法确实有效,实际上写的是“Hello\n”。检查Say方法输出。模仿os.Stdout。例如,

user.go

package user

import (
    "fmt"
    "io"
    "os"
)

type user struct{}

const hello = "Hello"

func (u user) Hello() {
    u.Say(hello)
}

var stdwrite = io.Writer(os.Stdout)

func (u user) Say(sentence string) {
    fmt.Fprintln(stdwrite, sentence)
}

user_test.go

package user

import (
    "bytes"
    "io"
    "testing"
)

func TestHello(t *testing.T) {
    u := user{}

    u.Hello() // for real

    defer func(w io.Writer) { stdwrite = w }(stdwrite)
    stdwrite = new(bytes.Buffer)

    u.Hello() // for test

    got := stdwrite.(*bytes.Buffer).String()
    want := hello + "\n"
    if got != want {
        t.Errorf("want: %q got: %q", want, got)
    }
}

输出:

$ go test -v
=== RUN   TestHello
Hello
--- PASS: TestHello (0.00s)
PASS
ok      say 0.001s