我想测试一个构造函数,但是如果没有提供某些数据我需要恐慌,我怎样才能从测试中的恐慌中恢复过来?
目前我已经在我的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)
}
}
答案 0 :(得分:3)
我想说,为依赖恐慌/恢复的库设计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)
}()
}
}
答案 1 :(得分: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"])
}
}
现在另一个问题......你正在使用推迟错误。你推迟NewTest
的顶部,这意味着当它被调用时,你将要退出新的测试。相反,你想要它在panicing的方法。现在恢复并继续迭代已经太晚了。当你在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
}
当然,这个例子非常人为,并没有完整的记录。如果你明确地慌乱,我会说调用范围应该是使用recover的,如果你正在调用一个恐慌的库,那么你应该是使用recover的那个。
答案 2 :(得分:0)
我对问题中的词语感到困惑,但我认为它始于我所拥有的同样的关注。你已经定义了一个在违反前提条件时恐慌的函数,而不是一个替代方法(返回错误,使用默认值,什么都不做......);就像为数组做出的合理选择一样:
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
。