如何测试错误条件,& Golang中的其他意外代码流?
假设我的代码如下:
800/9 = 88.88
import crypto
func A(args) error {
x, err := crypto.B()
if err != nil {
return err
}
return nil
}
是一些功能。我相信我测试此失败场景的唯一方法是为我的测试更改B
的值(模拟它以便返回错误)。我尝试过的事情:
1)在测试之前对函数进行monkeypatch,然后取消。 这是一个可怕的想法。造成各种各样奇怪的问题 测试正在进行中。
2)将B
作为参数传递给B
。这一切都很好,但也很好
意味着我必须更改A
的定义,然后更新
每次实施改变时都会使用它。此外,
A
可能正在使用许多导入的函数并将它们全部放入
A
的类型定义似乎很丑陋而不是惯用语。
3)为我的测试更改A
的值,然后将其更改回来。
B
我一直在使用方法#3,因为它允许我进行精细的单元测试控制,同时不会产生太多的开销。也就是说,它很丑陋并导致混淆,例如“为什么我们要重命名所有导入的函数?”。
我该怎么办?请帮助我,以便我的代码可以更好:)
答案 0 :(得分:2)
和你一样,我从来没有见过这个问题的解决方案让我完全满意。
关于您的示例3,请记住您可以defer
重置cryptoB。这与模拟函数的良好命名相结合,可以清楚地表明您要完成的任务。这种方法显然仍然存在代码风格问题,在文件开头两行都列出了所有引用。
func TestSomething(t *testing.T) {
cryptoB = mockedFunc
defer func() {
cryptoB = crypto.B
}
// Testing goes on here
}
选项4
另一种方法(我赞成)是将您导出的函数转换为CryptoA结构的方法。该结构将存储它所需的任何依赖关系和状态。像这样:
type CryptoA struct {
cryptoB func() error
}
func (a *CryptoA) CryptoA() error {
return a.cryptoB()
}
func NewCryptoA() *CryptoA {
return &CryptoA{
cryptoB: func() error {
return nil
},
}
}
和嘲弄非常相似:
func TestSomething(t *testing.T) {
a := NewCryptoA()
a.cryptoB = mockedFunc
// Testing goes on here
}
使用这种方法,您的API会因为调用a := NewCryptoA()
而需要额外的步骤而丢失一些,并且您仍然必须为所有依赖项命名,但是通过使API的状态特定于每个依赖项来获得收益客户端。
也许您的API存在缺陷,并且您在某处意外泄漏数据,或者您有一些您不期望的状态修改。如果为每个调用者创建一个新的CryptoA,那么泄漏的数据量或者具有损坏状态的客户端数量可能是有限的,因此影响不那么严重/可滥用。我很明显这会对你的代码库有什么影响,但希望你能知道这是怎么回事。
此外,如果您想让用户能够指定自己的哈希算法,您可以在内部交换它,因为它是私有的,您可以确信该功能符合您的API标准。再一次,我显然是在吐痰。
我会撇开答案,看看是否有一种我不知道的惯用方法。
答案 1 :(得分:2)
我首选的方法是将A
作为方法,并将依赖项存储在receiver对象中。例如:
import crypto;
type CryptoIface interface {
B() (string, error)
}
type standardCrypto struct {}
var _ CryptoIface = &standardCrypto{}
func (c *standardCrypto) B() (string, error) {
return crypto.B()
}
func main() {
crypto = &standardCrypto{}
err = A(crypto, ...)
// stuff
}
func A(crypto CryptoIface, args ...string) error {
result, err := crypto.B()
if err != nil {
return err
}
// do something with result
return nil
}
然后,对于您的测试,您可以轻松创建CryptoIface
的模拟版本:
type mockCrypto struct {
Bfunc func(args ...string) error
}
func (c *mockCrypto) B(args ...string) error {
return c.Bfunc(args...)
}
func TestA(t *testing.T) {
c := &mockCrypto{
Bfunc: func(_ ...string) error {
return errors.New("test error")
}
}
err := A(c)
if err.String() != "test error" {
t.Errorf("Unexpected error: %s", err)
}
}
这种方法通常对大型项目或API最有用,因为在单个对象上包含许多方法是有意义的。自行决定。对于较小的情况,这将是过度的。