Testes em Python - Parte 2: Testes orientados ao comportamento
Tags: python, testes, bdd, behave
Published: 2020-06-07 20:58:47


Essa é a continuação da [parte 1](https://dev.to/womakerscode/testes-em-python-parte-1-introducao-43ei) da série "Testes em Python". 

Na série de hoje iremos ver como realizar testes de comportamento resgatando alguns conceitos visto na primeira parte.

### Pré-requisito

- Conhecimento básico de Python
- Conceito básico sobre testes e o porque eles são importantes ([Veja a parte 1 da série](https://dev.to/womakerscode/testes-em-python-parte-1-introducao-43ei))

### Roteiro

1. O que são testes de comportamento
2. O que é BDD
3. Behave
4. Exemplo
5. Considerações adicionais
6. Conclusão

---

## 1. O que são testes de comportamento

### 1.1 Motivação

Ao receber uma demanda de uma implementação de funcionalidade, normalmente são os próprios desenvolvedores que implementam e realizam os casos de testes para validar se tudo funciona como o esperado. 

No entanto, essas implementações são escritas de uma maneira muito técnica, de modo que somente quem possui conhecimento de certas tecnologias possa entender, não permitindo uma clareza de comunicação com profissionais não-técnicos, que geralmente são os que demandam a funcionalidade.

### 1.2 Testes de comportamento

Testes de comportamento surgiu a partir da necessidade de validar cenários por meio de uma comunicação mais transparente. Deste modo, conseguimos validar qual deve ser o comportamento do nosso código e realizar assertivas em cima desses cenários.

Esses testes permitem:

- Entendimento comum por meio do escopo do negócio (regras de negócio)
- Comunicação clara por meio de linguagem comum entre todos os participantes envolvidos
- Melhora na documentação sobre os cenários da aplicação
- Ajuda em um melhor acordo comum da "definição de pronto" do sistema

## 2. O que é o BDD

[BDD](https://cucumber.io/docs/bdd/) é uma sigla que significa **B**ehavior-**D**riven **D**evelopment, do português desenvolvimento orientado ao comportamento. Ele foi introduzido por [Dan North](https://dannorth.net/introducing-bdd/) em resposta ao tradicional TDD¹

> TDD¹ é uma sigla que significa **T**est-**D**riven **D**evelopment, do português desenvolvimento orientado a testes. Resgatando o que foi visto na parte 1 da série, seria escrever os cenários de testes antes de implementar o nosso código propriamente dito. Assim, estaríamos garantindo que toda a nossa aplicação estaria coberta por testes. Veja mais em https://www.eusoudev.com.br/tdd/

  

Isso significa que vamos primeiro escrever cenários com comportamentos em linguagem natural e depois escrever nosso código orientado a isso. Veremos que esses comportamentos serão fluxos do usuário.

O BDD segue algumas [filosofias](https://behave.readthedocs.io/en/latest/philosophy.html), dentre elas pode se destacar a entrega de valor ao negócio. Assim, diferentes personas envolvidas na elaboração de um projeto como desenvolvedores, QA,  participantes do negócio e outras pessoas não técnicas podem garantir que o valor está sendo entregue a partir de uma [documentação clara e simplificada](https://lizkeogh.com/2007/06/13/bdd-tdd-done-well/).

Quando todos possuem uma visão clara dos requerimentos para um programa funcionar, todos ficam mais alinhados com o valor que será entregue, pois a aplicação estará coberta por esses cenários orientados ao comportamento. Isso inclusive pode levar com que todos descubram cenários não pensados anteriormente.

## 3. Behave

[Behave](https://behave.readthedocs.io/en/latest/index.html) é uma biblioteca em Python que iremos utilizar para fazer nossos testes orientados ao comportamento. Ela utiliza componentes do [cucumber](https://cucumber.io/docs/installation/), que é uma ferramenta para rodar testes escritos em linguagem natural.

Veremos como utilizar na prática por meio de um exemplo.

## 4. Exemplo

### 4.1 O escopo do exemplo

Vamos realizar um pequeno sistema de criação de conta de um banco simplificado. Nele, podemos além de criar uma conta, depositar e sacar dinheiro.

Vamos considerar:

1. Nome do cliente
2. Número da conta
3. Senha
4. Saldo

As ações de depositar e sacar dinheiro só poderão ser feitas se o cliente estiver autenticado.

**Nota:** Repare que esse fluxo é bem simplificado e vários outros fatores estão sendo desconsiderados dessa abstração para que possamos nos concentrar no BDD.

### 4.2 Preparando o ambiente

Vamos criar um diretório para conter o nosso código, vamos chamar a pasta de **simple_bank_account**.

Antes de começar a implementação, vamos escrever o comportamento esperado. Para isso vamos instalar o [behave](https://behave.readthedocs.io/en/latest/install.html).

OBS.: Sugiro utilizar um ambiente virtual para instalar as dependências, pois irá possibilitar isolar o nosso código de outros recursos e projetos que temos no nosso computador. [Recomendo o tutorial do Django Girls para isso](https://tutorial.djangogirls.org/pt/django_installation/#ambiente-virtual).

Quando estiver no ambiente virtual, rode o comando para instalar o behave

```python
pip install behave
```

Agora vamos implementar nossa estrutura base de arquivos. Teremos nossa estrutura raiz que terá o projeto e o behave sempre procura os arquivos dentro de um diretório chamado **features** (irá conter as funcionalidades do sistema) e as **steps** (local onde iremos realizar as assertivas para garantir que as funcionalidades estão funcionando).

No final vamos ter algo parecido com essa estrutura:

![Estrutura de pastas de testes com behave](https://dev-to-uploads.s3.amazonaws.com/i/ovmop537lf086zl25cih.png)

Feita a criação dos diretório, chegamos a essa estrutura

![Estrutura do código de exemplo](https://dev-to-uploads.s3.amazonaws.com/i/fo59ykpgoil46f5zjskn.png)

Vamos agora implementar nossa primeira funcionalidade

### 4.3 Escrevendo a estória do usuário (features)

Vamos escrever o nosso primeiro fluxo do usuário.

Esses fluxos são arquivos terminados em **.feature** e são interpretados pelo behave a partir de uma linguagem chamada [Gherkin](https://cucumber.io/docs/gherkin/reference/#spoken-languages). 

Essa linguagem permite interpretar a linguagem escrita de maneira natural. Diversos idiomas são suportados e para o nosso exemplo vamos ver o uso desse documento escrito em português.

**Fluxo de criação da conta**

Dentro da pasta **features** vamos criar um arquivo chamado **create_account.feature** e nele iremos escrever a funcionalidade de criação de conta.

Seguindo a nomenclatura da [documentação](https://behave.readthedocs.io/en/latest/tutorial.html), mas escrevendo em português, temos o seguinte fluxo

```python
# language: pt

Funcionalidade: criar conta

  """
    Como cliente, eu quero que seja possível criar uma nova conta no banco para possibilitar depositar e sacar dinheiro
  """

  Cenário: cliente solicita criação de conta
    Quando a conta do cliente for criada
    Então o saldo de sua conta deve estar zerado
```

4.3.1  create_account.feature

Repare que aqui temos algumas palavras chaves como **Funcionalidade**, **Cenário**, **Quando** e **Então**.

- **Funcionalidade**: descreve o comportamento esperado de uma funcionalidade da aplicação. 
- **Cenário**: descreve uma parte do comportamento esperado da funcionalidade. Faz referência com cada cenário de teste que vimos com unittest.
- **Quando**: descreve uma ação que acontece na nossa aplicação
- **Então**: o que deve ser retornado quando a ação for realizada

Repare que esse documento poderá ser escrito por analistas de negócios, analista de qualidade e profissionais não técnicos justamente por estar em linguagem natural.

Outras palavras chaves podem ser utilizadas e o mapeamento completo pode ser visto na documentação do [cucumber](https://cucumber.io/docs/gherkin/reference/)


Vamos rodar no terminal o comando behave dentro da raiz do nosso projeto e ver o que acontece

```bash
behave
```

![Avisos ao rodar o behave pela primeira vez](https://dev-to-uploads.s3.amazonaws.com/i/4nja61r3zqavmbwisqtf.png)

Repare que temos alguns avisos nos alertando que não temos nenhuma definição de **step** implementada.  Vamos implementar essas steps.

### 4.4 Escrevendo as steps (implementando as assertivas)

Aqui é onde tudo será validado. Steps são arquivos em python (.py) que irá implementar as palavras chaves utilizadas no arquivo de **feature**.

Dentro da pasta **features/steps** vamos criar uma step que valide a feature de criação de conta. Para isso, vamos criar o arquivo **create_account.py** criando uma estrutura desse jeito

![Nota estrutura de arquivos](https://dev-to-uploads.s3.amazonaws.com/i/n68h8dab0p82det83t0n.png)

Repare que no arquivo **4.3.1 create_account.feature** utilizamos algumas palavras chave. Essas palavras chaves serão utilizadas com base em um [decorator](https://realpython.com/primer-on-python-decorators/) implementado pela própria biblioteca do behave. Vamos importar as palavras mapeadas

```python
from behave import when, then

@when("a conta do cliente for criada")
def create_account(context):
    pass

@then("o saldo de sua conta deve estar zerado")
def validate_create_account(context):
    pass
```

4.4.1 create_account.py 

Repare que nesse arquivo python utilizamos as palavras chaves **when** e **then** que respectivamente significam o **quando** e **então** que utilizamos no nosso arquivo **4.3.1 create_account.feature**. E o parâmetro que é passado pra esse decorator (@when e @then) é justamente o texto que escrevemos na nossa feature.

Vamos rodar o behave novamente

![Testes passaram sem implementação](https://dev-to-uploads.s3.amazonaws.com/i/7lwdstpo0bn6d0envtwy.png)

Veremos que tudo passou, mas na verdade não estamos validando nada ainda. Vamos começar a rascunhar nosso código e importar no arquivo de step.

### 4.5 Interagindo com o código e o teste

Iremos criar uma estrutura de classe simples para conseguir importar no nosso arquivo de teste. Vamos criar fora da pasta de features

![Arquivo novo que irá conter nosso código simplificado de criação de conta](https://dev-to-uploads.s3.amazonaws.com/i/4bya3767gbg63nqpqniu.png)

```python
class Client:
    def __init__(self):
        pass

class SimpleBankAccount(Client):
    def __init__(self):
        super().__init__()
```

4.5.1 simple_bank_account.py 

Considerando os requisitos levantados no escopo do exemplo, vamos passar para criação da conta o nome do cliente, a numeração da conta (sim, nesse exemplo fictício o cliente especifica isso) e a senha. Logo depois, validar se o saldo da conta é zero.

```python
from behave import when, then
from simple_bank_account import SimpleBankAccount

@when("a conta do cliente for criada")
def create_account(context):
    account = SimpleBankAccount(client_name="Shizuku Tsukishima", number=1, password=123456)
    context.result = account.balance

@then("o saldo de sua conta deve estar zerado")
def validate_create_account(context):
    assert context.result == 0
```

4.5.2 create_account.py 

Na primeira step **@when("a conta do cliente for criada")** instanciamos nossa classe **SimpleBankAccount** passando os dados necessários que gostaríamos.

Logo em seguida, salvamos dentro do contexto da nossa step, o resultado que queremos validar através de um [comportamento dinâmico que o behave implementa](https://www.geeksforgeeks.org/monkey-patching-in-python-dynamic-behavior/). Isso é importante para compartilhar valores com outras steps.

Já na step **@then("o saldo de sua conta deve estar zerado")** pegamos o valor salvo dentro do contexto e validamos se é igual a zero com o `assert`

Vamos rodar o behave novamente

![Falha nos testes com behave, TypeError](https://dev-to-uploads.s3.amazonaws.com/i/zmwvnscnks46ckf4gy0u.png)

Veremos que começou a aparecer alguns erros, o primeiro ele é um **TypeError** pois o nosso código não tem ainda nada implementado, somente a base. Vamos voltar no arquivo  4.5.1 simple_bank_account.py e inicializar a classe com esses argumentos.

```python
class Client:
    def __init__(self, client_name: str):
        self.name = client_name

class SimpleBankAccount(Client):
    def __init__(self, client_name: str, number: int, password: int):
        super().__init__(client_name=client_name)
        self.number = number
        self.password = password

```

4.5.3 simple_bank_account.py 

Rodando novamente o behave vamos perceber que não existe também o atributo **balance** que irá conter o saldo da conta. Isso acontece pois no **4.5.2 create_account.py** estamos acessando **account.balance** e esse atributo realmente não existe em nossa classe. Vamos adicioná-lo

```python
class Client:
    def __init__(self, client_name: str):
        self.name = client_name

class SimpleBankAccount(Client):
    def __init__(self, client_name: str, number: int, password: int):
        super().__init__(client_name=client_name)
        self.number = number
        self.password = password
        self.balance = 0
```

Rodando novamente o behave veremos que a funcionalidade **criar conta** deve funcionar

![Testes passando e implementado com behave](https://dev-to-uploads.s3.amazonaws.com/i/j636rgnesoso3xbd71yx.png)

E repare que dessa vez ele passou pois realmente estava testando nosso cenário. Se trocássemos o assert da nossa step para 

```python
assert context.result != 0
```

por exemplo, veremos que os testes não irão mais passar

![Testes irão falhar se a assertiva for modificada](https://dev-to-uploads.s3.amazonaws.com/i/awcarsi4whl0sfgku3kg.png)

É bom fazer essas validações com outros tipos de valores para garantir que o teste realmente está  sendo implementado.

### 4.6 Funcionalidade de depositar e sacar

Para esse artigo não ficar muito grande, não iremos ver nesse artigo como ficaram as outras funcionalidades. No entanto, o código poderá ser visto [aqui](https://github.com/gabriellydeandrade/python-material/tree/master/tests/bdd/simple_bank_account). 

No final, teremos um retorno que descreve exatamente o fluxo de interação do usuário com o nosso sistema. 

![Testes passando para a funcionalidade de depositar e sacar da nossa conta simplificada](https://dev-to-uploads.s3.amazonaws.com/i/127iaaictfsetcznlms6.png)

## 5. Considerações adicionais

Aqui, listo alguns tópicos que são interessantes levar em consideração:

- Para conseguir fazer um bom levantamento nos arquivos de funcionalidade, é uma boa prática implementar uma cultura de [Discovery](https://cucumber.io/docs/bdd/discovery-workshop/) para envolver diferentes profissionais para discutir a cerca do negócio e da implementação.
- É importante seguir o fluxo: escrever a funcionalidade > implementar os steps > implementar o código para programar orientado ao comportamento. E, ao longo desse processo, rodar o behave para ver os testes pendentes, falhando e passando. Mas nunca se esqueça de refatorar o código. Nesse código de exemplo mesmo poderíamos ter desacoplado algumas coisas como a parte de autenticação (pode ser visto no código completo do [github](https://github.com/gabriellydeandrade/python-material/tree/master/tests/bdd/simple_bank_account)) e se ao rodar os testes eles passarem, ainda estaríamos garantindo que os comportamentos permanecem os mesmos.
- [Utilize hooks para evitar steps repetidas nos testes de comportamento](https://behave.readthedocs.io/en/latest/context_attributes.html?highlight=hooks#context-attributes)
- Aprender sobre as regras de negócio é tão importante quanto aprender sobre tecnologia. Lembre-se que nosso papel como profissional da área de tecnologia é resolver problemas de um certo domínio de negócio. Entender como essas regras funcionam são importantes para ajudar a pensar em cenários e corner cases nas validações.

## 6. Conclusão

O BDD reforça o pensamento "Outside-in" (de fora para dentro) e é guiado a entrega de valor ao negócio. Os colaboradores então trabalham em conjunto no levantamento dos cenários que serão entregues e que automaticamente estão relacionados com a cobertura de testes do código em questão. 

Usar ou não BDD? Depende. Se for necessária interações entre diferentes personas (analista de negócios, desenvolvedores, testadores, stackholder) pode ser interessante principalmente pela escrita de um documento em linguagem natural. 

De toda forma é interessante saber que existe essa outra maneira de implementar testes e que tendo um documento comum que será utilizado tanto para validar os cenários do sistema tanto como definições dos critérios de aceitação entre os analistas de negócio é algo bem legal :)

> A parte mais difícil de construir um sistema é decidir precisamente o que construir

– Fred Brooks, O Mítico Homem-Mês: Ensaios Sobre Engenharia de Software

![https://media.giphy.com/media/lD76yTC5zxZPG/giphy.gif](https://media.giphy.com/media/lD76yTC5zxZPG/giphy.gif)