📚 Módulo 09: DevOps - Testes Pytest, Docker, CI/CD e Checklist
Neste módulo de encerramento da TecLoja 02, consolidaremos a infraestrutura de DevOps e qualidade de software do nosso ecossistema Python e Vue 3.
Implementaremos a esteira de testes automatizados com pytest e Mockito equivalentes (unittest.mock), estruturaremos arquivos Dockerfiles otimizados para produção, configuraremos os fluxos de entrega contínua (CI/CD no GitHub Actions para Render e Netlify) e entregaremos o Checklist de Validação Autoguiado (Rubrica) para garantir que o projeto atenda a todos os quesitos acadêmicos e corporativos.
🗺️ 1. Fluxo de Integração e Deploy Multirepo
Abaixo, revisamos visualmente a pipeline de entrega independente de ambas as partes do sistema:
flowchart TD
%% Styling
classDef gitHub fill:#24292e,stroke:#333,stroke-width:2px,color:#fff;
classDef actions fill:#2088ff,stroke:#005cc5,stroke-width:2px,color:#fff;
classDef prod fill:#28a745,stroke:#22863a,stroke-width:2px,color:#fff;
subgraph Repositorio_Backend ["📦 Repositório: tecloja-backend"]
A[Git Push 'main']:::gitHub --> B[GitHub Action CI/CD Backend]:::actions
B --> C[pytest<br>Unit Tests]:::actions
B --> D[Docker Build<br>Multi-Stage Python Slim]:::actions
D --> E[Deploy na Render Container]:::prod
end
subgraph Repositorio_Frontend ["📦 Repositório: tecloja-frontend"]
F[Git Push 'main']:::gitHub --> G[GitHub Action CI/CD Frontend]:::actions
G --> H[npm run build<br>Vue 3 + Vite Production]:::actions
G --> I[Deploy na Netlify CDN]:::prod
end
E <-->|CORS Habilitado / JWT Bearer| I
🧪 2. Testes de Unidade Assíncronos com Pytest
Em Python, testar métodos assíncronos que interagem com sessões de banco de dados do SQLAlchemy exige mocks robustos. Utilizaremos o módulo nativo unittest.mock para simular as entidades e certificar o correto fluxo de rollback de transações.
Crie o arquivo em tests/test_pedido_service.py no seu repositório backend:
import pytest
from decimal import Decimal
from unittest.mock import AsyncMock, MagicMock
from app.exceptions import BusinessException, ResourceNotFoundException
from app.models.pedido import Cliente
from app.models.produto import Produto
from app.schemas.pedido_schema import PedidoFormSchema, ItemPedidoFormSchema
from app.services.pedido_service import PedidoService
@pytest.mark.asyncio
async def test_realizar_pedido_sucesso():
# 1. Preparar massa de teste falsa (Mocks)
session_mock = AsyncMock()
# Mock do begin() para gerenciar a transação assíncrona
session_mock.begin = AsyncMock()
cliente_mock = Cliente(id=1, nome="Maria Silva", email="maria@gmail.com")
produto_mock = Produto(id=10, nome="Notebook Gamer", preco=Decimal("5000.00"), estoque=5)
# Configura o retorno do session.get() para simular o banco
async def mock_get(model, pk):
if model == Cliente:
return cliente_mock
if model == Produto:
return produto_mock
return None
session_mock.get = AsyncMock(side_effect=mock_get)
# Instancia DTO de entrada
item_form = ItemPedidoFormSchema(produto_id=10, quantidade=2)
form = PedidoFormSchema(cliente_id=1, itens=[item_form])
# 2. Executar a lógica de serviço
service = PedidoService(session_mock)
resultado = await service.realizar_pedido(form)
# 3. Asserções e Validações de Qualidade
assert resultado is not None
assert resultado.cliente_nome == "Maria Silva"
assert resultado.valor_total == Decimal("10000.00") # 2 unidades x R$ 5000
# Valida integridade referencial: estoque reduzido fisicamente no objeto
assert produto_mock.estoque == 3 # 5 - 2 = 3
# Garante que o flush e commit de persistência foram invocados
assert session_mock.flush.called
@pytest.mark.asyncio
async def test_realizar_pedido_estoque_insuficiente():
session_mock = AsyncMock()
session_mock.begin = AsyncMock()
cliente_mock = Cliente(id=1, nome="Maria Silva", email="maria@gmail.com")
produto_mock = Produto(id=10, nome="Notebook Gamer", preco=Decimal("5000.00"), estoque=5)
async def mock_get(model, pk):
if model == Cliente:
return cliente_mock
if model == Produto:
return produto_mock
return None
session_mock.get = AsyncMock(side_effect=mock_get)
# Solicita 6 unidades, mas há apenas 5 em estoque
item_form = ItemPedidoFormSchema(produto_id=10, quantidade=6)
form = PedidoFormSchema(cliente_id=1, itens=[item_form])
service = PedidoService(session_mock)
# Executa e valida o lançamento de BusinessException
with pytest.raises(BusinessException) as exc_info:
await service.realizar_pedido(form)
assert "Estoque insuficiente" in str(exc_info.value)
# Princípio ACID: Garante que o estoque não foi debitado em caso de falha
assert produto_mock.estoque == 5
# Garante que nenhuma alteração foi persistida no banco (flush nunca executado)
assert not session_mock.flush.called
🐳 3. Conteinerização de Produção com Docker
Para garantir portabilidade e deploy seguro, criaremos imagens leves de produção.
🐍 1. Dockerfile Otimizado da API (FastAPI)
O padrão Multi-stage separa o compilador C do Python do runtime final, reduzindo a imagem para apenas ~100MB.
Crie na raiz do repositório backend o arquivo Dockerfile:
# Estágio 1: Instalação de dependências e build
FROM python:3.11-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends gcc python3-dev
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
# Estágio 2: Imagem final leve de execução
FROM python:3.11-slim
WORKDIR /app
# Cria usuário não-root por motivos de conformidade de segurança corporativa
RUN addgroup --system teclojagroup && adduser --system --group teclojauser
USER teclojauser
# Copia os pacotes instalados do estágio builder
COPY --from=builder /root/.local /home/teclojauser/.local
COPY . .
ENV PATH=/home/teclojauser/.local/bin:$PATH
ENV PORT=8000
EXPOSE 8000
CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT}"]
💚 2. Dockerfile Otimizado do Frontend (Nginx para Vue 3)
Compila os arquivos JS/HTML da SPA estática via Vite e os serve usando um servidor Nginx leve, que lida perfeitamente com re-links internos do Vue Router.
Crie na raiz do repositório frontend o arquivo Dockerfile:
# Estágio 1: Build da SPA estática
FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Estágio 2: Nginx Web Server
FROM nginx:alpine
# Copia a configuração customizada de rewrite do Vue Router
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
⚙️ Arquivo complementar: nginx.conf
Evita erro 404 ao atualizar rotas internas virtuais do Vue (ex: /carrinho).
Crie no frontend o arquivo nginx.conf:
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}
🚀 4. Pipelines CI/CD com GitHub Actions
Configuraremos as esteiras de testes e implantações automáticas nos dois repositórios independentes do GitHub.
🐍 1. Pipeline Backend (.github/workflows/deploy-backend.yml)
Crie no repositório backend a estrutura .github/workflows/deploy-backend.yml:
name: CI/CD Backend - FastAPI
on:
push:
branches: [ main ]
jobs:
test-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Código
uses: actions/checkout@v3
- name: Configurar Python (3.11)
uses: actions/setup-python@v4
with:
python-version: '3.11'
cache: 'pip'
- name: Instalar Dependências
run: |
pip install -r requirements.txt
pip install pytest pytest-asyncio pytest-mock
- name: Executar Testes Unitários
run: pytest
- name: Trigger Deploy na Render (Webhook)
if: success()
run: |
curl -X POST "$"
💚 2. Pipeline Frontend (.github/workflows/deploy-frontend.yml)
Crie no repositório frontend a estrutura .github/workflows/deploy-frontend.yml:
name: CI/CD Frontend - Vue 3 SPA
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Código
uses: actions/checkout@v3
- name: Configurar Node (v18)
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: Instalar Dependências e Compilar
run: |
npm ci
npm run build
- name: Instalar Netlify CLI
run: npm install -g netlify-cli
- name: Deploy Produção para Netlify CDN
env:
NETLIFY_AUTH_TOKEN: $
NETLIFY_SITE_ID: $
run: |
netlify deploy --dir=dist --prod --message="Deploy Automático Vue 3 - Commit $GITHUB_SHA"
✅ Pré-Requisitos deste Módulo
Antes de finalizar e realizar a avaliação final da sua TecLoja 02, certifique-se de que:
- As telas de CRUD e o fluxo de checkout do carrinho de compras estão implementados no frontend (Módulo 08).
- A API REST do backend está estável e passa nas validações manuais de endpoints com OpenAPI/Swagger.
- Você possui o gerenciador Docker instalado na máquina local de desenvolvimento (caso pretenda testar a conteinerização localmente).
🤔 Por que fizemos assim?
- Por que usar mocks (
AsyncMockeunittest.mock) para testar o serviço de pedidos de forma assíncrona? O SQLAlchemy 2.0 e o FastAPI funcionam de forma assíncrona. Testar serviços batendo no banco de dados físico de teste (ou pior, em produção) torna a suíte de testes lenta, propensa a falhas de infraestrutura e dependente de limpeza prévia do banco. UsarAsyncMockpermite simular com precisão a resposta assíncrona de métodos de sessão (session.get(),session.flush()) garantindo que testemos puramente as regras de negócio de estoque e preço no serviço, simulando até falhas transacionais e rollbacks sem tocar no banco de dados. - Por que Dockerfiles Multi-stage para FastAPI Slim e Nginx no Vue 3? Imagens Docker padrão contêm compiladores, documentações e binários desnecessários para produção. O padrão Multi-stage compila as dependências e o código estático em um estágio builder descartável e transfere apenas os pacotes prontos para a imagem final mínima. No frontend Vue 3, em vez de usar um servidor Node.js pesado para responder requisições, compilamos o app em HTML/JS estáticos (
dist) e os servimos via Nginx Alpine, reduzindo o tamanho de gigabytes para megabytes e gerando uma enorme economia de custos de nuvem. - Por que configurar
try_filesno Nginx para o Vue Router? Em uma SPA (Single Page Application), o roteamento de telas é virtual e controlado no navegador via JavaScript. Se o usuário acessarhttp://dominio/carrinhodireto ou apertar F5, o Nginx não encontrará nenhuma pasta física chamada/carrinho/no servidor e retornará erro 404 Not Found. A diretivatry_files $uri $uri/ /index.htmlforça o Nginx a servir oindex.htmlpara qualquer caminho que ele não encontre localmente, repassando o tratamento da rota para o Vue Router processar no cliente.
🔍 Checkpoint
- Validação de Testes: Execute o comando
pytestna pasta raiz do backend e valide se a esteira de testes assíncronos de pedido roda e conclui com 100% de sucesso. - Verificação do Container: Rode
docker build -t tecloja-backend-prod .no backend e confirme se a imagem final foi compilada sem falhas de pacotes e possui tamanho enxuto. - Roteamento Nginx Local: Com o container do frontend no ar sob o Nginx, acesse diretamente a URL do carrinho no navegador e atualize a página (F5) para validar se o rewrite de rotas virtuais funciona perfeitamente sem exibir erro 404.
⚠️ Erros Comuns
| Erro | Causa | Solução |
|---|---|---|
RuntimeError: Task <Task...> got Future <Future...> attached to a different loop no Pytest |
Conflito na criação automática de event loops concorrentes por parte do plugin pytest-asyncio entre testes assíncronos. |
Crie um arquivo pytest.ini na raiz do backend declarando a diretiva asyncio_mode = auto, ou force o escopo do fixture de event loop para sessões. |
O build Docker do Vue 3 falha alegando que a pasta node_modules já existe ou causa lentidão extrema |
O comando COPY . . incluiu os pacotes do node_modules local de desenvolvimento para dentro da imagem de build do container Alpine. |
Crie um arquivo .dockerignore na raiz do repositório frontend e adicione as linhas node_modules e dist para evitar a cópia inútil de dependências locais de desenvolvimento para o container. |
| Erro 404 na API ao disparar requisições no deploy final de produção | O frontend foi compilado em produção carregando a URL da API fixada como http://localhost:8000 em vez do endereço na Render. |
Utilize variáveis de ambiente injetadas no build do Vite (import.meta.env.VITE_API_URL) e preencha essa variável com o domínio de produção HTTPS correto nas plataformas Netlify e Render. |
📝 5. Checklist de Validação Autoguiado (Auto-avaliação)
Utilize este roteiro de testes prático para verificar a corretude acadêmica de sua própria implementação da TecLoja 02:
📋 Rubrica de Testes Operacionais:
| Item de Avaliação | Objetivo do Teste | Como Testar | Resultado Esperado | Status |
|---|---|---|---|---|
| 1. SQLite local e Seeders | Massa de dados didática | Inicie a API FastAPI localmente e execute o script data_seeder.py. Abra o arquivo tecloja.db. |
O banco de dados deve conter as tabelas criadas pelo Alembic e 5 eletrônicos populados. | [ ] |
| 2. OpenAPI/Swagger Docs | Documentação viva de engenharia | Inicie a API com Uvicorn e acesse http://localhost:8000/docs em seu navegador. |
O painel interativo Swagger deve carregar com todos os endpoints documentados. | [ ] |
| 3. Segurança JWT | Bloqueio de acessos não autorizados | No Swagger Docs, tente fazer um POST no endpoint de criar produto /api/produtos sem o token Bearer. |
Deve bloquear e retornar o código HTTP 401 Unauthorized. | [ ] |
| 4. Validação de Transação | Integridade referencial de Banco | Tente faturar uma compra contendo 2 produtos: um iPhone (estoque=10) solicitando 1 unidade, e outro produto com estoque zerado pedindo 1 unidade. | A API deve retornar 400 Bad Request (“Estoque insuficiente”). Garanta no banco SQLite que o estoque do iPhone continuou sendo 10 (Rollback). | [ ] |
| 5. Reatividade no Vue 3 | Estado reativo (Ref/Computed) | Abra a vitrine do Vue 3 localmente, clique 3 vezes no produto “Galaxy S24”. | A quantidade no carrinho da Navbar deve computar instantaneamente para (3) sem piscar a tela. |
[ ] |
| 6. CI/CD verde | Pipelines de automação | Realize um commit na branch main de ambos os repositórios. |
Os fluxos no GitHub Actions devem concluir com sucesso e atualizar automaticamente os deploys públicos. | [ ] |
🏆 Conclusão do Curso
Fantástico! Você concluiu com excelência o desenvolvimento completo da TecLoja 02!
Você dominou uma das stacks mais robustas e eficientes do mercado: mapeou relações físicas e associativas complexas assíncronas no SQLAlchemy 2.0, estruturou migrações físicas via Alembic, blindou barreira de dados DTO com Pydantic v2, construiu controle transacional robusto contra anomalias concorrentes, blindou a API com segurança stateless baseada em JWT, programou a SPA estática em Vue 3 utilizando Composition API altamente otimizada com TypeScript, conteinerizou as aplicações com Docker e implementou esteiras automáticas de CI/CD no GitHub Actions!
Com esses conhecimentos sólidos, você está plenamente preparado para encarar qualquer desafio profissional avançado de Banco de Dados e Engenharia de Software! 🚀