Capítulo 15: Engenharia de Software: Ciclo de Vida, Testes Automatizados e Padrões GoF

Módulo 01: Fundamentos da Computação • Ciência da Computação Formal


🗺️ 1. Pirâmide de Testes e Design de Software

flowchart TD
    subgraph PIRAMIDE["Pirâmide de Qualidade de Software"]
        E2E["Testes Ponta a Ponta (E2E / UI)
(Poucos, Lentos, Alto Custo de Manutenção)"]
        INT["Testes de Integração
(Comunicação entre Módulos, Banco e APIs)"]
        UNIT["Testes Unitários Automatizados (TDD)
(Milhares, Rápidos, Isolados, Baixo Custo)"]
        
        E2E --- INT
        INT --- UNIT
    end

    style UNIT fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style INT fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style E2E fill:#ffebee,stroke:#f44336,stroke-width:2px

🏛️ 2. Fundamentação Teórica

A Engenharia de Software aplica abordagens sistemáticas, disciplinadas e quantificáveis ao desenvolvimento, operação e manutenção de sistemas complexos.

2.1 Modelos de Processo de Software

Modelo Dinâmica de Ciclo Vantagens Desvantagens Quando Utilizar
Cascata (Waterfall) Linear e sequencial (Requisitos $\to$ Design $\to$ Impl $\to$ Testes) Rigor documental, marcos bem definidos Inflexível a mudanças; atraso na entrega de valor Projetos críticos regulados (aeroespacial, médico)
Espiral (Boehm) Iterativo com análise formal de riscos a cada ciclo Gestão precoce e rigorosa de riscos técnicos Custo elevado de planejamento e especialistas Sistemas corporativos de grande porte
Ágil (Scrum/XP) Ciclos curtos (Sprints de 1-4 semanas) com feedback contínuo Alta adaptabilidade a mudanças de escopo Risco de perda de visão arquitetural global Startups, SaaS e produtos digitais modernos

2.2 Catálogo de Padrões de Projeto (GoF - Gang of Four)

Os 23 padrões clássicos dividem-se em três macrocategorias:

  1. Padrões Criacionais (Como objetos são instanciados):
    • Factory Method: Define interface para criar objetos, delegando às subclasses a decisão da classe concreta.
    • Singleton: Garante que uma classe possua apenas uma única instância em todo o ciclo de vida da aplicação.
    • Builder: Separa a construção de um objeto complexo da sua representação final.
  2. Padrões Estruturais (Como classes e objetos são compostos):
    • Adapter: Converte a interface de uma classe na interface esperada pelo cliente.
    • Decorator: Agrega comportamentos adicionais a um objeto dinamicamente sem alterar sua estrutura.
    • Facade: Provê uma interface simplificada e unificada para um subsistema complexo.
  3. Padrões Comportamentais (Como classes distribuem responsabilidades e algoritmos):
    • Strategy: Permite definir uma família de algoritmos e alterná-los em tempo de execução.
    • Observer: Notifica automaticamente múltiplos observadores quando um objeto sofre mutação de estado.
    • Command: Encapsula uma requisição como um objeto, permitindo parametrização e operações de desfazer/refazer (undo/redo).

2.3 Disciplina TDD e Tipologias de Testes

O Test-Driven Development (TDD) inverte o fluxo tradicional através do ciclo:

  1. RED: Escrever um teste unitário automatizado que falha antes de qualquer linha de código de produção.
  2. GREEN: Escrever o código mínimo e estritamente necessário para fazer o teste passar.
  3. REFACTOR: Limpar o design, remover duplicações e otimizar mantendo a suíte de testes 100% verde.

💻 3. Implementação do Padrão Strategy com Teste Automatizado em Python

from abc import ABC, abstractmethod
from typing import List

# 1. Padrão GoF Strategy
class EstrategiaDesconto(ABC):
    @abstractmethod
    def calcular(self, valor: float) -> float:
        pass

class DescontoEstudante(EstrategiaDesconto):
    def calcular(self, valor: float) -> float:
        return valor * 0.50

class DescontoBlackFriday(EstrategiaDesconto):
    def calcular(self, valor: float) -> float:
        return valor * 0.70

class Carrinho:
    def __init__(self, estrategia: EstrategiaDesconto):
        self._estrategia = estrategia
        self._itens: List[float] = []

    def adicionar(self, preco: float):
        self._itens.append(preco)

    def total(self) -> float:
        subtotal = sum(self._itens)
        return self._estrategia.calcular(subtotal)

# 2. Mini Suíte de Teste Automatizado (Princípio TDD)
def testar_regras_carrinho():
    carrinho_estudante = Carrinho(DescontoEstudante())
    carrinho_estudante.adicionar(100.0)
    carrinho_estudante.adicionar(100.0)
    assert carrinho_estudante.total() == 100.0, "Erro: Estudante deveria pagar 50%!"

    carrinho_bf = Carrinho(DescontoBlackFriday())
    carrinho_bf.adicionar(100.0)
    assert carrinho_bf.total() == 70.0, "Erro: Black Friday deveria cobrar 70%!"
    print("Todas as asserções de teste unitário PASSARAM com sucesso!")

if __name__ == "__main__":
    print("=== ENGENHARIA DE SOFTWARE: STRATEGY & TESTES ===")
    testar_regras_carrinho()