Practical Go. Amit Saha

Читать онлайн книгу.

Practical Go - Amit Saha


Скачать книгу
in the -v flag when running go test also displays the test functions that are being run and the result.

      Next, consider the validateArgs() function defined as func validateArgs(c config) error. Based on the function specification, we will once again define a slice of test cases. However, instead of defining a named struct type, we will use an anonymous struct type instead as follows:

      // chap1/manual-parse/validate_args_test.go package main import ( "errors" "testing" ) func TestValidateArgs(t *testing.T) { // TODO Insert definition tests[] slice as above for _, tc := range tests { err := validateArgs(tc.c) if tc. err != nil && err.Error() != tc.err.Error() { t.Errorf("Expected error to be: %v, got: %v\n", tc.err, err) } if tc.err == nil && err != nil { t.Errorf("Expected nil error, got: %v\n", err) } } }

      In the same subdirectory as Listing 1.2, save Listing 1.3 to a file called validate_args_test.go. Now run the tests using the go test command. It will now run both the TestParseFlags and TestValidateArgs tests.

      tests := []struct { c config input string output string err error }{ { c: config{printUsage: true}, output: usageString, }, { c: config{numTimes: 5}, input: "", output: strings.Repeat("Your name please? Press the Enter key when done.\n", 1), err: errors.New("You didn't enter your name"), }, { c: config{numTimes: 5}, input: "Bill Bryson", output: "Your name please? Press the Enter key when done.\n" + strings.Repeat("Nice to meet you Bill Bryson\n", 5), }, }

      The field c is a config object representing the incoming configuration, input is the test input received by the program from the user interactively, output is the expected output, and err represents any error that is expected based on the test input and configuration.

      When you write a test for a program where you have to mimic an input from the user, this is how you can create a io.Reader from a string:

       r := strings.NewReader(tc.input)

      Thus, when the getName() function is called with io.Reader r as created above, calling scanner.Text() will return the string in tc.input .

      // chap1/manual-parse/run_cmd_test.go package main import ( "bytes" "errors" "strings" "testing" ) func TestRunCmd(t *testing.T) { // TODO Insert definition tests[] array as earlier byteBuf := new(bytes.Buffer) for _, tc := range tests { rd := strings.NewReader(tc.input) err := runCmd(rd, byteBuf, tc.c) if err != nil && tc.err == nil { t.Fatalf("Expected nil error, got: %v\n", err) } if tc.err != nil && err.Error() != tc.err.Error() { t.Fatalf("Expected error: %v, Got error: %v\n", tc.err.Error(), err.Error()) } gotMsg := byteBuf.String() if gotMsg != tc.output { t.Errorf("Expected stdout message to be: %v, Got: %v\n", tc.output, gotMsg) } byteBuf.Reset() } }

      We call the byteBuf.Reset() method so that the buffer is emptied before executing the next test case. Save Listing 1.4 into the same directory as Listings 1.1, 1.2, and 1.3. Name the file run_cmd_test.go and run all of the tests:

      You may be curious to find out what the test coverage looks like and visually see which parts of your code are not tested. To do so, run the following command first to create a coverage profile:

      $ go test -coverprofile cover.out PASS coverage: 71.7% of statements ok github.com/practicalgo/code/chap1/manual-parse 0.084s

      The above output tells us that our tests cover 71.7 percent of the code in main.go. To see which parts of the code are covered, run the following:

      $ go tool cover -html=cover.out

      This will open your default browser application and show the coverage of your code in an HTML file. Notably, you will see that the main() function is reported as uncovered since we didn't write a test for it. This leads nicely to Exercise 1.1.

       EXERCISE 1.1: TESTING THE MAIN() FUNCTION In this exercise, you will write a test for the main() function. However, unlike with other functions, you will need to test the exit status for different input arguments. To do so, your test should do the following:

      1 Build the application. You will find using the special TestMain() function useful here.

      2 Execute the application with different command-line arguments using the os.Exec() function. This will allow you to verify both the standard output and the exit code.

      Congratulations! You have written your first command-line application. You parsed the os.Args slice to allow the user to provide input to the application. You learned how to make use of the io . Reader and io . Writer interfaces to write code that is unit testable.

      Next, we will see how the standard library's flag package automatically takes care of the command-line argument parsing, validation of the type of data, and more.

      Before we dive into the flag package, let's refresh our memory of what a typical command-line application's user interface looks like. Let's consider a command-line application called application. Typically, it will have an interface similar to the following:

      The user interface has the following components:

       -h is a Boolean option usually specified to print a help text.

       -n <value> expects the user


Скачать книгу