📚 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:


🤔 Por que fizemos assim?


🔍 Checkpoint

  1. Validação de Testes: Execute o comando pytest na pasta raiz do backend e valide se a esteira de testes assíncronos de pedido roda e conclui com 100% de sucesso.
  2. 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.
  3. 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! 🚀


Voltar para o Sumário