I am trying to repeat example from golang testing
package main
import (
"testing"
)
func TestSomeTest(t *testing.T) {}
func TestMain(m *testing.M) { // cleaning after each test}
I want TestMain function to run after every test.
Running command go test
And the compiler says
./testingM_test.go:9: undefined: testing.M
So how to clean after executing every test?
Check you go version output: this is for go 1.4+ only.
The testing package has a new facility to provide more control over running a set of tests. If the test code contains a function
func TestMain(m *testing.M)
that function will be called instead of running the tests directly.
The M struct contains methods to access and run the tests.
You can see that feature used here:
The introduction of TestMain() made it possible to run these migrations only once. The code now looks like this:
func TestSomeFeature(t *testing.T) {
defer models.TestDBManager.Reset()
// Do the tests
}
func TestMain(m *testing.M) {
models.TestDBManager.Enter()
// os.Exit() does not respect defer statements
ret := m.Run()
models.TestDBManager.Exit()
os.Exit(ret)
}
While each test must still clean up after itself, that only involves restoring the initial data, which is way faster than doing the schema migrations.
Related
I have several tests using testify/suite package and I execute them in parallel as follows
type IntegrationSuite struct {
suite.Suite
}
func TestIntegrationSuite(t *testing.T) {
suite.Run(t, &IntegrationSuite{})
}
func (is *IntegrationSuite) TestSomething() {
is.T().Log("\tIntegration Testing something")
for i := range myTestTable {
i := i
is.T().Run("Testing "+myTestTable[i].scenarioName, func(_ *testing.T) {
is.T().Parallel()
...
func (is *IntegrationSuite) TestSomethingElse() {
is.T().Log("\tIntegration Testing something else")
for i := range myOtherTestTable {
i := i
is.T().Run("Testing "+myOtherTestTable[i].scenarioName, func(_ *testing.T) {
is.T().Parallel()
...
})
However this panics with
panic: testing: t.Parallel called multiple times [recovered]
panic: testing: t.Parallel called multiple times
How can one leverage parallelism with the specific package?
You're calling t.Parallel() on the wrong instance of testing.T.
You're spinning up multiple subtests using:
is.T().Run("Testing "+myOtherTestTable[i].scenarioName, func(_ *testing.T) {...}
But instead of marking the subtest as parallel, you're marking the outer parent test as parallel for every subtest you start:
is.T().Parallel() // marks outer test as parallel
Instead, bind the subtest func's testing.T param to a variable and call Parallel on the subtest:
is.T().Run("Testing "+myOtherTestTable[i].scenarioName, func(t *testing.T) {
t.Parallel() // this is the subtest's T instance
// test code
})
This marks all subtests as parallel within the parent test. If you also want to mark the parent test as parallel with other tests in the package, call is.T().Parallel() once at the beginning of TestSomethingElse (not within the subtest)
For more details on parallelism of subtests, see: Control of Parallelism
Please note that the testify suite is currently not "thread safe" and therefore does not support concurrency. There are cases in which tests pass successfully even though they should fail.
Please find the current status as well the reported issues here: https://github.com/stretchr/testify/issues/187
I have a method that I am testing, and everything seems fine. However, when I run the tests in GoLand, I can see in the output that the tests "PASS" but the test runner says "no tests were run".
Here's the sample method in calculator.go
package calculator
import (
"fmt"
)
type Calculator struct {}
func New() Calculator {
return Calculator{}
}
func (s *Calculator) AddTwoNumbers(num_one, num_two int) int {
fmt.Printf("adding")
return num_one + num_two
}
Here's the test in calculator_test.go:
package calculator
import (
"fmt"
"testing"
)
func Test_Calculator_AddTwoNumbers(t *testing.T) {
// Arrange
calculator := New()
// Act
total := calculator.AddTwoNumbers(1,2)
// Assert
if total != 3 {
msg := fmt.Sprintf("total should have been %d but instead was %d", 3, total)
t.Error(msg)
}
}
What am I doing wrong?
Instead of fmt.Printf(... in AddTwoNumbers try either fmt.Println(... or fmt.Printf("foo\n')
The absence of the newline in the output of your AddTwoNumbers method is is causing the format of the test execution outputs to not have each test in a new line. The test runner is not being able to interpret that a test was run. Adding that newline, keeps a clean output.
I would suggest using to check the verbose output of the test run.
go test -v
-v
Verbose output: log all tests as they are run. Also print all
text from Log and Logf calls even if the test succeeds.
I have a test package in go which tests some stuff that depend on reading a configuration. I want to read that configuration once before running all tests so I'm trying to use TestMain(m *testing.M):
main.go:
package tests
import (
...
)
var logger = logging.MustGetLogger("tests")
func TestMain(m *testing.M) {
logger.Info("Initializing test suite")
viper.SetConfigName("config")
viper.AddConfigPath("..\\..\\")
err := viper.ReadInConfig()
if err == nil {
os.Exit(m.Run())
} else {
logger.Fatal("Could not read configuration")
}
}
And I have another file in the same directory (and package) with a test.
repository_test.go:
package tests
import (
...
)
func TestCreation(t *testing.T) {
aa := myModule.CreateRepository()
assert.NotNil(t, aa)
}
My problem is that the test fails because the configuration is not read from the file. When I try to debug the test in Gogland a breakpoint inside TestMain is not hit. When I run the tests from command line I don't see any printouts from TestMain.
Is there something special I should do to make it work? From what I read online I understood that if I define TestMain(m *testing.M) then it's going to run just once for the package and that's where I'm supposed to write any setup or teardown code.
TestMain is only executed in test files (suffix _test.go).
Move the function to the repository_test.go file to fix this.
Make sure that the run configuration is set to Package not File in Run | Edit Configurations... | Go Test | Name of your configuration and this should work. If it doesn't, please post the commands the IDE runs to execute the tests.
I have a production golang code and functional tests for it written not in golang. Functional tests run compiled binary. Very simplified version of my production code is here: main.go:
package main
import (
"fmt"
"math/rand"
"os"
"time"
)
func main() {
rand.Seed(time.Now().UTC().UnixNano())
for {
i := rand.Int()
fmt.Println(i)
if i%3 == 0 {
os.Exit(0)
}
if i%2 == 0 {
os.Exit(1)
}
time.Sleep(time.Second)
}
}
I want to build coverage profile for my functional tests. In order to do it I add main_test.go file with content:
package main
import (
"os"
"testing"
)
var exitCode int
func Test_main(t *testing.T) {
go main()
exitCode = <-exitCh
}
func TestMain(m *testing.M) {
m.Run()
// can exit because cover profile is already written
os.Exit(exitCode)
}
And modify main.go:
package main
import (
"flag"
"fmt"
"math/rand"
"os"
"runtime"
"time"
)
var exitCh chan int = make(chan int)
func main() {
rand.Seed(time.Now().UTC().UnixNano())
for {
i := rand.Int()
fmt.Println(i)
if i%3 == 0 {
exit(0)
}
if i%2 == 0 {
fmt.Println("status 1")
exit(1)
}
time.Sleep(time.Second)
}
}
func exit(code int) {
if flag.Lookup("test.coverprofile") != nil {
exitCh <- code
runtime.Goexit()
} else {
os.Exit(code)
}
}
Then I build coverage binary:
go test -c -coverpkg=. -o myProgram
Then my functional tests run this coverage binary, like this:
./myProgram -test.coverprofile=/tmp/profile
6507374435908599516
PASS
coverage: 64.3% of statements in .
And I build HTML output showing coverage:
$ go tool cover -html /tmp/profile -o /tmp/profile.html
$ open /tmp/profile.html
Problem
Method exit will never show 100% coverage because of condition if flag.Lookup("test.coverprofile") != nil. So line os.Exit(code) is kinda blind spot for my coverage results, although, in fact, functional tests go on this line and this line should be shown as green.
On the other hand, if I remove condition if flag.Lookup("test.coverprofile") != nil, the line os.Exit(code) will terminate my binary without building coverage profile.
How to rewrite exit() and maybe main_test.go to show coverage without blind spots?
The first solution that comes into mind is time.Sleep():
func exit(code int) {
exitCh <- code
time.Sleep(time.Second) // wait some time to let coverprofile be written
os.Exit(code)
}
}
But it's not very good because will cause production code slow down before exit.
As per our conversation in the comments our coverage profile will never include that line of code because it will never be executed.
With out seeing your full code it is hard to come up with a proper solutions however there a few things you can do to increase the coverage with out sacrificing too much.
func Main and TestMain
Standard practice for GOLANG is to avoid testing the main application entry point so most professionals extract as much functionality into other classes so they can be easily tested.
GOLANG testing framework allows you to test your application with out the main function but in it place you can use the TestMain func which can be used to test where the code needs to be run on the main thread. Below is a small exert from GOLANG Testing.
It is sometimes necessary for a test program to do extra setup or teardown before or after testing. It is also sometimes necessary for a test to control which code runs on the main thread. To support these and other cases, if a test file contains a function: func TestMain(m *testing.M)
Check GOLANG Testing for more information.
Working example
Below is an example (with 93.3% coverage that we will make it 100%) that tests all the functionality of your code. I made a few changes to your design because it did not lend itself very well for testing but the functionality still the same.
package main
dofunc.go
import (
"fmt"
"math/rand"
"time"
)
var seed int64 = time.Now().UTC().UnixNano()
func doFunc() int {
rand.Seed(seed)
var code int
for {
i := rand.Int()
fmt.Println(i)
if i%3 == 0 {
code = 0
break
}
if i%2 == 0 {
fmt.Println("status 1")
code = 1
break
}
time.Sleep(time.Second)
}
return code
}
dofunc_test.go
package main
import (
"testing"
"flag"
"os"
)
var exitCode int
func TestMain(m *testing.M) {
flag.Parse()
code := m.Run()
os.Exit(code)
}
func TestDoFuncErrorCodeZero(t *testing.T) {
seed = 2
if code:= doFunc(); code != 0 {
t.Fail()
}
}
func TestDoFuncErrorCodeOne(t *testing.T) {
seed = 3
if code:= doFunc(); code != 1 {
t.Fail()
}
}
main.go
package main
import "os"
func main() {
os.Exit(doFunc());
}
Running the tests
If we build our application with the cover profile.
$ go test -c -coverpkg=. -o example
And run it.
$ ./example -test.coverprofile=/tmp/profile
Running the tests
1543039099823358511
2444694468985893231
6640668014774057861
6019456696934794384
status 1
PASS
coverage: 93.3% of statements in .
So we see that we got 93% coverage we know that is because we don't have any test coverage for main to fix this we could write some tests for it (not a very good idea) since the code has os.Exit or we can refactor it so it is super simple with very little functionality we can exclude it from our tests.
To exclude the main.go file from the coverage reports we can use build tags by placing tag comment at the first line of the main.go file.
//+build !test
For more information about build flags check this link: http://dave.cheney.net/2013/10/12/how-to-use-conditional-compilation-with-the-go-build-tool
This will tell GOLANG that the file should be included in the build process where the tag build is present butNOT where the tag test is present.
See full code.
//+build !test
package main
import "os"
func main() {
os.Exit(doFunc());
}
We need need to build the the coverage application slightly different.
$ go test -c -coverpkg=. -o example -tags test
Running it would be the same.
$ ./example -test.coverprofile=/tmp/profile
We get the report below.
1543039099823358511
2444694468985893231
6640668014774057861
6019456696934794384
status 1
PASS
coverage: 100.0% of statements in .
We can now build the coverage html out.
$ go tool cover -html /tmp/profile -o /tmp/profile.html
In my pkglint project I have declared a package-visible variable:
var exit = os.Exit
In the code that sets up the test, I overwrite it with a test-specific function, and when tearing down a test, I reset it back to os.Exit.
This is a simple and pragmatic solution that works well for me, for at least a year of extensive testing. I get 100% branch coverage because there is no branch involved at all.
I'm trying to make this test suite pass through the command prompt (hello-world_test.go):
package helloworld
import "testing"
func TestHelloWorld(t *testing.T) {
output := sayHello()
if "Hello, World!" != output {
t.Fatalf("output: %s\nexpected: Hello,World!", output)
}
}
my code is as follows (helloworld.go):
package helloworld
import "fmt"
func sayHello() {
fmt.Println("Hello, World!")
}
and the error code through the command prompt is:
C:\Users\Troy\exercism\go\hello-world\go test -v
# _/C_/Users/Troy/exercism/go/hello-world
.\hello-world_test.go:6: sayHello() used as value
FAIL _/C_/Users/Troy/exercism/go/hello-world [build failed]
I have developed (for this kind of unit test) godbg.Out() and godbg.Err(), two io.Writer which, by default, are os.Stdout and os.Stderr.
That means I no longer use fmt.Println("Hello, World!").
Instead I do:
fmt.Fprintln(Out(), "Hello, World!")
That allows me, when I want to unit test that output (and not having the unit test own output filled with fmt.Println results), to change the default io.Writer with ones dedicated for that test, using the pdbg.SetBuffer() function.
That way, my test becomes:
Convey("Test custom buffer reset on global pdbg", func() {
SetBuffers(nil)
fmt.Fprint(Out(), "test content")
So(OutString(), ShouldEqual, `test content`)
fmt.Fprint(Err(), "err1 cerr")
So(ErrString(), ShouldEqual, `err1 cerr`)
ResetIOs()
fmt.Fprint(Out(), "test2 content2")
So(OutString(), ShouldEqual, `test2 content2`)
fmt.Fprint(Err(), "err2 cerr2")
So(ErrString(), ShouldEqual, `err2 cerr2`)
})
I can now test outputs done on stdout or stderr by replacing (during the unit test only) those writers with my own.
Notes:
I use the goconvey unit test framework here.
the design is still in progress. I might move those writer functions in their own package later on.
Apparently all i had to do to get the original test suite to pass was define the sayHello() function as a string, and return its value "Hello, World!. Also, i made lines 3 and 6 comments, as they do not affect the code directly:
package helloworld
//import "fmt"
func sayHello() string {
//fmt.Println("Hello, World!")
return "Hello, World!"
}
do the comments make the style of the code "tacky"?