当前位置:网站首页>Suggestions and skills for advanced version of go language test

Suggestions and skills for advanced version of go language test

2022-06-12 12:10:00 Just want to call Yoko

Before reading this article , You'd better already know how to write basic unit tests . This article contains 3 A little suggestion , as well as 7 A little trick .

Recommendation 1 , Don't use frames

Go The language itself already has a great testing framework , It allows you to use Go Write test code , There is no need to learn additional libraries or test engines . Help functions for assertions , You can look at this testing, Or this assert.go ?

Recommendation two , Use "_test" Package name

Instead of using the package name of the tested code directly , Use *_test The package name allows the test code to access only the exposed interfaces in the package . This makes you write tests from the perspective of package users , This allows you to think about whether the package interface is properly designed .

Recommendation three , Avoid global constant configuration items

Avoid using global constant configuration items , Because the test code cannot modify constants . Here are three examples for comparison :

// 1.  Not good. , The test code cannot modify it 
const port = 8080

// 2.  Better , The test code can modify it 
var port = 8080

// 3.  A better way , The test code can pass  struct  To configure  Port
const defaultPort = 8080
type AppConfig {
  Port int //  The constructor is initialized to  defaultPort
}

Skill 1 , Load test data

Go It provides very good support for loading test data from files . First ,Go Will be ignored at compile time testdata Catalog . then , When the test code runs ,Go The current directory will be used as the directory of the package . This allows you to use relative paths to access testdata Catalog . Look at examples :

func helperLoadBytes(t *testing.T, name string) []byte {
  path := filepath.Join("testdata", name) // relative path
  bytes, err := ioutil.ReadFile(path)
  if err != nil {
    t.Fatal(err)
  }
  return bytes
}

Tip two , Save the expected results of the test to .golden In file

Save the expected results of the test to .golden In file . And provide a flag To decide whether to update it . Using this technique can avoid hard coding the expected output in the test code . Look at examples :

var update = flag.Bool("update", false, "update .golden files")
func TestSomething(t *testing.T) {
  actual := doSomething()
  golden := filepath.Join(“testdata”, tc.Name+”.golden”)
  if *update {
    ioutil.WriteFile(golden, actual, 0644)
  }
  expected, _ := ioutil.ReadFile(golden)

  if !bytes.Equal(actual, expected) {
    // FAIL!
  }
}

Tip three , Initialization during test 、 Clean up the code

Sometimes the test code is complicated , Running test case You need to initialize the environment before , This may involve a lot of unrelated error checking , For example, test whether the file is loaded successfully , Test whether the data can be pressed json Format parsing and so on . This makes the test code not purely elegant .

To solve this problem , You can put irrelevant code into the help function . These functions never return error, But in *testing.T, When an error occurs, it is directly asserted that an error is reported .

alike , If the help function needs to clean up after the completion , The help function should return a function to clean up . Look at examples :

func testChdir(t *testing.T, dir string) func() {
  old, err := os.Getwd()
  if err != nil {
    t.Fatalf("err: %s", err)
  }
  if err := os.Chdir(dir); err != nil {
    t.Fatalf("err: %s", err)
  }
  return func() { //  Return cleanup function , It is called for external cleaning 
    if err := os.Chdir(old); err != nil {
       t.Fatalf("err: %s", err)
    }
  }
}
func TestThing(t *testing.T) {
  defer testChdir(t, "/other")()
  // ...
}

The above example contains another example about defer Using very cool techniques .defer testChdir(t, "/other")() Will execute first testChdir Code in , And in TestThing Execute at the end testChdir The code in the returned cleanup function .

Tip four , When relying on third-party executable programs

Sometimes test code relies on third-party executables , We can check whether the program exists by the following methods , If it exists, execute the test , If not, skip the test .

var testHasGit bool
func init() {
  if _, err := exec.LookPath("git"); err == nil {
    testHasGit = true
  }
}
func TestGitGetter(t *testing.T) {
  if !testHasGit {
    t.Log("git not found, skipping")
    t.Skip()
  }
  // ...
}

Tip five , The test contains os.Exit Code for

This method starts the child process , Avoid tests that include os.Exit Your code caused the test program to exit prematurely . Look at examples :

func CrashingGit() {
  os.Exit(1)
}
func TestFailingGit(t *testing.T) {
  if os.Getenv("BE_CRASHING_GIT") == "1" { //  The child process enters this logical branch 
    CrashingGit()
    return
  }
  //  By  go test  perform ,
  //  Set the environment variables , Start the subprocess to execute again  TestFailingGit
  cmd := exec.Command(os.Args[0], "-test.run=TestFailingGit")
  cmd.Env = append(os.Environ(), "BE_CRASHING_GIT=1")
  err := cmd.Run()
  if e, ok := err.(*exec.ExitError); ok && !e.Success() {
    return
  }
  t.Fatalf("Process ran with err %v, want os.Exit(1)", err)
}

The idea of the above example is , When Go The test framework runs TestFailingGit when , Start a subprocess (os.Args[0] That is generated Go The test program ). The subprocess runs the test program again , And only perform TestFailingGit( Through parameters -test.run=TestFailingGit Realization ), And set the environment variable BE_CRASHING_GIT=1, So the subprocess will execute CrashingGit().

Tip six , take mocks、helpers Put in testing.go In file

testing.go The file will be treated as a normal source file , Instead of testing code files . In this way, these can be used in other packages or test code of other packages mocks、helpers.

Tip seven , Handle time-consuming tests alone

When there are some time-consuming tests , Waiting for all the tests to finish is annoying . The solution is to put these time-consuming tests into _integration_test.go In file , And add compilation in the header of the file tag. Look at examples :

// +build integration

such Go These test code will not be run by default when testing .
If you want to run all the test code , You can do this :

go test -tags=integration

Here is my personal use alias A simple command to do , Can run the current directory and subdirectories except vendoer All tests outside the directory :

alias gtest="go test \$(go list ./… | grep -v /vendor/) -tags=integration"

This command can be used in conjunction with -v Parameters use :

 $ gtest
 …
 $ gtest -v
 …

Thank you for reading , Original English address :Go advanced testing tips & tricks (https://medium.com/@povilasve/go-advanced-tips-tricks-a872503ac859)

The author of this article : yoko
Link to this article : http://www.pengrl.com/p/32101/
Copyright notice : All articles in this blog except special statement , All adopt CC BY-NC-SA 3.0 license agreement . Reprint please indicate the source !

原网站

版权声明
本文为[Just want to call Yoko]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/163/202206121206218080.html