Go: Go Testing
A biblioteca padrão do Go vem com uma estrutura de testes integrada — não são necessárias bibliotecas de testes ou de asserções de terceiros; é possível escrever testes de alta qualidade usando apenas o pacote básico
testing.
A cadeia de ferramentas de teste integrada do Go é única no setor: o go test detecta automaticamente as funções de teste, os testes orientados por tabelas são uma marca registrada da comunidade Go, e os benchmarks e a cobertura de código estão disponíveis logo de cara. Nesta lição, você vai dominar todos os aspectos essenciais dos testes em Go.
1. Você aprenderá
testing.T: Noções básicas sobre testes unitários- Testes orientados por tabelas
- Subteste (
t.Run) testing.BTeste de desempenhoTestMain: Ponto de entrada do teste e preparação/desmontagem- Cobertura
go test -cover httptestTestes HTTP- Estudo de caso abrangente: Conjunto de testes do módulo de pagamentos
2. A história real de um engenheiro de refatoração
(1) Problema: A alteração de uma função fez com que três módulos travassem simultaneamente
Alice é engenheira de back-end na equipe de pagamentos. Ela foi encarregada de refatorar a lógica de cálculo de impostos no módulo de pagamentos:
“Acabei de alterar o nome de um campo, achando que não haveria problema. Mas, após o lançamento, 30% dos pedidos apresentavam cálculos de impostos incorretos — o gerente de projeto disse que perdemos US$ 5.000. Como não testamos a alteração, ninguém sabia onde ela havia sido feita.”
Ela abriu o código do módulo de pagamentos e descobriu que o projeto inteiro tinha zero arquivos de teste:
// payment.go — no corresponding payment_test.go
func CalculateTax(amount float64, country string) float64 {
// No one knows if this function is correct—there are no tests
switch country {
case "US":
return amount * 0.08
case "CN":
return amount * 0.13
default:
return amount * 0.10
}
}
Depois de executar o teste, Alice percebeu imediatamente o problema — ela havia definido anteriormente a alíquota para “Reino Unido” como 20%, em vez da alíquota correta de IVA de 20% (na verdade, era 20%, mas, no caso extremo em que amount=0, o resultado era NaN).
(2) Go Solution: Estrutura de testes integrada
// payment_test.go
package main
import "testing"
// Table-driven test
func TestCalculateTax(t *testing.T) {
tests := []struct {
name string
amount float64
country string
want float64
}{
{"US standard", 100.0, "US", 8.0},
{"CN standard", 100.0, "CN", 13.0},
{"UK default", 100.0, "UK", 10.0},
{"zero amount", 0.0, "US", 0.0},
{"negative amount", -50.0, "US", -4.0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := CalculateTax(tt.amount, tt.country)
if got != tt.want {
t.Errorf("CalculateTax(%v, %s) = %v, want %v",
tt.amount, tt.country, got, tt.want)
}
})
}
}
Executar teste:
$ go test -v
=== RUN TestCalculateTax/US_standard
=== RUN TestCalculateTax/CN_standard
=== RUN TestCalculateTax/UK_default
=== RUN TestCalculateTax/zero_amount
=== RUN TestCalculateTax/negative_amount
--- PASS: TestCalculateTax (0.00s)
--- PASS: TestCalculateTax/US_standard (0.00s)
--- PASS: TestCalculateTax/CN_standard (0.00s)
--- PASS: TestCalculateTax/UK_default (0.00s)
--- PASS: TestCalculateTax/zero_amount (0.00s)
--- PASS: TestCalculateTax/negative_amount (0.00s)
PASS
ok payment 0.123s
(3) Resultados: com testes versus sem testes
| Dimensão | Sem teste | Teste |
|---|---|---|
| Recuperando a confiança | Medo de alterar até mesmo uma única linha | Executar go test imediatamente após fazer as alterações |
| Problemas de posicionamento | Relatos de erros dos usuários após a inicialização | Falhas na fase de desenvolvimento |
| Qualidade do código | Intuição | Orientado por dados |
| Primeiros passos | Medo de fazer alterações | Depois de fazer as alterações e executar os testes, você pode ficar tranquilo |
| Custos de devolução | Verificação manual | Automação |
3. testing.T: Noções básicas sobre testes de unidade
(1) Regras das funções de teste
// Rules:
// 1. File name must end with _test.go
// 2. Function signature must be func TestXxx(t *testing.T)
// 3. Xxx must start with an uppercase letter
// math_test.go
package main
import "testing"
func TestAdd(t *testing.T) {
got := Add(2, 3)
want := 5
if got != want {
t.Errorf("Add(2,3) = %d, want %d", got, want)
}
}
func TestSubtract(t *testing.T) {
got := Subtract(5, 3)
want := 2
if got != want {
t.Errorf("Subtract(5,3) = %d, want %d", got, want)
}
}
(2) Métodos comuns em testes.T
| Método | Ação | Continuar? |
|---|---|---|
t.Log(args...) |
Imprime um log (exibido apenas quando -v é especificado) | ✅ |
t.Error(args...) |
Marcar como falha + continuar a execução | ✅ |
t.Errorf(format, args...) |
Erro de formatação | ✅ |
t.Fatal(args...) |
Marcar como reprovado + Interromper o teste atual | ❌ |
t.Fatalf(format, args...) |
Erro fatal de formatação | ❌ |
t.Skip(args...) |
Pular este teste | ✅ |
(3) ▶ Exemplo: Quatro maneiras de escrever uma função de teste
package main
import (
"fmt"
"testing"
)
// Function under test
func Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, ErrDivisionByZero
}
return a / b, nil
}
var ErrDivisionByZero = fmt.Errorf("division by zero")
// Style 1: Simple assertion
func TestDivideBasic(t *testing.T) {
result, err := Divide(10, 2)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 5.0 {
t.Errorf("got %f, want %f", result, 5.0)
}
}
// Style 2: Error vs Fatal
func TestDivideByZero(t *testing.T) {
_, err := Divide(10, 0)
if err == nil {
t.Fatal("expected error, got nil")
}
if err.Error() != "division by zero" {
t.Errorf("wrong error message: %v", err)
}
}
// Style 3: Table-driven test
func TestDivideTable(t *testing.T) {
tests := []struct {
name string
a, b float64
want float64
wantErr bool
}{
{"10/2", 10, 2, 5, false},
{"0/5", 0, 5, 0, false},
{"-6/3", -6, 3, -2, false},
{"1/0", 1, 0, 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Divide(tt.a, tt.b)
if tt.wantErr {
if err == nil {
t.Error("expected error")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != tt.want {
t.Errorf("got %f, want %f", got, tt.want)
}
})
}
}
4. Testes orientados por tabelas (o estilo característico do Go)
(1) Modelo padrão
package main
import "testing"
func TestMax(t *testing.T) {
// 1. Define test table
tests := []struct {
name string // Test name (for subtests)
a, b int // Input
want int // Expected output
}{
{name: "first larger", a: 10, b: 3, want: 10},
{name: "second larger", a: 3, b: 10, want: 10},
{name: "equal", a: 5, b: 5, want: 5},
{name: "negative", a: -3, b: -10, want: -3},
{name: "zero", a: 0, b: 5, want: 5},
}
// 2. Iterate through table
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 3. Execute + assert
got := Max(tt.a, tt.b)
if got != tt.want {
t.Errorf("Max(%d, %d) = %d, want %d",
tt.a, tt.b, got, tt.want)
}
})
}
}
func Max(a, b int) int {
if a > b {
return a
}
return b
}
(2) ▶ Exemplo: Orientado por tabela + entrada complexa
package main
import (
"testing"
)
// Function under test: validate password strength
type PasswordStrength int
const (
Weak PasswordStrength = iota
Medium
Strong
)
func CheckPassword(pwd string) PasswordStrength {
if len(pwd) < 6 {
return Weak
}
if len(pwd) >= 12 {
return Strong
}
return Medium
}
func TestCheckPassword(t *testing.T) {
tests := []struct {
name string
pwd string
want PasswordStrength
}{
{"short", "abc", Weak},
{"medium 6", "abcdef", Medium},
{"medium 8", "abcdefgh", Medium},
{"strong 12", "abcdefghijkl", Strong},
{"empty", "", Weak},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := CheckPassword(tt.pwd)
if got != tt.want {
t.Errorf("CheckPassword(%q) = %v, want %v",
tt.pwd, got, tt.want)
}
})
}
}
5. Teste de benchmark testing.B
(1) Noções básicas sobre benchmark
package main
import (
"testing"
)
// Benchmark Função: func BenchmarkXxx(b *testing.B)
func BenchmarkAdd(b *testing.B) {
a, c := 100, 200
for i := 0; i < b.N; i++ {
Add(a, c)
}
}
$ go test -bench=.
goos: darwin
goarch: amd64
pkg: example
BenchmarkAdd-8 1000000000 0.25 ns/op
PASS
ok example 0.3s
(2) ▶ Exemplo: Comparando o desempenho de dois métodos de concatenação de 文字列
// concat_test.go
package main
import (
"strings"
"testing"
)
func ConcatPlus(n int) string {
s := ""
for i := 0; i < n; i++ {
s += "a"
}
return s
}
func ConcatBuilder(n int) string {
var sb strings.Builder
sb.Grow(n)
for i := 0; i < n; i++ {
sb.WriteByte('a')
}
return sb.String()
}
func BenchmarkConcatPlus(b *testing.B) {
for i := 0; i < b.N; i++ {
ConcatPlus(1000)
}
}
func BenchmarkConcatBuilder(b *testing.B) {
for i := 0; i < b.N; i++ {
ConcatBuilder(1000)
}
}
$ go test -bench=. -benchmem
BenchmarkConcatPlus-8 13134 91238 ns/op 530296 allocs/op
BenchmarkConcatBuilder-8 283321 4221 ns/op 56 allocs/op
+ 1.000 vezes é 20 vezes mais lento do que usar o Builder e aloca 10.000 vezes mais memória — a opção -benchmem permite que você veja a diferença na alocação de memória.
(3) Interpretação dos resultados dos testes de desempenho
| Item de saída | Significado |
|---|---|
BenchmarkConcatBuilder-8 |
Nome do teste-8 (8 CPUs) |
283321 |
b.N = 283.321 iterações |
4221 ns/op |
4221 nanossegundos por operação |
56 allocs/op |
56 alocações de memória por operação |
6. TestMain: Ponto de entrada do teste
(1) TestMain: preparação / desmontagem
// main_test.go
package main
import (
"fmt"
"os"
"testing"
)
func TestMain(m *testing.M) {
// Setup (runs once for the entire package)
fmt.Println("=== Setup: Initializing database connection ===")
// Run all tests
code := m.Run()
// Teardown
fmt.Println("=== Teardown: Closing database connection ===")
os.Exit(code)
}
func TestSomething(t *testing.T) {
t.Log("Test A")
}
func TestAnother(t *testing.T) {
t.Log("Test B")
}
$ go test -v
=== Setup: Initializing database connection ===
=== RUN TestSomething
main_test.go:16: Test A
--- PASS: TestSomething (0.00s)
=== RUN TestAnother
main_test.go:20: Test B
--- PASS: TestAnother (0.00s)
=== Teardown: Closing database connection ===
PASS
ok example 0.1s
(2) ▶ Exemplo: função auxiliar testing.Helper
package main
import "testing"
func Add(a, b int) int { return a + b }
func assertEqual(t testing.TB, got, want interface{}) {
t.Helper()
if got != want {
t.Errorf("got %v, want %v", got, want)
}
}
func TestWithHelper(t *testing.T) {
assertEqual(t, Add(2, 3), 5)
assertEqual(t, Add(0, 0), 0)
assertEqual(t, Add(-1, 1), 0)
}
t.Helper() garante que as mensagens de erro sejam rastreadas até o número da linha do chamador, em vez de dentro da própria função auxiliar. Essa é uma prática recomendada fundamental ao escrever funções utilitárias de teste.
7. Cobertura
(1) Fundamentos da cobertura
// math.go
package main
func IsEven(n int) bool {
return n%2 == 0
}
func IsPositive(n int) bool {
return n > 0
}
// math_test.go
package main
import "testing"
func TestIsEven(t *testing.T) {
tests := []struct {
n int
want bool
}{
{2, true},
{3, false},
}
for _, tt := range tests {
if got := IsEven(tt.n); got != tt.want {
t.Errorf("IsEven(%d) = %v", tt.n, got)
}
}
}
$ go test -coverprofile=coverage.out
ok example 0.1s coverage: 50.0% of statements
$ go tool cover -html=coverage.out # Open coverage report in browser
(2) go test -cover: Comandos comuns
| Comando | Função |
|---|---|
go test -cover |
Cobertura exibida no terminal |
go test -coverprofile=c.out |
Arquivo de perfil de cobertura de saída |
go tool cover -html=c.out |
Visualizar o relatório visual em um navegador |
go test -covermode=count |
Registra o número de vezes que cada linha é executada |
8. Teste de HTTP com o httptest
(1) httptest.Server + httptest.ResponseRecorder
package main
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
// Handler under test
type UserHandler struct{}
func (h UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
resp := map[string]string{"status": "ok", "message": "hello"}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
// Test 1: httptest.ResponseRecorder (testing the handler itself)
func TestUserHandler(t *testing.T) {
req := httptest.NewRequest("GET", "/", nil)
rec := httptest.NewRecorder()
handler := UserHandler{}
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("got status %d, want %d", rec.Code, http.StatusOK)
}
var resp map[string]string
json.Unmarshal(rec.Body.Bytes(), &resp)
if resp["status"] != "ok" {
t.Errorf("got status %q, want %q", resp["status"], "ok")
}
}
// Test 2: httptest.Server (testing a complete HTTP service)
func TestUserHandlerWithServer(t *testing.T) {
server := httptest.NewServer(UserHandler{})
defer server.Close()
resp, err := http.Get(server.URL)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("got %d, want %d", resp.StatusCode, http.StatusOK)
}
}
(2) ▶ Exemplo: httptest + orientado por tabela
package main
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
// Handler under test
func greetingHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "World"
}
resp := map[string]string{"message": "Hello, " + name + "!"}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func TestGreetingHandler(t *testing.T) {
tests := []struct {
name string
query string
want string
status int
}{
{"with name", "name=Alice", "Hello, Alice!", 200},
{"empty name", "", "Hello, World!", 200},
{"with special chars", "name=Go+Lang", "Hello, Go Lang!", 200},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest("GET", "/?"+tt.query, nil)
rec := httptest.NewRecorder()
greetingHandler(rec, req)
if rec.Code != tt.status {
t.Errorf("status = %d, want %d", rec.Code, tt.status)
}
var resp map[string]string
json.Unmarshal(rec.Body.Bytes(), &resp)
if resp["message"] != tt.want {
t.Errorf("message = %q, want %q", resp["message"], tt.want)
}
})
}
}
(3) Dois modos do httptest
| Modo | httptest.NewRecorder | httptest.NewServer |
|---|---|---|
| Assunto do teste | Manipulador único | Serviço HTTP completo |
| Carga inicial | Nenhuma | Sim (escuta em uma porta aleatória) |
| Casos de uso | Testes unitários | Testes de integração |
| É possível testar o middleware? | ✅ Criado manualmente | ✅ Automaticamente ao longo de todo o pipeline |
9. Exemplo completo: refatoração do conjunto de testes do módulo de pagamentos
// payment_test.go
package main
import (
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
// ---------- Code under test ----------
type PaymentRequest struct {
UserID string `json:"user_id"`
Amount float64 `json:"amount"`
Country string `json:"country"`
}
type PaymentResponse struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Tax float64 `json:"tax,omitempty"`
Total float64 `json:"total,omitempty"`
}
func CalculateTax(amount float64, country string) (float64, error) {
if amount < 0 {
return 0, errors.New("negative amount")
}
switch country {
case "US":
return amount * 0.08, nil
case "CN":
return amount * 0.13, nil
case "DE":
return amount * 0.19, nil
default:
return amount * 0.10, nil
}
}
func paymentHandler(w http.ResponseWriter, r *http.Request) {
var req PaymentRequest
json.NewDecoder(r.Body).Decode(&req)
tax, err := CalculateTax(req.Amount, req.Country)
if err != nil {
json.NewEncoder(w).Encode(PaymentResponse{
Success: false,
Message: err.Error(),
})
return
}
json.NewEncoder(w).Encode(PaymentResponse{
Success: true,
Tax: tax,
Total: req.Amount + tax,
})
}
// ---------- Test code ----------
func jsonBody(s string) *strings.Reader {
return strings.NewReader(s)
}
// 1. Unit test: CalculateTax table-driven
func TestCalculateTax(t *testing.T) {
tests := []struct {
name string
amount float64
country string
want float64
wantErr bool
}{
{"US $100", 100, "US", 8.0, false},
{"CN $100", 100, "CN", 13.0, false},
{"DE $100", 100, "DE", 19.0, false},
{"UK default", 100, "UK", 10.0, false},
{"zero amount", 0, "US", 0, false},
{"negative amount", -100, "US", 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CalculateTax(tt.amount, tt.country)
if tt.wantErr {
if err == nil {
t.Error("expected error")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != tt.want {
t.Errorf("CalculateTax(%v, %s) = %v, want %v",
tt.amount, tt.country, got, tt.want)
}
})
}
}
// 2. Benchmark: different ways to calculate tax
func BenchmarkCalculateTax(b *testing.B) {
for i := 0; i < b.N; i++ {
CalculateTax(100.0, "US")
}
}
// 3. HTTP test: paymentHandler
func TestPaymentHandler(t *testing.T) {
tests := []struct {
name string
body string
wantStatus int
wantSuccess bool
}{
{"US payment", `{"user_id":"u1","amount":100,"country":"US"}`, 200, true},
{"CN payment", `{"user_id":"u2","amount":200,"country":"CN"}`, 200, true},
{"negative amount", `{"user_id":"u3","amount":-50,"country":"US"}`, 200, false},
{"invalid JSON", `not json`, 200, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest("POST", "/pay",
jsonBody(tt.body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
paymentHandler(rec, req)
if rec.Code != tt.wantStatus {
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
}
var resp PaymentResponse
json.Unmarshal(rec.Body.Bytes(), &resp)
if resp.Success != tt.wantSuccess {
t.Errorf("success = %v, want %v", resp.Success, tt.wantSuccess)
}
})
}
}
sequenceDiagram
participant Dev
participant Terminal as go test
participant Package as Package Under Test
participant Coverage as coverage.out
Dev->>Terminal: go test -v -cover
Terminal->>Package: 1. Find *_test.go
Terminal->>Package: 2. Call TestMain(m)
Package->>Package: 3. Setup
Package->>Package: 4. Execute all TestXxx
Package->>Package: 5. Execute BenchmarkXxx
Package->>Package: 6. Teardown
Package-->>Terminal: PASS / FAIL
Terminal-->>Coverage: Coverage data
Terminal-->>Dev: Result summary
_test.go; caso contrário, go test não será executado. A assinatura da função deve ser estritamente func TestXxx(t *testing.T) — o parâmetro é *testing.T, e não *testing.TT ou testing.T.
❓ Perguntas Frequentes
P: Quais são as principais funções do pacote
testing? R:testing.T(testes unitários),testing.B(testes de benchmark),testing.M(ponto de entrada do teste),testing.Helper()(anotação de função auxiliar),testing.Short()(ignora testes longos).
P: Como escrevo testes baseados em tabelas? R: Defina um
[]structcomo a tabela de testes, em que cada caso contenhaname+ entrada + saída esperada. Percorra a tabela e uset.Run(tt.name, ...)para executar subtestes. A comunidade Go considera essa a maneira padrão de escrever testes.
P: Como faço para executar um teste de desempenho? R:
go test -bench=.executa todos os testes de desempenho;go test -bench=FuncNameexecuta uma função específica;-benchmemexibe informações sobre a alocação de memória. b.N é determinado automaticamente pela estrutura.
P: Como posso verificar a cobertura? R:
go test -coverexibe a porcentagem;go test -coverprofile=c.outgera um arquivo;go tool cover -html=c.outoferece uma visualização no navegador. O padrão é 70% ou mais, e recomenda-se 90% ou mais para a lógica principal.
P: Como faço para usar o httptest para testar um manipulador HTTP? R: Existem duas maneiras:
httptest.NewRecorder()para testar o manipulador diretamente (teste de unidade) ehttptest.NewServer(handler)para iniciar um servidor HTTP real para testes (teste de integração). Recomendamos usar primeiro o Recorder.
P: Qual é a ordem de execução das funções Setup e Teardown no TestMain? R: O TestMain é executado uma vez antes de todas as funções de teste do pacote. A função Setup é executada antes de
m.Run(), e a função Teardown é executada depois. Observação: a saída é armazenada em cache; portanto, é necessário usaros.Exit(code)para sair.
P: O que o
go testarmazena em cache? R: Por padrão, ogo testarmazena em cache os resultados dos testes (com base no código e no ambiente). Se não houver alterações quando você executá-lo novamente, ele exibirá(cached). Usego test -count=1para forçar uma nova execução.
P: Como faço para pular determinados testes? R:
t.Skip("reason")pula o teste atual;testing.Short()funciona em conjunto comgo test -shortpara pular testes que demoram muito;t.Skipf(format, args...)serve para pulos formatados.
📖 Resumo
- O Go inclui um pacote
testingintegrado, portanto, não é necessária nenhuma estrutura de testes de terceiros - Os testes baseados em tabelas são uma marca registrada da comunidade Go:
[]struct+t.Run - O sub-teste
t.Runapresenta uma hierarquia clara dos resultados dos testes testing.Bteste de desempenho:b.Najusta automaticamente,-benchmemverifica a memóriaTestMain(m)oferece inicialização e desativação no nível do pacotego test -covermede a cobertura da linhahttptest.NewRecorderehttptest.NewServerpara testes de HTTP- Arquivo de teste denominado
_test.go, assinatura da funçãofunc TestXxx(t *testing.T)
📝 Exercícios
-
Problema básico (Dificuldade ⭐): Escreva um teste baseado em tabela para
max(nums ...int) intda Lição 4 (Funções), abrangendo os cinco casos a seguir: números positivos, números negativos, uma mistura de números positivos e negativos, um único elemento e argumentos vazios. -
Exercício avançado (Dificuldade ⭐⭐): Escreva testes para o
Exporterda Lição 10 (E/S de arquivos e JSON): use oos.CreateTemppara criar um arquivo temporário para testar a exportação; use ojson.Unmarshalpara verificar se o conteúdo está correto; use ot.Cleanuppara limpar o arquivo temporário. -
Desafio (Dificuldade: ⭐⭐⭐): Escreva um conjunto completo de testes para o sistema de gateway de pagamentos múltiplos da Lição 7: (1) Testes baseados em tabelas para
MockPaymentGateway(retorna resultados fixos); (2) Compare a velocidade das implementações do Stripe e do PayPal; (3) Usehttptestpara testar o formato de resposta JSON do manipulador de pagamentos; (4) Cobertura de código ≥ 90%.