Live data from Hacker News

Ask HN: How to approach testing in Golang?

news.ycombinator.com

11–20 of 37 posts

Re: Ask HN: How to approach testing in Golang?

#12
Java and co can generate logic at runtime, go cannot so you have to manually write mocks, or "generate" them with a third party executable.

You cannot mock anything if you don't use go interfaces at first place, there is no inheritance in go so you can't swap types unless they are interfaces or convertible.

given an interface Fooer :

    type Fooer interface {
       Do(int)int 
    }
and a method to test

    func Accept(f Fooer){
       f.Do(10)
    }
it's easy to write a mock

    type FooImpl struct {
      T testing.T
    }

    func(f FooImpl)Do(i int)int{
      f.T.Helper()
      if i!=10{
         f.T.Fail("i=10 expected")
      }
      return 0
    }

    // the test
    func TestAccept(t testing.T){
       Accept(FooImpl{T:t})
    }

    
Again, your methods have to accept interfaces at first place or you can't mock anything. With Go you'll often have to step back and do manually the things you took for granted in Java.

Only go maintainers can fix the "issue", by allowing the method definition on ad hoc structs, which is impossible right now.

Re: Ask HN: How to approach testing in Golang?

#13

Also, consider what you are really testing with mocks. Your implementation, or your mock? You can test without mocks..,

A mock is a fake. Just so we are using the same terminology I'll define my terms:

Fake: a test implementation of something that stands in for production code to solve certain testing problems.

A fake can implement an API and provide simplified behaviour. A good example might be a piece of code that stands in for a hardware device driver that you use when you don't have access to the hardware. Another very common use of a fake is to fake out a UI layer. There are lots of other common places for pure fakes.

There are 2 special kinds of fakes:

- Stub: An implementation that returns canned data.

- Mock: An implementation that encapsulates an expectation.

The most common type of mock is a mock that encapsulates the expectation that a certain function is called.

Sometimes you don't want to write a pure fake because it's more than you need. Most of the time you can adequately test what you want without talking directly to some inconvenient real API with a stub.

There are many good reasons to use pure fakes or stubs. Like I mentioned, it might be because hardware is involved. Sometimes it's because you are using some SaaS that you don't want to really access in your tests. Very often it's because the real service is just too slow (I'll need another post to describe why this is super important, but even if you don't agree I hope you will see that it's not the only reason to use a fake or a stub).

As soon as you use a fake or a stub, though, you leave a hole in your tests -- you have no way of knowing that the production code uses the real service in the production code. This is when you need a mock. You can go without testing it, but mocking it in this circumstance is almost always a better solution.

Of course, there is a school of thought that reaches for mocks first. This school of thought is often referred to as the "London School" because it is very popular (and probably originated from) several very famous people who work in London. The GOOSE book, which describes outside-in programming is probably the best description of this school of thought. I've met quite a few of the people who are standard bearers for this school of thought and they are very talented developers. Again, I would need another post to describe why I don't think it's the best technique to reach for first, but it's an area where reasonable people can disagree.

Outside-in and promiscuous mocking has several advantages -- especially when you are less experienced. It provides a framework for reasoning about how to do design. Personally, I am not a big fan in general, but there are times I use it -- I just replace the mocks with non-faked tests after the fact.

So, while I agree that one should have the mindset to use mocks as a technique of last resort, it's still a valuable tool in your arsenal.

Re: Ask HN: How to approach testing in Golang?

#15
We make use of https://github.com/onsi/ginkgo and Gomega https://github.com/onsi/gomega for BDD testing with more expressive matchers. You might be familiar with some of these concepts from Cucumber/Jasmine/Mocha.

They get significant use in Cloud Foundry and in the Kubernetes e2e tests.

I appreciate a lot of people like the simplicity of the standard test library and if you're starting with Go development, it's not a bad idea to get used to that before exploring external dependencies.

Caveat: While not necessarily a "maintainer", I have been doing a bunch of maintenance on these projects the past few weeks.

Edit: Also we use counterfeiter https://github.com/maxbrunsfeld/counterfeiter for fakes.

Re: Ask HN: How to approach testing in Golang?

#17
post #16

The age of Docker means IMO you should avoid mocks. There's no reason not to have a full integration test against your real API especially because you can compile and run test binaries independently.

Not an apologist for unit tests, but I'll bite here: unit tests are still valuable when you have the capacity to run ful integration tests.

A unit test can run really fast, for one, it can cover future use cases that don't get covered in integration, it edge cases you don't expect to hit, but are still important to cover in case something happens.

I'm sure there are more, good reasons to write unit tests and integration tests, but those two easy pickings are probably the source of your down votes

Re: Ask HN: How to approach testing in Golang?

#18
post #4

Check out https://github.com/stretchr/testify for more convenient assertions, as well as mocking support for implementing interfaces within your tests. You cannot do ad-hoc mocking of arbitrary functions in Go. If you want to write component-level tests that mock out other components, then you need an interface boundry between those components. Or better yet, you just write tests at the network level, spinning up moc…

I find vanilla Go testing (if res != exp { t.Fatal(...) }) much more versatile than testify. Testify did not cover some of my mocking use cases, and response on the repo was tepid, so I ditched it and was happier (this is a user POV, please don't come bashing me on not contributing to open source, etc.) For mocks, I use GoMocks ( https://github.com/golang/mock ), again a "standard" lib of go. It has Expect syntax whe…

Not a good example (if res != exp { t.Fatal}) is almost entirely require.Equal in testify (the message is logged that you specify, and the test is immediately stopped).

https://github.com/stretchr/testify/blob/master/require/requ...

I get a lot of the rationale behind the standard way ethos. I think this is just a bad example of it. Tests and lack of good OOB patterns for passing up error information being two of my primary issues with the "Go" way (and it only intensifies over time), but that's just my feelings and I get I'm in the minority (though Cheney seems to have a lot of the same issues I have with the standard error package https://dave.cheney.net/2016/04/27/dont-just-check-errors-ha...)

Re: Ask HN: How to approach testing in Golang?

#20

Also, consider what you are really testing with mocks. Your implementation, or your mock? You can test without mocks..,

A mock is a fake. Just so we are using the same terminology I'll define my terms: Fake: a test implementation of something that stands in for production code to solve certain testing problems. A fake can implement an API and provide simplified behaviour. A good example might be a piece of code that stands in for a hardware device driver that you use when you don't have access to the hardware. Another very common use…

>Outside-in and promiscuous mocking has several advantages -- especially when you are less experienced. It provides a framework for reasoning about how to do design.

I find that any additional couplings to your code makes the couplings you do have more painful to work with.

Some people think that by adding this kind of 'tight coupling' - low level mocks / unit tests - you feel that pain earlier on in the design process and that pain drives you to make a better, more loosely coupled code design.

Personally, I think that this approach is rather like forcing kids to smoke cigarettes every day to show them how much of a filthy dirty habit it is.

Post reply on HN