【问题标题】:How to validate that a method is called in a separate go routine如何验证是否在单独的 go 例程中调用了方法
【发布时间】:2017-11-24 05:36:51
【问题描述】:

在为 go 中的方法编写单元测试时,我被一个问题难住了。一、被测代码sn-p:

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 例程中。我该如何测试?

【问题讨论】:

    标签: unit-testing go testify


    【解决方案1】:

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

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

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

    更新:附加了错误的测试。固定的。

    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")
    }
    

    【讨论】:

    • 您在v1v2 上存在数据竞争:变量在单独的goroutine 中更新,这些goroutine 运行测试服务器实例的处理程序,然后在另一个goroutine 中读取。竞争检测器没有捕捉到这种情况的事实是因为httptest.NewServer() 创建的实例上的Close() 方法恰好执行了同步,并且竞争检测器只关心发生在后续事件之间的any 同步。从不同的 goroutine 完成对同一个变量的读/写或写/读访问。
    • 构建这样一个测试的(语义上)正确的方法是创建一个容量为 2 的通道(因为您有两个服务器)并让它们写入该通道而不是更新变量。对这些服务器的请求完成后,您必须排空通道并检查结果。当然,您可以使用两个通道或以其他方式构建代码(例如使用互斥锁)。
    • @kostix 感谢您的关注!抱歉,我附加了另一个测试,其中比赛条件并不重要。请找到正确的。主要区别:它使用WaitGroup 等待两个函数完成。
    【解决方案2】:

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

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

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

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

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

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

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

    【讨论】:

    • 如果被测单元的需求是“节省审计”,即使是在单独的goroutine中的一个函数完成,也不能忽略,对吧?您是否有建议,例如验证某个值是通过通道发送的?这对我来说听起来很难嘲笑。
    • @minitauros:您的“要求”符合我在开头段落中的解释:这不是 unit 测试,而是集成测试。与调用它的函数分开测试你的“审计”函数(你在 goroutine 中运行的那个)。这是进行真正的单元测试的唯一方法。
    【解决方案3】:

    @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() 被调用感觉很糟糕。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-05-26
      • 1970-01-01
      • 2012-12-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-12
      相关资源
      最近更新 更多