Book Image

Go Design Patterns

By : Mario Castro Contreras
Book Image

Go Design Patterns

By: Mario Castro Contreras

Overview of this book

Go is a multi-paradigm programming language that has built-in facilities to create concurrent applications. Design patterns allow developers to efficiently address common problems faced during developing applications. Go Design Patterns will provide readers with a reference point to software design patterns and CSP concurrency design patterns to help them build applications in a more idiomatic, robust, and convenient way in Go. The book starts with a brief introduction to Go programming essentials and quickly moves on to explain the idea behind the creation of design patterns and how they appeared in the 90’s as a common "language" between developers to solve common tasks in object-oriented programming languages. You will then learn how to apply the 23 Gang of Four (GoF) design patterns in Go and also learn about CSP concurrency patterns, the "killer feature" in Go that has helped Google develop software to maintain thousands of servers. With all of this the book will enable you to understand and apply design patterns in an idiomatic way that will produce concise, readable, and maintainable software.
Table of Contents (17 chapters)
Go Design Patterns
Credits
About the Author
About the Reviewer
www.PacktPub.com
Customer Feedback
Preface

Testing and TDD


When you write the first lines of some library, it's difficult to introduce many bugs. But once the source code gets bigger and bigger, it becomes easier to break things. The team grows and now many people are writing the same source code, new functionality is added on top of the code that you wrote at the beginning. And code stopped working by some modification in some function that now nobody can track down.

This is a common scenario in enterprises that testing tries to reduce (it doesn't completely solve it, it's not a holy grail). When you write unit tests during your development process, you can check whether some new feature is breaking something older or whether your current new feature is achieving everything expected in the requirements.

Go has a powerful testing package that allows you also to work in a TDD environment quite easily. It is also very convenient to check the portions of your code without the need to write an entire main application that uses it.

The testing package

Testing is very important in every programming language. Go creators knew it and decided to provide all libraries and packages needed for the test in the core package. You don't need any third-party library for testing or code coverage.

The package that allows for testing Go apps is called, conveniently, testing. We will create a small app that sums two numbers that we provide through the command line:

func main() { 
    //Atoi converts a string to an int 
    a, _ := strconv.Atoi(os.Args[1]) 
    b, _ := strconv.Atoi(os.Args[2]) 
 
    result := sum(a,b) 
    fmt.Printf("The sum of %d and %d is %d\n", a, b, result) 
} 
 
func sum(a, b int) int { 
    return a + b 
} 

Let's execute our program in the terminal to get the sum:

$ go run main.go 3 4
The sum of 3 and 4 is 7

By the way, we're using the strconv package to convert strings to other types, in this case, to int. The method Atoi receives a string and returns an int and an error that, for simplicity, we are ignoring here (by using the underscore).

Tip

You can ignore variable returns by using the underscores if necessary, but usually, you don't want to ignore errors.

Ok, so let's write a test that checks the correct result of the sum. We're creating a new file called main_test.go. By convention, test files are named like the files they're testing plus the _test suffix:

func TestSum(t *testing.T) { 
    a := 5 
    b := 6 
    expected := 11 
 
    res := sum(a, b) 
    if res != expected { 
        t.Errorf("Our sum function doens't work, %d+%d isn't %d\n", a, b, res) 
    } 
} 

Testing in Go is used by writing methods started with the prefix Test, a test name, and the injection of the testing.T pointer called t. Contrary to other languages, there are no asserts nor special syntax for testing in Go. You can use Go syntax to check for errors and you call t with information about the error in case it fails. If the code reaches the end of the Test function without arising errors, the function has passed the tests.

To run a test in Go, you must use the go test -v command (-v is to receive verbose output from the test) keyword, as following:

$ go test -v
=== RUN   TestSum
--- PASS: TestSum (0.00s)
PASS
ok   github.com/go-design-patterns/introduction/ex_xx_testing 0.001s

Our tests were correct. Let's see what happens if we break things on purpose and we change the expected value of the test from 11 to 10:

$ go test
--- FAIL: TestSum (0.00s)
    main_test.go:12: Our sum function doens't work, 5+6 isn't 10
FAIL
exit status 1
FAIL  github.com/sayden/go-design-patterns/introduction/ex_xx_testing 0.002s

The test has failed (as we expected). The testing package provides the information you set on the test. Let's make it work again and check test coverage. Change the value of the variable expected from 10 to 11 again and run the command go test -cover to see code coverage:

$ go test -cover
PASS
coverage: 20.0% of statements
ok  github.com/sayden/go-design-patterns/introduction/ex_xx_testing 0.001s

The -cover options give us information about the code coverage for a given package. Unfortunately, it doesn't provide information about overall application coverage.

What is TDD?

TDD is the acronym for Test Driven Development. It consists of writing the tests first before writing the function (instead of what we did just before when we wrote the sum function first and then we wrote the test function).

TDD changes the way to write code and structure code so that it can be tested (a lot of code you can find in GitHub, even code that you have probably written in the past is probably very difficult, if not impossible, to test).

So, how does it work? Let's explain this with a real life example--imagine that you are in summer and you want to be refreshed somehow. You can build a pool, fill it with cold water, and jump into it. But in TDD terms, the steps will be:

  1. You jump into a place where the pool will be built (you write a test that you know it will fail).

  2. It hurts... and you aren't cool either (yes... the test failed, as we predicted).

  3. You build a pool and fill it with cold water (you code the functionality).

  4. You jump into the pool (you repeat the point 1 test again).

  5. You're cold now. Awesome! Object completed (test passed).

  6. Go to the fridge and take a beer to the pool. Drink. Double awesomeness (refactor the code).

So let's repeat the previous example but with a multiplication. First, we will write the declaration of the function that we're going to test:

func multiply(a, b int) int { 
    return 0 
} 

Now let's write the test that will check the correctness of the previous function:

import "testing" 
 
func TestMultiply(t *testing.T) { 
    a := 5 
    b := 6 
    expected := 30 
 
    res := multiply(a, b) 
    if res != expected { 
        t.Errorf("Our multiply function doens't work, %d*%d isn't %d\n", a, b, res) 
    } 
} 

And we test it through the command line:

$ go test
--- FAIL: TestMultiply (0.00s)
main_test.go:12: Our multiply function doens't work, 5+6 isn't 0
FAIL
exit status 1
FAIL    github.com/sayden/go-designpatterns/introduction/ex_xx_testing/multiply    

0.002s

Nice. Like in our pool example where the water wasn't there yet, our function returns an incorrect value too. So now we have a function declaration (but isn't defined yet) and the test that fails. Now we have to make the test pass by writing the function and executing the test to check:

func multiply(a, b int) int { 
    return a*b 
} 

And we execute again our testing suite. After writing our code correctly, the test should pass so we can continue to the refractoring process:

$ go test
PASS
ok      github.com/sayden/go-design-patterns/introduction/ex_xx_testing/multiply    
0.001s

Great! We have developed the multiply function following TDD. Now we must refactor our code but we cannot make it more simple or readable so the loop can be considered closed.

During this book, we will write many tests that define the functionality that we want to achieve in our patterns. TDD promotes encapsulation and abstraction (just like design patterns do).