Learning Go – Day 7

Featured image by Egon Elbre

  • Counting words or lines from stdin.
    • os.Stdin implements the io.Reader inteface.
    • bufio.Scanner can be used to iterate over words (ScanWords), lines (ScanLines), runes [aka Unicode characters] (ScanRunes) or bytes (ScanBytes).
import (
	"bufio"
	"io"
	"os"
)

func count(r io.Reader, countLines bool) int {
	scanner := bufio.NewScanner(r)

	if !countLines {
		scanner.Split(bufio.ScanWords)
	}

	wc := 0

	for scanner.Scan() {
		wc++
	}

	return wc
}

// Count words
count(os.Stdin, false)
// Count lines (\n) [except the trailing one]
count(os.Stdin, true)
  • Playing well with others the Unix way. Meaning write error messages to stderr (and not stdout) and return non zero exit codes.
if err := someOperation(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(42)
}
  • The arguments passed to a go application can be accessed with os.Args (which is declared as var Args []string in the os package). Remember item 0 is the path of the application and thus you can use Args[1:] to return the slice of only the passed arguments.
  • Visual Studio Code can easilly navigate to the source code for any standard library by just Cmd+Click on the name.
  • You can determine the platform the app is running on by querying things from the runtime package. For example runtime.GOOS == "windows".

Unit-testing

Generally all .go files in the same directory has to belong to the same Go package except for unit-tests that are allowed to belong to a different package. This helps ensure that your package only exports the types and functions/methods as you intended and thus the unit-test can only import what you exported.

Convention is to name unit-test files with the _test.go suffix and also for the package name.

Example: main.go will be unit-tested with main_test.go and that has a package main_test as the first line.

  • Tests can be run with more verbosity.
$ go test -v
=== RUN   TestOne
--- PASS: TestOne (0.00s)
=== RUN   TestSomething
--- PASS: TestSomething (0.00s)
=== RUN   TestTwo
--- PASS: TestTwo (0.00s)
PASS
ok      todo    0.097s

$ go test -v
=== RUN   TestOne
--- FAIL: TestOne (0.00s)
...
  • Visual Studio Code makes unit-testing really nice and even does code coverage with highlighting the code that was covered.

  • Tests can be run with code coverage and a specified timeout. This is what VS Code uses.
$ go test -timeout 30s -coverprofile=/path/to/codecoverage packagename
  • To run an individual test. Note that you actually specify a regex of which tests to run. In this case a function matching “TestAdd” in the package “todo”.
$ go test -timeout 30s -run ^TestAdd$ todo
  • Example of a unit-test function. Note that unit-tests are declared with a prefix “Test” and the argument is (t *testing.T).
package todo_test

import (
	"testing"
	"todo"
)

func TestAdd(t *testing.T) {
	// Do something you want to test
	...

	if result != expected {
		t.Errorf("Expected %q, got %q\n", expected, result)
	}
}
  • To see an awesome way of doing an integration test of your CLI app, by which the unit-test compiles the app, runs and test the app. See the code for “interacting/todo/cmd/main_test.go” from this book’s source code.
  • You can place resource files to be used by your tests in a special directory called testdata. The Go tooling will ignore these files.