dongyou5098 2018-06-19 00:56
浏览 96

Golang单元测试:错误条件

How do I test error conditions, & other unexpected code flow, in Golang?

Suppose I have code as follows:

import crypto
func A(args) error {
    x, err := crypto.B()
    if err != nil {
        return err
    }
    return nil
}

B is some function. I believe that the only way for me to test this failure scenario is to change the value of B for my test (mock it so it returns an error). Things I have tried:

1) monkeypatch the function just before the test and unpatch afterwards. This was a horrible idea. Caused all sorts of weird issues as the tests were running.

2) Pass B as an argument to A. This is all well and good but it also means that I have to change the definition for A and then update every use of it every time the implementation changes. Furthermore, A may be using many imported functions and putting them all in the type definition for A just seems ugly and not idiomatic.

3) Change the value of B for my test, then change it back.

import crypto.B
cryptoB = crypto.B
func A(args) error {
    x, err := cryptoB()
    if err != nil {
        return err
    }
    ...
 }

 func Test_A(t *testing.T) {
     oldB := cryptoB
     cryptoB = mockB
     // run test
     cryptoB = oldB
     ...
 }

I've been using method #3 as it allows me fine grained unit testing control while not creating too much overhead. That said, it's ugly and causes confusion e.g. "Why are we renaming all of the imported functions?".

What should I be doing? Please help me so my codes can be more better :)

  • 写回答

2条回答 默认 最新

  • dongzhe3171 2018-06-19 02:00
    关注

    Like you, I've never seen a solution to this problem that's totally satisfied me.

    In regards to your example 3, remember that you can defer the reset of the cryptoB. This, combined with good naming of the mock functions, would make it clear what you are trying to accomplish. There are obviously still code-style issues with this approach, with having all of your references listed line by line, twice, at the start of your file.

    func TestSomething(t *testing.T) {
        cryptoB = mockedFunc
        defer func() {
            cryptoB = crypto.B
        }
        // Testing goes on here
    }
    

    Option 4

    The other approach (which I would favor) would be to turn the functions you export into methods of a CryptoA struct. The struct would store whatever dependencies and state it requires. Something like this:

    type CryptoA struct {
        cryptoB func() error
    }
    func (a *CryptoA) CryptoA() error {
        return a.cryptoB()
    }
    func NewCryptoA() *CryptoA {
        return &CryptoA{
            cryptoB: func() error {
                return nil
            },
        }
    }
    

    and mocking would be very similar:

    func TestSomething(t *testing.T) {
        a := NewCryptoA()
        a.cryptoB = mockedFunc
    
        // Testing goes on here
    }
    

    With this approach you lose some by your API having an extra step for invocation, a := NewCryptoA(), and you still have to name all of your dependencies, but you make gains by having the state of your API specific to each client.

    Maybe there is a flaw in your API, and you leak data somewhere unexpected, or there is some state modifications that you don't expect. If you create a new CryptoA for each caller, then maybe the amount of data you leak, or the number of clients with a corrupted state, is limited, and therefore the impact less severe/abusable. I'm obviously spitballing at how this applies to your codebase, but hopefully you can get the idea of how this is a win.

    Also, if you want to give the ability for users to specify their own hash algorithm, you can swap it out internally, and since it's private you maintain confidence that the function is up to the standards of your API. Again, I'm obviously spitballing.

    I'll be skimming the answers to see if there's an idiomatic way to do this that I'm unaware of.

    评论

报告相同问题?

悬赏问题

  • ¥100 set_link_state
  • ¥15 虚幻5 UE美术毛发渲染
  • ¥15 CVRP 图论 物流运输优化
  • ¥15 Tableau online 嵌入ppt失败
  • ¥100 支付宝网页转账系统不识别账号
  • ¥15 基于单片机的靶位控制系统
  • ¥15 真我手机蓝牙传输进度消息被关闭了,怎么打开?(关键词-消息通知)
  • ¥15 装 pytorch 的时候出了好多问题,遇到这种情况怎么处理?
  • ¥20 IOS游览器某宝手机网页版自动立即购买JavaScript脚本
  • ¥15 手机接入宽带网线,如何释放宽带全部速度