Em vez de apresentar
Este artigo é para quem, como eu, veio do mundo do Django para o Go. Bem, Django nos estragou. Basta executar os testes, pois ele mesmo, por baixo do capô, criará um banco de dados de teste, executará as migrações e, após a execução, fará a limpeza. Convenientemente? Certamente. É que leva tempo para fazer migrações - uma carruagem, mas isso parece ser um pagamento razoável pelo conforto, além disso, sempre há--reuse-db
... O mais choque cultural é quando Junglers experientes vêm para outras línguas como Go. Ou seja, como é que não há automigrações antes e depois? Com suas mãos? E a base? Mãos também? E depois dos testes? O quê, e uma corda bamba com as mãos? E então o programador, intercalando o código com suspiros e suspiros, começa a escrever junga em Go em um projeto separado. Claro, tudo parece muito triste. No entanto, no Go é perfeitamente possível escrever testes de unidade rápidos e confiáveis sem usar serviços de terceiros, como um banco de dados de teste ou cache.
Esta será minha história.
O que estamos testando?
Vamos imaginar que precisamos escrever uma função que verifique a presença de um funcionário no banco de dados por número de telefone.
func CheckEmployee(db *sqlx.DB, phone string) (error, bool) {
err := db.Get(`SELECT * FROM employees WHERE phone = ?`, phone)
if err != nil {
return err, false
}
return nil, true
}
Ok, eles escreveram. Como testar? Você pode, é claro, criar um banco de dados de teste antes de executar os testes, criar tabelas nele e, após executar esse banco de dados, travá-lo suavemente.
Mas também há outra maneira.
Interfaces
, , , Get
. , -, , , , , , .
. Go? , — -, , , , , . , ?
.
:
type ExampleInterface interface {
Method() error
}
, , :
type ExampleStruct struct {}
func (es ExampleStruct) Method() error {
return nil
}
, ExampleStruct
ExampleInterface
, , - ExampleInterface
, ExampleStruct
.
?
, Get
, , , , , Get
sqlx.Get
.
Talk is cheap, let's code!
:
Get(dest interface{}, query string, args ...interface{}) error
, Get
:
type BaseDBClient interface {
Get(interface{}, string, ...interface{}) error
}
:
func CheckEmployee(db BaseDBClient, phone string) (err error, exists bool) {
var employee interface{}
err = db.Get(&employee, `SELECT name FROM employees WHERE phone = ?`, phone)
if err != nil {
return err, false
}
return nil, true
}
, , , , sqlx.Get
, sqlx
, , BaseDBClient
.
, .
, , .
, BaseDBClient
:
type TestDBClient struct {}
func (tc *TestDBClient) Get(interface{}, string, ...interface{}) error {
return nil
}
, , , , , , , .
, — CheckEmployee
:
func TestCheckEmployee() {
test_client := TestDBClient{}
err, exists := CheckEmployee(&test_client, "nevermind")
assert.NoError(t, err)
assert.Equal(t, exists, true)
}
, . , , :
type BaseDBClient interface {
Get(interface{}, string, ...interface{}) error
}
type TestDBClient struct {
success bool
}
func (t *TestDBClient) Get(interface{}, string, ...interface{}) error {
if t.success {
return nil
}
return fmt.Errorf("This is a test error")
}
func TestCheckEmployee(t *testing.T) {
type args struct {
db BaseDBClient
}
tests := []struct {
name string
args args
wantErr error
wantExists bool
}{
{
name: "Employee exists",
args: args{
db: &TestDBClient{success: true},
},
wantErr: nil,
wantExists: true,
}, {
name: "Employee don't exists",
args: args{
db: &TestDBClient{success: false},
},
wantErr: fmt.Errorf("This is a test error"),
wantExists: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotErr, gotExists := CheckEmployee(tt.args.db, "some phone")
if !reflect.DeepEqual(gotErr, tt.wantErr) {
t.Errorf("CheckEmployee() gotErr = %v, want %v", gotErr, tt.wantErr)
}
if gotExists != tt.wantExists {
t.Errorf("CheckEmployee() gotExists = %v, want %v", gotExists, tt.wantExists)
}
})
}
}
! , , , , , go.
, , .
Claro, essa abordagem tem suas desvantagens. Por exemplo, se sua lógica estiver ligada a algum tipo de lógica interna de banco de dados, esse teste não será capaz de identificar erros causados pelo banco de dados. Mas acredito que o teste com a participação de um banco de dados e serviços de terceiros não é mais sobre testes unitários, são mais integração ou até mesmo testes e2e, e estão um pouco além do escopo deste artigo.
Obrigado por ler e escrever testes!