2017-11-18 32 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'的连接。如何测试你自己? –