The missed benefit of unit testing

When it comes to unit testing, many developers understand that they “should” but the desire for fast progress often gets in the way.

The excuses in our head are:

For a codebase without tests:

  • Adding tests would be nice, but adding them takes too much time. Especially the first one when a testing library is getting figured out.

  • In addition to unit tests, I’ll want integration tests, too..

  • Other members of my team do not see the benefit of testing. They think it’s a waste of time.

For a codebase with tests:

  • A test broke, and it would take too long to figure out why, so comment it out and move on.

  • There are tests, but many of them don’t work/fail. What’s the point? Tests are added tech debt

The number one reason I hear for testing is preventing regressions, but I think that’s not the primary reason for testing.

The real benefit of testing is enhancing your own productivity.

When a system becomes sufficiently complex, the time to start up the app (backend, frontend, etc…) increases. However, by writing tests in isolation you can quickly figure out if something works.

For example, I was working on a problem where a button had to be clicked on the front-end to kick off some tasks on the back-end. Because of a bug in my code, the front-end would not automatically reconnect to the backend. So to test my code changes, I had to restart the back-end, and then refresh the front-end to click a button.

The code I wanted to test was an assignment piece, that pulled tasks from a map:

type TaskAssigner struct {
	Assignments map[Type][]Task
}

func (t *TaskAssigner) Create() {
	assignments := make(map[Type][]Task)
    ... populate map with things ...
}

There’s a lot that can go wrong when trying to load and populate a map of lists (especially if there’s persistence involved). I’ve seen JIRAs to do similar things in Java webapps take half a day by competent developers when all of the deployment + code changes + framework jank gets thrown in.

By writing a test, I could skip all of those steps. My test ended up looking like the below:

func TestAssignment(t *testing.T) {
	assigner := TaskAssigner{}
	assigner.Create()
	fmt.Printf("%+v\n", assigner.Assignments)
	if assigner.Assignments == nil {
		t.Errorf("Assignments nil")
	}
	tasks := assigner.Assignments[UNIPEDAL]
	if len(tasks) != 5 {
		t.Errorf("Needs 5 tasks")
	}
	for task := range tasks {
		log.Println(tasks[task].Description)
	}
}

By writing this test, within 5 minutes I knew my code would work once deployed.

So if you thought testing was only for finding regressions, please reconsider! It’s worth the learning effort because it will boost your productivity by a lot.