• Stars
    star
    142
  • Rank 258,445 (Top 6 %)
  • Language
    Go
  • License
    MIT License
  • Created over 4 years ago
  • Updated 9 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Exercises in the fundamentals of Go, to accompany the book 'For the Love of Go', by John Arundel.

Fundamentals of Go

Hello, and welcome to learning Go! It's great to have you here.

What's this?

This is a set of introductory exercises in Go programming, suitable for complete beginners. If you don't know anything about Go yet, or programming, but would like to learn, you're in the right place!

(If you do already know something about Go, you should find these exercises relatively straightforward, but still worth doing.)

Is there a book to accompany these exercises?

Absolutely. For the Love of Go guides you through every step of the process, from installing Go to explaining all the ins and outs of test-driven development. It also contains almost all the code you'll need to solve these exercises, with full details of how it works.

You don't need to buy the book to work through these exercises, though it'll definitely help! But if you do buy it, you'll be helping support me and my work, including developing new exercises and books, and mentoring people from non-traditional backgrounds to help them into tech and software development careers. Which would be amazing! You can also sponsor me on GitHub.

Here it is:

There's also a video course to accompany the book:

What you'll learn

In this set of exercises, you'll learn:

  • How to run tests for a Go program
  • How to automatically format your Go code correctly
  • The basic pattern that all Go tests should follow
  • How to declare and import Go packages (units of code)
  • How to design multiple test cases and use them in your tests
  • How to test error handling in your programs
  • A simple, reliable, test-first development workflow

How to do the exercises

Each exercise is described in a separate section of this README file. Each section briefly explains what the exercise is about, and outlines what you'll be doing. There'll be one or more goals for you to achieve, marked with GOAL: in bold. (Don't worry if you're not sure how to do it. There'll usually be some helpful suggestions following the goal description.)

If you're finding the goals easy, or want a bit more of a challenge, there are some stretch goals, too. These are optional; if you're not interested or not ready to tackle them, just skip over them.

So let's get started!

1: Testing times

It's your first day as a Go developer at Texio Instronics, a major manufacturer of widgets and what-nots, and you've been assigned to the Future Projects division to work on a very exciting new product: a Go-powered electronic calculator! Welcome aboard.

In this exercise, all you need to do is make sure your Go environment is set up and everything is working right.

One of your new colleagues has already made a start on the calculator project. She's placed a Go package in the calculator.go file, and an accompanying test in calculator_test.go. You're going to run this test and make sure it passes.

GOAL: Successfully run the test.

STRETCH GOAL: Use your editor's Go support features to run the test directly in the editor. For example, in Visual Studio Code, there's a little 'Run test' link above each test function, and a 'Run package tests' link at the top of the file. Find out how to do this in the editor you're using.

2: Getting in shape

The next thing to do is ensure that your Go code is formatted correctly. All Go code is formatted in a standard way, using a tool called gofmt (pronounced, in case you were wondering, 'go-fumpt'). In this exercise you'll see how to use it to check and reformat your code (if necessary).

The file calculator.go in this directory contains a deliberate formatting mistake, just to make sure you're on your toes!

GOAL: Run gofmt to check the formatting and find out what is wrong.

GOAL: Use gofmt to fix the formatting automatically.

STRETCH GOAL: Experiment with gofmt. Try formatting your code in different ways and see what changes gofmt makes to it. It's interesting to note what it does and doesn't change.

3: Fixing a hole

Now that your development environment is all set up, your colleague needs your help. She has been working on getting the calculator to subtract numbers, but there's a problem: the test is not passing. Can you help?

You'll find the test code commented out in the calculator_test.go file:

// func TestSubtract(t *testing.T) {
// 	t.Parallel()
// 	var want float64 = 2
// 	got := calculator.Subtract(4, 2)
// 	if want != got {
// 		t.Errorf("want %f, got %f", want, got)
// 	}
// }

GOAL: Get this test passing!

STRETCH GOAL: Find out what other ways there are to fail a test using the testing.T. What other methods does it have? Do any of them look potentially useful?

4: Go forth and multiply

Excellent work! You now have a calculator which can add and (correctly) subtract. That's a great start. Let's turn to multiplication now.

Up to now you've been running existing tests and modifying existing code. For the first time you're going to write the test, and the function it's testing!

GOAL: Write a test for a function Multiply() which, just like the Add() and Subtract() functions, takes two numbers as parameters, and returns a single number representing the result.

GOAL: Write the minimum code necessary to get the test to compile and fail.

GOAL: Write the minimum code necessary to get TestMultiply to pass.

Why did we say "the minimum code necessary"? Because, in a sense, the test itself defines what Multiply needs to do. Once it does that, and the test passes, we should stop coding.

GOAL: Make TestMultiply fail deliberately.

There's one final step required for us to be really sure that both the test and the code are correct. We need to see the test fail again. To do this, change the expected return value from Multiply to something that you know is wrong, and run go test again.

STRETCH GOAL: Think about other ways this test could be wrong. Try some of them out. What happens? What could you do to catch such potential problems?

Nice work! Go on to the next section.

5: Testing to destruction

Now that you've successfully designed and implemented a new calculator feature, test-first, it's time to think a little more about testing, and how you can extend it. The Add, Subtract, and Multiply functions are so simple that it's fairly difficult (though not at all impossible) to get them wrong. If TestAdd passes for one set of inputs, for example, it's hard to imagine other inputs that would make it fail.

But this doesn't apply to more complicated functions, where it's quite likely that they could work correctly for some inputs, but not for others. In this situation it will be a good idea to test multiple cases for each function: that is to say, different inputs with different expected results.

GOAL: Define a struct type suitable for expressing test cases for your calculator functions. It should include the test inputs, and the expected output.

GOAL: Declare a slice of test cases for TestAdd, using your struct type, and rewrite the test function to loop over them, testing each case in turn.

GOAL: Rewrite TestSubtract, and TestMultiply to test multiple cases for each function, too. Try to think of interesting and varied test cases: for example, negative numbers, zeroes, fractional numbers.

As before, once each test is passing, break it deliberately and make sure it fails in the expected way, with a helpful failure message.

STRETCH GOAL: Add a string field name to your test cases, and supply a human-readable name for each case. For example, a test case for Add might be named "Two negative numbers which sum to a positive". Output the name of the test case along with the failure message.

When you're happy with the expanded tests, move on to the next section.

6: Errors and expectations

The board reviewed the calculator project yesterday, and they're really pleased with your progress! The only remaining major feature to be completed is division, and that needs a little more thought than the others.

Why? Because it's possible to write division expressions that have undefined results. For example:

6 / 0

This looks reasonable at first sight, but what we're really asking here is "What number, multiplied by zero, gives 6?" And clearly there is no such number. What should our calculator do in this case?

Functions that return errors

The standard way to do this in Go is to write a function signature like:

func Divide(a, b float64) (float64, error)

GOAL: Define a test case struct type which allows us to test both valid and invalid inputs to Divide.

We need a way for the test case to specify whether or not the given inputs should produce an error. The simplest way to do that is to use a bool value, which we could call something like errExpected. If this value is true, it's saying the test case should cause Divide to return a non-nil error.

GOAL: Write a set of test cases for Divide using the new struct type. Make sure to include both valid and invalid inputs.

GOAL: Write the necessary logic for the TestDivide function, handling each of the possible outcomes correctly.

GOAL: Write the minimum code necessary to make TestDivide pass.

When your test cases all pass, and you've verified them all by deliberately breaking their expectations, and you've checked that the test outputs the correct failure message for unexpected error statuses, then you can move on to the next (and final) section.

STRETCH GOAL: Write a new test for one or more of your functions which generates random test inputs instead of using prepared cases (you can use the math/rand library for this). For example, you might write a TestAddRandom function which generates two random float64 values, sums them to get the expected result (not using the Add function!), and calls Add to verify that it returns the same answer. Do this many times, say, a hundred times, to give the function a good workout.

7: Roots music

Well done! That was a good piece of work getting the test cases and error handling set up for the Divide function. In fact, you now know just about everything you need to write great tests in Go!

The structure of tests

All tests follow more or less the same basic structure:

  • Define the test cases with their inputs and expected outputs (including any error outputs)
  • Iterate over the test cases, calling the function under test with each set of inputs
  • Check that the outputs match expectations, and fail otherwise with some helpful output

And you've also practiced a specific workflow, or sequence of actions, that you can use when developing any piece of Go code:

The development process

  1. Start by writing a test for the function (even though it doesn't exist yet)
  2. See the test fail with a compilation error because the function doesn't exist yet.
  3. Write the minimum code necessary to make the test compile and fail.
  4. Make sure that the test fails for the reason you expected.
  5. Write the minimum code necessary to make the test pass.
  6. Change the test expectations to deliberately make the test fail, and check that the failure message is accurate and informative.
  7. Restore the original expectations and make sure the test still passes.
  8. Commit!

In practice, we may go through this loop a few times with any particular test. We start with something very simple, so that we are never facing an insurmountable problem. If we are, then we try to break that problem down into smaller problems, and repeat this process with those.

A Sqrt function

Our basic calculator is complete, but you've just received an urgent message from the VP of Sales. She wants an extra premium feature which can be used to upsell users to the 'Enterprise' calculator. The ordinary calculator has been re-branded as the 'Home' edition.

It seems that enterprise users would like the ability to take square roots, so the VP's asking you to develop a Sqrt function. You already know everything you need to know to design, test, and develop this feature, so let's get started!

GOAL: Develop a Sqrt function which takes a float64 value and returns its square root as a float64 value.

You can use any standard or third-party libraries you think are appropriate. The function should return an error for negative inputs (no real number squared equals a negative number, so taking the square root of a negative number is not a valid operation). Your test should check the error handling in the same way as the test for Divide.

When the feature is complete to your own satisfaction, you're done! Nice work. The company has awarded you a sizeable bonus, and an all-expenses-paid vacation. Enjoy it!

STRETCH GOAL: Extend your existing tests and functions to allow two or more inputs to Add, Subtract, Multiply, and Divide. For example, calling Divide(12, 4, 3) should divide 12 by 4, divide the result of that by 3, then return that result. Calling Multiply with ten inputs should return the result of multiplying them all together.

STRETCH GOAL 2: Write a function which accepts strings of the form 12 * 3, for example, and returns the result. You only need to handle expressions which have a single floating-point value, followed by a single operator, followed by a single floating-point value, ignoring any whitespace. For example, the following should all be valid inputs to your function:

2*2
1 + 1.5
18   /   6
100-0.1

Who wrote this?

John Arundel is a Go teacher and mentor of many years experience. He's helped literally thousands of people to learn Go, with friendly, supportive, professional mentoring, and he can help you too! Find out more:

Further reading

What's next?

You might like to try another set of exercises in this series:

  • The G-machine guides you through the development of a simplified virtual CPU in Go, complete with its own machine language. Along the way you'll learn some useful computer science fundamentals.

Further reading

You can find more Go tutorials and exercises here:

Gopher images by the magnificent egonelbre

More Repositories

1

script

Making it easy to write shell-like scripts in Go
Go
5,457
star
2

gotestdox

A tool for formatting Go test results as readable documentation
Go
112
star
3

ftl-code

Code listings accompanying the 'For the Love of Go' book
Go
96
star
4

tpg-tools

Code examples from the book 'The Power of Go: Tools'
Go
68
star
5

kg-generics

Exercises and solutions from the book 'Know Go: Generics'
Go
67
star
6

uptimerobot

Client library for UptimeRobot v2 API
Go
57
star
7

gmachine

A set of Go exercises implementing a virtual computer system
Go
56
star
8

puppet-beginners-guide-3

Example code repo for the Puppet 5 Beginner's Guide, 3rd Edition
Shell
44
star
9

puppet-beginners-guide

Play along with the Puppet Beginner's Guide, 2nd edition!
37
star
10

tpg-tools2

Code examples from the book 'The Power of Go: Tools'
Go
26
star
11

tpg-tests

Code examples from the book 'The Power of Go: Tests'
Go
17
star
12

qrand

Quantum randomness source using the ANU hardware QRNG
Go
15
star
13

ftl-data

Exercises to accompany the book 'For the Love of Go: Data', by John Arundel.
Go
15
star
14

terraform-provider-checkly

A Terraform provider for the Checkly monitoring service
Go
15
star
15

control-repo-3

A complete example Puppet infrastructure
Shell
13
star
16

control-repo

A complete example Puppet infrastructure
Puppet
13
star
17

weaver

A simple link checker in Go
Go
10
star
18

kg-generics2

Exercises and solutions from the book 'Know Go: Generics' (2024 edition)
Go
10
star
19

procrastiproxy

A project template for a blocking proxy server in Go.
Go
9
star
20

shellspy

A project template for a shell transcript recorder in Go
Go
9
star
21

weather

A project template for a weather client in Go
Go
9
star
22

morningpost

A project template for a personalised newspaper in Go
Go
8
star
23

know-go

Exercises and solutions from the book 'Know Go'
Go
6
star
24

lander

Crowdsourced lunar lander game written by students, faculty, and friends at the Bitfield Institute of Technology
Go
6
star
25

vim-gitgo

Golang colorscheme for Vim, inspired by GitHub
Vim Script
6
star
26

key

A password strength checking library in Go
Go
6
star
27

checkly

A Go library for use with the Checkly API
Go
6
star
28

cookbook

Shell
5
star
29

eg-crypto

Go code samples and exercises for the book 'Explore Go: Cryptography'
Go
4
star
30

checkd

A Go library for writing programs which collect metrics
Go
4
star
31

tsr-tools

Code examples, exercises, and solutions from the book 'The Secrets of Rust: Tools'
Rust
3
star
32

habit

A project template for a habit tracker in Go
Go
3
star
33

cronrun

A library for parsing crontab strings
Go
3
star
34

grink

A tool to check web links in Markdown files
Rust
3
star
35

shift

A simple shift cipher demonstration in Go
Go
2
star
36

rskey

A simple key-value store in Rust
Rust
2
star
37

adventure

A simple adventure game challenge in Go
Go
2
star
38

checklist

Turn a text file into an interactive checklist
Go
2
star
39

terraform-ci

A single container image for CI-based Terraform testing
Dockerfile
2
star
40

cargo-testdox

Formats Rust test results as readable documentation
Rust
2
star
41

rmachine

A simple RISC CPU for emulation exercises
2
star
42

checkepub

A Go library and CLI tool for validating EPUB files using the HamePub Lint API
Go
2
star
43

linkcheck

A project template for a website link checker in Go.
Go
2
star
44

options

Simple example of functional options in Go
Go
2
star
45

pbg_ntp

Example Puppet module to manage NTP
Puppet
2
star
46

terraform-provider-uptimerobot

A Terraform provider for the Uptime Robot website monitoring service
Go
2
star
47

trm32

A reference emulator in Rust for the TRM32 architecture
Rust
2
star
48

microbit-clock

A digital clock in Rust for the BBC micro:bit
Rust
2
star
49

sicp

Scheme exercises in the 'Structure & Interpretation of Computer Programs' book
Scheme
1
star
50

txtar-c

A tool for creating txtar archives
Go
1
star
51

microbit-beachball

A Rust project for the BBC micro:bit
Rust
1
star
52

tcptest

Go
1
star
53

yijing

A library and command-line tool for consulting the I Ching
Go
1
star
54

embed

Go
1
star
55

edger

Test your Dockerfiles against the latest version of base images
Go
1
star
56

love

Go code samples and exercises for the book 'For the Love of Go'
Go
1
star
57

gcp

A simple client library for accessing Google Cloud resources
Go
1
star
58

Powering-up-with-Puppet

Template and sample code for getting started using Puppet
1
star