maintest

package module
v0.0.0-...-c7e1837 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 12, 2023 License: MIT Imports: 11 Imported by: 0

README

About

❗ This package requires Go 1.20 or higher

Testing your main package has always been challenging with Go, but no longer.

With Go 1.20 there has been recent improvements that make running your executables with the standard testing framework possible.

This package maintest makes it even easier to test your executables end-to-end while still getting all the nice features of built-in Golang testing framework.

Usage

See example/ directory for full source code of the below example

Below is a simple executable called add that adds two numbers: add 2 3 -> 5

main.go

package main

import (
	"flag"
	"fmt"
	"os"
	"strconv"
)

func main() {
	flag.Parse()
	a, _ := strconv.Atoi(flag.Arg(0))
	b, _ := strconv.Atoi(flag.Arg(1))
	fmt.Printf("%d\n", a+b)
	os.Exit(0)
}

With maintest we can easily test this by just running our executable directly and asserting on the output:

main_test.go

package main

import (
	"os"
	"testing"

	"github.com/masp/maintest"
)

var exe *maintest.Exe

func TestMain(m *testing.M) {
	// Build this package as a test executable called add
	exe, err = maintest.Build("add")
	if err != nil {
		log.Fatal(err)
	}
	
	rc := m.Run()

	// Cleanup and print code coverage
	if err := exe.Finish(); err != nil {
		log.Fatal(err)
	}
	os.Exit(rc)
}

func TestAdd4(t *testing.T) {
	out, _ := exe.Command("1", "3").CombinedOutput()
	result := string(out[0])
	if result != "4" {
		t.Errorf("got %s, expected 4", result)
	}
}

maintest will compile the current package once at the start, and then makes it invocable with exe.Command. Every run will automatically capture coverage and store it at coverprofile and gocoverdir.

> go test -coverprofile=coverage.txt
> go tool cover -func=coverage.txt                                       
github.com/masp/maintest/example/main.go:10:    main            100.0%
total:                                          (statements)    100.0%
go test will report the coverage as 0% on output, but all other tools will report the correct coverage from maintest (including go tool cover). This is because go test does its own calculations, which can't be overriden.

Debugging Failing Tests

If a test fails, trying to debug the test like normal will only show the test fixture or what runs the main executable -- not your code that's failing!

To debug the actual executable, you can use the maintest.DebugFlag.

var debugFlag = maintest.DebugFlag("example.debug")

func TestMain(m *testing.M) {
	flag.Parse() // flag.Parse must be called before Build
	exe, _ = maintest.Build("add", *debugFlag)
	...
}

func TestAdd4(t *testing.T) {
	cmd, _ := exe.Command("1", "3")
	out, _ := cmd.CombinedOutput() // cmd will block on start and wait for the you to attach
	...
}

Run the test

go test -test.run TestAdd4 -example.debug 25514

and the debugger will listen on localhost:25514. You can connect with dlv with dlv connect localhost:25514, set breakpoints on main.go and continue like a normal debugging session.

If you are using VS code, you can connect by adding this to your launch.json configuration:

{
	"name": "Attach Integration Test",
	"type": "go",
	"request": "attach",
	"debugAdapter": "dlv-dap",
	"mode": "remote",
	"port": 25514
}

Documentation

Overview

Package maintest makes it easy to create end-to-end tests for executables with support for code coverage, debugging, and more.

maintest must be used within the `main` package within the `TestMain` function.

Index

Constants

This section is empty.

Variables

View Source
var (
	DebugLog *log.Logger = log.New(io.Discard, "", 0)
)

Functions

This section is empty.

Types

type Exe

type Exe struct {
	Path        string // Path is the full path to the executable file useful for passing to exec.Command
	CoverageDir string // Directory where coverage results will temporarily go after every invocation
	// contains filtered or unexported fields
}

Exe is an instrumented executable that can be easily run in integration tests.

To execute, use Command(args...).

After all commands have finished, Finish must be called for the coverage data to be written.

func Build

func Build(exeName string, opts ...Option) (*Exe, error)

Build builds the named executable using the local module (meant to be run in TestMain). The test executable will be called exeName and placed in a temporary directory. The executable can be run

func (*Exe) Command

func (b *Exe) Command(args ...string) *exec.Cmd

Command returns Cmd ready to be run that will invoke the built test executable and output the results to the shared coverage directory.

func (*Exe) Finish

func (b *Exe) Finish() error

Finish will merge all the coverage from the previous executions and write the output to -coverprofile. This func uses the `go tool covdata` command with textfmt for backwards compatibility with existing tools, e.g. `go tool covdata textfmt -i b.CoverageDir -o '-coverprofile'`. -coverprofile is parsed from os.Args.

type Option

type Option func(e *Exe)

func Debug

func Debug(dlvArgs ...string) Option

Debug builds the executable with debug symbols enabled and optionally starts dlv for debugging.

This is useful if you want to attach and debug your executable in your integration tests while they run. Using dlv exec (see https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv_exec.md) allows a test to be "paused" and attached to interactively, which makes it possible to debug the actual executable rather than the test fixture which is much less interesting. dlvArgs will be passed to the run like `dlv exec [dlvArgs...] [exeAndFlags...]`

func DebugFlag

func DebugFlag(name string) *Option

DebugFlag will add a flag to the command line that can be specified to enable debug mode

func Package

func Package(pkg string) Option

Package will cause `go build` to run on a different package than the current directory. The package must have an executable (`package main`).

func WriteCoverage

func WriteCoverage(path string) Option

WriteCoverage redirects the coverage from -coverprofile to override path.

Directories

Path Synopsis
add

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL