Gopher Gym - Gopher Quiz 2 - TDD!
Tags: go, tdd, gophers
Published: 2019-06-03 06:15:29


2 Jun 2019 at 01:52
- - - -

We finished the last post with three tasks to tackle:
- Adding tests
- Keeping track of the score
- Adding a flag that allows the CSV file to be customised. 

The priority is to add tests, as I’m a firm believer in the benefits of Test Driven Development, often abbreviated as TDD. In a nutshell, you write a test of some behaviour you want to implement. You then run the test, see it fail, and make the smallest possible change to make it pass. Once your test is green, it’s time to refactor. Thus it becomes a cycle; red, green, refactor. 

It helps you avoid a lot of common pitfalls when it comes to coding, as well as forcing you to think about your code and domain more abstractly.

Go has a `testing` package from the standard library, so let’s have a try at writing a unit test (a unit test is a test that tests a small ‘unit’ of code). Let’s pick a small bit of code that performs a single function, and write a test for it. In cases you forgot what the code looks like at the moment, here’s the entire thing

``` go
package main

import (
	"bufio"
	"encoding/csv"
	"fmt"
	"log"
	"os"
	"strings"
)

type quizItem struct {
	question string
	answer   string
}

func main() {
	csvFile, err := os.Open("problems.csv")
	if err != nil {
		log.Fatal(err)
	}
	defer csvFile.Close()

	reader := csv.NewReader(csvFile)
	records, err := reader.ReadAll()

	if err != nil {
		log.Fatal(err)
	}

	for i := 0; i < len(records); i++ {
		// Create quizItem object
		quizItem := quizItem{records[i][0], records[i][1]}
		// Print out the question
		fmt.Println("Question:", quizItem.question)
		// Create reader and allow user to input their answer
		reader := bufio.NewReader(os.Stdin)
		fmt.Print("Enter your answer now: ")
		// Expect answer to be given once they hit return
		text, _ := reader.ReadString('\n')
		fmt.Println("Your answer is:", text)
		// Trim the newline suffix from the input
		text = strings.TrimSuffix(text, "\n")
		if text == quizItem.answer {
			fmt.Println("Correct! Well done")
		} else {
			fmt.Println("WRONG! Answer is:", quizItem.answer)
		}
	}

}
```

Let’s recap on what the `main` function is doing.

1. It reads a CSV file and loads it into memory until the `main` function finishes executing (`defer` is a keyword that allows you to run some code at the end of a function’s execution)
2. It instantiates a CSV reader, and uses `reader.ReadAll()` to create a slice of slices composed of strings (the successful return value type for ReadAll is `[][]string` which translates to 2 dimensional array where all elements in the inner slices are strings. 
3. We create a for loop which will iterate through each of the **inner** slices in the outermost slice (inner slice being a line in the CSV, and the outermost slice being all of the CSV lines. 
4. We create instantiate a `quizItem` struct from the information contained in the **inner** slice.
5. We print the question from the `quizItem` struct, and print an instruction to the user.
6. We instantiate a `bufio` reader to take the input from `stdin` until it receives a `/n` keypress. 
7. We print the user’s answer, and then use `TrimSuffix` to remove the invisible `/n` character from the user’s answer.
8. Finally, we compare the two, and print a message to the user, which has different content depending on whether they got the answer right or wrong. 

Point 8 is something that I would like to test; so let’s imagine what the code would look like in an ideal world. I want it to take two arguments, both of type string, and I want it to return a string too. I want the return value to be determined by the arguments that are passed in, specifically if they match or not. 

# 3 Jun 2019 at 04:05
[Chrono Trigger: Main Theme, a song by London Philharmonic Orchestra on Spotify](https://open.spotify.com/track/3ymuMYnJtBvcs5xk8MuBRl?si=3yKs1ScWR_yA_3rSFy1RUQ)

Let’s write a test in Go! There are three steps here 
1. Creating a function which tests the unit of code in question. The test function should have the format `function TestXxx(*testing.T)` where `Xxx` does not start with a lowercase letter. (The asterisk is a pointer to the location of the testing variable in memory)
2. Creating a file whose name ends in `_test.go`, and placing our test inside the file. 
3. Run `go test` and see blood (hopefully)!

It’s quite simple isn’t it? Let’s get started. 
`$ touch main_test.go` to create the test file in our local directory, which now contains 4 files;

— `GopherQuiz` The binary executable we compiled with `go build`
— `main.go` The file containing our code 
— `main_test.go` Our newly created test file
— `problems.csv` The CSV file containing our quiz

For the time being, we will keep it like this, but it’s important to recognise that as a project grows, organising your files is extremely important. It helps others find what they are looking for faster, while also providing context to the code. `pool/write.go` is easy to recognise as a file related  writing to a pool, while `channel/write.go` would be relating to writing to a channel. 

Tangent over, let’s go back to writing our test in `main_test.go`

``` go
package main

import “testing”

func TestCompareAnswer(t *testing.T) {
  got := CompareAnswer(“3”, “3”)
  if got != true {
  t.Errorf(“CompareAnswer = %v; want true”, got)
 }
}
```

First we specify the package we are testing, which in this case is `main`. We import the `testing` package so that we can use it (remember the format of the test function requires `t *testing.T` as an argument. Then we define our test. 

Now I’m going to go on a small tangent about Test Driven Development (TDD). When doing TDD, **you always write a test first**, before ANY other code. What does this mean? It means that before you think about **how** to do something, you think about **what you want to do**. 

I want to compare two strings and see if they match, and return a different string depending on whether they match or not. So I will call my test `CompareAnswer`. But is it just comparing the answers? No, it’s also returning either a success or a failure message depending on the comparison. So (without going into the rabbit hole of naming stuff), I’m going to change the name of the test to `CheckAnswer`. 

``` go
func TestCheckAnswer(t *testing.T) {
  got := CheckAnswer(“3”, “3”)
  if got != true {
    t.Errorf(“CheckAnswer = %v; want true”, got)
  }
}
```

Going back to the code; we then set the output of the function we are testing `CheckAnswer` to a variable called `got`. Incase you forgot, `:=` is shorthand for declaring a variable with implicit type. We then make our assertion: is what we got `true`? If is is, fail the test using `t.ErrorF` (prints an error with formatting), in which we let the user know what they got, and what we expected. The test passes if it executes without failure. (FYI, `ErrorF` is simply `LogF` followed by `Fail`. Had we wished to fail the test without giving a message, we could’ve just used `Fail`.) 

Pretty simple right? No-clickbait, but this one simple trick will save you hours and hours of development time; a good test suite is the difference between hating and loving your job when things go tits up and you need to apply a patch. 

I left my first job because of the stress incurred from working on production web applications with zero tests; nothing is more frightening then having to change code and not knowing if you’re about to break something else. 

Right, back to the test. It’s actually a bad test right now because although it’s named correctly, the assertion is wrong. Let’s think back; **what do we want this code to do?** We want it to compare two strings, and return one string if they match, and a different string if they do not. Let’s make those changes, to the sweet sounds of the best video game OST ever [Secret of the Forest](https://open.spotify.com/track/4YgdFe3M47sj0ysVQhOF75?si=EQEqLneaSIOzRTcWM5jyog). 

``` go
func TestCheckAnswer(t *testing.T) {
  gotCorrect := CheckAnswer(“3”, “3”)
  if gotCorrect != “Correct! Well done” {
    t.Errorf(“CheckAnswer = %v; want ‘Correct! Well Done’”, gotCorrect)
  }
  gotIncorrect := CheckAnswer(“3”, “2”)
  if gotIncorrect != “WRONG! Answer is: 2” {
    t.Errorf(“CheckAnswer = %v; want ‘WRONG! Answer is: 2’”, gotIncorrect)
  }
}
```

Right, let’s go through this step by step; we assign the return of the function we are testing to `gotCorrect`, compare it to the string we expect to receive, and fail if we do not receive what we expect. We then do the same thing with an answer which is incorrect, repeat the process. Nice! 

But there’s a slight issue here; something that is seemingly innocuous and innocent. Something that can quite easily make a test suite, your shield and sword against bugs, heavy and unwieldy, quickly becoming a burden that will make your fellow developers curse and can put off inexperienced developers writing tests at all. 

Recall the reason we began writing this test; we wanted to take a **unit** of our code and test that it works independently. If it works by itself, it stands to reason that if the program isn’t working, the fault lies with another part of the program. By making small, modular **units** of code, we can test each part independently, and thus the surface area where bugs can hide becomes smaller. This concept of small, modular units of code that do one thing independently of all else is often referred to as the **Single Responsibility Principle** (SRP). 

Our test violates the SRP; it has two responsibilities. An easy way to check if your code violates SRP is to ask it questions about what it does. 

> **ME**: What’s cracking `TestCheckAnswer`, how’s it hanging? You up too much these days?   
>   
> `TestCheckAnswer`: Shiiiiiiiiiiiiiiiet, they got me on some slave-type shit bro.   
>   
> **ME**: What you mean man? I thought you were just checking answers, that’s what you do right?   
>   
> `TestCheckAnswer`: Dude, look at my fricking name, I’m `TestCheckAnswer`, but for some reason I have to check two answers. What happens when I wanna go on holiday? Y’all ain’t gonna have no one testing `CheckAnswer`! In fact, I don’t even wanna be called `TestCheckAnswer`, I wanna be called `TestCheckCorrectAnswer` so I don’t have to be here doing the work of two people! Do me a solid and change that ASAP, cause `CheckAnswer` might start getting more work to do, and then y’all are gonna have me doing more work, and ergo **you** are gonna have to do more work! Come on bro work with me here, I ain’t trying to end up like `main`, that dude got so many problems I don’t even know how he’s still around!   

(I always end up personifying my tests based on a good friend of mine who’s pretty lazy. Naturally, he’s a great programmer!)

Tests are functions, functions should have a single responsibility, ergo, we should change this test to have a single responsibility. We do that by splitting them out, which in this case is pretty simple; 

``` go
package main

import “testing”

func TestCheckCorrectAnswer(t *testing.T) {
  got := CheckAnswer(“3”, “3”)
  if got != “Correct! Well done” {
    t.Errorf(“CheckCorrectAnswer = %v; want ‘Correct! Well Done’”, got)
  }
}

func TestCheckIncorrectAnswer(t *testing.T) {
  got := CheckAnswer(“3”, “2”)
  if got != “WRONG! Answer is: 2” {
    t.Errorf(“CheckIncorrectAnswer = %v; want ‘WRONG! Answer is: 2’”, got)
  }
}

```

The benefit of this is that it makes our code more flexible; flexible code is good because the future is uncertain, and in the face of uncertainty, we must be able to adapt to new conditions quickly. 

Right! We’ve got our test, now what? We run it, and watch it fail!
``` shell
06:24 $ go test
# github.com/adilw3nomad/GopherQuiz [github.com/adilw3nomad/GopherQuiz.test]
./main_test.go:6:9: undefined: CheckAnswer
./main_test.go:13:9: undefined: CheckAnswer
FAIL    github.com/adilw3nomad/GopherQuiz [build failed]
```

Now is the fun part. We write the bare minimum, simplest code to make the error message change. Imagine the test error output as your hint guide; it tells us `CheckAnswer` is undefined, so let’s define it!

``` go
func CheckAnswer() {
}
```

That’s it! We make the smallest change possible to satisfy the error we received. Now we run the test again. 
``` shell
06:29 $ go test
# github.com/adilw3nomad/GopherQuiz [github.com/adilw3nomad/GopherQuiz.test]
./main_test.go:6:20: too many arguments in call to CheckAnswer
        have (string, string)
        want ()
./main_test.go:6:20: CheckAnswer("3", "3") used as value
./main_test.go:13:20: too many arguments in call to CheckAnswer
        have (string, string)
        want ()
./main_test.go:13:20: CheckAnswer("3", "2") used as value
FAIL    github.com/adilw3nomad/GopherQuiz [build failed]
```

Success! We have got a different error message. A different error message from a test is always a good thing; it means that you are making progress. This one is a little different; it is telling us that `TestCheckCorrectAnswer` is sending too many arguments to `CheckAnswer`. It even helps us more by telling us what we have given it `have (string, string)`, and what it wants `want ()`. 

Remember, we want to **satisfy the test**. So if the test has two strings to pass, we must make `CheckAnswer` accept two strings. 
``` go
func CheckAnswer(string, string) {
  return
}
```

Running the test again gives us a more peculiar output; 
```shell
06:35 $ go test
# github.com/adilw3nomad/GopherQuiz [github.com/adilw3nomad/GopherQuiz.test]
./main_test.go:6:20: CheckAnswer("3", "3") used as value
./main_test.go:13:20: CheckAnswer("3", "2") used as value
FAIL    github.com/adilw3nomad/GopherQuiz [build failed]
```

This error message is a bit different, but still useful none the less! It tells us that the function call we made is used as a value. What does this mean? 

If you have a look at the `CheckAnswer` function above, you can see that we don’t specify a return type. This means the function itself is returned. This is pretty cool, I wonder if it means you could create functions that return other functions? 

Anyways, to fix this, we add a return value. 

``` go
func CheckAnswer(string, string) int {
  return 1
}
```

``` shell
06:36 $ go test
# github.com/adilw3nomad/GopherQuiz [github.com/adilw3nomad/GopherQuiz.test]
./main_test.go:7:9: cannot convert "Correct! Well done" (type untyped string) to type int
./main_test.go:7:9: invalid operation: got != "Correct! Well done" (mismatched types int and string)
./main_test.go:14:9: cannot convert "WRONG! Answer is: 2" (type untyped string) to type int
./main_test.go:14:9: invalid operation: got != "WRONG! Answer is: 2" (mismatched types int and string)
FAIL    github.com/adilw3nomad/GopherQuiz [build failed]
```

I used `int` to show you that, although we know what our function will do (since we spiked it), there’s a lot of scenarios where you have no idea where to begin. In these scenarios, the best thing to do is to make a hypothesis, and run the test to see what happens. In this case, it’s an easy fix.

``` go
func CheckAnswer(string, string) string {
  return ""
}
```

``` shell
06:47 $ go test
--- FAIL: TestCheckCorrectAnswer (0.00s)
    main_test.go:8: CheckCorrectAnswer = ; want 'Correct! Well Done'
--- FAIL: TestCheckIncorrectAnswer (0.00s)
    main_test.go:15: CheckIncorrectAnswer = ; want 'WRONG! Answer is: 2'
FAIL
exit status 1
FAIL    github.com/adilw3nomad/GopherQuiz       0.005s
```

We’re getting closer! Let’s do the simplest thing possible to make one of the tests pass. 

``` go
func CheckAnswer(string, string) string {
  return "Correct! Well done"
}
```

``` shell
06:51 $ go test
--- FAIL: TestCheckIncorrectAnswer (0.00s)
    main_test.go:15: CheckIncorrectAnswer = Correct! Well done; want 'WRONG! Answer is: 2'
FAIL
exit status 1
FAIL    github.com/adilw3nomad/GopherQuiz       0.005s
```

Finally! A passing test! But we’re not done yet, because one of the other tests is still failing. But this is the essence of TDD; you write small changes incrementally, and run your tests each time you go along. 

When you hit an error message, you make a hypothesis	 as to it’s cause, make the relevant change to your code, and test it by…. Running the tests! In this way, you are protected from adding needless code that isn’t used. 

I’m going to skip ahead now and show you the final `CheckAnswer` function. 

```go
func CheckAnswer(answer string, correctAnswer string) string {
  if answer == correctAnswer {
    return “Correct! Well done”
  } else {
    return (“WRONG! Answer is: “ + correctAnswer)
  }
}
```

``` shell
06:56 $ go test
PASS
ok      github.com/adilw3nomad/GopherQuiz       0.004s
```
And there we have it! No more failing tests! 

I hope you’ve enjoyed this blog post and found it informative, and as always, C&C is encouraged and appreciated. I’m going to leave it here since it’s gotten quite long, but in the next blog post we’ll continue the task by.

- Adding a customisable timer 
- Implementing score
- Allow custom quiz’s to be pass by file name