diff --git a/README.md b/README.md index e72459d..bcc93ac 100644 --- a/README.md +++ b/README.md @@ -31,4 +31,6 @@ Vamos tentar nos preparar para situações ruins que possam acontecer e garantir 1. [🧰 Escolhendo as melhores ferramentas](ferramentas.md) -1. [📐 Iniciando o projeto](projeto.md) \ No newline at end of file +1. [📐 Iniciando o projeto](projeto.md) + +1. [👋 Olá API](hello_api.md) \ No newline at end of file diff --git a/imgs/codigo.png b/imgs/codigo.png new file mode 100644 index 0000000..aa2c029 Binary files /dev/null and b/imgs/codigo.png differ diff --git a/imgs/healthcheck.png b/imgs/healthcheck.png new file mode 100644 index 0000000..973064c Binary files /dev/null and b/imgs/healthcheck.png differ diff --git a/imgs/tdd.png b/imgs/tdd.png new file mode 100644 index 0000000..78e5f34 Binary files /dev/null and b/imgs/tdd.png differ diff --git a/ola_api.md b/ola_api.md new file mode 100644 index 0000000..125874b --- /dev/null +++ b/ola_api.md @@ -0,0 +1,367 @@ +# 👋 Olá API + +

+ mãos escrevendo código com caneca de café ao fundo +

+ +## ✍️ Escrevendo código + +É chegada a tão esperada hora de escrevermos código, porém, como aprendemos que podemos ser guiados por testes para ajudar a concepção da arquitetura do nosso programa, faremos as coisas um pouco diferente. + +Utilizaremos os ciclos do [TDD](tdd.md) para nos auxiliarem e assim garantiremos uma qualidade de código ao final. + +Estão lembrados o que é a nossa aplicação? Caso não se recorde leia o nosso [planejamento](planejando.md) novamente. + +Acho que podemos iniciar com os dois requisitos listados abaixo: + +- [ ] Deve apresentar uma interface que possa ser consumida tanto por um website, tanto por um aplicativo para dispositivos móveis. + +- [ ] Deve prover um _endpoint_ que indique a saúde do sistema. + +Mas como fazer isto se não temos nem mesmo uma aplicação ainda? Por onde começo? + +Inicie criando um diretório com o nome `tests`, onde colocaremos os testes do nosso programa. + +Lá dentro, crie um arquivo com nome `test_api.py` que deve ficar da seguinte maneira: + +``` +. +├── LICENSE +├── poetry.lock +├── pyproject.toml +├── README.md +└── tests + └── test_api.py +``` + +Agora vamos escrever nosso primeiro teste! + +> ℹ️ O código está escrito em português para ajudar na didática. + +Para indicar a integridade do nosso sitema, vamos ter um _endpoint_ `/healthcheck`, que ao receber um requisição, deve retornar o código de status `200 OK` e este será nosso primeiro teste. + +Traduzindo em um teste automatizado, vamos escrever o seguinte código no arquivo `tests/test_api.py`. + +> tests/test_api.py +```python +from fastapi.testclient import TestClient +from http import HTTPStatus + + +def test_quando_verificar_integridade_devo_ter_como_retorno_codigo_de_status_200(): + cliente = TestClient(app) + resposta = cliente.get("/healthcheck") + assert resposta.status_code == HTTPStatus.OK +``` + +> ℹ️ Note a utilização do prefixo `test` nos diretórios e arquivos de testes. Isto é necessário para que a ferramenta de testes do Python consiga identificar os testes e executá-los. + +Vamos rodar pela primeira vez os testes no nosso projeto. + +> ℹ️ Para não precisar digitar a todo momento os comandos em sua forma extensa `poetry run ` vamos ativar nosso ambiente virtual com o comando `poetry shell`. + +``` +python -m pytest tests/ +``` +😱 Nossa! Ocorreu um erro! + +``` +$ python -m pytest tests/ +======================================================================================= test session starts ======================================================================================= +platform linux -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 +rootdir: /home/cassiobotaro/Projects/tutorial-pybr +collected 1 item + +tests/test_api.py F [100%] + +============================================================================================ FAILURES ============================================================================================= +__________________________________________________________ test_quando_verificar_integridade_devo_ter_como_retorno_codigo_de_status_200 ___________________________________________________________ + + def test_quando_verificar_integridade_devo_ter_como_retorno_codigo_de_status_200(): +> cliente = TestClient(app) +E NameError: name 'app' is not defined + +tests/test_api.py:6: NameError +===================================================================================== short test summary info ===================================================================================== +FAILED tests/test_api.py::test_quando_verificar_integridade_devo_ter_como_retorno_codigo_de_status_200 - NameError: name 'app' is not defined +======================================================================================== 1 failed in 0.23s ======================================================================================== +``` + +O teste falha pois ainda não temos a nossa aplicação. + +A primeira coisa que precisamos fazer é criar um diretório onde colocaremos nossos códigos. vamos chama-lo de `api_pedidos`. + +Dentro dele criamos um novo arquivo `api_pedidos/api.py`, e neste arquivo vamos iniciar uma aplicação da seguinte maneira. + +> api_pedidos/api.py +```python +from fastapi import FastAPI + + +app = FastAPI() +``` + +Nosso diretório ficará da seguinte maneira: + +``` +. +├── api_pedidos +│ └── api.py +├── LICENSE +├── poetry.lock +├── pyproject.toml +├── README.md +└── tests + └── test_api.py + +``` +Agora vamos voltar ao arquivo `tests/test_api.py` e adicionamos a seguinte linha. + +> tests/test_api.py +```python +from api_pedidos.api import app +``` + +Rode novamente os testes. + +``` +python -m pytest tests/ +``` + +❌ Os testes continuam falhando! + +Agora temos nossa aplicação, mas nosso _endpoint_ ainda não foi criado. + +No arquivo `api_pedidos/api.py` vamos criar o endpoint `/healthcheck`: + +> api_pedidos/api.py +```python +@app.get("/healthcheck") +async def healthcheck(): + return +``` + +Rode novamente os testes. + +``` +python -m pytest +``` +✔️ Legal! Temos um teste funcionando! Nossa aplicação está retornando o código de status 200 OK, ainda que a funcionalidade completa não esteja pronta. + +👶 Damos o nome de `baby step`, esta maneira de construir uma aplicação dando pequenos passos de cada vez. + +Nosso recurso deve ter o formato [json](http://json.org/), que é um formato textual estruturado, bem simples e leve para troca de informações. + +Mas como checamos isto? + +Vamos escrever um novo teste! + +> tests/test_api.py +```python +def test_quando_verificar_integridade_formato_de_retorno_deve_ser_json(): + cliente = TestClient(app) + resposta = cliente.get("/healthcheck") + assert resposta.headers["Content-Type"] == "application/json" +``` + +Rode os testes novamente. Caso esqueça o comando, volte um pouco atrás e copie. + +👀 O novo teste está passando ?!?! + +Acontece que por padrão, o fastapi já define que o formato será "json". + +Normalmente, queremos que testes falhem, porém este teste pode ser útil como documentação do seu recurso. + +Vamos deixa-lo e vamos seguir em frente, mas agora tentando escrever um teste que realmente falhe. + +Quando verificar integrade o retorno deve possuir o seguinte formato: +```json +{ + "status": "ok" +} +``` + +Assim se precisarmos adicionar mais detlhes neste _endpoint_ podemos adicionar novas chaves a esta estrutura. +Estes detalhes podem envolver o estado da conexão com o banco de dados. + +> tests/test_api.py +```python +def test_quando_verificar_integridade_deve_conter_informacoes(): + cliente = TestClient(app) + resposta = cliente.get("/healthcheck") + assert resposta.json() == { + "status": "ok" + } +``` + +Rode novamente os testes. + +❌ O teste falha e isto é bom! + +Vamos continuar nosso ciclo e corrigir o código. + +> api_pedidos/api.py +```python +@app.get("/healthcheck") +async def healthcheck(): + return {"status": "ok"} +``` + +✔️ Aew! Testes estão passando novamente! + +🚦 Perceberam que estamos guiando o nosso desenvolvimento a partir dos testes? Pouco a pouco temos a funcionalidade de listagem sendo desenhada. + +Neste passo os arquivos devem estar da seguinte maneira: + +> api_pedidos/api.py +``` +from fastapi import FastAPI + + +app = FastAPI() + + +@app.get("/healthcheck") +async def healthcheck(): + return {"status": "ok"} +``` + +> tests/test_api.py +``` +from fastapi.testclient import TestClient +from http import HTTPStatus +from api_pedidos.api import app + + +def test_quando_verificar_integridade_devo_ter_como_retorno_codigo_de_status_200(): + cliente = TestClient(app) + resposta = cliente.get("/healthcheck") + assert resposta.status_code == HTTPStatus.OK + + +def test_quando_verificar_integridade_formato_de_retorno_deve_ser_json(): + cliente = TestClient(app) + resposta = cliente.get("/healthcheck") + assert resposta.headers["Content-Type"] == "application/json" + + +def test_quando_verificar_integridade_deve_conter_informacoes(): + cliente = TestClient(app) + resposta = cliente.get("/healthcheck") + assert resposta.json() == { + "status": "ok", + } +``` + +Os testes estão funcionando? Parabéns! 👏👏 👏 Agora vamos refatorar o código. + +## 🪄 Refatorando o código + +Se reparar bem, estamos repetindo a seguinte linha de código três vezes. + +``` +client = TestClient(app) +``` + +Vamos utilizar uma _fixture_ (é um ambiente usado para testar consistentemente algum item) para criar um cliente. + +> tests/test_api.py +```python +from http import HTTPStatus + +import pytest +from api_pedidos.api import app +from fastapi.testclient import TestClient + + +@pytest.fixture +def cliente(): + return TestClient(app) + + +def test_quando_verificar_integridade_devo_ter_como_retorno_codigo_de_status_200(cliente): + resposta = cliente.get("/healthcheck") + assert resposta.status_code == HTTPStatus.OK + + +def test_quando_verificar_integridade_formato_de_retorno_deve_ser_json(cliente): + resposta = cliente.get("/healthcheck") + assert resposta.headers["Content-Type"] == "application/json" + + +def test_quando_verificar_integridade_deve_conter_informacoes(cliente): + resposta = cliente.get("/healthcheck") + assert resposta.json() == { + "status": "ok", + } +``` + +Após esta mudança, os testes devem continuar passando. + +## 🔧 Testando manualmente + +Para testar nossa aplicação manualmente, precisamos colocar nossa aplicação no ar. + +O comando para isto é `uvicorn --reload api_pedidos.api:app`. + +Voilá, sua aplicação está no ar. Vamos utilizar a ferramenta `httpie` para testar a aplicação. + +![implementação do healthcheck](imgs/healthcheck.png "implementação do healthcheck") + +> ℹ️ Como adicionamos a opção `--reload`, cada vez que modificamos o código, o resultado é modificado também, sem precisar desligar e rodar de novo a aplicação. + +## 💾 Salvando a versão atual do código + +Com tudo terminado, vamos salvar a versão atual do código. + +Primeiro passo é checar o que foi feito até agora: + +```bash +$ git status +On branch main +Your branch is up to date with 'origin/main'. + +Untracked files: + (use "git add ..." to include in what will be committed) + api_pedidos/ + tests/ + +nothing added to commit but untracked files present (use "git add" to track) +``` + +Vemos dois diretórios não rastreados e precisamos avisar ao controle de versão para monitora-los. + +``` +git add api_pedidos tests +``` + +💾 Agora vamos marcar esta versão como salva. + +``` +git commit -m "Adicionando healthcheck" +``` + +:octocat: Por fim envie ao github a versão atualizada do projeto. + +``` +git push +``` + +Parabéns! A aplicação está tomando forma! 🎉 + +Podemos marcar como pronto as seguintes tarefas: + +- [x] Deve apresentar uma interface que possa ser consumida tanto por um website, tanto por um aplicativo para dispositivos móveis. + +- [x] Deve prover um _endpoint_ que indique a saúde do sistema. + +- [x] O sistema deve apresentar testes. (Acabamos cumprindo uma tarefa a mais!) + + +> 🐂 Uma API robusta provê maneiras de verificar sua integridade. + +[Integração com serviços externos ➡️](integracao.md) + +[⬅️ Iniciando o projeto](projeto.md) + +[↩️ Voltar ao README ](README.md) \ No newline at end of file diff --git a/tdd.md b/tdd.md new file mode 100644 index 0000000..3f2af11 --- /dev/null +++ b/tdd.md @@ -0,0 +1,70 @@ +# 🐐 Desenvolvimento guiado por testes (TDD) + +

+ ciclos do tdd +

+ +## Testes automatizados + +Talvez tenha chegado aqui sem conhecimento sobre testes automatizados, por isso vamos a uma breve explicação. + +É muito comum enquanto estamos desenvolvendo, testarmos manualmente as funcionalidades que estamos implementando, mas o que pode ocorrer se eu me esquecer de testar alguma funcionalidade? Por isso escrevemos código para testar o código das nossas funcionalidades. + +Automatizar testes é uma maneira de agilizar nosso processo de desenvolvimento, garantindo que novas funcionalidades não impactam sobre as antigas. + +Um exemplo de teste automatizado. + +**código** + +```python +def é_impar(numero): + 'Retorna True se um número é verdadeiro, senão False.' + return numero % 2 != 0 +``` + +**teste** + +```python +def test_quando_entrada_é_três_retorna_verdadeiro(): + assert é_impar(3) is True + + +def test_quando_entrada_é_dois_retorna_falso(): + assert é_impar(2) is False +``` + + +É importante notar que nos testes eu tento cobrir todas as possibilidades de resultado daquela função, como no exemplo que testo a função para valores pares e ímpares. + +## O que é TDD? + +Desenvolvimento guiado por testes(Test Driven Development) é uma técnica de desenvolvimento de software que baseia em um ciclo curto de repetições: Primeiramente o desenvolvedor escreve um caso de teste automatizado que define uma melhoria desejada ou uma nova funcionalidade. Então, é produzido código que possa ser validado pelo teste para posteriormente o código ser refatorado para um código sob padrões aceitáveis. + +Mas por que TDD? + +Escrever testes antes do código ajuda no planejamento da arquitetura da aplicação, e os testes podem ser um guia de como a aplicação deve se comportar. + +## O ciclo + +**1 - Adicione um teste** + +Normalmente analisamos alguma funcionalidade que desejamos implementar ou validar e escrevemos um teste que será executado automaticamente relacionado aquela funcionalidade. +Ainda que uma função/classe não exista, devemos escrever o comportamento esperado da mesma. + +**2 - Verifique se algum teste quebrou** + +Neste ponto devemos verificar se os testes passam a falhar(os antigos e o que você acabou de escrever) + +**3 - Escreva código** + +Escreva código necessário para que seu teste seja contemplado, mas evite escrever muito além do que necessário. + +**4 - Refatore seu código** + +Com os testes passando, analise se é possível alguma refatoração. + +**5 - Volte para o passo 1** + +:worried: Ainda não ficou claro o processo? Não se preocupe, daqui pra frente iremos ver este ciclo na prática. + +[↩️ Voltar](ola_api.md) \ No newline at end of file