diff --git a/README.md b/README.md index 67437137..c170f2c2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ![Build Status](https://travis-ci.org/larien/learn-go-with-tests.svg?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/larien/learn-go-with-tests)](https://goreportcard.com/report/github.com/quii/learn-go-with-tests) -- Formatos: [Gitbook](https://larien.gitbook.io/aprenda-go-com-testes), [EPUB or PDF](https://github.com/larien/learn-go-with-tests/releases) +- Formatos: [Gitbook](https://larien.gitbook.io/aprenda-go-com-testes), [EPUB ou PDF](https://github.com/larien/learn-go-with-tests/releases) - Versão original: [English](https://quii.gitbook.io/learn-go-with-tests/) - Traduções: [中文](https://studygolang.gitbook.io/learn-go-with-tests) @@ -27,7 +27,7 @@ 4. [Iteração](primeiros-passos-com-go/iteracao.md) - Aprenda sobre `for` e benchmarking. 5. [Arrays e slices](primeiros-passos-com-go/arrays-e-slices.md) - Aprenda sobre arrays, slices, `len`, variáveis recebidas como argumentos, `range` e cobertura de testes. 6. [Estruturas, métodos e interfaces](primeiros-passos-com-go/structs-methods-and-interfaces.md) - Aprenda sobre `structs`, métodos, `interface` e testes orientados a tabela \(table driven tests\). -7. [Ponteiros e erros](primeiros-passos-com-go/pointers-and-errors.md) - Aprenda sobre ponteiros e erros. +7. [Ponteiros e erros](primeiros-passos-com-go/ponteiros-e-erros.md) - Aprenda sobre ponteiros e erros. 8. [Maps](primeiros-passos-com-go/maps.md) - Aprenda sobre armazenamento de valores na estrutura de dados `map`. 9. [Injeção de dependência](primeiros-passos-com-go/injecao-de-dependencia.md) - Aprenda sobre injeção de dependência, qual sua relação com interfaces e uma introdução a I/O. 10. [Mocking](primeiros-passos-com-go/mocks.md) - Use injeção de dependência com mocking para testar um código sem nenhum teste. @@ -37,7 +37,7 @@ 14. [Sync](primeiros-passos-com-go/sync.md) - Conheça algumas funcionalidades do pacote `sync`, como `WaitGroup` e `Mutex`. 15. [Context](primeiros-passos-com-go/context.md) - Use o pacote `context` para gerenciar e cancelar processos de longa duração. -### Crie uma aplicação +### Criando uma aplicação Agora que você já deu seus _Primeiros Passos com Go_, esperamos que você tenha uma base sólida das principais funcionalidades da linguagem e como TDD funciona. diff --git a/SUMMARY.md b/SUMMARY.md index 5fc3c17d..6551937d 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -11,7 +11,7 @@ - [Iteração](primeiros-passos-com-go/iteracao.md) - [Arrays e slices](primeiros-passos-com-go/arrays-e-slices.md) - [Structs, métodos e interfaces](primeiros-passos-com-go/structs-methods-and-interfaces.md) -- [Ponteiros e erros](primeiros-passos-com-go/pointers-and-errors.md) +- [Ponteiros e erros](primeiros-passos-com-go/ponteiros-e-erros.md) - [Maps](primeiros-passos-com-go/maps.md) - [Injeção de dependência](primeiros-passos-com-go/dependency-injection.md) - [Mocking](primeiros-passos-com-go/mocks.md) diff --git a/build.books.sh b/build.books.sh index 7255fc2b..b3f75344 100755 --- a/build.books.sh +++ b/build.books.sh @@ -11,7 +11,7 @@ docker run -v `pwd`:/source jagregory/pandoc -o aprenda-go-com-testes.pdf --late primeiros-passos-com-go/iteracao.md \ primeiros-passos-com-go/arrays-e-slices.md \ primeiros-passos-com-go/structs-methods-and-interfaces.md \ - primeiros-passos-com-go/pointers-and-errors.md \ + primeiros-passos-com-go/ponteiros-e-erros.md \ primeiros-passos-com-go/maps.md \ primeiros-passos-com-go/injecao-de-dependencia.md \ primeiros-passos-com-go/mocks.md \ @@ -39,7 +39,7 @@ docker run -v `pwd`:/source jagregory/pandoc -o aprenda-go-com-testes.epub --lat primeiros-passos-com-go/iteracao.md \ primeiros-passos-com-go/arrays-e-slices.md \ primeiros-passos-com-go/structs-methods-and-interfaces.md \ - primeiros-passos-com-go/pointers-and-errors.md \ + primeiros-passos-com-go/ponteiros-e-erros.md \ primeiros-passos-com-go/maps.md \ primeiros-passos-com-go/injecao-de-dependencia.md \ primeiros-passos-com-go/mocks.md \ diff --git a/pointers/v1/wallet.go b/pointers/v1/wallet.go deleted file mode 100644 index 44b3199f..00000000 --- a/pointers/v1/wallet.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import "fmt" - -// Bitcoin represents a number of Bitcoins -type Bitcoin int - -func (b Bitcoin) String() string { - return fmt.Sprintf("%d BTC", b) -} - -// Wallet stores the number of Bitcoin someone owns -type Wallet struct { - balance Bitcoin -} - -// Deposit will add some Bitcoin to a wallet -func (w *Wallet) Deposit(amount Bitcoin) { - w.balance += amount -} - -// Balance returns the number of Bitcoin a wallet has -func (w *Wallet) Balance() Bitcoin { - return w.balance -} diff --git a/pointers/v1/wallet_test.go b/pointers/v1/wallet_test.go deleted file mode 100644 index bb8a1c17..00000000 --- a/pointers/v1/wallet_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package main - -import ( - "testing" -) - -func TestWallet(t *testing.T) { - - wallet := Wallet{} - - wallet.Deposit(Bitcoin(10)) - - got := wallet.Balance() - - want := Bitcoin(10) - - if got != want { - t.Errorf("got %s want %s", got, want) - } -} diff --git a/pointers/v2/wallet.go b/pointers/v2/wallet.go deleted file mode 100644 index 81cc897d..00000000 --- a/pointers/v2/wallet.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import "fmt" - -// Bitcoin represents a number of Bitcoins -type Bitcoin int - -func (b Bitcoin) String() string { - return fmt.Sprintf("%d BTC", b) -} - -// Wallet stores the number of Bitcoin someone owns -type Wallet struct { - balance Bitcoin -} - -// Deposit will add some Bitcoin to a wallet -func (w *Wallet) Deposit(amount Bitcoin) { - w.balance += amount -} - -// Withdraw subtracts some Bitcoin from the wallet -func (w *Wallet) Withdraw(amount Bitcoin) { - w.balance -= amount -} - -// Balance returns the number of Bitcoin a wallet has -func (w *Wallet) Balance() Bitcoin { - return w.balance -} diff --git a/pointers/v2/wallet_test.go b/pointers/v2/wallet_test.go deleted file mode 100644 index f114be4f..00000000 --- a/pointers/v2/wallet_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "testing" -) - -func TestWallet(t *testing.T) { - - assertBalance := func(t *testing.T, wallet Wallet, want Bitcoin) { - t.Helper() - got := wallet.Balance() - - if got != want { - t.Errorf("got %s want %s", got, want) - } - } - - t.Run("Deposit", func(t *testing.T) { - wallet := Wallet{} - wallet.Deposit(Bitcoin(10)) - assertBalance(t, wallet, Bitcoin(10)) - }) - - t.Run("Withdraw", func(t *testing.T) { - wallet := Wallet{balance: Bitcoin(20)} - wallet.Withdraw(10) - assertBalance(t, wallet, Bitcoin(10)) - }) - -} diff --git a/pointers/v3/wallet.go b/pointers/v3/wallet.go deleted file mode 100644 index 2e32b16a..00000000 --- a/pointers/v3/wallet.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "errors" - "fmt" -) - -// Bitcoin represents a number of Bitcoins -type Bitcoin int - -func (b Bitcoin) String() string { - return fmt.Sprintf("%d BTC", b) -} - -// Wallet stores the number of Bitcoin someone owns -type Wallet struct { - balance Bitcoin -} - -// Deposit will add some Bitcoin to a wallet -func (w *Wallet) Deposit(amount Bitcoin) { - w.balance += amount -} - -// Withdraw subtracts some Bitcoin from the wallet, returning an error if it cannot be performed -func (w *Wallet) Withdraw(amount Bitcoin) error { - - if amount > w.balance { - return errors.New("oh no") - } - - w.balance -= amount - return nil -} - -// Balance returns the number of Bitcoin a wallet has -func (w *Wallet) Balance() Bitcoin { - return w.balance -} diff --git a/pointers/v3/wallet_test.go b/pointers/v3/wallet_test.go deleted file mode 100644 index 3ece5e7b..00000000 --- a/pointers/v3/wallet_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "testing" -) - -func TestWallet(t *testing.T) { - - assertBalance := func(t *testing.T, wallet Wallet, want Bitcoin) { - t.Helper() - got := wallet.Balance() - - if got != want { - t.Errorf("got %s want %s", got, want) - } - } - - assertError := func(t *testing.T, err error) { - t.Helper() - if err == nil { - t.Error("wanted an error but didnt get one") - } - } - - t.Run("Deposit", func(t *testing.T) { - wallet := Wallet{} - wallet.Deposit(Bitcoin(10)) - - assertBalance(t, wallet, Bitcoin(10)) - }) - - t.Run("Withdraw with funds", func(t *testing.T) { - wallet := Wallet{Bitcoin(20)} - wallet.Withdraw(Bitcoin(10)) - - assertBalance(t, wallet, Bitcoin(10)) - }) - - t.Run("Withdraw insufficient funds", func(t *testing.T) { - startingBalance := Bitcoin(20) - wallet := Wallet{startingBalance} - err := wallet.Withdraw(Bitcoin(100)) - - assertBalance(t, wallet, startingBalance) - assertError(t, err) - }) -} diff --git a/pointers/v4/wallet.go b/pointers/v4/wallet.go deleted file mode 100644 index 082b3945..00000000 --- a/pointers/v4/wallet.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "errors" - "fmt" -) - -// Bitcoin represents a number of Bitcoins -type Bitcoin int - -func (b Bitcoin) String() string { - return fmt.Sprintf("%d BTC", b) -} - -// Wallet stores the number of Bitcoin someone owns -type Wallet struct { - balance Bitcoin -} - -// Deposit will add some Bitcoin to a wallet -func (w *Wallet) Deposit(amount Bitcoin) { - w.balance += amount -} - -// ErrInsufficientFunds means a wallet does not have enough Bitcoin to perform a withdraw -var ErrInsufficientFunds = errors.New("cannot withdraw, insufficient funds") - -// Withdraw subtracts some Bitcoin from the wallet, returning an error if it cannot be performed -func (w *Wallet) Withdraw(amount Bitcoin) error { - - if amount > w.balance { - return ErrInsufficientFunds - } - - w.balance -= amount - return nil -} - -// Balance returns the number of Bitcoin a wallet has -func (w *Wallet) Balance() Bitcoin { - return w.balance -} diff --git a/pointers/v4/wallet_test.go b/pointers/v4/wallet_test.go deleted file mode 100644 index dd20d82c..00000000 --- a/pointers/v4/wallet_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package main - -import ( - "testing" -) - -func TestWallet(t *testing.T) { - - t.Run("Deposit", func(t *testing.T) { - wallet := Wallet{} - wallet.Deposit(Bitcoin(10)) - - assertBalance(t, wallet, Bitcoin(10)) - }) - - t.Run("Withdraw with funds", func(t *testing.T) { - wallet := Wallet{Bitcoin(20)} - err := wallet.Withdraw(Bitcoin(10)) - - assertBalance(t, wallet, Bitcoin(10)) - assertNoError(t, err) - }) - - t.Run("Withdraw insufficient funds", func(t *testing.T) { - startingBalance := Bitcoin(20) - wallet := Wallet{startingBalance} - err := wallet.Withdraw(Bitcoin(100)) - - assertBalance(t, wallet, startingBalance) - assertError(t, err, ErrInsufficientFunds) - }) -} - -func assertBalance(t *testing.T, wallet Wallet, want Bitcoin) { - t.Helper() - got := wallet.Balance() - - if got != want { - t.Errorf("got '%s' want '%s'", got, want) - } -} - -func assertNoError(t *testing.T, got error) { - t.Helper() - if got != nil { - t.Fatal("got an error but didnt want one") - } -} - -func assertError(t *testing.T, got error, want error) { - t.Helper() - if got == nil { - t.Fatal("didn't get an error but wanted one") - } - - if got != want { - t.Errorf("got '%s', want '%s'", got, want) - } -} diff --git a/ponteiros/v1/carteira.go b/ponteiros/v1/carteira.go new file mode 100644 index 00000000..4992eb9a --- /dev/null +++ b/ponteiros/v1/carteira.go @@ -0,0 +1,25 @@ +package main + +import "fmt" + +// Bitcoin representa o número de Bitcoins +type Bitcoin int + +func (b Bitcoin) String() string { + return fmt.Sprintf("%d BTC", b) +} + +// Carteira armazena o número de bitcoins que uma pessoa tem +type Carteira struct { + saldo Bitcoin +} + +// Depositar vai adicionar Bitcoins à carteira +func (c *Carteira) Depositar(quantidade Bitcoin) { + c.saldo += quantidade +} + +// Saldo retorna o número de Bitcoins que uma carteira tem +func (c *Carteira) Saldo() Bitcoin { + return c.saldo +} diff --git a/ponteiros/v1/carteira_test.go b/ponteiros/v1/carteira_test.go new file mode 100644 index 00000000..ab268daf --- /dev/null +++ b/ponteiros/v1/carteira_test.go @@ -0,0 +1,19 @@ +package main + +import ( + "testing" +) + +func TestCarteira(t *testing.T) { + carteira := Carteira{} + + carteira.Depositar(Bitcoin(10)) + + resultado := carteira.Saldo() + + esperado := Bitcoin(10) + + if resultado != esperado { + t.Errorf("resultado %s, esperado %s", resultado, esperado) + } +} diff --git a/ponteiros/v2/carteira.go b/ponteiros/v2/carteira.go new file mode 100644 index 00000000..bcc5ad47 --- /dev/null +++ b/ponteiros/v2/carteira.go @@ -0,0 +1,30 @@ +package main + +import "fmt" + +// Bitcoin representa o número de Bitcoins +type Bitcoin int + +func (b Bitcoin) String() string { + return fmt.Sprintf("%d BTC", b) +} + +// Carteira armazena o número de bitcoins que uma pessoa tem +type Carteira struct { + saldo Bitcoin +} + +// Depositar vai adicionar Bitcoins à carteira +func (c *Carteira) Depositar(quantidade Bitcoin) { + c.saldo += quantidade +} + +// Retirar substrai alguns Bitcoins da carteira +func (c *Carteira) Retirar(quantidade Bitcoin) { + c.saldo -= quantidade +} + +// Saldo retorna o número de Bitcoins que uma carteira tem +func (c *Carteira) Saldo() Bitcoin { + return c.saldo +} diff --git a/ponteiros/v2/carteira_test.go b/ponteiros/v2/carteira_test.go new file mode 100644 index 00000000..8d31aeba --- /dev/null +++ b/ponteiros/v2/carteira_test.go @@ -0,0 +1,29 @@ +package main + +import ( + "testing" +) + +func TestCarteira(t *testing.T) { + confirmaSaldo := func(t *testing.T, carteira Carteira, esperado Bitcoin) { + t.Helper() + resultado := carteira.Saldo() + + if resultado != esperado { + t.Errorf("resultado %s, esperado %s", resultado, esperado) + } + } + + t.Run("Depositar", func(t *testing.T) { + carteira := Carteira{} + carteira.Depositar(Bitcoin(10)) + confirmaSaldo(t, carteira, Bitcoin(10)) + }) + + t.Run("Retirar", func(t *testing.T) { + carteira := Carteira{saldo: Bitcoin(20)} + carteira.Retirar(10) + confirmaSaldo(t, carteira, Bitcoin(10)) + }) + +} diff --git a/ponteiros/v3/carteira.go b/ponteiros/v3/carteira.go new file mode 100644 index 00000000..825ce7f3 --- /dev/null +++ b/ponteiros/v3/carteira.go @@ -0,0 +1,39 @@ +package main + +import ( + "errors" + "fmt" +) + +// Bitcoin representa o número de Bitcoins +type Bitcoin int + +func (b Bitcoin) String() string { + return fmt.Sprintf("%d BTC", b) +} + +// Carteira armazena o número de bitcoins que uma pessoa tem +type Carteira struct { + saldo Bitcoin +} + +// Depositar vai adicionar Bitcoins à carteira +func (c *Carteira) Depositar(quantidade Bitcoin) { + c.saldo += quantidade +} + +// Retirar substrai alguns Bitcoins da carteira +func (c *Carteira) Retirar(quantidade Bitcoin) error { + + if quantidade > c.saldo { + return errors.New("eita") + } + + c.saldo -= quantidade + return nil +} + +// Saldo retorna o número de Bitcoins que uma carteira tem +func (c *Carteira) Saldo() Bitcoin { + return c.saldo +} diff --git a/ponteiros/v3/carteira_test.go b/ponteiros/v3/carteira_test.go new file mode 100644 index 00000000..8a13688b --- /dev/null +++ b/ponteiros/v3/carteira_test.go @@ -0,0 +1,46 @@ +package main + +import ( + "testing" +) + +func TestCarteira(t *testing.T) { + confirmaSaldo := func(t *testing.T, carteira Carteira, esperado Bitcoin) { + t.Helper() + resultado := carteira.Saldo() + + if resultado != esperado { + t.Errorf("resultado %s, esperado %s", resultado, esperado) + } + } + + confirmaErro := func(t *testing.T, erro error) { + t.Helper() + if erro == nil { + t.Error("esperava um erro, mas nenhum ocorreu.") + } + } + + t.Run("Depositar", func(t *testing.T) { + carteira := Carteira{} + carteira.Depositar(Bitcoin(10)) + + confirmaSaldo(t, carteira, Bitcoin(10)) + }) + + t.Run("Retirar com saldo suficiente", func(t *testing.T) { + carteira := Carteira{Bitcoin(20)} + carteira.Retirar(Bitcoin(10)) + + confirmaSaldo(t, carteira, Bitcoin(10)) + }) + + t.Run("Retirar com saldo insuficiente", func(t *testing.T) { + saldoInicial := Bitcoin(20) + carteira := Carteira{saldoInicial} + erro := carteira.Retirar(Bitcoin(100)) + + confirmaSaldo(t, carteira, saldoInicial) + confirmaErro(t, erro) + }) +} diff --git a/ponteiros/v4/carteira.go b/ponteiros/v4/carteira.go new file mode 100644 index 00000000..db2d42e2 --- /dev/null +++ b/ponteiros/v4/carteira.go @@ -0,0 +1,42 @@ +package main + +import ( + "errors" + "fmt" +) + +// Bitcoin representa o número de Bitcoins +type Bitcoin int + +func (b Bitcoin) String() string { + return fmt.Sprintf("%d BTC", b) +} + +// Carteira armazena o número de bitcoins que uma pessoa tem +type Carteira struct { + saldo Bitcoin +} + +// Depositar vai adicionar Bitcoins à carteira +func (c *Carteira) Depositar(quantidade Bitcoin) { + c.saldo += quantidade +} + +// ErroSaldoInsuficiente significa que uma carteira não tem Bitcoins suficientes para fazer uma retirada +var ErroSaldoInsuficiente = errors.New("não é possível retirar: saldo insuficiente") + +// Retirar substrai alguns Bitcoins da carteira, retorna um erro se não puder ser executado +func (c *Carteira) Retirar(quantidade Bitcoin) error { + + if quantidade > c.saldo { + return ErroSaldoInsuficiente + } + + c.saldo -= quantidade + return nil +} + +// Saldo retorna o número de Bitcoins que uma carteira tem +func (c *Carteira) Saldo() Bitcoin { + return c.saldo +} diff --git a/ponteiros/v4/carteira_test.go b/ponteiros/v4/carteira_test.go new file mode 100644 index 00000000..dfbc05bf --- /dev/null +++ b/ponteiros/v4/carteira_test.go @@ -0,0 +1,58 @@ +package main + +import ( + "testing" +) + +func TestCarteira(t *testing.T) { + t.Run("Depositar", func(t *testing.T) { + carteira := Carteira{} + carteira.Depositar(Bitcoin(10)) + + confirmaSaldo(t, carteira, Bitcoin(10)) + }) + + t.Run("Retirar com saldo suficiente", func(t *testing.T) { + carteira := Carteira{Bitcoin(20)} + erro := carteira.Retirar(Bitcoin(10)) + + confirmaSaldo(t, carteira, Bitcoin(10)) + confirmaErroInexistente(t, erro) + }) + + t.Run("Retirar com saldo insuficiente", func(t *testing.T) { + saldoInicial := Bitcoin(20) + carteira := Carteira{saldoInicial} + erro := carteira.Retirar(Bitcoin(100)) + + confirmaSaldo(t, carteira, saldoInicial) + confirmaErro(t, erro, ErroSaldoInsuficiente) + }) +} + +func confirmaSaldo(t *testing.T, carteira Carteira, esperado Bitcoin) { + t.Helper() + resultado := carteira.Saldo() + + if resultado != esperado { + t.Errorf("resultado %s, esperado %s", resultado, esperado) + } +} + +func confirmaErroInexistente(t *testing.T, resultado error) { + t.Helper() + if resultado != nil { + t.Fatal("erro inesperado recebido") + } +} + +func confirmaErro(t *testing.T, resultado error, esperado error) { + t.Helper() + if resultado == nil { + t.Fatal("esperava um erro, mas nenhum ocorreu") + } + + if resultado != esperado { + t.Errorf("erro resultado %s, erro esperado %s", resultado, esperado) + } +} diff --git a/primeiros-passos-com-go/pointers-and-errors.md b/primeiros-passos-com-go/pointers-and-errors.md deleted file mode 100644 index 87cdec16..00000000 --- a/primeiros-passos-com-go/pointers-and-errors.md +++ /dev/null @@ -1,662 +0,0 @@ -# Ponteiros e erros - -[**You can find all the code for this chapter here**](https://github.com/quii/learn-go-with-tests/tree/master/pointers) - -We learned about structs in the last section which let us capture a number of values related around a concept. - -At some point you may wish to use structs to manage state, exposing methods to let users change the state in a way that you can control. - -**Fintech loves Go** and uhhh bitcoins? So let's show what an amazing banking system we can make. - -Let's make a `Wallet` struct which let's us deposit `Bitcoin`. - -## Write the test first - -```go -func TestWallet(t *testing.T) { - - wallet := Wallet{} - - wallet.Deposit(10) - - got := wallet.Balance() - want := 10 - - if got != want { - t.Errorf("got %d want %d", got, want) - } -} -``` - -In the [previous example](structs-methods-and-interfaces.md) we accessed fields directly with the field name, however in our _very secure wallet_ we don't want to expose our inner state to the rest of the world. We want to control access via methods. - -## Try to run the test - -`./wallet_test.go:7:12: undefined: Wallet` - -## Write the minimal amount of code for the test to run and check the failing test output - -The compiler doesn't know what a `Wallet` is so let's tell it. - -```go -type Wallet struct { } -``` - -Now we've made our wallet, try and run the test again - -```go -./wallet_test.go:9:8: wallet.Deposit undefined (type Wallet has no field or method Deposit) -./wallet_test.go:11:15: wallet.Balance undefined (type Wallet has no field or method Balance) -``` - -We need to define these methods. - -Remember to only do enough to make the tests run. We need to make sure our test fails correctly with a clear error message. - -```go -func (w Wallet) Deposit(amount int) { - -} - -func (w Wallet) Balance() int { - return 0 -} -``` - -If this syntax is unfamiliar go back and read the structs section. - -The tests should now compile and run - -`wallet_test.go:15: got 0 want 10` - -## Write enough code to make it pass - -We will need some kind of _balance_ variable in our struct to store the state - -```go -type Wallet struct { - balance int -} -``` - -In Go if a symbol \(so variables, types, functions et al\) starts with a lowercase symbol then it is private _outside the package it's defined in_. - -In our case we want our methods to be able to manipulate this value but no one else. - -Remember we can access the internal `balance` field in the struct using the "receiver" variable. - -```go -func (w Wallet) Deposit(amount int) { - w.balance += amount -} - -func (w Wallet) Balance() int { - return w.balance -} -``` - -With our career in fintech secured, run our tests and bask in the passing test - -`wallet_test.go:15: got 0 want 10` - -### ???? - -Well this is confusing, our code looks like it should work, we add the new amount onto our balance and then the balance method should return the current state of it. - -In Go, **when you call a function or a method the arguments are** _**copied**_. - -When calling `func (w Wallet) Deposit(amount int)` the `w` is a copy of whatever we called the method from. - -Without getting too computer-sciency, when you create a value - like a wallet, it is stored somewhere in memory. You can find out what the _address_ of that bit of memory with `&myVal`. - -Experiment by adding some prints to your code - -```go -func TestWallet(t *testing.T) { - - wallet := Wallet{} - - wallet.Deposit(10) - - got := wallet.Balance() - - fmt.Printf("address of balance in test is %v \n", &wallet.balance) - - want := 10 - - if got != want { - t.Errorf("got %d want %d", got, want) - } -} -``` - -```go -func (w Wallet) Deposit(amount int) { - fmt.Printf("address of balance in Deposit is %v \n", &w.balance) - w.balance += amount -} -``` - -The `\n` escape character, prints new line after outputting the memory address. We get the pointer to a thing with the address of symbol; `&`. - -Now re-run the test - -```text -address of balance in Deposit is 0xc420012268 -address of balance in test is 0xc420012260 -``` - -You can see that the addresses of the two balances are different. So when we change the value of the balance inside the code, we are working on a copy of what came from the test. Therefore the balance in the test is unchanged. - -We can fix this with _pointers_. [Pointers](https://gobyexample.com/pointers) let us _point_ to some values and then let us change them. So rather than taking a copy of the Wallet, we take a pointer to the wallet so we can change it. - -```go -func (w *Wallet) Deposit(amount int) { - w.balance += amount -} - -func (w *Wallet) Balance() int { - return w.balance -} -``` - -The difference is the receiver type is `*Wallet` rather than `Wallet` which you can read as "a pointer to a wallet". - -Try and re-run the tests and they should pass. - -## Refactor - -We said we were making a Bitcoin wallet but we have not mentioned them so far. We've been using `int` because they're a good type for counting things! - -It seems a bit overkill to create a `struct` for this. `int` is fine in terms of the way it works but it's not descriptive. - -Go lets you create new types from existing ones. - -The syntax is `type MyName OriginalType` - -```go -type Bitcoin int - -type Wallet struct { - balance Bitcoin -} - -func (w *Wallet) Deposit(amount Bitcoin) { - w.balance += amount -} - -func (w *Wallet) Balance() Bitcoin { - return w.balance -} -``` - -```go -func TestWallet(t *testing.T) { - - wallet := Wallet{} - - wallet.Deposit(Bitcoin(10)) - - got := wallet.Balance() - - want := Bitcoin(10) - - if got != want { - t.Errorf("got %d want %d", got, want) - } -} -``` - -To make `Bitcoin` you just use the syntax `Bitcoin(999)`. - -By doing this we're making a new type and we can declare _methods_ on them. This can be very useful when you want to add some domain specific functionality on top of existing types. - -Let's implement [Stringer](https://golang.org/pkg/fmt/#Stringer) on Bitcoin - -```go -type Stringer interface { - String() string -} -``` - -This interface is defined in the `fmt` package and lets you define how your type is printed when used with the `%s` format string in prints. - -```go -func (b Bitcoin) String() string { - return fmt.Sprintf("%d BTC", b) -} -``` - -As you can see, the syntax for creating a method on a type alias is the same as it is on a struct. - -Next we need to update our test format strings so they will use `String()` instead. - -```go - if got != want { - t.Errorf("got %s want %s", got, want) - } -``` - -To see this in action, deliberately break the test so we can see it - -`wallet_test.go:18: got 10 BTC want 20 BTC` - -This makes it clearer what's going on in our test. - -The next requirement is for a `Withdraw` function. - -## Write the test first - -Pretty much the opposite of `Deposit()` - -```go -func TestWallet(t *testing.T) { - - t.Run("Deposit", func(t *testing.T) { - wallet := Wallet{} - - wallet.Deposit(Bitcoin(10)) - - got := wallet.Balance() - - want := Bitcoin(10) - - if got != want { - t.Errorf("got %s want %s", got, want) - } - }) - - t.Run("Withdraw", func(t *testing.T) { - wallet := Wallet{balance: Bitcoin(20)} - - wallet.Withdraw(Bitcoin(10)) - - got := wallet.Balance() - - want := Bitcoin(10) - - if got != want { - t.Errorf("got %s want %s", got, want) - } - }) -} -``` - -## Try to run the test - -`./wallet_test.go:26:9: wallet.Withdraw undefined (type Wallet has no field or method Withdraw)` - -## Write the minimal amount of code for the test to run and check the failing test output - -```go -func (w *Wallet) Withdraw(amount Bitcoin) { - -} -``` - -`wallet_test.go:33: got 20 BTC want 10 BTC` - -## Write enough code to make it pass - -```go -func (w *Wallet) Withdraw(amount Bitcoin) { - w.balance -= amount -} -``` - -## Refactor - -There's some duplication in our tests, lets refactor that out. - -```go -func TestWallet(t *testing.T) { - - assertBalance := func(t *testing.T, wallet Wallet, want Bitcoin) { - t.Helper() - got := wallet.Balance() - - if got != want { - t.Errorf("got %s want %s", got, want) - } - } - - t.Run("Deposit", func(t *testing.T) { - wallet := Wallet{} - wallet.Deposit(Bitcoin(10)) - assertBalance(t, wallet, Bitcoin(10)) - }) - - t.Run("Withdraw", func(t *testing.T) { - wallet := Wallet{balance: Bitcoin(20)} - wallet.Withdraw(Bitcoin(10)) - assertBalance(t, wallet, Bitcoin(10)) - }) - -} -``` - -What should happen if you try to `Withdraw` more than is left in the account? For now, our requirement is to assume there is not an overdraft facility. - -How do we signal a problem when using `Withdraw` ? - -In Go, if you want to indicate an error it is idiomatic for your function to return an `err` for the caller to check and act on. - -Let's try this out in a test. - -## Write the test first - -```go -t.Run("Withdraw insufficient funds", func(t *testing.T) { - startingBalance := Bitcoin(20) - wallet := Wallet{startingBalance} - err := wallet.Withdraw(Bitcoin(100)) - - assertBalance(t, wallet, startingBalance) - - if err == nil { - t.Error("wanted an error but didn't get one") - } -}) -``` - -We want `Withdraw` to return an error _if_ you try to take out more than you have and the balance should stay the same. - -We then check an error has returned by failing the test if it is `nil`. - -`nil` is synonymous with `null` from other programming languages. Errors can be `nil` because the return type of `Withdraw` will be `error`, which is an interface. If you see a function that takes arguments or returns values that are interfaces, they can be nillable. - -Like `null` if you try to access a value that is `nil` it will throw a **runtime panic**. This is bad! You should make sure that you check for nils. - -## Try and run the test - -`./wallet_test.go:31:25: wallet.Withdraw(Bitcoin(100)) used as value` - -The wording is perhaps a little unclear, but our previous intent with `Withdraw` was just to call it, it will never return a value. To make this compile we will need to change it so it has a return type. - -## Write the minimal amount of code for the test to run and check the failing test output - -```go -func (w *Wallet) Withdraw(amount Bitcoin) error { - w.balance -= amount - return nil -} -``` - -Again, it is very important to just write enough code to satisfy the compiler. We correct our `Withdraw` method to return `error` and for now we have to return _something_ so let's just return `nil`. - -## Write enough code to make it pass - -```go -func (w *Wallet) Withdraw(amount Bitcoin) error { - - if amount > w.balance { - return errors.New("oh no") - } - - w.balance -= amount - return nil -} -``` - -Remember to import `errors` into your code. - -`errors.New` creates a new `error` with a message of your choosing. - -## Refactor - -Let's make a quick test helper for our error check just to help our test read clearer - -```go -assertError := func(t *testing.T, err error) { - t.Helper() - if err == nil { - t.Error("wanted an error but didnt get one") - } -} -``` - -And in our test - -```go -t.Run("Withdraw insufficient funds", func(t *testing.T) { - wallet := Wallet{Bitcoin(20)} - err := wallet.Withdraw(Bitcoin(100)) - - assertBalance(t, wallet, Bitcoin(20)) - assertError(t, err) -}) -``` - -Hopefully when returning an error of "oh no" you were thinking that we _might_ iterate on that because it doesn't seem that useful to return. - -Assuming that the error ultimately gets returned to the user, let's update our test to assert on some kind of error message rather than just the existence of an error. - -## Write the test first - -Update our helper for a `string` to compare against. - -```go -assertError := func(t *testing.T, got error, want string) { - t.Helper() - if got == nil { - t.Fatal("didn't get an error but wanted one") - } - - if got.Error() != want { - t.Errorf("got '%s', want '%s'", got, want) - } -} -``` - -And then update the caller - -```go -t.Run("Withdraw insufficient funds", func(t *testing.T) { - startingBalance := Bitcoin(20) - wallet := Wallet{startingBalance} - err := wallet.Withdraw(Bitcoin(100)) - - assertBalance(t, wallet, startingBalance) - assertError(t, err, "cannot withdraw, insufficient funds") -}) -``` - -We've introduced `t.Fatal` which will stop the test if it is called. This is because we don't want to make any more assertions on the error returned if there isn't one around. Without this the test would carry on to the next step and panic because of a nil pointer. - -## Try to run the test - -`wallet_test.go:61: got err 'oh no' want 'cannot withdraw, insufficient funds'` - -## Write enough code to make it pass - -```go -func (w *Wallet) Withdraw(amount Bitcoin) error { - - if amount > w.balance { - return errors.New("cannot withdraw, insufficient funds") - } - - w.balance -= amount - return nil -} -``` - -## Refactor - -We have duplication of the error message in both the test code and the `Withdraw` code. - -It would be really annoying for the test to fail if someone wanted to re-word the error and it's just too much detail for our test. We don't _really_ care what the exact wording is, just that some kind of meaningful error around withdrawing is returned given a certain condition. - -In Go, errors are values, so we can refactor it out into a variable and have a single source of truth for it. - -```go -var ErrInsufficientFunds = errors.New("cannot withdraw, insufficient funds") - -func (w *Wallet) Withdraw(amount Bitcoin) error { - - if amount > w.balance { - return ErrInsufficientFunds - } - - w.balance -= amount - return nil -} -``` - -The `var` keyword allows us to define values global to the package. - -This is a positive change in itself because now our `Withdraw` function looks very clear. - -Next we can refactor our test code to use this value instead of specific strings. - -```go -func TestWallet(t *testing.T) { - - t.Run("Deposit", func(t *testing.T) { - wallet := Wallet{} - wallet.Deposit(Bitcoin(10)) - assertBalance(t, wallet, Bitcoin(10)) - }) - - t.Run("Withdraw with funds", func(t *testing.T) { - wallet := Wallet{Bitcoin(20)} - wallet.Withdraw(Bitcoin(10)) - assertBalance(t, wallet, Bitcoin(10)) - }) - - t.Run("Withdraw insufficient funds", func(t *testing.T) { - wallet := Wallet{Bitcoin(20)} - err := wallet.Withdraw(Bitcoin(100)) - - assertBalance(t, wallet, Bitcoin(20)) - assertError(t, err, ErrInsufficientFunds) - }) -} - -func assertBalance(t *testing.T, wallet Wallet, want Bitcoin) { - t.Helper() - got := wallet.Balance() - - if got != want { - t.Errorf("got '%s' want '%s'", got, want) - } -} - -func assertError(t *testing.T, got error, want error) { - t.Helper() - if got == nil { - t.Fatal("didn't get an error but wanted one") - } - - if got != want { - t.Errorf("got '%s', want '%s'", got, want) - } -} -``` - -And now the test is easier to follow too. - -I have moved the helpers out of the main test function just so when someone opens up a file they can start reading our assertions first, rather than some helpers. - -Another useful property of tests is that they help us understand the _real_ usage of our code so we can make sympathetic code. We can see here that a developer can simply call our code and do an equals check to `ErrInsufficientFunds` and act accordingly. - -### Unchecked errors - -Whilst the Go compiler helps you a lot, sometimes there are things you can still miss and error handling can sometimes be tricky. - -There is one scenario we have not tested. To find it, run the following in a terminal to install `errcheck`, one of many linters available for Go. - -`go get -u github.com/kisielk/errcheck` - -Then, inside the directory with your code run `errcheck .` - -You should get something like - -`wallet_test.go:17:18: wallet.Withdraw(Bitcoin(10))` - -What this is telling us is that we have not checked the error being returned on that line of code. That line of code on my computer corresponds to our normal withdraw scenario because we have not checked that if the `Withdraw` is successful that an error is _not_ returned. - -Here is the final test code that accounts for this. - -```go -func TestWallet(t *testing.T) { - - t.Run("Deposit", func(t *testing.T) { - wallet := Wallet{} - wallet.Deposit(Bitcoin(10)) - - assertBalance(t, wallet, Bitcoin(10)) - }) - - t.Run("Withdraw with funds", func(t *testing.T) { - wallet := Wallet{Bitcoin(20)} - err := wallet.Withdraw(Bitcoin(10)) - - assertBalance(t, wallet, Bitcoin(10)) - assertNoError(t, err) - }) - - t.Run("Withdraw insufficient funds", func(t *testing.T) { - wallet := Wallet{Bitcoin(20)} - err := wallet.Withdraw(Bitcoin(100)) - - assertBalance(t, wallet, Bitcoin(20)) - assertError(t, err, ErrInsufficientFunds) - }) -} - -func assertBalance(t *testing.T, wallet Wallet, want Bitcoin) { - t.Helper() - got := wallet.Balance() - - if got != want { - t.Errorf("got %s want %s", got, want) - } -} - -func assertNoError(t *testing.T, got error) { - t.Helper() - if got != nil { - t.Fatal("got an error but didnt want one") - } -} - -func assertError(t *testing.T, got error, want error) { - t.Helper() - if got == nil { - t.Fatal("didn't get an error but wanted one") - } - - if got != want { - t.Errorf("got %s, want %s", got, want) - } -} -``` - -## Wrapping up - -### Pointers - -* Go copies values when you pass them to functions/methods so if you're writing a function that needs to mutate state you'll need it to take a pointer to the thing you want to change. -* The fact that Go takes a copy of values is useful a lot of the time but sometimes you wont want your system to make a copy of something, in which case you need to pass a reference. Examples could be very large data or perhaps things you intend only to have one instance of \(like database connection pools\). - -### nil - -* Pointers can be nil -* When a function returns a pointer to something, you need to make sure you check if it's nil or you might raise a runtime exception, the compiler wont help you here. -* Useful for when you want to describe a value that could be missing - -### Errors - -* Errors are the way to signify failure when calling a function/method. -* By listening to our tests we concluded that checking for a string in an error would result in a flaky test. So we refactored to use a meaningful value instead and this resulted in easier to test code and concluded this would be easier for users of our API too. -* This is not the end of the story with error handling, you can do more sophisticated things but this is just an intro. Later sections will cover more strategies. -* [Don’t just check errors, handle them gracefully](https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully) - -### Create new types from existing ones - -* Useful for adding more domain specific meaning to values -* Can let you implement interfaces - -Pointers and errors are a big part of writing Go that you need to get comfortable with. Thankfully the compiler will _usually_ help you out if you do something wrong, just take your time and read the error. - diff --git a/primeiros-passos-com-go/ponteiros-e-erros.md b/primeiros-passos-com-go/ponteiros-e-erros.md new file mode 100644 index 00000000..d7ee0e5a --- /dev/null +++ b/primeiros-passos-com-go/ponteiros-e-erros.md @@ -0,0 +1,661 @@ +# Ponteiros e erros + +[**Você pode encontrar todos os códigos deste capítulo aqui**](https://github.com/larien/learn-go-with-tests/tree/master/ponteiros) + +Aprendemos sobre estruturas na última seção, o que nos possibilita capturar valores com conceito relacionado. + +Em algum momento talvez você deseje utilizar estruturas para gerenciar valores, expondo métodos que permita aos usuários mudá-los de um jeito que você possa controlar. + +**[Fintechs](https://www.infowester.com/fintech.php) amam Go** e uhh bitcoins? Então vamos mostrar um sistema bancário incrível que podemos construir. + +Vamos construir uma estrutura de `Carteira` que possamos depositar `Bitcoin`. + +## Escreva o teste primeiro + +```go +func TestCarteira(t *testing.T) { + carteira := Carteira{} + + carteira.Depositar(10) + + resultado := carteira.Saldo() + esperado := 10 + + if resultado != esperado { + t.Errorf("resultado %s, esperado %s", resultado, esperado) + } +} +``` + +No [exemplo anterior](structs-methods-and-interfaces.md) acessamos campos diretamente pelo nome. Entretanto, na nossa _carteira super protegida_, não queremos expor o valor interno para o resto do mundo. Queremos controlar o acesso por meio de métodos. + +## Execute o teste + +`./carteira_test.go:7:12: undefined: Carteira` + +## Escreva o mínimo de código possível para fazer o teste rodar e verifique a saída do teste que tiver falhado + +O compilador não sabe o que uma `Carteira` é, então vamos declará-la. + +```go +type Carteira struct { } +``` + +Agora que declaramos nossa carteira, tente rodar o teste novamente: + +```go +./carteira_test.go:9:8: carteira.Depositar undefined (type Carteira has no field or method Depositar) +./carteira_test.go:11:15: carteira.Saldo undefined (type Carteira has no field or method Saldo) +``` + +Precisamos definir estes métodos. + +Lembre-se de apenas fazer o necessário para fazer os testes rodarem. Precisamos ter certeza que nossos testes falhem corretamente com uma mensagem de erro clara. + +```go +func (c Carteira) Depositar(quantidade int) { + +} + +func (c Carteira) Saldo() int { + return 0 +} +``` + +Se essa sintaxe não for familiar, dê uma lida na seção de estruturas. + +Os testes agora devem compilar e rodar: + +`carteira_test.go:15: resultado 0, esperado 10` + +## Escreva código o suficiente para fazer o teste passar + +Precisaremos de algum tipo de variável de _saldo_ em nossa estrutura para guardar o valor: + +```go +type Carteira struct { + saldo int +} +``` + +Em Go, se uma variável, tipo, função e etc, começam com uma letra minúsculo, então esta será privada para _outros pacotes que não seja o que a definiu_. + +No nosso caso, queremos que apenas nossos métodos sejam capazes de manipular os valores. + +Lembre-se que podemos acessar o valor interno do campo `saldo` usando a variável "receptora". + +```go +func (c Carteira) Depositar(quantidade int) { + c.saldo += quantidade +} + +func (c Carteira) Saldo() int { + return c.saldo +} +``` + +Com a nossa carreira em Fintechs segura, rode os testes para nos aquecermos para passarmos no teste. + +`carteira_test.go:15: resultado 0, esperado 10` + +### ???? + +Ok, isso é confuso. Parece que nosso código deveria funcionar, pois adicionamos nosso novo valor ao saldo e o método Saldo deveria retornar o valor atual. + +Em Go, **quando uma função ou um método é invocado, os argumentos são** _**copiados**_. + +Quando `func (c Carteira) Depositar(quantidade int)` é chamado, o `c` é uma cópia do valor de qualquer lugar que o método tenha sido chamado. + +Sem focar em Ciência da Computação, quando criamos um valor (como uma carteira), esse valor é alocado em algum lugar da memória. Você pode descobrir o _endereço_ desse bit de memória usando `&meuValor`. + +Experimente isso adicionando alguns prints no código: + +```go +func TestCarteira(t *testing.T) { + carteira := Carteira{} + + carteira.Depositar(10) + + resultado := carteira.Saldo() + + fmt.Printf("O endereço do saldo no teste é %v \n", &carteira.saldo) + + esperado := 10 + + if resultado != esperado { + t.Errorf("resultado %d, esperado %d", resultado, esperado) + } +} +``` + +```go +func (c Carteira) Depositar(quantidade int) { + fmt.Printf("O endereço do saldo no Depositar é %v \n", &c.saldo) + c.saldo += quantidade +} +``` + +O `\n` é um caractere de escape queeadiciona uma nova linha após imprimir o endereço de memória. Conseguimos acessar o ponteiro para algo com o símbolo de endereço `&`. + +Agora rode o teste novamente: + +```text +O endereço do saldo no Depositar é 0xc420012268 +O endereço do saldo no teste é is 0xc420012260 +``` + +Podemos ver que os endereços dos dois saldos são diferentes. Então, quando mudamos o valor de um dos saldos dentro do código, estamos trabalhando em uma cópia do que veio do teste. Portanto, o saldo no teste não é alterado. + +Podemos consertar isso com _ponteiros_. [Ponteiros](https://gobyexample.com/pointers) nos permitem _apontar_ para alguns valores e então mudá-los. Então, em vez de termos uma cópia da Carteira, usamos um ponteiro para a carteira para que possamos alterá-la. + +```go +func (c *Carteira) Depositar(quantidade int) { + c.saldo += quantidade +} + +func (c *Carteira) Saldo() int { + return c.saldo +} +``` + +A diferença é que o tipo do argumento é `*Carteira` em vez de `Carteira` que você pode ler como "um ponteiro para uma carteira". + +Rode novamente os testes e eles devem passar. + +## Refatoração + +Dissemos que estávamos fazendo uma carteira Bitcoin, mas até agora não os mencionamos. Estamos usando `int` porque é um bom tipo para contar coisas! + +Parece um pouco exagerado criar uma `struct` para isso. `int` é o suficiente nesse contexto, mas não é descritivo o suficiente. + +Go permite criarmos novos tipos a partir de tipos existentes. + +A sintaxe é `type MeuNome TipoOriginal` + +```go +type Bitcoin int + +type Carteira struct { + saldo Bitcoin +} + +func (c *Carteira) Depositar(quantidade Bitcoin) { + c.saldo += quantidade +} + +func (c *Carteira) Saldo() Bitcoin { + return c.saldo +} +``` + +```go +func TestCarteira(t *testing.T) { + + carteira := Carteira{} + + carteira.Depositar(Bitcoin(10)) + + resultado := carteira.Saldo() + + esperado := Bitcoin(10) + + if resultado != esperado { + t.Errorf("resultado %d, esperado %d", resultado, esperado) + } +} +``` + +Para criarmos `Bitcoin`, basta usar a sintaxe `Bitcoin(999)`. + +Ao fazermos isso, estamos criando um novo tipo e podemos declarar _métodos_ nele. Isto pode ser muito útil quando queremos adicionar funcionalidades de domínios específicos a tipos já existentes. + +Vamos implementar um [Stringer](https://golang.org/pkg/fmt/#Stringer) para o Bitcoin: + +```go +type Stringer interface { + String() string +} +``` + +Essa interface é definida no pacote `fmt` e permite definir como seu tipo é impresso quando utilizado com o operador de string `%s` em prints. + +```go +func (b Bitcoin) String() string { + return fmt.Sprintf("%d BTC", b) +} +``` + +Como podemos ver, a sintaxe para criar um método em um tipo definido por nós é a mesma que a utilizada em uma struct. + +Agora precisamos atualizar nossas impressões de strings no teste para que usem `String()`. + +```go + if resultado != esperado { + t.Errorf("resultado %s, esperado %s", resultado, esperado) + } +``` + +Para ver funcionando, quebre o teste de propósito para que possamos ver: + +`carteira_test.go:18: resultado 10 BTC, esperado 20 BTC` + +Isto deixa mais claro o que está acontecendo em nossos testes. + +O próximo requisito é criar uma função de `Retirar`. + +## Escreva o teste primeiro + +É basicamente o aposto da função `Depositar()`: + +```go +func TestCarteira(t *testing.T) { + t.Run("Depositar", func(t *testing.T) { + carteira := Carteira{} + + carteira.Depositar(Bitcoin(10)) + + resultado := carteira.Saldo() + + esperado := Bitcoin(10) + + if resultado != esperado { + t.Errorf("resultado %s, esperado %s", resultado, esperado) + } + }) + + t.Run("Retirar", func(t *testing.T) { + carteira := Carteira{saldo: Bitcoin(20)} + + carteira.Retirar(Bitcoin(10)) + + resultado := carteira.Saldo() + + esperado := Bitcoin(10) + + if resultado != esperado { + t.Errorf("resultado %s, esperado %s", resultado, esperado) + } + }) +} +``` + +## Execute o teste + +`./carteira_test.go:26:9: carteira.Retirar undefined (type Carteira has no field or method Retirar)` + +## Escreva o mínimo de código possível para fazer o teste rodar e verifique a saída do teste que tiver falhado + +```go +func (c *Carteira) Retirar(quantidade Bitcoin) { +} +``` + +`carteira_test.go:33: resultado 20 BTC, esperado 10 BTC` + +## Escreva código o suficiente para fazer o teste passar + +```go +func (c *Carteira) Retirar(quantidade Bitcoin) { + c.saldo -= quantidade +} +``` + +## Refatoração + +Há algumas duplicações em nossos testes, vamos refatorar isso. + +```go +func TestCarteira(t *testing.T) { + confirmaSaldo := func(t *testing.T, carteira Carteira, valorEsperado Bitcoin) { + t.Helper() + resultado := carteira.Saldo() + + if resultado != esperado { + t.Errorf("resultado %s, esperado %s", resultado, esperado) + } + } + + t.Run("Depositar", func(t *testing.T) { + carteira := Carteira{} + carteira.Depositar(Bitcoin(10)) + confirmaSaldo(t, carteira, Bitcoin(10)) + }) + + t.Run("Retirar", func(t *testing.T) { + carteira := Carteira{saldo: Bitcoin(20)} + carteira.Retirar(10) + confirmaSaldo(t, carteira, Bitcoin(10)) + }) +} +``` + +O que aconteceria se você tentasse `Retirar` mais do que há de saldo na conta? Por enquanto, nossos requisitos são assumir que não há nenhum tipo de cheque-especial. + +Como sinalizamos um problema quando estivermos usando `Retirar` ? + +Em Go, se você quiser indicar um erro, sua função deve retornar um `err` para que quem a chamou possar verificá-lo e tratá-lo. + +Vamos tentar fazer isso em um teste. + +## Escreva o teste primeiro + +```go +t.Run("Retirar com saldo insuficiente", func(t *testing.T) { + saldoInicial := Bitcoin(20) + carteira := Carteira{saldoInicial} + erro := carteira.Retirar(Bitcoin(100)) + + confirmaSaldo(t, carteira, saldoInicial) + + if erro == nil { + t.Error("Esperava um erro mas nenhum ocorreu") + } +}) +``` + +Queremos que `Retirar` retorne um erro se tentarmos retirar mais do que temos e o saldo deverá continuar o mesmo. + +Verificamos se um erro foi retornado falhando o teste se o valor for `nil`. + +`nil` é a mesma coisa que `null` de outras linguagens de programação. + +Erros podem ser `nil`, porque o tipo do retorno de `Retirar` vai ser `error`, que é uma interface. Se você vir uma função que tem argumentos ou retornos que são interfaces, eles podem ser nulos. + +Do mesmo jeito que `null`, se tentarmos acessar um valor que é `nil`, isso irá disparar um **pânico em tempo de execução**. Isso é ruim! Devemos ter certeza que tratamos os valores nulos. + +## Execute o teste + +`./carteira_test.go:31:25: carteira.Retirar(Bitcoin(100)) used as value` + +Talvez não esteja tão claro, mas nossa intenção era apenas invocar a função `Retirar` e ela nunca irá retornar um valor pois o saldo será diretamente subtraído com o ponteiro e a função deve apenas retornar o erro (se houver). Para fazer compilar, precisaremos mudar a função para que retorne um tipo. + +## Escreva o mínimo de código possível para fazer o teste rodar e verifique a saída do teste que tiver falhado + +```go +func (c *Carteira) Retirar(quantidade Bitcoin) error { + c.saldo -= quantidade + return nil +} +``` + +Novamente, é muito importante escrever apenas o suficiente para compilar. Corrigimos o método `Retirar` para retornar `error` e por enquanto temos que retornar _alguma coisa_, então vamos apenas retornar `nil` . + +## Escreva código o suficiente para fazer o teste passar + +```go +func (c *Carteira) Retirar(quantidade Bitcoin) error { + if quantidade > c.saldo { + return errors.New("eita") + } + + c.saldo -= quantidade + return nil +} +``` + +Lembre-se de importar `errors`. + +`errors.New` cria um novo `error` com a mensagem escolhida. + +## Refatoração + +Vamos fazer um método auxiliar de teste para nossa verificação de erro para deixar nosso teste mais legível. + +```go +confirmaErro := func(t *testing.T, erro error) { + t.Helper() + if erro == nil { + t.Error("esperava um erro, mas nenhum ocorreu.") + } +} +``` + +E em nosso teste: + +```go +t.Run("Retirar com saldo insuficiente", func(t *testing.T) { + saldoInicial := Bitcoin(20) + carteira := Carteira{saldoInicial} + erro := carteira.Retirar(Bitcoin(100)) + + confirmaSaldo(t, carteira, saldoInicial) + confirmaErro(t, erro) +}) +``` + +Espero que, ao retornamos um erro do tipo "eita", você pense que _devêssemos_ deixar mais claro o que ocorreu, já que esta não parece uma informação útil para nós. + +Assumindo que o erro enfim foi retornado para o usuário, vamos atualizar nosso teste para verificar o tipo espcífico de mensagem de erro ao invés de apenas verificar se um erro existe. + +## Escreva o teste primeiro + +Atualize nosso helper para comparar com uma `string`: + +```go +confirmarErro := func(t *testing.T, valor error, valorEsperado string) { + t.Helper() + if resultado == nil { + t.Fatal("esperava um erro, mas nenhum ocorreu") + } + + if resultado != esperado { + t.Errorf("resultado %s, esperado %s", resultado, esperado) + } +} +``` + +E então atualize o invocador: + +```go +t.Run("Retirar saldo insuficiente", func(t *testing.T) { + saldoInicial := Bitcoin(20) + carteira := Carteira{saldoInicial} + erro := carteira.Retirar(Bitcoin(100)) + + confirmaSaldo(t, carteira, saldoInicial) + confirmaErro(t, erro, "não é possível retirar: saldo insuficiente") +}) +``` + +Usamos o `t.Fatal` que interromperá o teste se for chamado. Isso é feito porque não queremos fazer mais asserções no erro retornado, se não houver um. Sem isso, o teste continuaria e causaria erros por causa do ponteiro `nil`. + +## Execute o teste + +`carteira_test.go:61: erro resultado 'eita', erro esperado 'não é possível retirar: saldo insuficiente'` + +## Escreva código o suficiente para fazer o teste passar + +```go +func (c *Carteira) Retirar(quantidade Bitcoin) error { + + if quantidade > c.saldo { + return errors.New("não é possível retirar: saldo insuficiente") + } + + c.saldo -= quantidade + return nil +} +``` + +## Refatoração + +Temos duplicação da mensagem de erro tanto no código de teste quanto no código de `Retirar`. + +Seria chato se o teste falhasse por alguém ter mudado a mensagem do erro e é muito detalhe para o nosso teste. Nós não _necessariamente_ nos importamos qual mensagem é exatamente, apenas que algum tipo de erro significativo sobre a função é retornado dada uma certa condição. + +Em Go, erros são valores, então podemos refatorar isso para ser uma variável e termos apenas uma fonte da verdade. + +```go +var ErroSaldoInsuficiente = errors.New("não é possível retirar: saldo insuficiente") + +func (c *Carteira) Retirar(amount Bitcoin) error { + + if quantidade > c.saldo { + return ErroSaldoInsuficiente + } + + c.saldo -= quantidade + return nil +} +``` + +A palavra-chave `var` no escopo do arquivo nos permite definir valores globais para o pacote. + +Esta é uma mudança positiva, pois agora nossa função `Retirar` parece mais limpa. + +Agora, podemos refatorar nosso código para usar este valor ao invés de uma string específica. + +```go +func TestCarteira(t *testing.T) { + t.Run("Depositar", func(t *testing.T) { + carteira := Carteira{} + carteira.Depositar(Bitcoin(10)) + + confirmaSaldo(t, carteira, Bitcoin(10)) + }) + + t.Run("Retirar com saldo suficiente", func(t *testing.T) { + carteira := Carteira{Bitcoin(20)} + erro := carteira.Retirar(Bitcoin(10)) + + confirmaSaldo(t, carteira, Bitcoin(10)) + confirmaErroInexistente(t, erro) + }) + + t.Run("Retirar com saldo insuficiente", func(t *testing.T) { + saldoInicial := Bitcoin(20) + carteira := Carteira{saldoInicial} + erro := carteira.Retirar(Bitcoin(100)) + + confirmaSaldo(t, carteira, saldoInicial) + confirmaErro(t, erro, ErroSaldoInsuficiente) + }) +} + +func confirmaSaldo(t *testing.T, carteira Carteira, esperado Bitcoin) { + t.Helper() + resultado := carteira.Saldo() + + if resultado != esperado { + t.Errorf("resultado %s, esperado %s", resultado, esperado) + } +} + +func confirmaErro(t *testing.T, resultado error, esperado error) { + t.Helper() + if resultado == nil { + t.Fatal("esperava um erro, mas nenhum ocorreu") + } + + if resultado != esperado { + t.Errorf("erro resultado %s, erro esperado %s", resultado, esperado) + } +} +``` + +Agora está mais fácil dar continuidade ao nosso teste. + +Nós apenas movemos os métodos auxiliares para fora da função principal de teste. Logo, quando alguém abrir o arquivo, começará lendo nossas asserções primeiro ao invés desses métodos auxiliares. + +Outra propriedade útil de testes é que eles nos ajudam a entender o uso _real_ do nosso código e assim podemos fazer códigos mais compreensivos. Podemos ver aqui que um desenvolvedor pode simplesmente chamar nosso código e fazer uma comparação de igualdade a `ErroSaldoInsuficiente`, e então agir de acordo. + +### Erros não verificados + +Embora o compilador do Go ajude bastante, há coisas que você pode acabar errando e o tratamento de erro pode se tornar complicado. + +Há um cenário que nós não testamos. Para descobri-lo, execute o comando a seguir no terminal para instalar o `errcheck`, um dos muitos linters disponíveis em Go. + +`go get -u github.com/kisielk/errcheck` + +Então, dentro do diretório do seu código, execute `errcheck .`. + +Você deve receber algo assim: + +`carteira_test.go:17:18: carteira.Retirar(Bitcoin(10))` + +O que isso está nos dizendo é que não verificamos o erro sendo retornado naquela linha de código. Aquela linha de código, no meu computador, corresponde para o nosso cenário normal de retirada, porque não verificamos que se `Retirar` é bem sucedido quando um erro _não_ é retornado. + +Aqui está o código de teste final que resolve isto. + +```go +func TestCarteira(t *testing.T) { + t.Run("Depositar", func(t *testing.T) { + carteira := Carteira{} + carteira.Depositar(Bitcoin(10)) + + confirmaSaldo(t, carteira, Bitcoin(10)) + }) + + t.Run("Retirar com saldo suficiente", func(t *testing.T) { + carteira := Carteira{Bitcoin(20)} + erro := carteira.Retirar(Bitcoin(10)) + + confirmaSaldo(t, carteira, Bitcoin(10)) + confirmaErroInexistente(t, erro) + }) + + t.Run("Retirar com saldo insuficiente", func(t *testing.T) { + saldoInicial := Bitcoin(20) + carteira := Carteira{saldoInicial} + erro := carteira.Retirar(Bitcoin(100)) + + confirmaSaldo(t, carteira, saldoInicial) + confirmaErro(t, erro, ErroSaldoInsuficiente) + }) +} + +func confirmaSaldo(t *testing.T, carteira Carteira, esperado Bitcoin) { + t.Helper() + resultado := carteira.Saldo() + + if resultado != esperado { + t.Errorf("resultado %s, esperado %s", resultado, esperado) + } +} + +func confirmaErroInexistente(t *testing.T, resultado error) { + t.Helper() + if resultado != nil { + t.Fatal("erro inesperado recebido") + } +} + +func confirmaErro(t *testing.T, resultado error, esperado error) { + t.Helper() + if resultado == nil { + t.Fatal("esperava um erro, mas nenhum ocorreu") + } + + if resultado != esperado { + t.Errorf("erro resultado %s, erro esperado %s", resultado, esperado) + } +} +``` + +## Resumo + +### Ponteiros + +* Go copia os valores quando são passados para funções/métodos. Então, se estiver escrevendo uma função que precise mudar o estado, você precisará de um ponteiro para o valor que você quer mudar. +* O fato de que Go pega um cópia dos valores é muito útil na maior parte do tempo, mas às vezes você não vai querer que o seu sistema faça cópia de alguma coisa. Nesse caso, você precisa passar uma referência. Podemos, por exemplo, ter dados muito grandes, ou coisas que você talvez pretenda ter apenas uma instância \(como conexões a banco de dados\). + +### nil + +* Ponteiros podem ser `nil`. +* Quando uma função retorna um ponteiro para algo, você precisa ter certeza de verificar se ele é `nil` ou isso vai gerar uma exceção em tempo de execução, já que o compilador não te consegue te ajudar nesses casos. +* Útil para quando você quer descrever um valor que pode estar faltando. + +### Erros + +* Erros são a forma de sinalizar falhas na execução de uma função/método. +* Analisando nossos testes, concluímos que buscar por uma string em um erro poderia resultar em um teste não muito confiável. Então, refatoramos para usar um valor significativo, que resultou em um código mais fácil de ser testado e concluímos que também seria mais fácil para usuários de nossa API. +* Este não é o fim do assunto de tratamento de erros. Você pode fazer coisas mais sofisticadas, mas esta é apenas uma introdução. Capítulos posteriores vão abordar mais estratégias. +* [Não somente verifique os erros, trate-os graciosamente](https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully) + +### Crie novos tipos a partir de existentes + +* Útil para adicionar domínios mais específicos a valores +* Permite implementar interfaces + +Ponteiros e erros são uma grande parte de escrita em Go que você precisa estar confortável. Por sorte, _na maioria das vezes_ o compilador irá ajudar se você fizer algo errado. É só tomar um tempinho lendo a mensagem de erro. +