 <!-- .element style="border: none; box-shadow: none; height: 100px" -->
## Test writing workshop
---
# Testing
--
## Why?
--
## How?
--
### TDD
--
### Test first
--
### Prototype first
--
## What?
--
### Unit level
--
### Integration level
---
 <!-- .element style="border: none; box-shadow: none; height: 100px" -->
## Test framework
--
### Test types
- Benchmark
- Example
- Test
--
### Anatomy of a test
files ending with `_test.go`
functions starting with `Test[A-Z]`
`func(t *testing.T)` signature
--
```go
import "testing"
func TestSomething(t *testing.T) {
// test something
// this will succeed
}
```
--
### Subtests
```go
import "testing"
func TestSomething(t *testing.T) {
t.Run("name", func(t *testing.T) {
})
t.Run("name2", func(t *testing.T) {
})
}
```
--
### Test output/result
- `t.Log(f)`
- `t.Error(f)`
- `t.Fatal(f)`
- `t.Skip(f)`
--
### Parallel test running
```go
import "testing"
func TestSomething(t *testing.T) {
t.Parallel()
}
```
--
### Running tests
```bash
# Run tests
go test
# Run tests for all packages
go test ./...
# Run tests in verbose mode to see every output
go test -v
# Run a specific test
go test -run ^TestIntegration$
```
---
## Go test practices
--
### Manual assertions
```go
if want, have := SOME_VALUE, result; want != have {
t.Errorf("value does not match the expected one\n"+
"actual: %s\n"+
"expected: %s",
have, want,
)
}
```
--
### `_test` package
- useful to avoid cyclic dependencies
- blackbox testing
- lets you use qualified names in examples
--
```go
// mypkg_test is allowed to be in the same directory as mypkg
package mypkg_test
import (
"testing"
"mypkg"
)
func TestSomething(t *testing.T) {
// this is fine
mypkg.New()
// this will fail
mypkg.new()
}
```
--
### Table-driven tests
```go
func TestSomething(t *testing.T) {
tests := struct{
name string
input int
output int
}{
{
name: "example name",
input: 1,
output: 2,
},
}
// ...
}
```
--
### Table-driven tests
```go
func TestSomething(t *testing.T) {
// ...
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
// do the test
})
}
}
```
--
### Integration tests
```go
// +build integration
package mypkg
// integration tests
```
```bash
go test -tags integration
```
--
### Integration tests
```go
func TestIntegration(t *testing.T) {
if m := flag.Lookup("test.run").Value.String(); m == "" ||
!regexp.MustCompile(m).MatchString(t.Name()
) {
t.Skip("skipping as execution was not requested"+
"explicitly using go test -run")
}
t.Parallel()
t.Run("SomeIntegrationTest", testSomeIntegrationTest)
}
```
---
## Testify
--
`github.com/stretchr/testify`
--
### `assert`
`assert.Equal(t, expected, actual)`
uses `t.Error` under the hood
--
### `require`
`require.NoError(t, err)`
uses `t.Fatal` under the hood
--
### `mock`
```go
import (
"github.com/stretchr/testify/mock"
)
type MockSomething struct {
mock.Mock
}
// implement Something interface
func (m *MockSomething) DoSomething(number int) (bool, error) {
args := m.Called(number)
return args.Bool(0), args.Error(1)
}
```
--
### `mock`
```go
func TestSomething(t *testing.T) {
something := new(MockSomething)
something.On("DoSomething", 1).Return(true, nil)
// use something
something.AssertExpectations(t)
}
```
--
### Mockery
`github.com/vektra/mockery`
```bash
mockery -name Something -inpkg -testonly
```
---
## Example 1: Calculator
--
### Outline
`Calculate` function accepts a number `x` and calculates a magic value.
--
### Task 1
`Calculate` returns `x+2`
----
*Hint: write a single test method with manual assertion*
--
### Task 2
`Calculate` returns:
- `x+1` if `x` is odd
- `x+2` otherwise
----
*Hint: write subtests with testify assertions*
--
### Task 3
`Calculate` returns:
- `0` if `x` is zero
- `x+1` if `x` is odd
- `x+2` otherwise
----
*Hint: write table-driven tests*
---
## Example 2: Car Rental
--
### Outline
Implement a Car Rental service that finds a car matching certain criteria:
- color
- brand
- model
(car should match at least brand and model, color is optional)
--
### Outline
Return information to the caller:
- Car information
--
<img src="img/car-rental-service-1.svg" alt="Car rental service" class="image stretch">
--
### Task 1
Implement the car rental service
Defer making decisions on finding cars
----
*Hint: generate mocks*
--
### Task 2
Implement a Car repository
Write tests for business logic (find a matching car)
----
*Hint: use table-driven tests*
--
### Task 3
Rewrite tests for the car rental service using real implementations.
--
### Task 4
Add generated ID to the returned Rent.
----
*Hint: mocks first then real implementation*
--
<img src="img/car-rental-service-2.svg" alt="Car rental service" class="image stretch">
--
### Task 4
Write integration tests for remote ID generator
----
*Hint: use any of the integration test strategies*
---
# The End