3

在为 go 中的方法编写单元测试时,我被一个问题难住了。首先,被测代码片段:

func MehodToBeTested(e Entity) {
  go saveAudit(e)

 //do something on which assertions can be done
}

可以模拟实体。在 saveAudit 方法中,Entity.Save 方法被调用。在我的 UT 中,我想断言 Entity.Save 方法被调用一次。以下是我目前的 UT:

func TestMethod(t *testing.T) {
  var mock = &mockEntity{}
  mock.On("Save").Return(nil)

  //make call to func under test
  MethodToBeTested(mock)

  // Assert that Save is called on Entity
  mock.AssertNumberOfCalls(t, "Save",1)
}

这给出了错误消息:预期调用次数 (1) 与实际调用次数 (0) 不匹配,因为实际调用发生在另一个 go 例程中。我该如何测试呢?

4

3 回答 3

3

我使用相同的技术。等待 goroutine 结束。很可能它还没有设置。

此外,我建议使用竞争条件检测器运行此类测试。它有助于捕捉这种情况。然后,您可以向测试添加一些同步以使其可靠。

我的测试中的示例。一个被测试的函数应该同时检查两个网页是否包含指定的字符串。所以测试应该检查被测试的功能是否访问了两个页面

更新:附加了不正确的测试。固定的。

func TestCheckSites_TwoSlowHandlers_BothContain(t *testing.T) {
    var config = GetConfig()
    var v1, v2 bool
    var wg sync.WaitGroup
    wg.Add(2)
    handler1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer wg.Done()
        v1 = true
        time.Sleep(2 * config.Http.Timeout) // Use double HTTP_TIMEOUT
        io.WriteString(w, "Present")
    })
    ts1 := httptest.NewServer(handler1)
    defer ts1.Close()

    handler2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer wg.Done()
        v2 = true
        time.Sleep(2 * config.Http.Timeout)
        io.WriteString(w, "Present")
    })
    ts2 := httptest.NewServer(handler2)
    defer ts2.Close()

    result, err := checkSites([]string{ts1.URL, ts2.URL}, "Present")
    assert.Equal(t, nil, err, "Error should be nil")
    assert.Contains(t, []string{""}, result, "Should be empty string")
    //assert.(t, ts1.URL, result, "Should first or second empty string")
    wg.Wait()
    assert.Equal(t, true, v1, "First server should be visited")
    assert.Equal(t, true, v2, "Second server should be visited")
}
于 2017-11-24T05:53:21.473 回答
0

首先,您所做的不是我认为真正的单元测试,因为您同时测试多个单元。对于“真正的”单元测试,分别测试每个功能:

func TestMethodToBeTested(t *testing.T) {
    // Test the main function

func TestAuditSave(t *testing.T) {
    // Test the code executed in the goroutine

有了这种关注点分离,剩下要做的就是在执行时(有意义地)执行 goroutine TestMethodToBeTested。这可以通过多种方式完成:

  1. 如果saveAudit可以忽略 的行为,则忽略它——但也不要对其进行测试。
  2. 它可以被移动到接口或其他变量中,以便可以将存根放在它的位置。例子:

    func (x *X) MethodToBeTested(e Entity) {
        go x.saveAudit(e)
        // more code
    }
    

    这样,您可以saveAudit在测试中替换一个虚拟方法。

后者是我通常推荐的方法,即使在使用非 go-routines 时也是如此,因为它可以很容易地单独测试每个组件(即我称之为“真正的”单元测试)。

于 2017-11-24T08:26:21.040 回答
-1

@Flimzy 在这里写这个是因为注释不能包含好的代码示例。希望没关系。考虑以下内容(愚蠢,但为了举例):

type MyStruct struct {
    counter int
}

func (s *MyStruct) Add(item string) {
    s.ConfirmAdd()
}

func (s *MyStruct) ConfirmAdd() {
    s.counter++
}

测试ConfirmAdd()可能如下

func TestConfirmAdd(t *testing.T) {
    s := MyStruct{}
    s.ConfirmAdd()
    Assert(s.counter, Equals, 1)
}

为 编写测试时Add(),您会什么都不写吗?感觉不好不断言ConfirmAdd()被调用。

于 2018-10-22T14:20:42.900 回答