2017-11-18 30 views
2

想象有一個User包,其中只包含兩個簡單的方法單元測試時,接收機的方法,叫對方

  1. Hello它說「你好」
  2. Say它實現了用戶如何說話

原創

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 - 使用拉姆達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") 
    } 
} 

我瞭解大多數的情況下,人們會建議使用第二種方法,因爲這是依賴注入圍棋是如何工作的。

但是,在這個例子中,將Say的實現提取到另一個層(我認爲不必要的複雜性)很奇怪。

有沒有更好的解決方案來解決這種依賴? 或你喜歡哪種方法,爲什麼?

+1

遵循你的推理,我們應該在訂單級別測試每個功能的完整性。 我們測試函數的結果而不是積分,所以測試應該只檢查函數'Hello'的輸出。 –

+1

當有一個函數'Hello',我需要測試它的正確性。通常,我會做兩件事。第一個是聲明輸出的正確性(黑盒測試類型),第二個是聲明calledCount,如果'Hello'調用任何其他函數(白盒測試類),則調用Param。 這個問題更關注於如何在不影響正常開發體驗的情況下優雅地完成第二個任務(我總結的兩種方法由於不必要的複雜性而是不好的開發經驗) – Even

回答

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 
+0

在我看來,這是集成測試的責任來檢查正在編寫「'Hello \ n'」,因爲現在'Hello'取決於'Say'。 在單元測試中,我們只關心'Hello'確實調用'Say'並將正確的參數傳遞給它。 在'Hello'上進行單元測試時,不需要了解'Say'的實現細節。 – Even

+0

截獲stdout非常有趣的方法。但從結構上講,這不是測試方法'Hello'本身,而是'Hello' +'Say'的連接。如何測試你自己? –