Practical Go. Amit Saha

Чтение книги онлайн.

Читать онлайн книгу Practical Go - Amit Saha страница 11

Автор:
Жанр:
Серия:
Издательство:
Practical Go - Amit Saha

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

will be using the standard library's testing package exclusively for writing all of the tests, and we will use Go test to drive all of the test executions. We have also used the excellent support provided by libraries such as net/http/httptest to test HTTP clients and servers. Similar support is provided by gRPC libraries. In the last chapter, we will use a third-party package, https://github.com/testcontainers/testcontainers-go, to create local testing environments using Docker Desktop.

      In some of the tests, especially when writing command-line applications, we have adopted the style of “Table Driven Tests,” as described at https://github.com/golang/go/wiki/TableDrivenTests, when writing the tests.

      In this introduction to the book, you installed the software necessary to build the various applications to be used in the rest of the book. Then I introduced some of the conventions and assumptions made throughout the remainder of the book. Finally, I described the key language features with which you will need to be familiar to make the best use of the material in the book.

      Great! You are now ready to start your journey with Chapter 1, where you will be learning how to build testable command-line applications.

      In this chapter, you will learn about the building blocks of writing command-line applications. You will use standard library packages to construct command-line interfaces, accept user input, and learn techniques to test your applications. Let's get started!

      All command-line applications essentially perform the following steps:

       Accept user input

       Perform some validation

       Use the input to perform some custom task

       Present the result to the user; that is, a success or a failure

      $ ./application 6 Your name please? Press the Enter key when done. Joe Cool Nice to meet you Joe Cool Nice to meet you Joe Cool Nice to meet you Joe Cool Nice to meet you Joe Cool Nice to meet you Joe Cool Nice to meet you Joe Cool

      First, let's look at the function asking a user to input their name:

      func getName(r io.Reader, w io.Writer) (string, error) { msg := "Your name please? Press the Enter key when done.\n" fmt.Fprintf(w, msg) scanner := bufio.NewScanner(r) scanner.Scan() if err := scanner.Err(); err != nil { return "", err } name := scanner.Text() if len(name) == 0 { return "", errors.New("You didn't enter your name") } return name, nil }

      The getName() function accepts two arguments. The first argument, r, is a variable whose value satisfies the Reader interface defined in the io package. An example of such a variable is Stdin, as defined in the os package. It represents the standard input for the program—usually the terminal session in which you are executing the program.

      The second argument, w, is a variable whose value satisfies the Writer interface, as defined in the io package. An example of such a variable is the Stdout variable, as defined in the os package. It represents the standard output for the application—usually the terminal session in which you are executing the program.

      You may be wondering why we do not refer to the Stdin and Stdout variables from the os package directly. The reason is that doing so will make our function very unfriendly when we want to write unit tests for it. We will not be able to specify a customized input to the application, nor will we be able to verify the application's output. Hence, we inject the writer and the reader into the function so that we have control over what the reader, r, and writer, w, values refer to.

      The getName() function returns two values: one of type string and the other of type error. If the user's input name was read successfully, the name is returned along with a nil error. However, if there was an error, an empty string and the error is returned.

      The next key function is parseArgs(). It takes as input a slice of strings and returns two values: one of type config and a second of type error :

      type config struct { numTimes int printUsage bool } func parseArgs(args []string) (config, error) { var numTimes int var err error c := config{} if len(args) != 1 { return c, errors.New("Invalid number of arguments") } if args[0] == "-h" || args[0] == "--help" { c.printUsage = true return c, nil } numTimes, err = strconv.Atoi(args[0]) if err != nil { return c, err } c.numTimes = numTimes return c, nil }

      Command-line arguments supplied to a program are available via the Args slice defined in the os package. The first element of the slice is the name of the program itself, and the slice os.Args[1:] contains the arguments that your program may care about. This is the slice of strings with which parseArgs() is called. The function first checks to see if the number of command-line arguments is not equal to 1, and if so, it returns an empty config object and an error using the following snippet:

      if len(args) != 1 { return c, errors.New("Invalid number of arguments") }

      If only one argument is specified, and it is -h or -help, the printUsage field is specified to true and the object, c, and a nil error are returned using the following snippet:

      if

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