miltonb
miltonb

Reputation: 7385

How can I do test setup using the testing package in Go

How can I do overall test setup processing which sets the stage for all the tests when using the testing package?

As an example in Nunit there is a [SetUp] attribute.

[TestFixture]
public class SuccessTests
{
  [SetUp] public void Init()
  { /* Load test data */ }
}

Upvotes: 199

Views: 220136

Answers (11)

bhantol
bhantol

Reputation: 9616

Another simpler one

func setupTest(t *testing.T) {
  // do some setup
  // .
  // e.g. 
  t.Setenv("MyFlag", "foo")

  t.Cleanup(func() {
     // do whatever cleanup
     // no need to clean anything set via t.Setenv as it is cleaned by the golang testing
  })

}
func TestMe(t *testing.T) {
    setupTest(t);
    // back to your regular business here
}

Upvotes: 1

Makks129
Makks129

Reputation: 582

Here is the minimal test suit framework to run subtests with

  • BeforeAll, ran before all subtests in a test
  • BeforeEach, ran before each subtest
  • AfterEach, ran after each subtest
  • AfterAll, ran after all subtests in a test
package suit

import "testing"

func Of(subTests *SubTests) *SubTests {
    if subTests.AfterAll != nil {
        subTests.T.Cleanup(subTests.AfterAll)
    }
    return subTests
}

type SubTests struct {
    T          *testing.T
    BeforeEach func()
    AfterEach  func()
    AfterAll   func()
}

func (s *SubTests) TestIt(name string, f func(t *testing.T)) {
    if s.AfterEach != nil {
        defer s.AfterEach()
    }
    if s.BeforeEach != nil {
        s.BeforeEach()
    }
    s.T.Run(name, f)
}

Usage

func TestFoo(t *testing.T) {
    // BeforeAll setup goes here

    s := suit.Of(&suit.SubTests{
        T:          t,
        BeforeEach: func() { ... },
        AfterEach:  func() { ... },
        AfterAll:   func() { ... },
    })

    s.TestIt("returns true", func(t *testing.T) {
        assert.Equal(t, 1, 1)
    })
}

Upvotes: 0

shanmukh patel
shanmukh patel

Reputation: 471

With the following template, you can make a one line call in each TestMethod that does both setup and tear-down.

func setupTest() func() {
    // Setup code here

    // tear down later
    return func() {
        // tear-down code here
    }
}

func TestMethod(t *testing.T) {
    defer setupTest()()
    // asserts, ensures, requires... here
}

Upvotes: 37

Saurabh
Saurabh

Reputation: 6960

You can use the testing package for test setup - which will set the stage for all tests and teardown - which will cleanup the stage after tests have run.

The below calculates the area of a rectangle:

package main

import (
    "errors"
    "fmt"
)

func area(height float64, width float64) (float64, error) {
    if height == width {
        fmt.Printf("Rectangle with given dimension is a square. Area is: %f\n", height * width)
        return height * width, nil
    } else if height <= 0 || width <= 0 {
        return 0, errors.New("Both dimensions need to be positive")
    } else {
        fmt.Printf("Area is: %f\n", height * width)
        return height * width, nil
    }
}

func main() {
    var length float64  = 4.0
    var breadth float64 = 5.0
    area(length, breadth)
}

This is the implementation for test setup and teardown using TestMain as Salvador Dali's explains. (Note that since v1.15 the TestMain function is no longer required to call os.Exit [ref])

package main

import (
    "log"
    "testing"
)

var length float64
var breadth float64

func TestMain(m *testing.M) {
    setup()
    m.Run() 
    teardown()
}

func setup() {
    length = 2.0
    breadth = 3.0
    log.Println("\n-----Setup complete-----")
}

func teardown() {
    length = 0
    breadth = 0
    log.Println("\n----Teardown complete----")
}

func TestAreaOfRectangle(t *testing.T) {
    val, err := area(length, breadth)   
    want := 6.0

    if val != want && err != nil {
        t.Errorf("Got %f and %v; Want %f and %v", val, err, want, nil)
    }
}

And this is the implementation for test setup and teardown using sub-tests:

package main

import "testing"

func TestInvalidRectangle(t *testing.T) {
    // setup
    var length float64 = -2.0
    var breadth float64 = 3.0
    t.Log("\n-----Setup complete for invalid rectangle-----")

    // sub-tests
    t.Run("invalid dimensions return value", func(t *testing.T) {
        val, _ := area(length, breadth)
        area := 0.0

        if val != area {
            t.Errorf("Got %f; Want %f", val, area)
        }
    })

    t.Run("invalid dimensions message", func(t *testing.T) {
        _, err := area(length, breadth)
        want := "Both dimensions need to be positive"

        if err.Error() != want {
            t.Errorf("Got error: %v; Want error: %v", err.Error(), want)
        }
    })

    // teardown
    t.Cleanup(func(){
        length = 0
        breadth = 0
        t.Log("\n----Teardown complete for invalid rectangle----")
    })
}

func TestRectangleIsSquare(t *testing.T) {
    var length float64 = 3.0
    var breadth float64 = 3.0
    t.Log("\n-----Rectangle is square setup complete-----")

    t.Run("valid dimensions value and message", func(t *testing.T) {
        val, msg := area(length, breadth)
        area := 9.0
        if val != area && msg != nil {
            t.Errorf("Got %f and %v; Want %f and %v", val, msg, area, nil)
        }
    })

    t.Cleanup(func(){
        length = 0
        breadth = 0
        t.Log("\n----Rectangle is square teardown Complete----")
    })
}

Upvotes: 4

jknair0
jknair0

Reputation: 1234

In case someone is looking an alternative of @BeforeEach (which runs before each test in a test file) and @AfterEach (which runs after test in a test file), here's a helper snippet.

func CreateForEach(setUp func(), tearDown func()) func(func()) {
    return func(testFunc func()) {
        setUp()
        testFunc()
        tearDown()
    }
}

You can use it like below with help of TestMain

var RunTest = CreateForEach(setUp, tearDown)

func setUp() {
   // SETUP METHOD WHICH IS REQUIRED TO RUN FOR EACH TEST METHOD
   // your code here
}

func tearDown() {
  // TEAR DOWN METHOD WHICH IS REQUIRED TO RUN FOR EACH TEST METHOD
  // your code here
}

fun TestSample(t *testing.T) {
  RunTest(func() {
    // YOUR CODE HERE
  })
}

also you can check: go-beforeeach

Upvotes: 6

miltonb
miltonb

Reputation: 7385

This can be achieved by putting a init() function in the myfile_test.go file. This will be run before the init() function.

// myfile_test.go
package main

func init() {
     /* load test data */
}

The myfile_test.init() will be called before the package init() function.

Upvotes: 85

houqp
houqp

Reputation: 771

Shameless plug, I created https://github.com/houqp/gtest to help solve exactly this problem.

Here is a quick example:

import (
  "strings"
  "testing"
  "github.com/houqp/gtest"
)

type SampleTests struct{}

// Setup and Teardown are invoked per test group run
func (s *SampleTests) Setup(t *testing.T)      {}
func (s *SampleTests) Teardown(t *testing.T)   {}
// BeforeEach and AfterEach are invoked per test run
func (s *SampleTests) BeforeEach(t *testing.T) {}
func (s *SampleTests) AfterEach(t *testing.T)  {}

func (s *SampleTests) SubTestCompare(t *testing.T) {
  if 1 != 1 {
    t.FailNow()
  }
}

func (s *SampleTests) SubTestCheckPrefix(t *testing.T) {
  if !strings.HasPrefix("abc", "ab") {
    t.FailNow()
  }
}

func TestSampleTests(t *testing.T) {
  gtest.RunSubTests(t, &SampleTests{})
}

You can create as any test group you want within a package with each of them using a different set of setup/teardown routines.

Upvotes: 2

Salvador Dali
Salvador Dali

Reputation: 222929

Starting with Go 1.4 you can implement setup/teardown (no need to copy your functions before/after each test). The documentation is outlined here in the Main section:

TestMain runs in the main goroutine and can do whatever setup and teardown is necessary around a call to m.Run. It should then call os.Exit with the result of m.Run

It took me some time to figure out that this means that if a test contains a function func TestMain(m *testing.M) then this function will be called instead of running the test. And in this function I can define how the tests will run. For example I can implement global setup and teardown:

func TestMain(m *testing.M) {
    setup()
    code := m.Run() 
    shutdown()
    os.Exit(code)
}

A couple of other examples can be found here.

The TestMain feature added to Go’s testing framework in the latest release is a simple solution for several testing use cases. TestMain provides a global hook to perform setup and shutdown, control the testing environment, run different code in a child process, or check for resources leaked by test code. Most packages will not need a TestMain, but it is a welcome addition for those times when it is needed.

Upvotes: 303

James Henstridge
James Henstridge

Reputation: 43949

The Go testing framework doesn't have anything equivalent to NUnit's SetUp attribute (marking a function to be called before each test in the suite). There are a few options though:

  1. Simply call your SetUp function from each test where it is needed.

  2. Use an extension to Go's testing framework that implements xUnit paradigms and concepts. Three strong options come to mind:

Each of these libraries encourage you to organize your tests into suites/fixtures similar to other xUnit frameworks, and will call the setup methods on the suite/fixture type before each of the Test* methods.

Upvotes: 16

Kare Nuorteva
Kare Nuorteva

Reputation: 1366

Given a simple function to unit test:

package math

func Sum(a, b int) int {
    return a + b
}

You can test it with a setup function that returns teardown function. And after calling setup() you can make a deferred call to teardown().

package math

import "testing"

func setupTestCase(t *testing.T) func(t *testing.T) {
    t.Log("setup test case")
    return func(t *testing.T) {
        t.Log("teardown test case")
    }
}

func setupSubTest(t *testing.T) func(t *testing.T) {
    t.Log("setup sub test")
    return func(t *testing.T) {
        t.Log("teardown sub test")
    }
}

func TestAddition(t *testing.T) {
    cases := []struct {
        name     string
        a        int
        b        int
        expected int
    }{
        {"add", 2, 2, 4},
        {"minus", 0, -2, -2},
        {"zero", 0, 0, 0},
    }

    teardownTestCase := setupTestCase(t)
    defer teardownTestCase(t)

    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            teardownSubTest := setupSubTest(t)
            defer teardownSubTest(t)

            result := Sum(tc.a, tc.b)
            if result != tc.expected {
                t.Fatalf("expected sum %v, but got %v", tc.expected, result)
            }
        })
    }
}

Go testing tool will report the logging statements in the shell console:

% go test -v
=== RUN   TestAddition
=== RUN   TestAddition/add
=== RUN   TestAddition/minus
=== RUN   TestAddition/zero
--- PASS: TestAddition (0.00s)
    math_test.go:6: setup test case
    --- PASS: TestAddition/add (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    --- PASS: TestAddition/minus (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    --- PASS: TestAddition/zero (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    math_test.go:8: teardown test case
PASS
ok      github.com/kare/go-unit-test-setup-teardown 0.010s
% 

You can pass some additional parameters to setup/teardown with this approach.

Upvotes: 58

Paul Hankin
Paul Hankin

Reputation: 58369

Typically, tests in go aren't written in the same style as other languages. Often, there's relatively fewer test functions, but each contains a table-driven set of test cases. See this article written by one of the Go team.

With a table-driven test, you simply put any setup code before the loop that executes the individual test-cases specified in the table, and put any cleanup code afterwards.

If you still have shared setup code between test functions, you can extract the shared setup code into a function, and use a sync.Once if it's important that it's executed exactly once (or as another answer suggests, use init(), but this has the disadvantage that the setup will be done even if the test cases aren't run (perhaps because you've limited the test cases by using go test -run <regexp>.)

I'd say if you think you need shared setup between different tests that gets executed exactly once you should have a think if you really need it, and if a table-driven test wouldn't be better.

Upvotes: 12

Related Questions