@Caust1c
17:12:07 $ go test -v . -run 'TestUserRegression'
=== RUN TestUserRegression
=== RUN TestUserRegression/Created_user_should_be_same_as_sent
--- FAIL: TestUserRegression (0.00s)
--- FAIL: TestUserRegression/Created_user_should_be_same_as_sent (0.00s)
user_test.go:7: Users are not equal.
Expected: Arthur Dent
Actual: Arthur Dent
FAIL
FAIL example.com/tron/thegrid/users 0.015s
If your tests aren't working for you, you're not testing correctly.
This talk is inspired by the similar talks by
Bad programmers worry about the code. Good programmers worry about data structures and their relationships.
type Thinger interface {
DoThing(input string) (Result, error)
}
// Suite tests all the functionality that Thingers should implement
func Suite(t *testing.T, impl Thinger) {
res, _ := impl.DoThing("thing")
if res != expected {
t.Fail("unexpected result")
}
}
// TestOne tests the first implementation of Thinger
func TestOne(t *testing.T) {
one := one.NewOne()
Suite(t, one)
}
// TestOne tests another implementation of Thinger
func TestTwo(t *testing.T) {
two := two.NewTwo()
Suite(t, two)
}
The bigger the interface, the weaker the abstraction.
– Rob Pike, Go Proverbs
Mocks and fakes pollute your API and increase the surface area you have to support.
Let the package consumer define an interface over just the parts they need for testing.
func Startup(settings provider.Remote) *Server {
s := settings.Get("foo")
log.Println(s)
return &Server{
setting: s,
}
}
type provider interface {
Get(string) string
}
func Startup(settings provider) *Server {
s := settings.Get("foo")
log.Println(s)
return &Server{
setting: s,
}
}
type testSetting string
func (s testSetting) Get(_ string) string {
return s
}
func TestStartup(t *testing.T) {
s := Startup(testSetting("whatever"))
if s.setting != "whatever" {
t.Error("Setting should be set")
}
}
How concurrency feels
Gyga8K - https://forum.golangbridge.org/t/image-big-gopher/3489
How testing concurrent code can feel
Why might a library expose channels?
Convenience. (time.Timer)
"Feels" like it should be a channel (message queues)
Speed?
Testing exported channels has many traps to avoid.
type dequeue struct {
c chan string
}
func NewDequeue() *dequeue {
d := &dequeue{c: make(chan string)}
go d.run()
}
func (d *dequeue) Messages() chan string {
return d.c
}
func TestMessages(t *testing.T) {
d := NewDequeue()
for item := range d.Messages() {
verifyItem(t, item)
}
}
func (d *dequeue) Scan() bool {
if d.err != nil {
return false
}
d.iter, d.err = d.read()
return d.err != nil
}
func TestMessages(t *testing.T) {
d := NewDequeue()
for d.Scan() {
verifyItem(t, d.Item())
}
if d.Err() != nil {
t.Fail()
}
}
func ChanMessages(d *dequeue) <-chan string {
ret := make(chan string)
go func() {
for d.Scan() {
ret <- d.Item()
}
if d.Err() != nil {
panic(d.Err())
}
}()
return ret
}
Easier to add concurrency than remove it
func TestServe(t *testing.T) {
// The method to use if you want to practice typing
s := &http.Server{
Handler: http.HandlerFunc(ServeHTTP),
}
// Pick port automatically for parallel tests and to avoid conflicts
l, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatal(err)
}
defer l.Close()
go s.Serve(l)
res, err := http.Get("http://" + l.Addr().String() + "/?sloths=arecool")
if err != nil {
log.Fatal(err)
}
greeting, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
log.Fatal(err)
}
fmt.Println(string(greeting))
}
func TestServeMemory(t *testing.T) {
// Less verbose and more flexible way
req := httptest.NewRequest("GET", "http://example.com/?sloths=arecool", nil)
w := httptest.NewRecorder()
ServeHTTP(w, req)
greeting, err := ioutil.ReadAll(w.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(greeting))
}
$ make test
real 1m39.922s
user 2m51.198s
sys 0m36.546s
$ make test110
real 0m15.445s
user 0m20.994s
sys 0m7.542s
## Before:
# TODO: remove loop in go1.10: https://github.com/golang/go/issues/6909
PKGS := $(shell go list ./...)
test: vet fmtchk
for f in $(PKGS); do \
cd $$GOPATH/src/$$f ;\
go test -v -race -coverprofile=.coverprofile $$f || exit 1 ;\
done
echo mode: atomic > .covertotal
find . -name .coverprofile | xargs cat | grep -v mode >> .covertotal
go tool cover -func=.covertotal
## After:
test: vet fmtchk
go test -v -race -coverprofile=.coverprofile ./...
go tool cover -func=.coverprofile