wymli
wymli

Reputation: 1283

Is Test_xxx func safe to access shared data in golang?

I'm confused about the golang unit test.

I have 2 Test_xxx funcs , like Test_1 and Test_2.
In Test_1, i will change a global variable , can Test_2 see the change?

Furthermore, if i use monkey patch instead of changing global var, will the other Test_xxx func perceive the patching?
i.e. do i have the necessary to cancel the func substitution using defer when Test_xxx returns?

Upvotes: 2

Views: 2062

Answers (5)

Dmitry Harnitski
Dmitry Harnitski

Reputation: 6008

In Test_1, i will change a global variable , can Test_2 see the change?

It could be safe only under some specific conditions:

  1. You run your tests in a single Goroutine. You cannot use t.Parallel() in test.
  2. You can run your tests only once. Otherwise, you have to implement an additional teardown routine to reset the data into its original state after every tests run.
  3. You cannot change tests order in the file(s). Developers used to rely on functions order being not important. Dependency on order could be very confusing for someone that moves the test without changing its code.

Those are just a few examples from the top of my head. Breaking any of these conditions will break a test. That is why such tests called fragile.

Check if that could be avoided.

Often that requires code change and introducing new patterns like Dependency Injection. Making code testable is a good thing. You make it more modular and easier to maintain.

Upvotes: 1

Benny Siegert
Benny Siegert

Reputation: 99

Note that a future Go version may change the order in which the tests are run, e.g. by randomizing order. If your test depends on the fact that Test_1 runs before Test_2 and changes a global variable, it will break.

A good idiom for changing a global variable is something like:

func Test_1(t *testing.T) {
  oldVal := myGlobalVariable
  defer func() { myGlobalVariable = oldVal }
  // rest of the test
}

Upvotes: 1

Tinkerer
Tinkerer

Reputation: 1068

While this is possible, you might want to consider initializing the global from both tests to generate consistent behavior. When you run the test from the go command line you can choose to only run one test function (go test foo -test.run Test_1) and this will otherwise generate inconsistent results.

Accessing global variables is subject to all sorts of races (in some cases involving partial reads of the data as it is being overwritten elsewhere) returning nonsense/impossible values! Use some sort of sync.Mutex to protect from these races:

import (
    "sync"
)

var mu sync.Mutex
var global int

func readGlobal() int {
    mu.Lock()
    defer mu.Unock()
    return global
}

func writeGlobal(val int) {
    mu.Lock()
    defer mu.Unock()
    global = val
}

// your test functions

Upvotes: 1

jub0bs
jub0bs

Reputation: 66284

Is Test_xxx func safe to access shared data in golang?

The answer entirely depends on whether those test functions are allowed to run in parallel.

By default, go test calls the test functions for a given package sequentially. However, if

  • you call t.Parallel() within both test functions, and
  • both functions access (write/write or write/read) the same global variable without any synchronization between them,

you'll likely get a data race.


To fix ideas, consider this simple test file:

package main

import (
    "fmt"
    "testing"
)

var count = 0

func Test_1(t *testing.T) {
    t.Parallel()
    count++
    fmt.Println(count)
}

func Test_2(t *testing.T) {
    t.Parallel()
    count++
    fmt.Println(count)
}

If you run go test -race, the race detector will slap you on the wrist:

==================
WARNING: DATA RACE
--snip--
FAIL
exit status 1
FAIL    whatever    0.730s

This should convince you that you should be careful about handling global state in tests. The best thing to do is to avoid global state altogether, if you can. Alternatively, remember that care must be taken to synchronize access to global state as soon as you activate parallel test execution.

Upvotes: 6

kozmo
kozmo

Reputation: 4471

In Test_1, i will change a global variable , can Test_2 see the change?

Yes.

var global = 0

func Test_1(t *testing.T) {
    for i := 0; i < 1000; i++ {
        global++
    }
    fmt.Println(global)
}

func Test_2(t *testing.T) {
    for i := 0; i < 1000; i++ {
        global++
    }
    fmt.Println(global)
}

Out

=== RUN   Test_1
1000
--- PASS: Test_1 (0.00s)
=== RUN   Test_22
2000
--- PASS: Test_22 (0.00s)
PASS

do i have the necessary to cacel the func substition using defer when Test_xxx returns?

You can use Cleanup function to remove changes of global variable

func Test_1(t *testing.T) {
    t.Cleanup(func() {
        global = 0
    })
    for i := 0; i < 1000; i++ {
        global++
    }
    fmt.Println(global)
}

PLAYGROUND

Upvotes: 2

Related Questions