验证模拟调用的顺序

Verify order of mocks call

我使用 testify (v1.6.1) 并且需要 测试 接口方法调用的顺序是否正确。我检查了 documentation 并试图在互联网上查找任何信息,但没有找到任何关于 mocks 订单检查的信息。

示例:

type InterfaceA interface {
    Execute()
}

type InterfaceB interface {
    Execute()
}
type Composition struct {
    a InterfaceA
    b InterfaceB
}

func (c * Composition) Apply() error {
    //How to check that "a" execute before "b"?
    c.a.Execute()
    c.b.Execute()
    return nil
}

虽然存在未解决的问题 (stretchr/testify/issue 741 "assert mock calls in order")

,但并未直接支持此功能

更普遍的问题684 "Assert call order?"包括反驳:

IMO you should check the output of the function but not how it works internally. It may lead to testing implementation which is very hard to maintain.

虽然,在同一个线程中:

IMO there are cases to enforce order. i.e if you mock a mutex, you better check that Lock was always called before Unlock.
We can have a simple implementation where the mock has an "assertExpectationsInOrder" true/false flag that can be set before any expectations are added.

这可能会导致一些测试,例如 cassandra-operator/cmd/operator/controller_test.go 记录事件以测试他们的订单。

IMO there are cases to enforce order. i.e if you mock a mutex, you better check that Lock was always called before Unlock.

简单的单线程实现:

func TestOrderOfMocks(t *testing.T) {
    order := 0
    amock := new(AMock)
    amock.On("Execute").Run(func(args mock.Arguments) {
        if order++; order != 1 {
            t.Fail()
        }
    })

    bmock := new(BMock)
    bmock.On("Execute").Run(func(args mock.Arguments) {
        if order++; order != 2 {
            t.Fail()
        }
    })

    c := &Composition{amock, bmock}
    err := c.Apply()
    require.NoError(t, err)
}

PLAYGROUND

如果有原因,可以将订单检查逻辑复杂化...

正如其他人所说,这是一个内部细节,确实会使您的测试与实现纠缠在一起。如果您确定顺序很重要,这就没有任何意义。在这里保持简洁是另一种使用最低限度测试顺序的解决方案。

创建两个实现 InterfaceA 和 InterfaceB 的间谍。

type SpyA struct {
    Calls *[]string
}

func (s *SpyA) Execute() {
    *s.Calls = append(*s.Calls, "ExecuteA")
}

type SpyB struct {
    Calls *[]string
}

func (s *SpyB) Execute() {
    *s.Calls = append(*s.Calls, "ExecuteB")
}

然后像这样使用它们。

func TestApply(t *testing.T) {
    got := []string{}
    c := &Composition{
        a: &SpyA{&got},
        b: &SpyB{&got},
    }

    c.Apply()

    expected := []string{"ExecuteA", "ExecuteB"}

    if len(got) != len(expected) {
        t.Fail()
    }

    for i := range got {
        if got[i] != expected[i] {
            t.Fail()
        }
    }
}

Playground