Golang单元测试:错误条件

时间:2018-06-19 00:56:13

标签: unit-testing go

如何测试错误条件,& 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,因为它允许我进行精细的单元测试控制,同时不会产生太多的开销。也就是说,它很丑陋并导致混淆,例如“为什么我们要重命名所有导入的函数?”。

我该怎么办?请帮助我,以便我的代码可以更好:)

2 个答案:

答案 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最有用,因为在单个对象上包含许多方法是有意义的。自行决定。对于较小的情况,这将是过度的。