5 simple tips and tricks for writing unit tests in #golang

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

Test-driven development is a great way to keep the quality of your code high, while protecting yourself from regression and proving to yourself and others that your code does what it is supposed to.

Here are five tips and tricks that can improve your tests.

1. Put your tests in a different package

Go insists that files in the same folder belong to the same package, that is except for `_test.go` files. Moving your test code out of the package allows you to write tests as though you were a real user of the package. You cannot fiddle around with the internals, instead you focus on the exposed interface and are always thinking about any noise that you might be adding to your API.

This frees you up to change the internals however you like without having to adjust your test code.

2. Internal tests go in a different file

If you do need to unit test some internals, create another file with `_internal_test.go` as the suffix. Internal tests will necessarily be more brittle than your interface tests — but they’re a great way to ensure internal components are behaving, and are especially useful if you do test-driven development.

3. Run all tests on save

Go builds and runs very quickly, so there’s little to no reason why you shouldn’t run your entire test suite every time you hit save.

While you’re at it, why not run go vet, golint and goimports at the same time?

In Sublime Text, this can be acheived by installing GoSublime and hitting `Cmd+.,5` before adding the following configuration items:

“on_save”: [{
“cmd”: “gs9o_run_many”, “args”: {
“commands”:[
[“clear”],
[“sh”, “if [ -f onsave.sh ]; then ./onsave.sh; else gofmt -s -w ./ && go build . errors && go test -i && go test && go vet && golint ; fi”]
],
“focus_view”: false
}
}],
“fmt_cmd”: [“goimports”]

The above script first checks to see if the project has an `onsave.sh` file, which it will run instead. This allows you to easily turn off the auto-test feature for packages where it is not appropriate.

4. Write table driven tests

Anonymous structs and composite literals allow us to write very clear and simple table tests without relying on any external package.

The following code allows us to setup a range of tests for an as-yet unwritten `Fib` function:

var fibTests = []struct {
n int // input
expected int // expected result
}{
{1, 1},
{2, 1},
{3, 2},
{4, 3},
{5, 5},
{6, 8},
{7, 13},
}

Then our test function just ranges over the slice, calling the `Fib` method for each `n`, before asserting that the results are correct:

func TestFib(t *testing.T) {
for _, tt := range fibTests {
actual := Fib(tt.n)
if actual != tt.expected {
t.Errorf("Fib(%d): expected %d, actual %d", tt.n, tt.expected, actual)
}
}
}

See if you can write the `Fib` function yourself to make the tests pass or you can get the answer from Dave Chaney.

5. Mock things using Go code

If you need to mock something that your code relies on in order to properly test it, chances are it is a good candidate for an interface. Even if you’re relying on an external package that you cannot change, your code can still take an interface that the external types will satisfy.

Some frameworks and packages do exist that attempt to solve mocking for you — but the trouble is, it creates something to learn for anybody who might want to contribute to your project. Sticking to native Go code, wherever possible, is always preferable.

Let’s say we’re importing this external package:

package mailman
import “net/mail”
type MailMan struct{}
func (m *MailMan) Send(subject, body string, to ...*mail.Address) {
// some code
}
func New() *MailMan {
return &MailMan{}
}

If the code we’re testing takes a `MailMan` object, the only way our test code can call it is by providing an actual `MailMan` instance.

func SendWelcomeEmail(m *MailMan, to ...*mail.Address) {...}

This means that whenever we run our tests, a real email could be sent. Imagine if we’ve implemented the on save feature from above. We’d quickly annoy our test users or run up big service bills.

An alternative is to add this simple interface to your code:

type EmailSender interface{
Send(subject, body string, to ...*mail.Address)
}

Of course, the `MailMan` already satisfies this interface since we took the `Send` method signature from him in the first place — so we can still pass in `MailMan` objects as before.

But now we can write a test email sender:

type testEmailSender struct{
lastSubject string
lastBody string
lastTo []*mail.Address
}
// make sure it satisfies the interface
var _ package.EmailSender = (*testEmailSender)(nil)
func (t *testEmailSender) Send(subject, body string, to ...*mail.Address) {
t.lastSubject = subject
t.lastBody = body
t.lastTo = to
}

Now we can update our `SendWelcomeEmail` function to take the interface, rather than the concrete type:

func SendWelcomeEmail(m EmailSender, to ...*mail.Address) {...}

In our test code, we can send in our fake sender instead and make assertions on the fields after calling the target function:

func TestSendWelcomeEmail(t *testing.T) {
sender := &testEmailSender{}
SendWelcomeEmail(sender, to1, to2)
if sender.lastSubject != "Welcome" {
t.Error("Subject line was wrong")
}
if sender.To[0] != to1 && sender.To[1] != to2 {
t.Error("Wrong recipients")
}
}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.