ChatGPT解决这个技术问题 Extra ChatGPT

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 */ }
}

0
030

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.


TestMain is once in a package, so it's not that useful. I find subtests are better for more complex purposes.
How are you supposed to pass context from the setup function to the tests without using global variables ? For example if mySetupFunction() creates a temporary directory to perform testing in (with a unique, random name), how do the tests know the name of the directory ? There must be a place to set this context ??
It seems that this is official way to handle before and after hooks for tests, see golang.org/pkg/testing/#hdr-Main for official documentation
@InancGumus lstat $GOROOT/subtests: no such file or directory
please note that ' code := m.Run() ' is the one that runs other TestFunctions !
m
miltonb

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.


I know you're answering your own question so this probably satisfies your own use case, but this isn't equivalent to the NUnit example you've included in your question.
Fair enough. What you've shown in this answer is somewhat closer to using NUnit's [TestFixtureSetUp] attribute instead.
it does not include tear down part
This is not a good solution if your test file is in the same package with main function.
Did you make a mistake? It says that the init() function will be run before the init() function, which I would imagine is not possible in the first place, but I suspect you meant to say before any of the test functions?
K
Kare Nuorteva

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.


Now that is a real simple but effective trick. Great use of Go syntax.
Yeah, but it increases nestedness (kind of pyramid of doom in javascript). And, tests do not run automatically by the suite as in the outer tests.
other problem is tearDown func can not get data of each case, and do the clean up thing base on test result
m
mdwhatcott

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:

Simply call your SetUp function from each test where it is needed. Use an extension to Go's testing framework that implements xUnit paradigms and concepts. Three strong options come to mind: gocheck testify gunit

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.


b
baijum

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.


That's great when testing trivial things like a flag parser, or an algorithm that churns on numbers. But it doesn't really help when trying to test diverse pieces of functionality that all require similar boilerplate code. I suppose I could define my test functions in an array and iterate over those, but then it's not really table-driven so much as a simple loop that should really just be built into the testing framework itself (in the form of a proper test suite with setup/teardown functions)
H
HairOfTheDog

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
}

j
jknair0

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


h
houqp

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.


S
Saurabh

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----")
    })
}