【问题标题】:Testing recover from panic测试从恐慌中恢复
【发布时间】:2015-10-20 18:29:07
【问题描述】:

我想测试一个构造函数,但是如果没有提供一些数据我需要恐慌,我该如何从测试中的恐慌中恢复?

目前我已经在我的 TestNew 函数中添加了一个带有恢复的延迟,但是如果我的地图中的一个元素的 URL 为空,则不会检查其余部分。

t.go

package testing

type test {
  url string
}

func New(ops map[string]string) *test {
  if ops["url"] == "" {
    panic("Url missing")
  }
  var t = new(test)
  t.url = ops["url"]
  return t
}

t_test.go

package testing

type testTest map[string]string
var testingTest = []testTest {
  testTest {
    "url": "test",
  },
  testTest{
    "url": "",
  },
}

func NewTest(t *testing.T) {
  defer func() {
    recover()
  }()

  for _, e := range testingTest {
    url := New(e)
    url.hasUrl(t, e["url"])
  }
}

func (s *test) hasUrl(t *testing.T, u string) {
  if s.url != u {
    t.Errorf("Expected %s to be equal with %s", s.url, u)
  }
}

【问题讨论】:

  • 你能发布你的延期恢复吗?这当然是你这样做的......我没有看到你的代码的猜测是你在你的恢复中返回并且删除返回语句会给你预期的结果。此外,在您提供回答问题所需的信息之前,我建议您阅读此内容; blog.golang.org/defer-panic-and-recover
  • @evanmcdonnal 现在看看,谢谢
  • 谢谢,我试图解释为什么你的代码现在没有按预期工作:D

标签: go


【解决方案1】:

我会说为依赖于恐慌/恢复的库设计 API 不是正确的方法。 Go 有错误模式,所以如果 New 方法无法测试,它可以返回状态。

package testing

type test {
  url string
}

func New(ops map[string]string) (*test, bool) {
  if ops["url"] == "" {
    return nil, false
  }
  var t = new(test)
  t.url = ops["url"]
  return t, true
}

然后

for _, e := range testingTest {
  url, ok := New(e)
  if ok {
    url.hasUrl(t, e["url"])
  }
}

如果您坚持使用panic,那么您可以将调用包装到函数中并在其中恢复。但是你仍然需要向调用者提供状态。

package main

import "fmt"

func test(e int) {
    if e == 2 {
        panic("panic!")
    }
}

func main() {
    for _, e := range []int{1, 2, 3} {
        func() {
            defer func() { recover() }()
            test(e)
            fmt.Println("testing", e)
        }()
    }
}

【讨论】:

    【解决方案2】:

    您的实施存在一些小问题。一方面,您甚至无法确定自己已经恢复,因为您正在调用该方法但忽略了返回值。所以对于初学者来说,让我们转换它;

    func NewTest(t *testing.T) {
      defer func() {
        recover()
      }()
    
      for _, e := range testingTest {
        url := New(e)
        url.hasUrl(t, e["url"])
      }
    }
    

    到这个;

    func NewTest(t *testing.T) {
      defer func() {
        if r := recover(); r != nil {
                fmt.Println("Recovered in NewTest", r)
            }
      }()
    
      for _, e := range testingTest {
        url := New(e)
        url.hasUrl(t, e["url"])
      }
    }
    

    现在另一个问题...您使用 defer 错误。您在NewTest 的顶部延迟,这意味着当它被调用时,您即将退出新测试。相反,您希望它出现在恐慌的方法中。现在恢复并继续迭代为时已晚。当您恢复它时,它位于调用 NewTest 的地方。所以应该这样做;

    func (s *test) hasUrl(t *testing.T, u string) {
      defer func() {
            if r := recover(); r != nil {
                    fmt.Println("Recovered in NewTest", r)
                }
          }()
      if s.url != u {
        t.Errorf("Expected %s to be equal with %s", s.url, u)
      }
    }
    

    我做了一个简单的示例问题来证明这一点。将延迟/恢复移动到 NewTest 中,您会发现它只打印一次,在当前形式下它打印 10 次,因为当我恢复时,我仍在循环中。 https://play.golang.org/p/ZA1Ijvsimz

    编辑:抱歉,我的示例有点误导,因为当我将您的代码部分复制到操场上时,我将恐慌/恢复位移动到 hasUrl。在你的情况下,它实际上是这样的;

    func New(ops map[string]string) *test {
      defer func() {
           if r := recover(); r != nil {
                 fmt.Println("Recovered while panicing in New")
           }
      }
      if ops["url"] == "" {
        panic("Url missing")
      }
      var t = new(test)
      t.url = ops["url"]
      return t
    }
    

    当然,这个例子是非常人为的,并没有完整的意义。如果您明确感到恐慌,我会说调用范围应该是使用恢复的那个,如果您正在调用一个恐慌的库,那么您应该是使用恢复的那个。

    【讨论】:

    • 让我恢复到方法中,并没有真正起作用
    • @alex 但为什么它不起作用?你是说你的设计不允许吗?这就是恐慌/恢复的工作原理......
    • 通过仅在方法中进行恢复,我无法恢复(我收到紧急错误并且我的测试停止)。但是通过使用@AlexAtNet 解决方案可以正常工作。
    • @alex 哦,是的,对不起,我的例子有点误导,因为我把恢复放在了另一种方法中(这与这里无关)。按照我上面所说的,恢复实际上会在New 中,但是像 AlexAtNet 那样将循环的内部封装在一个闭包中可能在你设计的例子中更合理(在您明确恐慌的方法)。在这种情况下,将恢复放入hasUrl 没有任何作用,因为恐慌是由 new 导致的,并且执行永远不会到达那里。我将编辑一个例子,说明 new 的主体是什么。
    【解决方案3】:

    我对问题中的措辞感到困惑,但我认为这与我的担忧相同。您已经定义了一个函数,当违反先决条件时会发生恐慌,而不是替代方案(返回错误,使用默认值,什么都不做......);就像为数组做出的合理选择一样:

    var a [1]int
    a[1] = 0 // panic
    

    有时您想编写一个测试来证明违反先决条件会导致恐慌并且不会被掩盖。以前的答案还不够完整,所以这是我要做的:

    func GreatBigFunction(url string) {
        if url == "" {
            panic("panic!")
        }
    }
    
    func main() {
        var tests = []struct {
            url       string
            completes bool
        }{
            {"a", true},
            {"", false},
            {"b", true},
            {"c", false}, // wrong expectation
            {"", true},   // wrong expectation
        }
        for _, test := range tests {
            fmt.Printf("testing that \"%s\" %s\n", test.url, map[bool]string{false: "panics", true: "completes"}[test.completes])
            func() {
                if !test.completes {
                    defer func() {
                        p := recover()
                        if p == nil {
                            fmt.Println("t.Fail: should have panicked")
                        }
                    }()
                }
                GreatBigFunction(test.url)
            }()
        }
    
        fmt.Println("Bye")
    }
    

    如果测试用例表明函数不应该发生恐慌,则最后一个测试用例检查正常的恐慌处理是否占上风。

    相同的代码是on Playground。也可以ignore the return value of recover,但仍然可靠地报告错过的panic

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-07
      • 1970-01-01
      • 2017-05-07
      • 2018-05-28
      • 1970-01-01
      • 2020-01-28
      相关资源
      最近更新 更多