preface

Unit tests are tests that verify the correctness of a module, function, or class. We use it to verify that the code behaves as expected, and if the unit test fails, either the code is buggy, or the test condition input is incorrect, or a fix is needed to make the unit test pass. One of the greatest benefits of unit testing is to ensure that a program module behaves as it was designed to, and that the code will still behave correctly if it is modified/refactored in the future.

Go’s support for unit tests is already fairly friendly, and the native Go Test library is dedicated to writing unit tests. There are conventions to follow when writing unit tests using Go Test. For example, all test code needs to be added to the test file at the end of _test.go so that it is excluded when building with Go Build. In addition, each Test function must import the Testing package. The name of the Test function must start with Test and the suffix after Test must start with an uppercase, so the declaration of the Test function should look like this:

  func TestSin(t *testing.T) { / *... * / }
  func TestCos(t *testing.T) { / *... * / }
  func TestLog(t *testing.T) { / *... * / }
Copy the code

The _test.go test file is usually stored in the same package as the file to be tested, such as the following code to be tested (word. Go file in the word package directory) :

package word
// Determine if a string s is a palindrome string
func IsPalindrome(s string) bool {
    for i := range s {
        ifs[i] ! = s[len(s)- 1-i] {
            return false}}return true
}
Copy the code

So when writing the test, we also create a word_test.go file in the word package directory, the unit test code is as follows:

package word

import "testing"

func TestPalindrome(t *testing.T) {
        // "detartrated" is a palindrome string, so IsPalindrome("detartrated") should return true
        // If false is returned, there is an existing problem and t.ror is required for error reporting
	if! IsPalindrome("detartrated") {
	    t.Error(`IsPalindrome("detartrated") = false`)}if! IsPalindrome("kayak") {
	    t.Error(`IsPalindrome("kayak") = false`)}}func TestNonPalindrome(t *testing.T) {
    if IsPalindrome("palindrome") {
        t.Error(`IsPalindrome("palindrome") = true`)}}Copy the code

If the test case succeeds, print:

=== RUN TestPalindrome -- PASS: TestPalindrome (0.00s) PASSCopy the code

Use case execution failure is:

=== RUN TestNonPalindrome -- FAIL: TestNonPalindrome (0.00s) nba_test.go:53: IsPalindrome("kayak") = true FAILCopy the code

The Go Test framework without assertions

As you can see from the unit test code, we do not use an assertion to determine whether the IsPalindrome method is working correctly. Instead, we use an if statement: if the result is wrong, we report the error through the t.ror method. This is because Go doesn’t support assertions in its native form, and the official website explains why they did it, simply because they don’t want developers to slack off on error handling.

Go doesn’t provide assertions. They are undeniably convenient, but our experience has been that programmers use them as a crutch to avoid thinking about proper error handling and reporting… (see the golang.org/doc/faq#ass…).

But for unit testing, having no assertions is really inconvenient. This is especially difficult for those of you who used to use Junit for unit testing in your Previous Java development days. In addition, the use of if statements to determine the output of a method, and the corresponding error handling, leads to a large number of if branches in the unit test code, which affects the readability of the code.

So, is there a better solution?

To solve this problem, we can introduce a third party assertion library.

Use Potency to assert

Of the choice of third-party assertion library, Potency and ease of use is the best.

Testify:github.com/stretchr/te…

Now, we can use Potency to reconstruct the IsPalindrome unit test case above:

package word

import "testing"
import "github.com/stretchr/testify/assert"

func TestPalindrome(t *testing.T) {
    // Assert that the IsPalindrome method returns True
    assert.True(t, IsPalindrome("detartrated"))
    assert.True(t, IsPalindrome("kayak"))}func TestNonPalindrome(t *testing.T) {
    // Assert that the IsPalindrome method returns False
    assert.False(t, IsPalindrome("palindrome"))}Copy the code

If the test case succeeds, print:

=== RUN TestPalindrome -- PASS: TestPalindrome (0.00s) PASSCopy the code

Use case execution failure is:

=== RUN   TestNonPalindrome
    palindrome_test.go:23: 
        	Error Trace:	palindrome_test.go:23
        	Error:      	Should be false
        	Test:       	TestNonPalindrome
--- FAIL: TestNonPalindrome (0.00s)
Copy the code

As a result, the refactored test cases are more concise and readable. Also, in the case of assertion errors, the printed error information is relatively rich.

The Potency underlying implementation is also based on the Go test framework, in which each assert method takes testing.t as its first entry to use the basic error reporting capability of the Go Test framework. In addition, if you need to assert many times in a test method, passing the testing.t parameter each time is tedious, you can simplify it by implementing:

func TestSomething(t *testing.T) {
    // Create an assert instance by passing testing.t once
    assert := assert.New(t)

    // Assert that the two are equal
    assert.Equal(123.123)

    // Assert that the two are not equal
    assert.NotEqual(123.456)

    // Assert that an instance is nil
    assert.Nil(object)

    // Assert that object instances are not nil
    if assert.NotNil(object) {
        // When we know that object is not nil, we can safely access it
        // Further assert that the Value attribute of object is "Something"
        assert.Equal("Something", object.Value)
    }
}
Copy the code

All of us can testify to a variety of assert methods, including assert.true, assert.nil, assert.equal, and assert.direxists, all of which are used to assert the existence of a directory. Assert whether a method throws panic assert.panics; Asserts whether the string conforms to the specified regular expression assert.Regexp, and so on. All in all, Potency is absolutely everything you need to know about Go unit test assertions.

Can be found at godoc.org/github.com/…

conclusion

This article focuses on how to use the Potency library to introduce assertions to Go unit tests to make test case code simpler. Assert is just one of the capabilities that the Potency library provides, such as the Mock package, which provides the ability to “pile”. Those of you who have written Java unit tests will be familiar with the Mockito and PowerMock frameworks in Java that provide similar functionality. The mock functionality of the Potency library is not covered in this article because there are better mock libraries out there. In the next article, we’ll show you how to drive piles efficiently and elegantly in Go unit tests.