Delving Into Unit Tests

I just got started with Golang a few weeks back and one thing I immediately missed from Python was pdb and pdbpp (a drop in replacement for pdb). Without a debugger I’m floating in print statements for minutes and sometimes hours. Often a debugger can help track down the issue in seconds. After joining CockroachDB (a Go shop through and through), I had the option of installing Goland. But leave my painstakingly tweaked vimrc, tmux.conf, and CLI life behind? Not at this point. I’m confident I can buy my burial plot at this point in my life and I’m thinking of somewhere nice in the green hills of the Vim docs. So what did I find to replace my pdb flow once I moved to Go? Delve, fo sho.

Let’s take some code like the following…

package mainimport "fmt"// Return the value of the n-th fibonacii number.
// 1, 2, 3, 4, 5, 6, 7
// 0, 1, 1, 2, 3, 5, 8
func fibonacci(n int) (int, error) {
if n < 1 {
return -1, fmt.Errorf("Number is too low: %d", n)
}
if n == 1 {
return 0, nil
}
if n == 2 {
return 1, nil
}
j, k := 0, 1 for i := 3; i <= n; i++ {
j, k = k, j+k
}
return k, nil
}
func main() {
rng := []int{1, 2, 3, 4, 5, 6}
for x := range rng {
result, err := fibonacci(x)
if err != nil {
fmt.Println(err)
}
fmt.Println(result)
}
}

And some test code like this…

package mainimport (
"fmt"
"testing"
)
func TestFibonacci(t *testing.T) {
rng := map[int]int{
1: 1,
2: 1,
3: 2,
4: 3,
}
for n, expected := range rng {
n, expected := n, expected
t.Run(fmt.Sprintf("fibonacci(%d)", n), func(t *testing.T) {
t.Parallel()
res, err := fibonacci(n)
if err != nil {
t.Errorf("%w", err)
}
if res != expected {
t.Errorf("fibonacci(%d) = %d; got %d", n, expected, res)
}
})
}
}

Running go test gives me this…

$ go test
--- FAIL: TestFibonacci (0.00s)
--- FAIL: TestFibonacci/fibonacci(1) (0.00s)
fib_test.go:25: fibonacci(1) = 1; got 0
--- FAIL: TestFibonacci/fibonacci(3) (0.00s)
fib_test.go:25: fibonacci(3) = 2; got 1
--- FAIL: TestFibonacci/fibonacci(4) (0.00s)
fib_test.go:25: fibonacci(4) = 3; got 2
FAIL
exit status 1

At this point, I’d love to drop a into a debugger right at the start of my test. I tried “dlv debug”, the same command I used to debug my “main” go program with, and couldn’t seem to set break points on the tests. The docs say that “dlv test [package]” is the command to try for that so let’s try…

$ dlv test
(dlv) break main
Command failed: Location "main" ambiguous: runtime.main, main.main…
(dlv) exit

Hmm, how about…

$ dlv test main
can't load package: package main: cannot find package "main" in any of:
/usr/local/Cellar/go@1.14/1.14.8/libexec/src/main (from $GOROOT)
/Users/paul/Code/go/src/main (from $GOPATH)
exit status 1

Hmm, I must be missing something. In Python, I can drop an “import pdb;pdb.set_trace()” anywhere and I’m set. But in Golang, I’m not sure how to do that. It would seem that all things start at “main.main” in compiled world. But with tests, the program doesn’t start there. Let’s try running “dlv test” again and instead breaking on just the test name, TestFibonacci…

$ dlv test     
(dlv) break TestFibonacci
Breakpoint 1 set at 0x115050b for _/Users/paul/Code/dlv-demo.TestFibonacci() ./fib_test.go:8

Bam!

Stepping through the test function, I hit another snag. I would like to jump into the inner test that is failing. However, when I try to “next” into the function, I get thrown right over it and the loop repeats.

> _/Users/paul/Code/dlv-demo.TestFibonacci() ./fib_test.go:17 (PC: 0x115079e)
12: 3: 2,
13: 4: 3,
14: }
15: for n, expected := range rng {
16: n, expected := n, expected
=> 17: t.Run(fmt.Sprintf("fibonacci(%d)", n), func(t *testing.T) {
18: t.Parallel()
19:
20: res, err := fibonacci(n)
21: if err != nil {
22: t.Errorf("%w", err)

So what are those inner functions inside of our test functions named? Well, there’s a way to find out right from inside dlv. If we want to see all the functions, that exist in the current package, we can run the following command (very similar to “ls *” at the command line) to list them…

(dlv) funcs TestFi*
_/Users/paul/Code/dlv-demo.TestFibonacci
_/Users/paul/Code/dlv-demo.TestFibonacci.func1

Can we break on that function? Yep.

(dlv) break TestFibonacci.func1
Breakpoint 2 set at 0x115094b for _/Users/paul/Code/dlv-demo.TestFibonacci.func1() ./fib_test.go:17

And could we have broken on a specific line number? Sure thing…

(dlv) break ./fib_test.go:24
Breakpoint 1 set at 0x1150afb for _/Users/paul/Code/dlv-demo.TestFibonacci.func1() ./fib_test.go:24

I’ve left it up to the reader to determine what the issues is. If you would like to clone the failing tests and run the commands, are repo with both is here: https://github.com/LogstonEducation/dlv-unittest-demo

--

--

--

Engineer, Professor

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Document with data

Web Application: What Business Must Know

A Newsletter Sending System Code Challenge

Refactoring — been there, done that

HappyForms Pro v1.26.3 — Friendly Drag And Drop Contact Form Builder

API for combining commerce with experience

No Silver Bullet — why agile is not the answer

Featured Story 33 — A Different「DFINITY」| What Is ic.rocks?

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Paul Logston

Paul Logston

Engineer, Professor

More from Medium

Behind the scene Golang Static Analysis

Storing and processing real-time time-series data with Influx DB

Getting started with GO Programming Language — Part Two

Part 2: Grpc Proto Code Generation Using Protoc for Message and Services