seams

package module
v0.0.0-...-5dd378c Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2019 License: MIT Imports: 8 Imported by: 0

README

seams

Go static analyzer that reports untestable function/method calls.

As a command

First, install seams command via go get.

$ go get github.com/ichiban/seam/cmd/seams

Then, run seams command at your package directory.

$ seams ./...
/Users/ichiban/src/swuntestable/main.go:8:17: untestable function/method call: time.Parse
/Users/ichiban/src/swuntestable/main.go:11:7: untestable function/method call: (time.Duration).Hours
/Users/ichiban/src/swuntestable/main.go:11:7: untestable function/method call: (time.Time).Sub
/Users/ichiban/src/swuntestable/main.go:11:16: untestable function/method call: time.Now
/Users/ichiban/src/swuntestable/main.go:12:2: untestable function/method call: fmt.Printf

As an analysis.Analyzer

First, install the package via go get.

$ go get github.com/ichiban/seams

Then, include cyclomatic.Analyzer in your checker.

package main

import (
	"golang.org/x/tools/go/analysis"
	"golang.org/x/tools/go/analysis/multichecker"
	"golang.org/x/tools/go/analysis/passes/nilfunc"
	"golang.org/x/tools/go/analysis/passes/printf"
	"golang.org/x/tools/go/analysis/passes/shift"

	"github.com/ichiban/seams"
)

func main() {
	multichecker.Main(
		// other analyzers of your choice
		nilfunc.Analyzer,
		printf.Analyzer,
		shift.Analyzer,

		seams.Analyzer,
	)
}

What do you mean by untestable?

This static analyzer reports function/method calls that are of:

  • functions/methods defined in other packages,
  • not builtin functions,
  • not in tests, nor
  • in generated files

because those function/method calls can't be replaced by test doubles in tests.

For example, time.Now() isn't testable because we can't change its behaviour for our tests. Though, timeNow() where var timeNow = time.Now is testable because we can change the behaviour in tests.

untestable example
package main

import (
        "fmt"
        "time"
)

var date = must(time.Parse(time.RFC3339, "2019-12-20T00:00:00+09:00"))

func main() {
        d := date.Sub(time.Now()).Hours() / 24
        fmt.Printf("%d days until Star Wars: The Rise of Skywalker\n", int(d))
}

func must(t time.Time, err error) time.Time {
        if err != nil {
                panic(err)
        }
        return t
}
testable example
package main

import (
    "fmt"
    "time"
)

var (
    timeParse         = time.Parse
    timeDurationHours = time.Duration.Hours
    timeTimeSub       = time.Time.Sub
    timeNow           = time.Now
    fmtPrintf         = fmt.Printf
)

var date = must(timeParse(time.RFC3339, "2019-12-20T00:00:00+09:00"))

func main() {
    d := timeDurationHours(timeTimeSub(date, timeNow())) / 24
    fmtPrintf("%d days until Star Wars: The Rise of Skywalker\n", int(d))
}

func must(t time.Time, err error) time.Time {
    if err != nil {
        panic(err)
    }
    return t
}
package main

import (
    "testing"
    "time"

    "github.com/stretchr/testify/assert"
)

func Test_main(t *testing.T) {
    t.Run("on the day", func(t *testing.T) {
        assert := assert.New(t)

        now, err := time.Parse(time.RFC3339, "2019-12-20T12:00:00+09:00")
        assert.NoError(err)

        n := timeNow
        defer func() { timeNow = n }()
        timeNow = func() time.Time {
            return now
        }

        called := false
        p := fmtPrintf
        defer func() { fmtPrintf = p }()
        fmtPrintf = func(format string, a ...interface{}) (int, error) {
            assert.Equal("%d days until Star Wars: The Rise of Skywalker\n", format)
            assert.Equal([]interface{}{0}, a)

            called = true
            return 0, nil
        }

        main()

        assert.True(called)
    })
}

License

This project is licensed under the MIT License - see the LICENSE.md file for details

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Analyzer = &analysis.Analyzer{
	Name:             "seams",
	Doc:              `find function/method calls which cannot be replaced by test doubles.`,
	Requires:         []*analysis.Analyzer{inspect.Analyzer},
	Run:              run,
	RunDespiteErrors: true,
}

Functions

This section is empty.

Types

This section is empty.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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