DevOps & CI/CD

Estratégias de Testes em Pipelines CI: Unit, Integration e Smoke Tests na Prática

22 min de leitura

Estratégias de Testes em Pipelines CI: Unit, Integration e Smoke Tests na Prática

Fundamentos de Estratégias de Testes em Pipelines CI A integração contínua (CI) é o coração do desenvolvimento moderno, mas sem uma estratégia de testes bem definida, ela vira apenas um sistema que falha rapidamente. Quando você estabelece um pipeline CI, você está criando um fluxo automatizado que compila, testa e valida o código a cada commit. A questão que surge é: quais testes executar, em que ordem e com qual custo de tempo/recursos? Aqui entra a pirâmide de testes — um conceito fundamental que você precisa internalizar desde agora. A base são os unit tests (muitos, rápidos, baratos), o meio são os integration tests (moderados, mais lentosque unit tests) e o topo são os smoke tests (poucos, focados em validar o caminho crítico). Ignorar essa estrutura leva a pipelines lentos, caros e pouco confiáveis. Vamos entender cada camada em profundidade. Unit Tests: A Fundação Sólida O que são e por que importam Unit tests são testes que validam unidades isoladas

<h2>Fundamentos de Estratégias de Testes em Pipelines CI</h2>

<p>A integração contínua (CI) é o coração do desenvolvimento moderno, mas sem uma estratégia de testes bem definida, ela vira apenas um sistema que falha rapidamente. Quando você estabelece um pipeline CI, você está criando um fluxo automatizado que compila, testa e valida o código a cada commit. A questão que surge é: quais testes executar, em que ordem e com qual custo de tempo/recursos?</p>

<p>Aqui entra a pirâmide de testes — um conceito fundamental que você precisa internalizar desde agora. A base são os unit tests (muitos, rápidos, baratos), o meio são os integration tests (moderados, mais lentosque unit tests) e o topo são os smoke tests (poucos, focados em validar o caminho crítico). Ignorar essa estrutura leva a pipelines lentos, caros e pouco confiáveis. Vamos entender cada camada em profundidade.</p>

<h2>Unit Tests: A Fundação Sólida</h2>

<h3>O que são e por que importam</h3>

<p>Unit tests são testes que validam unidades isoladas de código — geralmente uma função ou um método. Eles devem rodar em milissegundos, não depender de banco de dados ou APIs externas, e responder a uma pergunta simples: &quot;essa função faz exatamente o que prometeu fazer?&quot; Se você tem uma função que calcula o preço final de um produto com desconto, um unit test validaria que <code>calcularPreco(100, 0.1)</code> retorna <code>90</code>.</p>

<p>A razão pela qual unit tests são a base da pirâmide é matemática simples: quanto mais cedo você detecta um bug, mais barato custa corrigi-lo. Um bug encontrado em um unit test custa minutos para corrigir. O mesmo bug encontrado em produção pode custar horas, dias ou mais. Além disso, unit tests bem escritos documentam o comportamento esperado do código — funcionam como uma especificação viva.</p>

<h3>Exemplo prático em Python</h3>

<p>Vamos imaginar um sistema de e-commerce. Você tem uma classe que aplica descontos:</p>

<pre><code class="language-python">class DescontoService:

def aplicar_desconto(self, valor_original: float, percentual: int) -&gt; float:

&quot;&quot;&quot;Aplica desconto percentual ao valor original.&quot;&quot;&quot;

if percentual &lt; 0 or percentual &gt; 100:

raise ValueError(&quot;Percentual deve estar entre 0 e 100&quot;)

return valor_original * (1 - percentual / 100)

def desconto_cliente_premium(self, valor_original: float, dias_cliente: int) -&gt; float:

&quot;&quot;&quot;Clientes com mais de 365 dias ganham 15% de desconto.&quot;&quot;&quot;

if dias_cliente &gt;= 365:

return self.aplicar_desconto(valor_original, 15)

return valor_original</code></pre>

<p>Agora os testes unitários usando <code>pytest</code>:</p>

<pre><code class="language-python">import pytest

from app.services import DescontoService

class TestDescontoService:

@pytest.fixture

def servico(self):

return DescontoService()

def test_aplicar_desconto_valido(self, servico):

resultado = servico.aplicar_desconto(100, 10)

assert resultado == 90

def test_aplicar_desconto_zero(self, servico):

resultado = servico.aplicar_desconto(100, 0)

assert resultado == 100

def test_aplicar_desconto_maximo(self, servico):

resultado = servico.aplicar_desconto(100, 100)

assert resultado == 0

def test_aplicar_desconto_percentual_invalido_negativo(self, servico):

with pytest.raises(ValueError):

servico.aplicar_desconto(100, -5)

def test_aplicar_desconto_percentual_invalido_acima_100(self, servico):

with pytest.raises(ValueError):

servico.aplicar_desconto(100, 150)

def test_desconto_cliente_premium_com_direito(self, servico):

resultado = servico.desconto_cliente_premium(100, 365)

assert resultado == 85 # 15% de desconto

def test_desconto_cliente_premium_sem_direito(self, servico):

resultado = servico.desconto_cliente_premium(100, 364)

assert resultado == 100 # sem desconto</code></pre>

<p>Note como cada teste é focado em um comportamento específico. Os testes validam casos válidos, limites e exceções. Quando você roda <code>pytest app/tests/test_services.py -v</code>, cada teste executa em milissegundos e você sabe imediatamente se suas funções fazem o que prometem.</p>

<h3>Configuração no Pipeline CI</h3>

<p>No seu arquivo <code>.github/workflows/ci.yml</code> (GitHub Actions), você deve rodar unit tests como primeiro passo:</p>

<pre><code class="language-yaml">name: CI Pipeline

on: [push, pull_request]

jobs:

test:

runs-on: ubuntu-latest

steps:

  • uses: actions/checkout@v3
  • name: Setup Python

uses: actions/setup-python@v4

with:

python-version: &#039;3.11&#039;

  • name: Install dependencies

run: |

pip install -r requirements.txt

pip install pytest pytest-cov

  • name: Run unit tests

run: pytest app/tests/unit/ -v --cov=app --cov-report=term-missing

  • name: Check coverage

run: |

coverage report --fail-under=80</code></pre>

<p>Essa configuração executa todos os unit tests e garante que sua cobertura de código (coverage) não caia abaixo de 80%. Se algum teste falhar, o pipeline para ali e você é notificado imediatamente.</p>

<h2>Integration Tests: Validando a Orquestração</h2>

<h3>O que são e quando usar</h3>

<p>Enquanto unit tests validam unidades isoladas, integration tests validam como esses componentes trabalham juntos. Um integration test pode testar se sua função de desconto interage corretamente com a função que salva o pedido no banco de dados, ou se o serviço de pagamento comunica corretamente com a API externa de processamento.</p>

<p>Integration tests são mais lentos porque geralmente envolvem I/O — banco de dados, APIs externas, sistemas de arquivos. Você tipicamente usa mocks ou containers para simular essas dependências. A grande diferença é que você está testando o comportamento da integração entre componentes, não apenas um componente isolado.</p>

<p>A regra de ouro é: você precisa de unit tests para cada unidade e integration tests para os fluxos críticos que conectam essas unidades. Não teste cada combinação possível em nível de integração — isso é explosão combinatória. Teste os caminhos que realmente importam para o negócio.</p>

<h3>Exemplo prático com banco de dados em memória</h3>

<p>Vamos expandir o exemplo anterior. Agora você tem um repositório que salva pedidos:</p>

<pre><code class="language-python">from dataclasses import dataclass

from datetime import datetime

from typing import Optional

@dataclass

class Pedido:

id: Optional[int] = None

cliente_id: int = None

valor_final: float = 0.0

desconto_aplicado: float = 0.0

data_criacao: datetime = None

class RepositorioPedidos:

def __init__(self, conexao):

self.conexao = conexao

def criar_pedido(self, pedido: Pedido) -&gt; int:

&quot;&quot;&quot;Cria pedido no banco e retorna o ID gerado.&quot;&quot;&quot;

cursor = self.conexao.cursor()

cursor.execute(

&quot;&quot;&quot;INSERT INTO pedidos (cliente_id, valor_final, desconto_aplicado, data_criacao)

VALUES (?, ?, ?, ?)&quot;&quot;&quot;,

(pedido.cliente_id, pedido.valor_final, pedido.desconto_aplicado, pedido.data_criacao)

)

self.conexao.commit()

return cursor.lastrowid

def obter_pedido(self, pedido_id: int) -&gt; Optional[Pedido]:

&quot;&quot;&quot;Obtém um pedido pelo ID.&quot;&quot;&quot;

cursor = self.conexao.cursor()

cursor.execute(&quot;SELECT id, cliente_id, valor_final, desconto_aplicado, data_criacao FROM pedidos WHERE id = ?&quot;, (pedido_id,))

row = cursor.fetchone()

if not row:

return None

return Pedido(id=row[0], cliente_id=row[1], valor_final=row[2], desconto_aplicado=row[3], data_criacao=row[4])

class ServicoProcessamentoPedido:

def __init__(self, repositorio: RepositorioPedidos, servico_desconto: DescontoService):

self.repositorio = repositorio

self.servico_desconto = servico_desconto

def processar_pedido(self, cliente_id: int, valor_original: float, dias_cliente: int) -&gt; int:

&quot;&quot;&quot;Processa um pedido aplicando desconto e salvando no banco.&quot;&quot;&quot;

valor_com_desconto = self.servico_desconto.desconto_cliente_premium(valor_original, dias_cliente)

desconto_valor = valor_original - valor_com_desconto

pedido = Pedido(

cliente_id=cliente_id,

valor_final=valor_com_desconto,

desconto_aplicado=desconto_valor,

data_criacao=datetime.now()

)

return self.repositorio.criar_pedido(pedido)</code></pre>

<p>Agora o teste de integração que valida o fluxo completo:</p>

<pre><code class="language-python">import pytest

import sqlite3

from datetime import datetime

from app.services import DescontoService, ServicoProcessamentoPedido

from app.repositories import RepositorioPedidos, Pedido

class TestServicoProcessamentoPedido:

@pytest.fixture

def db_em_memoria(self):

&quot;&quot;&quot;Cria um banco de dados em memória para os testes.&quot;&quot;&quot;

conexao = sqlite3.connect(&quot;:memory:&quot;)

cursor = conexao.cursor()

cursor.execute(&quot;&quot;&quot;

CREATE TABLE pedidos (

id INTEGER PRIMARY KEY AUTOINCREMENT,

cliente_id INTEGER NOT NULL,

valor_final REAL NOT NULL,

desconto_aplicado REAL NOT NULL,

data_criacao TIMESTAMP NOT NULL

)

&quot;&quot;&quot;)

conexao.commit()

yield conexao

conexao.close()

@pytest.fixture

def servico_processamento(self, db_em_memoria):

&quot;&quot;&quot;Instancia o serviço com dependências.&quot;&quot;&quot;

repositorio = RepositorioPedidos(db_em_memoria)

servico_desconto = DescontoService()

return ServicoProcessamentoPedido(repositorio, servico_desconto)

def test_processar_pedido_cliente_premium(self, servico_processamento, db_em_memoria):

&quot;&quot;&quot;Testa fluxo completo: desconto aplicado e pedido salvo.&quot;&quot;&quot;

pedido_id = servico_processamento.processar_pedido(

cliente_id=123,

valor_original=100,

dias_cliente=365

)

Valida que o pedido foi criado

assert pedido_id is not None

Valida que os dados foram salvos corretamente

repositorio = RepositorioPedidos(db_em_memoria)

pedido = repositorio.obter_pedido(pedido_id)

assert pedido is not None

assert pedido.cliente_id == 123

assert pedido.valor_final == 85 # 100 - 15%

assert pedido.desconto_aplicado == 15

def test_processar_pedido_cliente_novo(self, servico_processamento, db_em_memoria):

&quot;&quot;&quot;Testa que clientes novos não recebem desconto.&quot;&quot;&quot;

pedido_id = servico_processamento.processar_pedido(

cliente_id=456,

valor_original=100,

dias_cliente=10

)

repositorio = RepositorioPedidos(db_em_memoria)

pedido = repositorio.obter_pedido(pedido_id)

assert pedido.valor_final == 100 # sem desconto

assert pedido.desconto_aplicado == 0</code></pre>

<p>Esse teste de integração executa o fluxo real do seu sistema — calcula desconto através do serviço, salva no banco de dados e valida que tudo foi persistido corretamente. Ele é mais lento que um unit test (tem I/O de banco de dados), mas é absolutamente necessário para garantir que seus componentes trabalham juntos.</p>

<h3>Rodando Integration Tests no Pipeline</h3>

<p>Adicione um segundo step no seu workflow após os unit tests:</p>

<pre><code class="language-yaml"> - name: Run integration tests

run: pytest app/tests/integration/ -v --tb=short

timeout-minutes: 5</code></pre>

<p>Integration tests devem ter um timeout definido. Se estiverem lentos demais, você tem um problema arquitetural para resolver. Idealmente, cada teste deve rodar em menos de 1 segundo.</p>

<h2>Smoke Tests: O Validador do Caminho Crítico</h2>

<h3>O que são e por que não testar tudo</h3>

<p>Smoke tests são testes de alta nível que validam se sua aplicação está &quot;respirando&quot; — se os componentes críticos funcionam end-to-end. Se um unit test pergunta &quot;essa função está correta?&quot;, um smoke test pergunta &quot;minha aplicação consegue processar um pedido do início ao fim?&quot; Ele não valida cada detalhe, apenas o caminho crítico.</p>

<p>A razão pela qual você não escreve smoke tests para tudo é simples: eles são caros em tempo de execução. Um smoke test que sobe a aplicação inteira, faz requisições HTTP, interage com bancos de dados e aguarda respostas pode levar segundos ou minutos. Se você tiver centenas deles, seu pipeline fica inviável. A estratégia correta é: muitos unit tests rápidos (80%), alguns integration tests focados (15%), poucos smoke tests críticos (5%).</p>

<h3>Exemplo prático com FastAPI e testes HTTP</h3>

<p>Vamos imaginar que você expôs sua lógica de pedidos através de uma API REST:</p>

<pre><code class="language-python">from fastapi import FastAPI, HTTPException

from pydantic import BaseModel

app = FastAPI()

class CriarPedidoRequest(BaseModel):

cliente_id: int

valor_original: float

dias_cliente: int

class PedidoResponse(BaseModel):

pedido_id: int

valor_final: float

desconto_aplicado: float

Instâncias globais (em produção, você usaria injeção de dependência)

servico_processamento = None

@app.post(&quot;/pedidos&quot;)

def criar_pedido(request: CriarPedidoRequest) -&gt; PedidoResponse:

&quot;&quot;&quot;Endpoint que processa um pedido.&quot;&quot;&quot;

try:

pedido_id = servico_processamento.processar_pedido(

cliente_id=request.cliente_id,

valor_original=request.valor_original,

dias_cliente=request.dias_cliente

)

Busca o pedido criado

pedido = servico_processamento.repositorio.obter_pedido(pedido_id)

return PedidoResponse(

pedido_id=pedido.id,

valor_final=pedido.valor_final,

desconto_aplicado=pedido.desconto_aplicado

)

except ValueError as e:

raise HTTPException(status_code=400, detail=str(e))

@app.get(&quot;/health&quot;)

def health_check():

&quot;&quot;&quot;Endpoint de health check.&quot;&quot;&quot;

return {&quot;status&quot;: &quot;ok&quot;}</code></pre>

<p>Agora o smoke test que valida esse endpoint funciona de ponta a ponta:</p>

<pre><code class="language-python">import pytest

from fastapi.testclient import TestClient

import sqlite3

from app.main import app

from app.services import DescontoService, ServicoProcessamentoPedido

from app.repositories import RepositorioPedidos

class TestSmokePedidos:

@pytest.fixture

def cliente_api(self):

&quot;&quot;&quot;Instancia o cliente HTTP para testes.&quot;&quot;&quot;

return TestClient(app)

@pytest.fixture(scope=&quot;module&quot;, autouse=True)

def setup_app(self):

&quot;&quot;&quot;Configura a aplicação com um banco em memória antes de rodar smoke tests.&quot;&quot;&quot;

conexao = sqlite3.connect(&quot;:memory:&quot;)

cursor = conexao.cursor()

cursor.execute(&quot;&quot;&quot;

CREATE TABLE pedidos (

id INTEGER PRIMARY KEY AUTOINCREMENT,

cliente_id INTEGER NOT NULL,

valor_final REAL NOT NULL,

desconto_aplicado REAL NOT NULL,

data_criacao TIMESTAMP NOT NULL

)

&quot;&quot;&quot;)

conexao.commit()

Injeta dependências na app

repositorio = RepositorioPedidos(conexao)

servico_desconto = DescontoService()

import app.main

app.main.servico_processamento = ServicoProcessamentoPedido(repositorio, servico_desconto)

yield

conexao.close()

def test_health_check(self, cliente_api):

&quot;&quot;&quot;Smoke test: aplicação está respondendo?&quot;&quot;&quot;

response = cliente_api.get(&quot;/health&quot;)

assert response.status_code == 200

assert response.json() == {&quot;status&quot;: &quot;ok&quot;}

def test_criar_pedido_end_to_end(self, cliente_api):

&quot;&quot;&quot;Smoke test: fluxo completo de criação de pedido funciona?&quot;&quot;&quot;

response = cliente_api.post(&quot;/pedidos&quot;, json={

&quot;cliente_id&quot;: 999,

&quot;valor_original&quot;: 200,

&quot;dias_cliente&quot;: 500

})

assert response.status_code == 200

dados = response.json()

assert dados[&quot;pedido_id&quot;] is not None

assert dados[&quot;valor_final&quot;] == 170 # 200 - 15%

assert dados[&quot;desconto_aplicado&quot;] == 30

def test_criar_pedido_invalido(self, cliente_api):

&quot;&quot;&quot;Smoke test: validação de erros funciona?&quot;&quot;&quot;

response = cliente_api.post(&quot;/pedidos&quot;, json={

&quot;cliente_id&quot;: 999,

&quot;valor_original&quot;: -100, # valor negativo

&quot;dias_cliente&quot;: 500

})

assert response.status_code == 400</code></pre>

<p>Repare que escrevemos apenas 3 smoke tests — um health check e dois cenários críticos do negócio (sucesso e erro). Não testamos todos os casos de desconto aqui; isso já foi feito nos unit tests. O smoke test apenas valida que a integração HTTP funciona e os dados chegam corretamente até o usuário.</p>

<h3>Smoke Tests no Pipeline</h3>

<p>Adicione um terceiro step, que roda por último:</p>

<pre><code class="language-yaml"> - name: Run smoke tests

run: pytest app/tests/smoke/ -v --tb=short

timeout-minutes: 2</code></pre>

<p>Smoke tests devem ser muito rápidos — idealmente menos de 2-3 minutos no total. Se estiverem lentos, reconsidere se você realmente precisa testar aquilo em cada commit.</p>

<h2>Orquestração Completa: Um Pipeline Realista</h2>

<h3>Estrutura de diretórios e configuração</h3>

<p>Até agora mostramos as peças individuais. Vamos montá-las em um pipeline completo e realista. Aqui está como você deve organizar seu projeto:</p>

<pre><code>meu_projeto/

├── app/

│ ├── main.py

│ ├── services.py

│ ├── repositories.py

│ └── models.py

├── tests/

│ ├── unit/

│ │ └── test_services.py

│ ├── integration/

│ │ └── test_repositories.py

│ └── smoke/

│ └── test_endpoints.py

├── .github/

│ └── workflows/

│ └── ci.yml

├── requirements.txt

├── pytest.ini

└── README.md</code></pre>

<p>O arquivo <code>pytest.ini</code> centraliza as configurações:</p>

<pre><code class="language-ini">[pytest]

testpaths = tests

python_files = test_*.py

python_classes = Test*

python_functions = test_*

addopts = -v --strict-markers

markers =

unit: testes unitários rápidos

integration: testes de integração

smoke: smoke tests críticos</code></pre>

<p>Agora o pipeline CI completo em <code>.github/workflows/ci.yml</code>:</p>

<pre><code class="language-yaml">name: CI Pipeline Estratégico

on:

push:

branches: [main, develop]

pull_request:

branches: [main, develop]

jobs:

test:

runs-on: ubuntu-latest

timeout-minutes: 10

steps:

  • name: Checkout code

uses: actions/checkout@v3

  • name: Set up Python 3.11

uses: actions/setup-python@v4

with:

python-version: &#039;3.11&#039;

  • name: Cache pip dependencies

uses: actions/cache@v3

with:

path: ~/.cache/pip

key: ${{ runner.os }}-pip-${{ hashFiles(&#039;**/requirements.txt&#039;) }}

restore-keys: |

${{ runner.os }}-pip-

  • name: Install dependencies

run: |

python -m pip install --upgrade pip

pip install -r requirements.txt

pip install pytest pytest-cov pytest-timeout

STAGE 1: Unit Tests (rápido, falha cedo)

  • name: Run unit tests

run: pytest tests/unit/ -v --tb=short --timeout=10 --cov=app --cov-report=term-missing:skip-covered

  • name: Check unit test coverage

run: |

coverage report --fail-under=80

STAGE 2: Integration Tests (moderado)

  • name: Run integration tests

if: success()

run: pytest tests/integration/ -v --tb=short --timeout=30

STAGE 3: Smoke Tests (validação final)

  • name: Run smoke tests

if: success()

run: pytest tests/smoke/ -v --tb=short --timeout=5

Relatório final

  • name: Generate coverage report

if: always()

run: coverage report --format=markdown &gt;&gt; $GITHUB_STEP_SUMMARY</code></pre>

<p>Repare nos pontos críticos:</p>

<ol>

<li><strong>Fases sequenciais com <code>if: success()</code></strong>: Se unit tests falham, não roda integration. Se integration falha, não roda smoke. Isso economiza tempo de CI.</li>

<li><strong>Timeouts</strong>: Cada stage tem timeout apropriado. Unit tests são os mais rápidos, smoke tests são os mais lentos.</li>

<li><strong>Coverage check</strong>: Garante que novos testes mantêm a cobertura acima do mínimo aceito.</li>

<li><strong>Caching</strong>: Dependencies são cacheadas entre runs — o CI roda mais rápido.</li>

</ol>

<h3>Exemplo de requirements.txt</h3>

<pre><code>fastapi==0.104.1

pytest==7.4.3

pytest-cov==4.1.0

pytest-timeout==2.2.0

uvicorn==0.24.0

pydantic==2.5.0</code></pre>

<h2>Otimizações e Boas Práticas</h2>

<h3>Quando NÃO escrever um teste</h3>

<p>Aqui está o segredo que ninguém fala: nem tudo precisa de teste. Você desperdiça tempo escrevendo testes para:</p>

<ul>

<li><strong>Código gerado</strong> (migrações de banco, modelos gerados automaticamente)</li>

<li><strong>Código trivial</strong> (getters e setters simples sem lógica)</li>

<li><strong>Código de infraestrutura pura</strong> (configuração de logging, setup de aplicação)</li>

</ul>

<p>Foque seus esforços em <strong>lógica de negócio</strong> — aquilo que diferencia sua aplicação. Se essa função calcula preços, aplica descontos ou determina permissões, precisa de teste.</p>

<h3>Paralelizar sem perder sanidade</h3>

<p>Seu pipeline CI pode rodar testes em paralelo para ir mais rápido:</p>

<pre><code class="language-yaml"> - name: Run unit tests in parallel

run: pytest tests/unit/ -n auto --timeout=10 --cov=app</code></pre>

<p>A flag <code>-n auto</code> do pytest-xdist roda tests em paralelo usando tantos workers quantos cores sua máquina tem. Mas cuidado: se seus testes compartilham estado (banco de dados, cache), a paralelização quebra tudo. Use um banco em memória por worker ou fixtures com escopo apropriado.</p>

<h3>Monitorar performance do pipeline</h3>

<p>Seu pipeline não deve levar mais de 10-15 minutos no total. Se estiver mais lento, algo está errado. Algumas causas comuns:</p>

<ul>

<li>Muitos testes de integração quando deveriam ser unit tests</li>

<li>Testes que não limpam recursos (conexões abertas, arquivos não deletados)</li>

<li>Dependências externas lentas (APIs, bancos de dados reais) em vez de mocks</li>

<li>Falta de cache de dependencies</li>

</ul>

<p>Um truque prático: adicione timestamps aos seu output de teste para identificar gargalos:</p>

<pre><code class="language-bash">pytest tests/ -v --tb=short --durations=10</code></pre>

<p>Isso mostra os 10 testes mais lentos.</p>

<h2>Conclusão</h2>

<p>Você aprendeu uma estratégia de testes que escala: <strong>muitos unit tests rápidos na base (80%), integration tests focados no meio (15%), e smoke tests críticos no topo (5%)</strong>. Essa pirâmide não é arbitrária — ela vem de anos de experiência em engenharia de software e é comprovada em empresas de todo o mundo.</p>

<p>O segundo ponto fundamental é que <strong>seu pipeline CI deve falhar rápido e dar feedback imediato</strong>. Organize seus testes em fases onde unit tests rodam primeiro (milissegundos), integration tests segundo (segundos), e smoke tests terceiro (mais segundos). Se algo quebrar cedo, você economiza tempo não rodando o resto.</p>

<p>Por fim, lembre-se de que <strong>testes são código também</strong> — merecem a mesma qualidade e revisão que seu código de produção. Não escreva testes como prova de que o código funciona; escreva testes como especificações vivas que documentam como o código deve se comportar. Foco em lógica de negócio, evite testes triviais, e sempre monitore a saúde do seu pipeline.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://docs.pytest.org/" target="_blank" rel="noopener noreferrer">Pytest Official Documentation</a></li>

<li><a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions" target="_blank" rel="noopener noreferrer">GitHub Actions - Workflow Syntax</a></li>

<li><a href="https://martinfowler.com/bliki/TestPyramid.html" target="_blank" rel="noopener noreferrer">Testing Strategies: The Test Pyramid by Martin Fowler</a></li>

<li><a href="https://www.atlassian.com/continuous-delivery/continuous-integration" target="_blank" rel="noopener noreferrer">Continuous Integration Best Practices</a></li>

<li><a href="https://fastapi.tiangolo.com/advanced/testing-dependencies/" target="_blank" rel="noopener noreferrer">FastAPI Testing Documentation</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em DevOps & CI/CD

Ansible Fundamentos: Inventário, Playbooks e Módulos na Prática
Ansible Fundamentos: Inventário, Playbooks e Módulos na Prática

Ansible Fundamentos: Inventário, Playbooks e Módulos Ansible é uma ferramenta...

Como Usar Docker Fundamentos: Imagens, Containers, Volumes e Redes em Produção
Como Usar Docker Fundamentos: Imagens, Containers, Volumes e Redes em Produção

Docker Fundamentos: Compreendendo o Ecossistema de Containerização Docker é u...

Guia Completo de Ansible Avançado: Roles, Templates Jinja2 e Ansible Vault
Guia Completo de Ansible Avançado: Roles, Templates Jinja2 e Ansible Vault

Entendendo Roles: A Estrutura Profissional do Ansible Uma role no Ansible é u...