用testify模拟表驱动测试

时间:2017-08-06 23:03:18

标签: testing go mocking testify table-driven

是否有使用testify编写干净的表驱动测试的示例。输入和预期输出的表驱动测试运行良好,但必须测试依赖项的输出似乎真的很难。

下面的示例使用一个模拟接口,并要求我编写一个全新的测试函数来验证测试中的函数是否正确处理依赖性错误。 我只是在寻找建议,使用testify模拟软件包进行更简化的编写单元测试。

package packageone

import (
    "errors"
    "musings/packageone/mocks"
    "testing"
)
//Regular Table driven test
func TestTstruct_DoSomething(t *testing.T) {
    testObj := new(mocks.Dinterface)

    passes := []struct {
        Input  int
        Output int
    }{{0, 0}, {1, 1}, {2, 4}, {100, 10000}}

    for _, i := range passes {
        testObj.On("DoSomethingWithD", i.Input).Return(i.Output, nil)
    }

    type fields struct {
        DC Dinterface
    }
    type args struct {
        i int
    }
    tests := []struct {
        name    string
        fields  fields
        args    args
        wantRes int
        wantErr bool
    }{
        {"Pass#0", fields{testObj}, args{passes[0].Input}, passes[0].Output, false},
        {"Pass#1", fields{testObj}, args{passes[1].Input}, passes[1].Output, false},
        {"Pass#2", fields{testObj}, args{passes[2].Input}, passes[2].Output, false},
        {"Pass#3", fields{testObj}, args{passes[3].Input}, passes[3].Output, false},
        {"Fail#4", fields{testObj}, args{-1}, 0, true},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            r := &Tstruct{
                DC: tt.fields.DC,
            }
            gotRes, err := r.DoSomething(tt.args.i)
            if (err != nil) != tt.wantErr {
                t.Errorf("Tstruct.DoSomething() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            if gotRes != tt.wantRes {
                t.Errorf("Tstruct.DoSomething() = %v, want %v", gotRes, tt.wantRes)
            }
        })
    }
}

//Separate Unit test for dependency returning errors.
func TestTstruct_ErrMock_DoSomething(t *testing.T) {
    testObj := new(mocks.Dinterface)
    testObj.On("DoSomethingWithD", 1).Return(0, errors.New(""))

    type fields struct {
        DC Dinterface
    }
    type args struct {
        i int
    }
    tests := []struct {
        name    string
        fields  fields
        args    args
        wantRes int
        wantErr bool
    }{
        {"Test#1", fields{testObj}, args{1}, 0, true},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            r := &Tstruct{
                DC: tt.fields.DC,
            }
            gotRes, err := r.DoSomething(tt.args.i)
            if (err != nil) != tt.wantErr {
                t.Errorf("Tstruct.DoSomething() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            if gotRes != tt.wantRes {
                t.Errorf("Tstruct.DoSomething() = %v, want %v", gotRes, tt.wantRes)
            }
        })
    }
}

1 个答案:

答案 0 :(得分:3)

编写单元测试相对容易。 编写好的单元测试很难。这没有任何帮助,因为我们将使用不能模仿现实生活用途的简单代码示例进行单元测试。

尽管需要验证依赖项的调用,但请尽量避免模拟。喜欢使用存根,假货或实际实现。知道何时使用每个都是经验问题和难点所在。另外,考虑一下你的设计。如果您发现难以进行单元测试,这可能是因为您需要重新设计。

单元测试需要时间来编写和维护。无需单元测试,您将始终更快地编写代码。但是,我们编写单元测试以确保我们的代码能够正常运行并且可以重新考虑因素。

因此,尝试针对行为(黑盒子)而不是实现(白盒子)编写测试非常重要。这并不总是可行的,但与实现相关的单元测试是脆弱的,不鼓励重构,有时也可能掩盖意外行为。

一些值得阅读的单元测试资源:

  1. Mocks Aren't Stubs
  2. Testing on the Toilet Blog
  3. TDD - Where it all went wrong
  4. 作为一个例子,考虑为简单的电子邮件地址验证器编写单元测试。我们想编写一个函数,它将获取一个字符串,并根据是否提供了有效的电子邮件地址返回true / false。

    一个简单的示例实现是:

    var re = regexp.MustCompile("[regular expression]")
    func ValidateEmail(s string) bool {
       return re.MatchString(s)
    }
    

    然后,我们将使用各种输入编写表驱动测试,例如: ""good@example.combad等,并验证结果是否正确。

    现在这是一个微不足道的例子,但说明了我的观点。有人可能会说这很容易,因为函数没有依赖关系,但确实如此!我们依赖于regexp实现和我们传递的正则表达式。

    这是测试所需的行为,而不是我们如何实现它。我们不关心它如何验证电子邮件地址,只是它确实如此。如果我们要调整正则表达式或完全改变实现,那么除非结果不正确,否则这些都不会中断测试。

    很少有人会建议我们应该通过模拟regexp并确保使用我们期望的正则表达式来调用它来隔离依赖项并测试验证函数。这将更加脆弱,但也没那么有用,即我们怎么知道正则表达式实际上会起作用呢?

    对于您的具体示例,您可以轻松避免嘲笑&使用一个琐碎的假来测试正常结果和错误情况。这将是:

    // Used to test error result, 
    var errFail = errors.New("Failed")
    
    // Fake type
    type fakeD func(input int) (int, error)
    
    // Implements Dinterface
    func (f fakeD) DoSomethingWithD(input int) (int, error) {
        return f(input)
    }
    
    // Fake implementation. Returns error on input 5, otherwise input * input
    var fake fakeD = func(input int) (int, error) {
        if input == 5 {
            return nil, errFail
        }
        return input * input, nil
    }
    

    然后简单地使用fake作为您的依赖项并正常运行基于表的测试。