"Go Language Bible" Study Notes Chapter 11 Test

"Go Language Bible" Study Notes Chapter 11 Test


table of Contents

  1. go test
  2. Test function
  3. Test coverage
  4. Benchmarks
  5. Anatomy
  6. Example function

Note: To study the notes of "Go Language Bible", click to download the PDF , it is recommended to read the book.
Go language learning notes for beginners, copy the contents of the book, the big guys do not spray when you read them, and you will summarize them into your own reading notes when you are familiar with it.


  1. Maurice Wilkes, the designer of the first stored-program computer EDSAC, had an epiphany when he was climbing stairs in the laboratory in 1949. In Memoirs of a Computer Pioneer, he recalled: "Suddenly there was a feeling of enlightenment, and the good time of the rest of my life will be spent in finding program bugs." Surely most normal coders since then will sympathize with Wilkes's overly pessimistic thoughts, although perhaps not nobody is confused by his naive view of the difficulty of software development.
  2. The current program is much larger and more complex than the Wilkes era, and there are many techniques that allow the complexity of the software to be
    controlled. Two of these techniques have proven to be more effective in practice. The first is that the code needs to be
    reviewed before being officially deployed . The second is testing, which is the topic of discussion in this chapter.
  3. When we talk about testing, we generally mean automated testing, that is, we write some small programs to check that the code under test (product
    code) behaves as expected. These are usually carefully designed to perform certain functions or
    The processing of the boundary is verified through random input.
  4. Software testing is a huge area. The task of testing may have taken up part of the time of some programmers and all of the time of others. There are thousands of books or blog posts related to software testing technology. For every mainstream programming language, there will be a dozen software packages for testing, as well as a large number of testing-related theories, and each attracts a large number of technical pioneers and followers. These are enough to convince programmers who want to write effective tests to relearn a whole new set of skills.
  5. Go language testing technology is relatively low-level. It relies on a go test test command and a set of test functions written in accordance with the convention, the test command can run these test functions. Writing relatively lightweight pure test code is effective, and it can easily be extended to benchmark tests and sample documents.
  6. In practice, there is not much difference between writing test code and writing the program itself. Each function we write is also for each specific task. We must handle the boundary conditions carefully, think about the appropriate data structure, and infer what kind of result output the appropriate input should produce. The process of programming test code is similar to writing ordinary Go code; it does not require learning new symbols, rules, and tools.

1. go test

  1. The go test command is a driver program that follows a certain convention and organization of test code. In the package directory, all source files with the suffix _test.go are not part of the go build package, they are part of the go test test.
  2. In the *_test.go file, there are three types of functions: test functions, benchmark test functions, and sample functions. A test function is a function with Test as the function name prefix, which is used to test whether some logical behaviors of the program are correct; the go test command will call these test functions and report that the test result is PASS or FAIL. Benchmark test functions are functions prefixed with the function name Benchmark. They are used to measure the performance of some functions; the go test command will run the benchmark function multiple times to calculate an average execution time. The example function is a function with Example as the function name prefix. It provides an example document whose correctness is guaranteed by the compiler
    . We will discuss all the details of the test function in Section 11.2, the details of the benchmark function in Section 11.4, and then the details of the example function in Section 11.6.
  3. The go test command will traverse all the functions in the *_test.go file that meet the above naming rules, and then generate a temporary main package for calling the corresponding test function, then build and run, report the test results, and finally clean up the generated functions in the test Temporary Files

2. Test function

  1. Each test function must import the test package. The test function has the following signature:
    Insert picture description here
  2. The name of the test function must start with Test, and the optional suffix name must start with a capital letter:
    Insert picture description here
  3. The t parameter is used to report test failures and additional log information. Let us define an example package gopl.io/ch11/word1, in which there is only one function IsPalindrome to check whether a string is read from front to back and from back to front. (The following implementation repeats the test twice before and after whether a string is a palindrome; we will discuss this issue later.)
  4. gopl.io/ch11/word1
    Insert picture description here
  5. In the same directory, the word_test.go test file contains two test functions, TestPalindrome and TestNonPalindrome. Each one is to test whether IsPalindrome gives correct results, and use t.Error to report failure information:
    Insert picture description here
  6. If the go test command does not specify a package with parameters, the package corresponding to the current directory will be used by default (same as the go build command). We can build and run the test with the following commands.
    Insert picture description here
  7. The result is quite satisfactory. We ran this program but did not exit early because we have not encountered a bug report. However, a French user named "Noelle Eve Elleon" will complain that the IsPalindrome function does not recognize "été". Another complaint from a user in the central United States is that "A man, a plan, a canal: Panama." is not recognized. Executing special and small BUG reports provides us with new and more natural test cases.
    Insert picture description here
  8. In order to avoid entering a long string twice, we use the Errorf function that provides a formatting function similar to Printf to report the error result.
  9. After adding these two test cases, go test returns the test failure information
    Insert picture description here
  10. It is a good testing practice to write test cases first and observe that the test cases trigger the same description as the error reported by the user. Only in this way can we locate the problem we want to really solve.
  11. Another benefit of writing test cases first is that running tests is usually faster than manually describing report processing, which allows us to iterate quickly. If the test set has many slow-running tests, we can speed up the test by selecting only certain tests to run.
  12. The parameter -v can be used to print the name and running time of each test function:
    Insert picture description here
  13. The parameter -run corresponds to a regular expression. Only the test function whose test function name is correctly matched will be run by the go test test command:
    Insert picture description here
  14. Of course, once we have fixed the failed test cases, before we submit the code update, we should run all the test cases with the go test command without parameters to ensure that the failed test is fixed without introducing new problems.
  15. Our task now is to fix these errors. After a brief analysis, it is found that the reason for the first bug is that we used byte instead of rune sequence, so non-ASCII characters like é in "été" cannot be processed correctly. The second BUG is caused by not ignoring the case of spaces and letters.
  16. In response to the above two BUGs, we carefully rewrote the function:
  17. gopl.io/ch11/word2
    Insert picture description here
  18. At the same time, we also merged all the previous test data into a test table.
    Insert picture description here
  19. Now our new test has passed:
    Insert picture description here
  20. This kind of table-driven testing is very common in the Go language. It is easy for us to add new test data to the table, and the following test logic is not redundant, so we can have more energy to improve the error message.
  21. The output of the failed test does not include stack call information at the time t.Errorf is called. Unlike assert assertions in other programming languages ​​or testing frameworks, the t.Errorf call also did not cause a panic exception or stop the execution of the test. Even if the first data in the table causes the test to fail, the test data behind the table will still run the test, so we may learn more than one failure information in a test.
  22. If we really need to stop the test, perhaps because the initialization failed or an earlier error caused a subsequent error, we can use t.Fatal or t.Fatalf to stop the current test function. They must be called in the same goroutine as the test function.
  23. The general form of test failure information is "f(x) = y, want z", where f(x) explains the failed operation and the corresponding output, y is the actual running result, and z is the expected correct result. Just like the previous example of checking the palindrome string, the actual function is used for the f(x) part. If the display x is an important part of the table-driven test, because the same assertion may be executed multiple times for different table items. Avoid useless and redundant information. When testing a function similar to IsPalindrome that returns a Boolean type, you can ignore the z part without additional information. If x, y, or z is the length of y, just output a concise summary of the relevant part. The author of the test should try to help the programmer diagnose the cause of the test failure.

1. Random test

  1. Table-driven testing facilitates the construction of test cases based on carefully selected test data. Another test idea is random testing, which is to test the behavior of the exploration function by constructing a wider range of random inputs.
  2. So for a random input, how can we know the desired output result? There are two processing strategies. The first is to write another control function, using a simple and clear algorithm, although it is inefficient but the behavior is consistent with the function to be tested, and then check the output results of both for the same random input. The second is that the generated random input data follows a specific pattern, so that we can know the expected output pattern.
  3. The following example uses the second method: The randomPalindrome function is used to randomly generate a palindrome string.
    Insert picture description here
  4. Although random testing will have uncertainties, it is also very important. We can get enough information from the logs of failed tests. In our example, entering the p parameter of IsPalindrome will tell us the real data, but for the function will accept more complex inputs, you don’t need to save all the inputs, just simply record the random number seed in the log
    (like the above the way) . With these random number initialization seeds, we can easily modify the test code to reproduce the failed random test.
  5. By using the current time as the random seed, new random data will be explored every time the test command is run throughout the process. If you are using an automated test integration system that runs regularly, random testing is especially valuable

2. Test a command

  1. For the test package go test is a useful tool, but with a little effort we can also use it to test executable programs. If the name of a package is main, then an executable program will be generated when building, but the main package can be imported by the tester code as a package.
  2. Let's write a test for the echo program in section 2.3.2. We first split the program into two functions: the echo function does the real work, and the main function is used to process command line input parameters and possible errors returned by echo.
  3. gopl.io/ch11/echo
    Insert picture description here
  4. In the test, we can call the echo function with various parameters and flags, and then check whether its output is correct. We increase the parameters to reduce the echo function's dependence on global variables. We also added a global variable named out instead of using os.Stdout directly, so that the test code can modify out to a different object as needed for easy inspection. Below is the test code in the echo_test.go file:
    Insert picture description here
  5. It should be noted that the test code and the product code are in the same package. Although it is the main package, there is also a corresponding main entry function, but the main package is just an ordinary package imported by the TestEcho test function during testing, and the main function is not exported, but ignored.
  6. By putting the tests in the table, we can easily add new test cases. Let me see how the failures are by adding the following test cases:
    Insert picture description here
  7. The output of go test is as follows:
    Insert picture description here
  8. The error message describes the attempted operation (using Go-like syntax), the actual result and the expected result. With this error message, you can easily locate the cause of the error before examining the code.
  9. It should be noted that log.Fatal or os.Exit is not called in the test code, because calling these functions will cause the program to exit early; the privilege of calling these functions should be placed in the main function. If something unexpected causes a panic exception in the function, the test driver should try to catch the exception with recover, and then treat the current test as a failure. If it is an expected error, such as illegal user input, file not found, or improper configuration file, it should be handled by returning a non-empty error. Fortunately (the accident above is just an episode), our echo example is relatively simple and there is no need to return a non-empty error.

3. White box testing

  1. One method of test classification is based on whether the tester needs to understand the internal working principle of the test object. Black box testing only needs the documents and API behaviors exposed by the test package, and the internal implementation is transparent to the test code. On the contrary, white box testing has access to the internal functions and data structure of the package, so it can do tests that cannot be achieved by ordinary clients. For example, a white box test can detect the invariant data type after each operation. (White box test is just a traditional name, in fact, it is more accurate to call clear box test.)
  2. The black box and white box test methods are complementary. Black box testing is generally more robust, and the test code rarely needs to be updated as the software is implemented. They can help testers understand the real customer needs, and can also help find some shortcomings in API design. In contrast, white box testing can provide more test coverage for some tricky internal implementations.
  3. We have seen two examples of tests. The TestIsPalindrome test only uses the exported IsPalindrome function, so this is a black box test. The TestEcho test calls the internal echo function and updates the internal out package-level variables. Both of these are not exported, so this is a white box test.
  4. When we were preparing for the TestEcho test, we modified the echo function to use the package-level out variable as the output object, so the test code can replace the standard output with another implementation, which makes it easier to compare the data output by echo. Using similar techniques, we can replace other parts of the product code with a pseudo object that is easy to test. The advantage of using pseudo objects is that we can easily configure, predict, be more reliable, and be easier to observe. At the same time, some undesirable side effects can be avoided, such as updating the production database or credit card consumption behavior.
  5. The following code demonstrates the quota detection logic in the web service that provides users with network storage. A reminder email will be sent when the user has used more than 90% of the storage quota.
  6. gopl.io/ch11/storage1
    Insert picture description here
  7. We want to test this code, but we don't want to send real emails. Therefore, we put the mail processing logic into a private notifyUser function.
  8. gopl.io/ch11/storage2
    Insert picture description here
  9. Now we can replace the real email sending function with the fake email sending function in the test. It simply records the user to be notified and the content of the email.
    Insert picture description here
  10. There is a problem here: When the test function returns, CheckQuota will not work properly, because notifyUsers still uses the pseudo email function of the test function (there is always this risk when updating the global object). We must modify the test code to restore the original state of notifyUsers so that subsequent tests have no effect. We must ensure that all execution paths can be restored, including test failures or panic exceptions. In this case, we recommend using the defer statement to delay the execution of the recovery code.
    Insert picture description here
  11. This processing mode can be used to temporarily save and restore all global variables, including command-line flag parameters, debugging options and optimization parameters; install and remove hook functions that cause production code to generate some debugging information; and some induce production code to enter Some important status changes, such as timeouts, errors, and even some deliberately created concurrent behaviors.
  12. It is safe to use global variables in this way, because the go test command does not execute multiple tests concurrently.

to be continued…

Guess you like

Origin blog.csdn.net/weixin_41910694/article/details/106399460