Versículo chave: "Consagre ao Senhor tudo o que você faz, e os seus planos serão bem-sucedidos." - Provérbios 16:3
🧪 Preparação: Para este conteúdo, o aluno deverá dispor de um computador com acesso à internet, um web browser com suporte a HTML 5 (Google Chrome, Mozilla Firefox, Microsoft Edge, Safari, Opera etc.), um editor de texto ou IDE (VSCode etc.).
Profissional de QA/QC com experiência multidisciplinar e visão sistêmica da qualidade de software ao longo de todo o ciclo de vida do produto, atuando de forma estratégica na prevenção de defeitos (Quality Assurance) e de forma operacional na detecção e controle da qualidade (Quality Control). Minha atuação não se limita à fase final do desenvolvimento, mas começa desde o entendimento de requisitos, regras de negócio e riscos, garantindo que a qualidade seja construída desde a concepção até a entrega e operação do software.
No contexto de Quality Assurance, trabalho com definição e evolução de processos de qualidade, estabelecendo padrões, critérios de aceitação e estratégias de teste alinhadas aos objetivos do negócio. Aplico princípios como shift-left testing, prevenção de defeitos, melhoria contínua e gestão de riscos, colaborando diretamente com times de produto, design e engenharia para garantir requisitos claros, testáveis e rastreáveis. Atuo com abordagens como TDD, BDD e ATDD, promovendo testes orientados a comportamento e valor de negócio, além de apoiar a adoção de boas práticas como pirâmide de testes, testabilidade de software e quality gates integrados ao CI/CD.
Na camada de Quality Control, executo e estruturo testes manuais e automatizados em diferentes níveis, abrangendo testes unitários, testes de integração, testes de sistema e testes de aceitação, garantindo que cada camada do sistema seja validada de forma adequada. Realizo testes funcionais, regressivos, exploratórios e de usabilidade, além de testes não funcionais como performance, carga, estresse, segurança, confiabilidade e compatibilidade. Trabalho com validação de APIs, interfaces web e aplicações distribuídas, assegurando consistência entre front-end, back-end e integrações externas.
Também atuo fortemente na automação de testes como meio de escalabilidade da qualidade, integrando suites automatizadas aos pipelines de CI/CD para garantir feedback rápido e confiável a cada commit e pull request. Utilizo estratégias de automação equilibradas, priorizando cenários críticos de negócio e evitando automação excessiva sem valor, sempre respeitando o custo-benefício e a manutenibilidade dos testes. A automação é tratada como código, seguindo princípios de Clean Code, reutilização, isolamento e clareza, garantindo que os testes evoluam junto com o sistema.
Ao longo das fases do desenvolvimento, participo ativamente de planejamento, refinamento, code reviews e retrospectivas, contribuindo para a melhoria contínua da qualidade do produto e do processo. Analiso métricas como cobertura de testes, taxa de defeitos, retrabalho, tempo de feedback e estabilidade das entregas, utilizando esses dados para orientar decisões técnicas e de produto. A qualidade deixa de ser subjetiva e passa a ser mensurável, observável e alinhada a indicadores reais de valor.
Além disso, atuo na validação da experiência do usuário e do comportamento do sistema em produção, colaborando com monitoramento, observabilidade e análise de incidentes para retroalimentar o processo de qualidade. A qualidade não termina no deploy, mas continua no acompanhamento do uso real do software, garantindo confiabilidade, segurança e evolução sustentável. Dessa forma, QA/QC se tornam pilares estratégicos para a entrega de software robusto, previsível e alinhado às expectativas técnicas e de negócio.
- https://medium.com/@n-demia/10-github-repositories-for-software-testers-121ff3b84fe1?source=email-afeafff77325-1699769789726-digest.reader--121ff3b84fe1----13-102------------------3e6a1339_641a_4699_9f6d_fd3dd3bcfe52-1
- https://youtu.be/x3cANGNPyx0
- https://substack.com/redirect/7de16a93-4fc6-4656-a50c-b1bfe73d5399?j=eyJ1IjoiMmRpcmZwIn0.DgQpD9vnxeDXnbOGqr5r4QICWGtxf2wFAnKNG8yY6Aw
Top AI Coding Tools for Developers You Can Use in 2025:
-
AI Code Assistants
- GitHub Copilot: Ferramenta de completação de código e programação automática.
- ChatGPT: Ajuda a escrever e depurar código com os modelos mais recentes.
- Claude: Conhecimento recente e especializado de programação para gerar código preciso e atualizado.
- Amazon CodeWhisperer: Assistente de IA no IDE
-
Curredor de IDEs
- Alimentados por IA: IDE com IA para Windows, macOS e Linux.
- Windsurf: IDE alimentado por IA que lida com tarefas complexas de forma independente.
- Replit: Crie aplicativos totalmente funcionais para entrar no ar rapidamente.
-
Team Productivity
- Cody: O assistente de código de IA corporativa para escrever, corrigir e manter código.
- Pieces: Ferramenta de produtividade habilitada por IA para ajudar desenvolvedores a gerenciar trechos de código.
- Visual Copilot: Converta designs Figma em código React, Vue, Svelte, Angular ou HTML.
-
Qualidade e Conclusão do Código
- Snyk: Varredura em tempo real de vulnerabilidades de código gerado por humanos e IA.
Imagine-se como um entusiasta da tecnologia, ansioso por entender como um software pode ser eficaz e confiável. Como garantir que um software atenda às expectativas dos usuários e funcione sem falhas? Esse dilema cria um conflito cognitivo que nos impulsiona a mergulhar mais fundo.
A história da Qualidade de Software nos mostra a evolução dos processos, desde as abordagens mais tradicionais até as metodologias ágeis modernas. Aprender sobre modelos de qualidade, como o ISO 25000, e as dimensões da qualidade, como funcionalidade e usabilidade, enriquece nossa compreensão. As peças se encaixam, transformando a questão inicial em um quebra-cabeça conceitual.
Imagine que você é um membro de uma equipe de desenvolvimento de software. Você é desafiado a aplicar os conceitos aprendidos em um cenário real. Você participa de revisões, que são análises colaborativas do código, e auditorias, que avaliam os processos utilizados. Essa simulação o coloca no ambiente profissional, onde as decisões têm impacto direto na qualidade do software.
Ao concluir as revisões e auditorias, você reflete sobre os processos e a eficácia das técnicas aplicadas. Reconhece a importância de identificar falhas cedo e como as revisões sistemáticas podem prevenir erros custosos. Você compreende que, assim como o software, o aprendizado é um processo contínuo (Continuous Learning). E à medida que você assimila esses conceitos, torna-se mais apto a contribuir para o desenvolvimento de software de alta qualidade, solidificando sua jornada na compreensão da Qualidade de Software
Note
Esse conteúdo é geralmente abordado no final das graduações da área de TI, ou seja, é um módulo avançado e atua com boas práticas do mercado. Então eu vou supor que todos já sabem desenvolver softwares e pular toda a parte de codificação com metodologias ágeis e ciclo de vida de desenvolvimento para partir pro ponto de qualidade de software. As abordagens e princípios de qualidade de software serão os pontos chaves para o ciclo de desenvolvimento, não o que prende cada conceito, mas o essencial para que todo fluxo de desenvolvimento funcione conforme o esperado.
Tip
Vamos recordar a importância das boas práticas de programação como um elemento essencial na busca pela qualidade de software. Revisitarmos a noção de código limpo e bem estruturado, que não só facilita a manutenção, mas também reduz erros e falhas futuras. Recordar como padrões de nomenclatura consistentes, comentários claros e modularização do código são cruciais para criar um produto de software robusto e de fácil compreensão. Isso nos lembrará que a qualidade não se limita apenas aos processos de teste, mas começa desde a concepção do código-fonte.
Primeiro, precisamos entender que a qualidade de software é uma área de grande importância no mercado de tecnologia da informação. E para garantir a qualidade de um software, é preciso conhecer seus fundamentos, entender sua história, compreender os custos envolvidos, realizar atividades de apoio, seguir padrões e avaliar seus atributos. A seguir, apresentamos alguns desses atributos:
Important
Definição: Qualidade é a característica ou atributo que define algo ou alguém em termos de excelência, valor, ou nível de desempenho. Pode se referir a propriedades como durabilidade, eficiência, eficácia, ou valor percebido. “Qualidade é a medida de quanto um projeto atende aos requisitos especificados no escopo.”
Desde muito tempo, muitos engenheiros de software e empresas desenvolveram softwares de modo casual, por acreditarem que a criação de programas não podia seguir regras, normas ou padrões. Porém, o poder da comunidade eletrônica característica do século XXI, criada por redes de computadores e softwares, instituiu a era da troca de informação e conhecimentos em todo o mundo.
Sendo assim, destaca-se a área de Engenharia de Software, que auxilia o entendimento do processo de desenvolvimento de softwares. Desta forma, nos processos de qualidade de software, a gerência de risco e o teste de software são pontos fundamentais para a garantia da qualidade do produto gerado.
A proposta da disciplina é oferecer a você um guia quanto à importância de as empresas implementarem normas e modelos que permitam a garantia e a correta avaliação de qualidade de produtos e de processos de desenvolvimento de software.
O objetivo da função de qualidade e testes de software é:
- Reconhecer a necessidade de se adotar um processo de testes para garantir a qualidade do desenvolvimento de software, bem como as métricas utilizadas nos testes;
- Desenvolver um plano de qualidade de software (SQA) que esteja em conformidade com normas e padrões de qualidade, bem como usar ferramentas para garantir a qualidade no seu desenvolvimento;
- Produzir planos de mitigação de riscos e identificar qual modelo se adapta para cada tipo de software.
Antes de passarmos para os modelos de integração, discutiremos brevemente sobre os VCS, Modelos de controle de versão.
Em uma equipe de desenvolvimento, precisaremos de uma ajuda para sincronizar e manter o histórico do código, de maneira concreta utilizamos ferramentas como Git e SVN.
A integração contínua não possui necessidade de muitas ferramentas, em tese, mas alguns auxílios que são padrão em práticas de desenvolvimento são requiridos.
O Git é o mais popular em integração hoje em dia, mas não precisamos utiliza-lo, basta que tenhamos algum modelo de controle de versão de nossa escolha.
A ferramenta em si não importa, mas o que devemos inserir em nosso sistema de controle? De maneira geral, deve conter tudo aquilo que é necessário para a construção do projeto.
- código
- scripts
- migrações, schemas
- IDE Configs
- Devemos definir uma formatação de código para a equipe. Para começarmos um projeto é necessário fazer o clone - a cópia local - e o comando unificado (deve ser fácil).
- Não significa que devemos comitar o artefato de construção, ou seja, no caso de um desenvolvedor Ruby ou Gem não deveria estar dentro do repositório, o mesmo ocorre no mundo Java. Resultados da construção do software não são comitados como gem, jar, image e modules.
Sabemos o que comitar e o que não comitar, então seguiremos estudando os modelos de repositório que exitem no mercado. Então, o que deve conter no repositório?
- Scripts de testes, os testes são uma parte essencial da aplicação. Os testes também são códigos, e scripts relacionados a eles devem entrar no repositório;
- Database Schema, faz parte da aplicação e é preciso para construir e executar o projeto.
Contudo, como organizar nossos repositórios? Em uma empresa encontramos mais de um projeto por vez, e é importante que saibamos como representar estes projetos dentro do repositório.
O que é mais natural é utilizar um repositório para cada projeto, de um tamanho razoável com escopo bem definido. Essa forma de organização é chamada Multi-repo.
No entanto, nos últimos anos, surgiu uma nova forma de organização dos nossos projetos dentro de repositórios. Empresas grandes de tecnologia como Google e Facebook não utilizam o esquema Multi-repo, porque são empresas que trabalham em inúmeros projetos de maneira concomitante. Empresas que atuam com outra dimensão de projetos utilizam o Mono-repo, ou seja, um único e gigantesco repositório que acumula todos os projetos.
Note
Mono-repo e multi-repo são conceitos centrais e amplamente adotados no ecossistema de micro-frontends, justamente porque a forma como o código é organizado tem impacto direto na autonomia das equipes, no deploy independente e na escalabilidade do projeto — pilares dos micro-frontends. E microserviços também adotam amplamente os modelos de mono-repo e multi-repo, e nesse contexto a escolha é tão ou até mais estratégica do que em micro-frontends, porque afeta diretamente autonomia operacional, versionamento, esteira de deploy, governança de APIs e isolamento entre times. Em microserviços, a estrutura do repositório se torna praticamente parte do design arquitetural, e as empresas costumam escolher um modelo de acordo com o nível de independência desejado, maturidade do time e complexidade do domínio.
Quando você fala de features como componentes dentro de uma plataforma componível, você está descrevendo exatamente o movimento moderno de arquitetura modular que unifica microserviços, microfrontends e evolução contínua de produto (Lean Manufacturing + Agile + Spotify Squad) em um ecossistema onde cada parte é independente, substituível e expansível. E sim: nesse cenário, cada feature deixa de ser apenas uma “função nova” e passa a ser uma unidade de evolução do produto, quase como um “módulo de negócios” versionado, isolado, escalável e plugável.
A ideia é que cada feature não seja só um pedaço de UI ou uma rota de API, mas um componente de domínio inteiro: comportamento, dados, lógica, integrações e interface. Em uma plataforma componível, isso vira um bloco arquitetural — uma peça que pode ser combinada com outras, assim como peças de LEGO, para formar um produto vivo.
Quando você expande isso para o mundo real, especialmente no contexto de microserviços e microfrontends, cada feature se transforma naturalmente em um building block que pode ser desenvolvido, implantado, versionado e escalado de forma independente. Um módulo do domínio, como “Billing”, “Checkout”, “User Profile”, “Recommendations”, “Inventory”, por exemplo, é tratado como uma feature-mãe, que por sua vez contém subfeatures. Essa modularidade profunda é o que permite a evolução contínua do produto sem criar aquele monolito rígido que só cresce e nunca melhora.
Dentro dessa lógica, um monorepo fortalece a colaboração quando as equipes compartilham código, protocolos, contratos de API, padrões de UI e testes. Já o multirepo fortalece a autonomia quando cada feature é um “produto dono de si mesmo”: sua pipeline, seu deploy, sua versão, seu runtime, sua granularidade. Plataformas composable normalmente aceitam os dois lados: repositório único quando faz sentido compartilhar infraestrutura; múltiplos repositórios quando a autonomia acelera o produto.
E, claro, quando você conecta tudo isso — features componíveis + microserviços + microfrontends + mono/multi-repo — o produto deixa de ser um software linear e vira um ecossistema modular que evolui como um organismo vivo. Cada feature nova é uma célula que nasce no sistema, cresce, se integra, e pode ser substituída sem derrubar o corpo inteiro. Essa é a essência da evolução contínua moderna: o produto nunca está pronto, ele sempre está se transformando em algo melhor.
A desvantagem do segundo modelo é o repositório precisará ser realmente grande, e o build pode ser lento, talvez nem o Git seja mais a ferramenta adequada para fazer o controle de versões para esta situação. Contudo, como temos apenas um grande repositório temos uma administração relativamente mais simples, então a verificação de padrões é facilitada. As refatorações são globais, afinal estão todos dentro da mesma base.
Vamos trabalhar com o Multi-repo, afinal é o mais comum no dia a dia da maioria dos desenvolvedores.
![]() |
![]() |
Monorepo vs. Microrepo. Qual é o melhor? Por que diferentes empresas escolhem opções diferentes?
Monorepo não é novidade; Linux e Windows foram ambos criados usando Monorepo. Para melhorar a escalabilidade e a velocidade de construção, o Google desenvolveu sua cadeia interna de ferramentas dedicada para escalá-la mais rapidamente e padrões rigorosos de qualidade de codificação para mantê-la consistente.
Amazon e Netflix são grandes embaixadoras da filosofia dos Microserviços. Essa abordagem naturalmente separa o código de serviço em repositórios separados. Escala mais rápido, mas pode causar problemas de governança no futuro.
Dentro do Monorepo, cada serviço é uma pasta, e cada pasta tem uma configuração BUILD e controle de permissões OWNERS. Cada membro do serviço é responsável por sua própria pasta.
Por outro lado, no Microrepo, cada serviço é responsável por seu repositório, com a configuração de compilação e as permissões normalmente definidas para todo o repositório.
No Monorepo, as dependências são compartilhadas por todo o código de código, independentemente do seu negócio, então quando há uma atualização de versão, cada base de código atualiza sua versão.
Como o TikTok gerencia um MonoRepositor de 200K com o Sparo:
Aviso: Os detalhes deste post foram derivados do TikTok Developer Blog. Todo o crédito pelos detalhes técnicos vai para a equipe de engenharia do TikTok. Os links para os artigos originais estão presentes na seção de referências ao final do post. Tentamos analisar os detalhes e dar nossa opinião sobre eles. Se você encontrar alguma imprecisão ou omissão, por favor, deixe um comentário e faremos o possível para corrigi-las.
O TikTok, a popular plataforma de compartilhamento de vídeos de formato curto, possui uma base de código grande e em rápido crescimento para seu front-end web. Este aplicativo de interface é construído usando TypeScript e está organizado como um monorepo – um único repositório Git contendo código para múltiplos projetos e bibliotecas.
À medida que a equipe de engenharia frontend e o conjunto de recursos do TikTok cresciam, também cresciam o tamanho e a complexidade desse mono-repo.
Com o tempo, expandiu-se para conter mais de 1.000 projetos separados e mais de 200.000 arquivos fonte. Embora a arquitetura monorepo oferecesse benefícios como código compartilhado e ferramentas, também começou a causar problemas significativos de desempenho.
Os desenvolvedores começaram a perceber lentidão nas operações comuns do Git, que são essenciais para seus fluxos de trabalho diários. Essas desacelerações foram um grande peso para a produtividade, desperdiçando tempo valioso de engenharia e prejudicando a experiência de desenvolvimento.
Os problemas vinham do tamanho enorme do código que o Git precisava processar para cada operação. Para resolver esse problema, a equipe de infraestrutura frontend do TikTok começou a explorar soluções. Eles experimentaram os recursos de desempenho embutidos do Git, como clones parciais, clones superficiais e o Git LFS, mas descobriram que eram insuficientes para a escala e taxa de crescimento do monorepo.
Por fim, a equipe desenvolveu uma ferramenta interna chamada Sparo para enfrentar os desafios de desempenho do monorepo. Eles também acabaram tornando o Sparo open-source.
Neste artigo, vamos explorar como o Sparo funciona e os benefícios que ele trouxe para a equipe de engenharia front-end do TikTok.
O problema da lentidão do git em monorepos grandes Um monorepo, abreviação de repositório monolítico, é uma estratégia de desenvolvimento de software onde um único repositório contém múltiplos projetos, bibliotecas e serviços, frequentemente mantidos por diferentes equipes. Isso contrasta com a abordagem multi-repositório, onde cada projeto tem seu repositório separado.
O diagrama abaixo mostra a diferença entre monorepos, multirepos e bases de código monolíticos.
Os monorepos ganharam popularidade entre grandes empresas de tecnologia como Google, Facebook e Microsoft por vários motivos.
Primeiramente, os monorepos permitem melhor compartilhamento de código e reutilização entre projetos, reduzindo duplicações e promovendo a padronização.
Em segundo lugar, eles simplificam o gerenciamento de dependências, já que projetos dentro do monorepo podem facilmente se referenciar e usar uns aos outros.
Terceiro, os monorepos fornecem uma visão unificada de toda a base de código, facilitando a realização de mudanças transversais e a manutenção de uma infraestrutura consistente de construção e teste.
No entanto, à medida que os monorepos crescem em tamanho e complexidade, as operações do Git se tornam cada vez mais lentas para todos.
Foi exatamente isso que aconteceu no TikTok. Comandos como git clone para baixar uma cópia do repositório, git checkout para alternar entre branches, git status para ver as mudanças atuais e git commit para salvar novas mudanças demoraram muito mais do que antes.
Aqui estão algumas estatísticas interessantes das observações deles:
Clonar o repositório para começar a trabalhar nele pode levar mais de 40 minutos para desenvolvedores com conexões de rede mais lentas. Mesmo em conexões rápidas, um clone completo levava mais de 20 minutos.
Conhecer uma agência diferente levou mais de um minuto e meio.
Só rodar o status git para ver o estado atual da cópia funcional levou 7 segundos, interrompendo o fluxo dos desenvolvedores.
Fazer commit de alterações no código também era doloroso, com cerca de 15 segundos por commit.
Como o Sparo melhora o desempenho do MonoRepo? No seu cerne, o Sparo aproveita dois recursos avançados do Git para acelerar drasticamente operações comuns em monorepos grandes: verificação esparsa e clone parcial.
Vamos analisar ambos em detalhes.
Checkout Esparso O recurso de checkout esparso do Git permite especificar um subconjunto de arquivos ou diretórios para ser desligado de um repositório, em vez de buscar todo o código base. O Sparo usa isso para apenas verificar os arquivos necessários para construir uma aplicação específica – ou seja, o projeto e suas dependências.
Em um monorepo com centenas ou milhares de projetos, os arquivos necessários para qualquer projeto são um subconjunto relativamente pequeno e de crescimento lento em comparação com o repositório como um todo.
Ao usar checkout esparso para limitar a cópia funcional a apenas esse subconjunto, o Sparo reduz significativamente a quantidade de dados que precisam ser buscados e o número de arquivos que as operações Git precisam processar. Isso resulta em tempos de finalização muito mais rápidos.
Veja o diagrama abaixo que explica o conceito de checkout esparso no Git, no qual o desenvolvedor só precisa trabalhar no Desenvolvimento de Cliente Android.
Clones Parciais Embora o checkout esparso reduza o número de arquivos na cópia funcional, um clone padrão do Git ainda recupera o conteúdo de todos os arquivos no repositório e seu histórico completo.
Para repositórios grandes, isso ainda significa uma quantidade considerável de transferência de dados e uso de disco, mesmo que grande parte não seja necessária localmente.
O recurso de clonagem parcial do Git, habilitado ao passar a opção --filter=blob:none para o clone do git, otimiza isso ao buscar o conteúdo dos arquivos sob demanda conforme necessário, e excluindo objetos que não são acessíveis por nenhuma referência. Isso reduz o tamanho do clone inicial e acelera as buscas subsequentes.
Veja o diagrama abaixo para uma representação visual do mesmo.
Diferente de um clone superficial, a história completa ainda está disponível se necessário, só que não é baixada com entusiasmo. Além disso, diferente do Git LFS, o clone parcial funciona automaticamente para todos os arquivos, sem um sistema de armazenamento separado.
Melhorias adicionais do Sparo O Sparo adiciona algumas melhorias além de aproveitar esses dois recursos principais do Git.
Confira Perfis O Sparo introduz o conceito de perfis de checkout, que são conjuntos pré-definidos de diretórios para serem incluídos em um checkout esparso. Perfis funcionam como um ponto de partida fácil para novos contratados ou colaboradores descobrirem qual parte do código é relevante para uma determinada equipe.
Por exemplo, uma equipe frontend pode definir um perfil que verifica seus cinco projetos mais desenvolvidos, as dependências desses projetos e alguns diretórios adicionais como docs e config.
Esses perfis são definidos em um arquivo JSON e registrados no repositório, facilitando para os desenvolvedores compartilharem perfis e configurarem rapidamente sua cópia de trabalho para corresponder ao ambiente padrão da equipe.
Veja o exemplo do arquivo de perfil:
{
"selections": [
{
"selector": "--to",
"argument": "project-a"
},
{
"selector": "--to",
"argument": "project-b"
},
{
"selector": "folder",
"argument": "docs"
}
]
}
Esse perfil ajudaria os desenvolvedores a conferir projeto-a, projeto-b e a pasta docs.
Com o perfil criado, os desenvolvedores podem usar o sparo checkout --profile para acessar o repositório de acordo com o perfil. Isso verifica apenas os projetos e pastas especificados, reduzindo significativamente os dados obtidos e o tempo levado.
Comandos Espelhados Para tornar a adoção sem atritos, o Sparo fornece sua interface de linha de comando que visa ser uma substituição direta para a CLI padrão do Git. Em outras palavras, o Sparo é totalmente compatível com o Git, então as equipes podem adotá-lo incrementalmente enquanto ainda interoperam com o uso padrão do Git.
Ao espelhar a interface Git, o Sparo minimiza a curva de aprendizado e permite que ela seja gradualmente adotada nos fluxos de trabalho existentes. Comandos familiares como clone, checkout, status, add, commit, etc., são todos fornecidos com a mesma sintaxe que seus equivalentes no Git.
Os desenvolvedores podem usar as versões Sparo dos comandos para aproveitar o desempenho otimizado, enquanto ainda recorrem aos comandos Git regulares, se necessário, para casos avançados.
Por trás do capot, as versões Sparo desses comandos configuram automaticamente as configurações apropriadas para permitir clones parciais e checkout esparso com base no perfil ativo. Eles também permitem coletar telemetria de uso anônimo para alimentar dashboards para monitorar a adoção e o desempenho da ferramenta.
Ganhos de desempenho em Sparo A equipe do TikTok teve melhorias dramáticas no desempenho após adotar o Sparo:
O tempo de clonagem caiu de 23 minutos para apenas 2 minutos
O tempo de finalização do check-in passou de 1,5 minuto para 30 segundos
O tempo de comando de status foi reduzido de 7 segundos para 1 segundo
O tempo de commit melhorou de 15 segundos para 11 segundos
Essas melhorias têm um enorme impacto na produtividade e experiência dos desenvolvedores. Em vez de esperar minutos para as operações do Git serem concluídas, os desenvolvedores podem iterar e comprometer mudanças rapidamente, mantendo seu fluxo e foco.
Conclusão À medida que os monorepos crescem em tamanho e complexidade, manter um bom desempenho para operações comuns no Git torna-se cada vez mais desafiador. A equipe frontend do TikTok experimentou isso em primeira mão, já que seu monorepo TypeScript ultrapassou 1.000 projetos e 200.000 arquivos-fonte, levando a quedas significativas em comandos como clone, checkout, status e commit.
Para resolver esse problema, a equipe do TikTok desenvolveu o Sparo, uma ferramenta de código aberto que aproveita os recursos de checkout e clones parciais do Git para acelerar as operações do Git em grandes monorepos.
A trajetória da equipe de engenharia do TikTok com a Sparo destaca a importância de abordar proativamente os problemas de desempenho em bases de código em rápido crescimento.
O roadmap da Spo inclui um sistema de plugins de telemetria para monitoramento e suporte a sistemas adicionais de construção front-end. À medida que mais empresas enfrentam desafios de desempenho com monorepos gigantes, ferramentas como o Sparo podem se tornar cada vez mais importantes para manter a produtividade dos desenvolvedores.
No Microrepo, as dependências são controladas dentro de cada repositório. As empresas escolhem quando atualizar suas versões com base em seus próprios cronogramas.
A Monorepo tem um padrão para check-ins. O processo de revisão de código do Google é famoso por estabelecer um padrão elevado, garantindo um padrão de qualidade coerente para o Monorepo, independentemente do negócio.
A Microrepo pode definir seu próprio padrão ou adotar um padrão compartilhado incorporando as melhores práticas. Pode escalar mais rápido para negócios, mas a qualidade do código pode ser um pouco diferente.
Engenheiros do Google construíram Bazel, e a Meta construiu Buck. Existem outras ferramentas de código aberto disponíveis, incluindo Nix, Lerna e outras.
Ao longo dos anos, o Microrepo teve mais ferramentas suportadas, incluindo Maven e Gradle para Java, NPM para NodeJS e CMake para C/C++, entre outras.
Deixando você: Qual opção você acha melhor? Qual estratégia de repositório de código sua empresa utiliza? Quais ferramentas sua equipe usa para enviar o código para produção e garantir a qualidade do código?
Como o TikTok gerencia um monorepo frontend de 200 mil arquivos? Um MonoRepo, abreviação de repositório monolítico, é uma estratégia de desenvolvimento de software onde um único repositório contém múltiplos projetos, bibliotecas e serviços.
As partes boas de um MonoRepo são:
- Melhor compartilhamento de código
- Gerenciamento simplificado de dependências
- Uma visão unificada da base de código
No entanto, quanto maior o MonoRepo fica, mais lentas são as várias operações do Git.
O TikTok enfrentou uma mudança semelhante com seu frontend TypeScript MonoRepo com 200 mil arquivos.
Para lidar com isso, o TikTok desenvolveu uma ferramenta chamada Sparo que otimiza o desempenho das operações do Git para grandes MonoRepos frontend.
A Sparo melhorou dramaticamente o desempenho das operações do Git. Algumas estatísticas são as seguintes
- O tempo de clonagem do Git passou de 40 minutos para apenas 2 minutos.
- O check-out foi de 1,5 minutos para 30 segundos.
- O status foi de 7 segundos para 1 segundo.
- O tempo de commit do git passou de 15 segundos para 11 segundos.
A abordagem geralmente depende do tamanho da empresa. Não existe uma solução única para todos, mas tentamos oferecer uma visão geral.
1-10 funcionários: Nos estágios iniciais de uma empresa, o foco é encontrar um ajuste produto-mercado. A ênfase está principalmente na entrega e experimentação. Utilizando ferramentas gratuitas ou de baixo custo existentes, os desenvolvedores cuidam dos testes e da implantação. Eles também prestam muita atenção ao feedback e aos relatórios dos clientes.
10-100 funcionários: Uma vez encontrado o ajuste produto-mercado, as empresas buscam escalar. Eles conseguem investir mais em qualidade para funcionalidades críticas e criar processos de evolução rápida, como implantações programadas e procedimentos de teste. As empresas também estabelecem proativamente processos de suporte ao cliente para lidar com problemas e fornecem alertas proativos.
100-1.000 funcionários: Quando a estratégia de entrada no mercado de uma empresa se mostra bem-sucedida e o produto escala e cresce rapidamente, ela começa a otimizar sua eficiência de engenharia. Mais ferramentas comerciais podem ser adquiridas, como produtos Atlassian. Um certo nível de padronização entre as ferramentas é introduzido, e a automação entra em ação.
1.000-10.000+ funcionários: Grandes empresas de tecnologia desenvolvem ferramentas experimentais e automação para garantir qualidade e coletar feedback dos clientes em larga escala. A Netflix, por exemplo, é bem conhecida por sua estratégia de "Teste em Produção", que realiza tudo por meio de experimentos.
Deixa você: cada empresa é única. Em que estágio sua empresa está atualmente e quais ferramentas você usa?
O melhor nome de branch no Git é aquele que comunica intenção, escopo e temporalidade, sem ambiguidade. Como você está falando de testes (automatizados ou manuais, unitários ou de integração), o nome da branch deve deixar claro o tipo de teste e o objetivo, não apenas “teste” de forma genérica.
Na prática, a convenção mais aceita hoje segue a ideia de prefixo semântico, muito alinhada com Git Flow, Trunk-Based ou variações modernas.
Para testes automatizados, o termo mais claro e profissional é usar test/, porque comunica que a branch existe para validar comportamento, não para feature nem correção pontual. Exemplos bem aceitos seriam algo como test/unit-order-service, quando o foco é teste unitário de um contexto específico, ou test/integration-payment-flow, quando o objetivo é validar integração entre componentes ou serviços. Isso deixa explícito que a branch é temporária e experimental.
Se o foco for testes manuais, geralmente não se cria branch só para isso, porque testes manuais normalmente validam código já integrado. Mas se houver necessidade — por exemplo, para QA ou homologação isolada —, nomes como qa/manual-tests ou test/manual-regression fazem mais sentido do que algo genérico. A palavra qa costuma ser usada exatamente para esse contexto de validação humana.
Para testes unitários, o mais limpo é explicitar unit, porque isso evita confusão com testes mais pesados. Algo como test/unit-auth-module comunica imediatamente o escopo técnico e reduz ruído na revisão de PR. Já para testes de integração, test/integration ou test/it-* (IT = Integration Tests) é uma convenção bastante comum em times mais maduros, especialmente em ambientes com microsserviços.
Se você quiser algo ainda mais alinhado com engenharia de software e menos “acadêmico”, pode usar nomes como validation/ ou verification/, mas isso só funciona bem se todo o time estiver alinhado, senão gera confusão. Em times menores ou projetos pessoais, test/ é direto e suficiente.
Note
Em resumo, o melhor nome não é “teste” puro. É algo que diga claramente o que está sendo testado e como. Algo como test/unit, test/integration ou qa/manual é muito mais legível, profissional e sustentável do que branch-teste ou variações vagas. Então, resumindo de forma direta: test/ é o padrão mais correto, mais neutro e mais alinhado com práticas modernas de versionamento. tests/ é aceitável, mas menos comum. tdd/ e bdd/ não são bons nomes de branch, porque descrevem processo mental, não objetivo técnico.
Pensando como engenheiro e não de preferência pessoal, o melhor padrão é test/, não tests/, e evitar tdd/ ou bdd/ como prefixo de branch. Isso não é porque TDD ou BDD sejam ruins, mas porque branch nomeia intenção operacional, não metodologia:
-
test/funciona melhor porque a branch representa uma atividade de validação temporária. Ela existe para testar algo, validar comportamento, rodar suites, ajustar cenários e depois morrer.tests/no plural costuma ser usado para pastas no código, não para fluxo de versionamento, e acaba misturando semânticas. -
tdd/ebdd/são ainda menos adequados como prefixo de branch porque TDD e BDD são estratégias de desenvolvimento, não tipos de branch. Você pode perfeitamente estar fazendo TDD dentro de umafeature/ou até numafix/. Criar uma branchtdd/dá a falsa impressão de que só ali se pratica TDD, o que conceitualmente não faz sentido.
O que realmente comunica bem é combinar test/ com o nível do teste e, se necessário, com o contexto do sistema. Algo como test/unit-auth, test/integration-payment-flow ou test/e2e-checkout. Isso deixa claro para qualquer pessoa do time que aquela branch não entrega funcionalidade nova, não corrige bug produtivo e não é release — ela existe para validar.
Se a distinção for entre manual e automatizado, o mais limpo é explicitar isso no sufixo quando realmente necessário, como test/manual-regression ou test/automation-smoke. Mas, na prática, testes manuais quase nunca justificam branch própria; eles validam código já integrado.
A realidade de times maduros em empresas grandes, especialmente aquelas que tratam engenharia de software como uma verdadeira fábrica de software (Lean Startup), e não apenas como entrega pontual de features. Quando a organização atinge certo nível de complexidade técnica e de negócio, o repositório deixa de ser apenas um lugar para “guardar código” e passa a ser um ativo crítico, governado por processos, padrões e decisões arquiteturais conscientes.
Em ambientes assim, seja em multirepos ou monorepos, cada repositório costuma representar um domínio, um bounded context ou ao menos uma responsabilidade bem definida dentro do ecossistema. Isso faz com que commits e PRs - pull requests não sejam avaliados apenas pelo “funciona ou não funciona”, mas pelo impacto estrutural que aquele código terá no médio e longo prazo. O code review deixa de ser cosmético e passa a ser um mecanismo de proteção do sistema contra degradação técnica, acúmulo de dívidas e introdução silenciosa de code smells que, no começo, parecem inofensivos, mas que no futuro comprometem legibilidade, manutenibilidade e estabilidade.
Por isso, a aprovação de PRs normalmente envolve múltiplos olhares: alguém atento à aderência a princípios como Clean Code e SOLID, alguém com visão mais arquitetural avaliando se aquela mudança respeita os limites do domínio e não viola decisões já consolidadas, e muitas vezes alguém com contexto histórico do sistema, capaz de identificar se aquilo está reforçando um legado problemático ou ajudando a reduzir complexidade. Em times mais avançados, esse processo não é visto como burocracia, mas como um filtro essencial para manter a saúde do software ao longo do tempo.
Quando entram conceitos como Arquitetura Limpa e DDD, o nível de exigência sobe ainda mais. Não basta escrever código “bonito”; é preciso garantir que as regras de negócio estejam isoladas, que dependências estejam apontando na direção correta e que o modelo de domínio continue expressivo e coerente com a linguagem do negócio. O PR passa a ser um espaço de discussão técnica profunda, onde se questiona se aquela abstração faz sentido, se aquele serviço realmente pertence àquele contexto ou se aquilo é apenas um atalho que vai custar caro depois.
No fim das contas, esse modelo de gestão de repositórios e revisões é justamente o que diferencia times que vivem apagando incêndio de times que conseguem evoluir sistemas grandes sem perder controle. A fábrica de software não produz apenas código que roda hoje, mas código que continua compreensível, sustentável e confiável daqui a anos, mesmo com pessoas entrando e saindo do time. É uma visão menos imediatista e muito mais estratégica da engenharia de software.
Note
FUNDAMENTOS DA QUALIDADE DE SOFTWARE: Incluem a definição de requisitos, o planejamento de testes e a execução de testes de forma sistemática. Esses processos são essenciais para garantir que o software desenvolvido atenda às necessidades do usuário e atinja a qualidade esperada.
No antigo Egito, há aproximadamente 4 mil anos, para que as construções fossem feitas com qualidade, definiu-se o cúbito, que era a distância do cotovelo à ponta do indicador do faraó. Uma das primeiras tentativas da humanidade de padronizar as medidas, gerando qualidade nas construções.
Nesta escultura, temos na base um compilado com as regras e leis a serem seguidas, impostas por Hamurabi, Rei da Babilônia, que era aplicada a cidadãos livres, comerciantes, escravos, etc. Uma das primeiras tentativas de padronizar as regras de convívio da sociedade, há aproximadamente 3.800 anos atrás.
Historicamente, o conceito de qualidade está ligado à ideia de atender padrões e requisitos acordados, com base em comunicação clara e objetivos definidos, seja na qualidade do ar, qualidade da água, da qualidade de vida ou qualidade do software. Portanto, a qualidade de software é a investigação do software a fim de fornecer informações sobre sua qualidade em relação ao contexto em que ele deve operar.
A busca por qualidade não é algo novo. Desde os primórdios, o ser humano busca aprimorar suas criações e produções, seja para torná-las mais duráveis, funcionais ou esteticamente agradáveis. Com o advento da Revolução Industrial, essa busca se intensificou, principalmente no que diz respeito à produção em massa.
Mas foi somente com a Segunda Guerra Mundial que a qualidade passou a ser vista como uma questão estratégica, com a necessidade de se produzir em larga escala produtos de alta qualidade para suprir as demandas militares. A partir daí, a qualidade começou a ser tratada como um processo e a ser estudada de forma mais sistemática. A evolução histórica da qualidade de software pode ser dividida em várias fases:
-
PRIMEIRA FASE: Conhecida como era dos “programadores heróis”, ocorreu na década de 1940 a 1950, quando o foco era na programação manual e não havia processos formais de garantia de qualidade.
-
SEGUNDA FASE: Foi a era dos “produtos de software”, que teve início nos anos 1960 e trouxe a necessidade de se preocupar com a qualidade dos softwares produzidos, mas ainda sem muita formalização.
-
TERCEIRA FASE: A era da “engenharia de software”, teve início nos anos 1970 e marcou o surgimento de processos formais de desenvolvimento de software e de metodologias para garantir a qualidade do produto final. Nesta fase, foi criada a ISO 9000, que estabeleceu padrões de qualidade para empresas e serviços em geral.
-
QUALIDADE TOTAL: Teve início na década de 1980, trouxe a ideia de que a qualidade não é responsabilidade apenas da área de desenvolvimento de software, mas sim de toda a organização. Foram estabelecidos processos de melhoria contínua e a necessidade de se envolver todos os departamentos e funcionários na busca pela qualidade.
-
QUALIDADE DE SOFTWARE: Teve início nos anos 1990, trouxe a necessidade de se preocupar com a qualidade do processo de desenvolvimento do software, e não apenas com o produto final. Surgiram novas metodologias e modelos de maturidade, como o Capability Matu rity Model Integration (CMMI) e o ISO/IEC 12207, que buscam garantir a qualidade do processo de desenvolvimento.
No início da década de 1980, o governo dos Estados Unidos reconheceu a necessidade de padronizar a qualidade do software. Em 1986, foi publicado o padrão IEEE 610.12, que definiu a terminologia básica usada em engenharia de software, incluindo definições de qualidade de software. Essas definições incluem “qualidade de software” como o grau em que um sistema, componente ou processo atende aos requisitos especificados e/ou implícitos e às necessidades ou expectativas do usuário.
A partir de então, a qualidade de software se tornou um foco importante na indústria de TI e foi acompanhada pelo surgimento de várias ferramentas e técnicas de teste e garantia de qualidade de software. A demanda por softwares confiáveis e de alta qualidade é cada vez maior, uma vez que muitos negócios e serviços dependem de sistemas de software em seus processos diários.
Mudança de Paradigma: Como a Relação Desenvolvedor-Testador Mudou de 1:1 para 100:1
1:1 ratio (~1997) - Softwares costumavam ser gravados em CDs físicos e entregues aos clientes. O processo de desenvolvimento era em estilo cascata, as versões eram certificadas e as versões eram lançadas aproximadamente a cada três anos. Se você tivesse um inseto, esse inseto viveria para sempre. Só anos depois as empresas adicionaram a capacidade de software para pingar a internet em busca de atualizações e instalá-las automaticamente.
10:1 ratio (~2009) - Por volta de 2009, a velocidade de lançamento para produção aumentou significativamente. Patches podiam ser instalados em poucas semanas, e o movimento ágil, junto com o desenvolvimento orientado por iterações, mudou o processo de desenvolvimento. Por exemplo, na Amazon, os serviços web são principalmente desenvolvidos e testados pelos desenvolvedores. Eles também são responsáveis por lidar com questões de produção, e os recursos de teste estão sobrecarregados (proporção 10:1).
100:1 ratio (~2020) - Por volta de 2015, grandes empresas de tecnologia como Google e Microsoft removeram os títulos SDET ou SETI, e a Amazon desacelerou a contratação de SDETs.
Mas como isso vai funcionar para as grandes empresas de tecnologia em termos de testes?
Primeiramente, o aspecto de teste do software evoluiu para ferramentas de teste altamente escaláveis e padronizadas. Essas ferramentas foram amplamente adotadas por desenvolvedores para construir seus próprios testes automatizados.
Em segundo lugar, testar o conhecimento é disseminado por meio de educação e consultoria.
Juntos, esses fatores facilitaram uma transição suave para a proporção de testes de 100:1 que vemos hoje.
Hoje em dia, a busca pela qualidade de software continua em constante evolução, com a introdução de novas tecnologias e metodologias, como a integração contínua e o desenvolvimento ágil, e inteligência artificial que buscam garantir a qualidade do software de forma mais eficiente e rápida.
No entanto, a busca pela qualidade de software ainda é um desafio constante. Novas tecnologias e metodologias surgem a cada dia, e os desenvolvedores de software devem estar atualizados sobre as novas tendências e ferramentas disponíveis. Além disso, a complexidade dos sistemas de software modernos também torna o controle de qualidade mais difícil, exigindo uma abordagem mais estruturada e orientada a processos.
Portanto, os aspectos históricos da qualidade de software nos mostram como essa área evoluiu ao longo do tempo. Desde a década de 1970, a qualidade de software vem sendo debatida e estudada, e hoje em dia é um tema muito importante em empresas de desenvolvimento de software.
Para aprofundar seus conhecimentos sobre qualidade de software, você pode se perguntar: como garantir que um software seja de qualidade? Uma resposta é adotar processos de garantia de qualidade, que englobam atividades como revisões, testes e inspeções para identificar e corrigir defeitos. Além disso, é importante definir critérios de qualidade e medir a qualidade do software com base nesses critérios.
Uma ilustração que pode ajudar a entender a importância da qualidade de software é pensar em um aplicativo de banco que apresenta erros constantemente. Isso certamente afetaria a confiança do usuário no aplicativo e, consequentemente, na instituição financeira. Por isso, a qualidade do software é fundamental para garantir a satisfação e fidelização do usuário.
Os fundamentos de teste de software (Software Testing) se referem a um conjunto de práticas e técnicas utilizadas para garantir que um software seja capaz de atender aos requisitos e expectativas dos usuários, além de estar livre de erros e falhas. Para ser eficaz, o teste de software deve ser planejado, executado e gerenciado de forma adequada. Isso envolve identificar e definir os objetivos do teste, estabelecer critérios de aceitação, selecionar as técnicas e ferramentas adequadas e documentar todo o processo.
Entre os fundamentos do teste de software estão:
-
Identificação de requisitos: Os requisitos (requirements) são funcionalidades que o produto precisa ter para atender às expectativas e necessidades das partes interessadas. É importante que todos os requisitos do software estejam claramente definidos e documentados antes do início do teste. Essa etapa é crucial para garantir que o software atenda às expectativas dos usuários e construir MVPs (Mínimo Produto Viável). O escopo é o conjunto de características desejadas que descrevem o resultado final do projeto.
-
Planejamento de teste: o planejamento do teste deve ser feito de forma estruturada e detalhada, incluindo a definição dos objetivos, recursos, cronograma, técnicas e critérios de aceitação.
-
Projeto de casos de teste: os casos de teste devem ser projetados de acordo com os requisitos e objetivos do teste, de forma a garantir a cobertura de todos os aspectos do software.
-
Execução de testes: a execução dos testes deve ser feita de forma sistemática e controlada, registrando todas as falhas encontradas.
-
Avaliação de resultados: os resultados dos testes devem ser avaliados de acordo com os critérios de aceitação para determinar se o software atendeu aos requisitos.
-
Relatórios e documentação: todos os resultados e conclusões do teste devem ser documentados de forma clara e concisa para permitir que outras pessoas entendam e repliquem o processo.
Além desses fundamentos básicos, existem diversas técnicas e ferramentas de teste que podem ser utilizadas para tornar o processo mais eficiente e eficaz. Essas incluem testes funcionais, testes de desempenho, testes de segurança, testes de usabilidade, dentre outros. É importante que o testador esteja familiarizado com as diferentes técnicas e saiba selecionar a mais adequada para cada situação.
Outro aspecto importante dos fundamentos de qualidade de software é a colaboração entre as equipes envolvidas no desenvolvimento. É preciso que todos os membros trabalhem em conjunto para garantir a qualidade do produto final. Isso inclui desde a comunicação eficiente entre os desenvolvedores e os testadores até a participação do usuário em testes de aceitação.
Important
Software Engineering: A Practitioner's Approach: Para aprofundar seus conhecimentos sobre qualidade de software, você pode recorrer a materiais como artigos científicos, livros e cursos on-line. Uma indicação de leitura é o livro Software Engineering: A Practitioner's Approach, de Roger Pressman, que aborda temas como garantia de qualidade, medição de qualidade e processos de software. Em resumo, entender os fundamentos de qualidade de software é essencial para garantir o sucesso de projetos de desenvolvimento. Isso inclui a compreensão do conceito de qualidade, a adoção de processos de garantia de qualidade, a definição de critérios de qualidade e a colaboração entre as equipes. Aprofundar seus conhecimentos neste tema é fundamental para se destacar no mercado de trabalho e garantir a satisfação do usuário.
Portanto, o teste de software (Software testing) para os profissionais de QA’s é a forma que utilizamos para avaliar um software, com um objetivo de assegurar e garantir a qualidade do mesmo, nos pontos de vista técnico e funcional.
O teste de software é a prática concreta que os profissionais de QA (no papel de testers, engenheiros de teste ou SDETs) usam para avaliar se o produto realmente cumpre o que deveria cumprir, tanto sob o ponto de vista técnico (estabilidade, performance, segurança) quanto funcional (se as regras de negócio e requisitos do usuário estão atendidos).
O desenvolvimento de software tem várias fases. Quando começamos a construir software, primeiro precisamos definir o que planejamos construir. Definimos alguns limites para o escopo do software que estamos construindo, definimos processos intermediários e marcos para colocar alguns pontos de verificação sobre se estamos indo na direção certa durante o processo de desenvolvimento e definimos critérios de sucesso para que o software saiba o que construímos é o que queríamos.
O termo mais amplo e sofisticado "Garantia de Qualidade" lida com o estabelecimento de limites e pontos de verificação, e "Teste" é um subconjunto do processo geral de garantia de qualidade que lida com a validação de que tudo isso se juntou para culminar em um produto final que deixará as partes interessadas felizes (principalmente). Se tudo isso é muito abstrato, não tema, explicaremos com um exemplo mais concreto em breve.
Digamos que temos uma ambição simples - queremos aceitar alguma entrada do usuário e imprimi-la de volta no console. A vida não poderia ser mais simples do que isso, pode? Vamos ver como isso se traduz no processo SDLC. Duvido que uma explicação seja necessária, mas aqui está, no entanto - não é um exemplo da vida real, mas simplificado demais para ilustrar o processo de pensamento e também por que o teste de aplicativos evoluiu como uma disciplina separada do desenvolvimento.
"Descobrir o inesperado é mais importante do que confirmar o conhecido." - George E.P.Box
Quando falamos de testes de software usamos bastante os conceitos de dois pontos de vista: o caminho testável e caminho feliz, mas cada um tem um propósito diferente dentro da estratégia de validação do sistema. O chamado “caminho feliz” (ou “happy path”) é aquele fluxo idealizado em que tudo acontece como esperado, sem erros, exceções ou desvios.
É o cenário perfeito, onde o usuário ou o sistema segue exatamente o que foi planejado, com entradas válidas, comportamento conforme os requisitos e saída correta. É fundamental porque garante que a funcionalidade principal está realmente atendendo ao objetivo esperado quando usada da maneira correta, e por isso é quase sempre o primeiro caso de teste escrito, seja em testes manuais, automatizados, unitários ou de integração.
Já o “caminho testável” (alternativo ou inesperado) pode ter interpretações ligeiramente diferentes dependendo do contexto, mas normalmente é usado para se referir a qualquer rota dentro do código ou do fluxo da aplicação que pode ser exercitada por um teste. É o conjunto de caminhos possíveis que o teste consegue alcançar, incluindo o caminho feliz e também as rotas alternativas, os cenários de exceção, entradas inválidas, erros de negócio, falhas de integração, entre outros. Em outras palavras, não é só garantir que a função principal funciona, mas também que o sistema se comporta corretamente quando algo sai do esperado. É aí que entram testes negativos, de borda, de falha, de performance ou até mesmo de segurança, que exercitam a aplicação de forma mais ampla e realista.
Portanto, quando você ouve falar de “caminho feliz”, é um subconjunto do “caminho testável”. O caminho feliz prova que o básico funciona; o caminho testável inclui todas as outras possibilidades para dar robustez à aplicação. Ou seja, o usuário é inesperado, então vamos precisar "preparar" a aplicação para coisas inesperadas.
No universo do desenvolvimento de software, a qualidade é um objetivo primordial, e dois termos frequentemente se entrelaçam nesse contexto: Quality Assurance (QA) e Quality Control (QC). Embora muitas vezes usados como sinônimos, esses conceitos representam abordagens distintas, porém complementares, para garantir que o produto final atenda aos mais altos padrões de excelência.
Quality Assurance (QA): Prevenindo os Erros Antes que Eles Acontecçam
Imagine um arquiteto meticuloso, que examina cada detalhe do projeto de uma casa, desde a fundação até o telhado, para garantir que a construção seja sólida, segura e atenda às necessidades dos moradores. Essa é a essência do QA, um processo proativo que se concentra em prevenir erros e defeitos durante todo o ciclo de desenvolvimento de software.
O QA atua como um guardião da qualidade, definindo e implementando processos, padrões e melhores práticas para garantir que o software seja desenvolvido da maneira correta, desde o início. Ele se concentra em:
- Definir e monitorar os processos de desenvolvimento: Implementar metodologias e práticas que garantam a qualidade em todas as etapas do desenvolvimento.
- Estabelecer padrões de qualidade: Definir critérios e métricas para avaliar a qualidade do software em diferentes aspectos, como funcionalidade, usabilidade, desempenho e segurança.
- Prevenir defeitos: Implementar medidas preventivas para evitar que erros e defeitos sejam introduzidos no software, como revisões de código, testes unitários e análise estática de código.
- Promover a cultura de qualidade: Incentivar a colaboração, a comunicação e o feedback entre os membros da equipe, criando um ambiente onde a qualidade é valorizada e priorizada.
Quality Control (QC): Detectando e Corrigindo os Bugs
Agora, imagine um inspetor de qualidade que examina cada cômodo da casa já construída, verificando se tudo está de acordo com o projeto e se não há nenhum problema. Essa é a essência do QC, um processo reativo que se concentra em detectar e corrigir defeitos no software após ele ter sido desenvolvido.
O QC atua como um detetive, buscando por bugs e falhas que podem comprometer a qualidade do software. Ele se concentra em:
- Executar testes: Realizar diferentes tipos de testes, como testes funcionais, de performance, de segurança e de usabilidade, para identificar defeitos no software.
- Reportar bugs: Documentar e reportar os bugs encontrados para a equipe de desenvolvimento, fornecendo informações detalhadas para que eles possam ser corrigidos.
- Verificar as correções: Após a correção dos bugs, o QC realiza novos testes para garantir que os problemas foram resolvidos e que não foram introduzidos novos bugs.
- Monitorar a qualidade do software: Acompanhar o desempenho do software em produção, coletando dados e feedback dos usuários para identificar e corrigir problemas.
![]() |
![]() |
QA e QC: Um Time Imbatível. QA e QC são como duas faces da mesma moeda, trabalhando em conjunto para garantir a qualidade do software. Enquanto o QA se concentra na prevenção de defeitos, o QC se concentra na detecção e correção dos problemas. A sinergia entre esses dois processos é essencial para construir um software robusto, confiável e que atenda às expectativas dos usuários.
Compreender a diferença entre QA e QC é fundamental para construir uma estratégia de qualidade de software eficaz. Ao integrar as práticas de QA e QC em todo o ciclo de desenvolvimento, as empresas podem garantir que seus softwares sejam desenvolvidos com qualidade, eficiência e segurança, satisfazendo os usuários e impulsionando o sucesso do negócio.
E dentro da distinção clássica entre QA (Quality Assurance) e QC (Quality Control), os testes de software se enquadram como atividades de QC, porque estão no nível da inspeção e verificação do produto final.
QA olha mais para os processos, prevenindo defeitos ao longo do ciclo (definição de padrões, auditorias de qualidade, práticas de engenharia), enquanto QC foca em detectar e validar defeitos no resultado tangível — o software em execução, as integrações funcionando, os requisitos sendo cumpridos.
Portanto, sim: quando falamos em testes manuais, automatizados, de integração, de aceitação, de regressão, de performance, etc., estamos atuando dentro da camada de QC. Eles são o mecanismo pelo qual conseguimos transformar “expectativas de qualidade” em evidências objetivas de que o software funciona (ou não) como esperado.
Important
Vantagens dos testes de software: Se você quer evoluir em testes de software e entender como integrar qualidade de forma colaborativa e contínua em equipes ágeis, o livro "Agile Testing: A Practical Guide for Testers and Agile Teams", escrito por Lisa Crispin e Janet Gregory, é uma leitura essencial para transformar a teoria em prática.
- Qualidade
- Suporte ao time
- Feedback constante
- Produtividade
- Documentação viva
- Alinhamento com o negócio
- Disseminação de conhecimento
- Prevenção de bugs: Evitar a continuação dos bugs
- Redução de Custos
A definição de “pronto” nada mais é do que um contrato firmado entre o time e o PO, que lista de forma clara os requisitos que determinam que uma User Story está completa.
E a resposta é simples: normalmente, quando perguntam se uma funcionalidade ou story está pronta, respondem: “sim, mas falta testar…”.
Isso significa que ao definir o conceito de “pronto”, é importante que o QA – que faz parte do time – esteja envolvido e possa sensibilizar os membros do time e PO para que os testes façam parte deste conceito.
Dessa forma, garantimos:
- Integração entre desenvolvedores e QA;
- Maior qualidade do time;
- Que não haja desentendimentos desnecessários;
- Story testada e com aceite formal.
As atividades de apoio da qualidade de software, como revisões, auditorias e inspeções, são essenciais para garantir que o software seja produzido de acordo com os padrões de qualidade estabelecidos. Essas atividades podem ser realizadas em diferentes momentos do ciclo de vida do software, desde a análise de requisitos até a manutenção.
As revisões são uma das atividades de apoio mais comuns e envolvem a análise do software por um grupo de pessoas para detectar erros e possíveis melhorias. Existem diferentes tipos de revisões, como revisões de código (Code Reviews), revisões de documentos e revisões de design.
Note
Real-Time Code Reviews Powered by AI: O Slack tornou a colaboração em tempo real fluida para as equipes. A CodeRabbit traz esse mesmo espírito para as revisões de código (Code Reviews). Ele analisa cada PR usando IA consciente do contexto que entende sua base de código, sugerindo mudanças, detectando bugs e até fazendo perguntas quando algo está errado. Perfeito para equipes rápidas que querem revisões de código de qualidade sem desacelerar. Integrado ao GitHub, GitLab, é como um engenheiro sênior revisando com você em cada commit. Gratuito para código aberto.
A CodeRabbit é um revisor de código com IA que ajuda você ou sua equipe a mesclar suas alterações de código mais rápido com qualidade superior. O CodeRabbit não aponta apenas problemas; Ele sugere, corrige e explica o raciocínio por trás das sugestões. Eleve a qualidade do código com análises baseadas em IA, conscientes do contexto, e correções em um clique.
O CodeRabbit oferece:
• Resumos automáticos de PR e guias de troca de arquivos.
• Roda linters populares como Biome, Ruff, PHPStan, etc.
• Destaca questões de segurança de código e configuração.
• Permite que você escreva instruções personalizadas de revisão de código e regras grep do AST.
Até o momento, o CodeRabbit já analisou mais de 5 milhões de PRs, está instalado em um milhão de repositórios, tem 15k+ interações diárias com desenvolvedores e é usado por 1000+ organizações.
As auditorias são semelhantes às revisões, mas geralmente são realizadas por equipes independentes de pessoas com habilidades específicas para avaliar o software em relação a padrões e regulamentações específicos. As auditorias podem ser internas ou externas e podem ser realizadas em diferentes fases do ciclo de vida do software (BRITO, 2006).
Já as inspeções são atividades mais formais e estruturadas, que seguem um processo específico para avaliar o software em relação a determinados padrões e especificações. As inspeções geralmente envolvem uma equipe multidisciplinar e podem ser realizadas em diferentes fases do ciclo de vida do software.
Essas atividades de apoio da qualidade de software são importantes porque ajudam a identificar problemas no software antes que ele seja lançado, reduzindo assim os custos e os riscos associados a falhas no software. Além disso, elas também podem ajudar a melhorar a eficiência e a eficácia do processo de desenvolvimento de software (SHAMA, 2016).
Para aprofundar seu conhecimento sobre atividades de apoio da qualidade de software, você pode se perguntar:
■ Quais são as diferenças entre as atividades de revisão, auditoria e inspeção?
■ Como as atividades de apoio podem ser integradas ao processo de desenvolvimento de software?
■ Qual é o papel dos diferentes membros da equipe na execução dessas atividades?
Além disso, uma ilustração útil pode ser um diagrama que mostre a relação entre as atividades de apoio da qualidade de software e outras atividades do ciclo de vida do software, como desenvolvimento, teste e implantação. Isso pode ajudar a entender como as atividades de apoio se encaixam no processo geral de desenvolvimento de software e como elas podem contribuir para a melhoria da qualidade do produto final.
| Tipos, técnicas e níveis de testes | Ferramentas para testes (CI/CD Pipeline) |
![]() |
![]() |
É importante destacar que a observabilidade (observability) não é exatamente parte do QA no sentido tradicional, mas ela se conecta de forma muito natural com a qualidade do software. QA (Quality Assurance) historicamente é voltado para prevenção de defeitos, validação e verificação de requisitos, testes funcionais, não funcionais e processos que garantem que o produto atenda ao que foi especificado.
Observabilidade, por outro lado, nasceu com foco na operação e na confiabilidade: é a capacidade de entender o que está acontecendo em um sistema, em tempo real, a partir de sinais como logs, métricas e traces. Ela vai além de simplesmente monitorar: é sobre ser capaz de responder perguntas desconhecidas sobre o comportamento do sistema.
A relação entre as duas áreas está no fato de que quanto mais observável um sistema é, mais fácil fica detectar, diagnosticar e corrigir problemas que impactam a qualidade. QA tradicional muitas vezes termina antes da aplicação ir para produção, mas bugs e falhas ainda podem aparecer em ambientes reais.
Com observabilidade, você complementa os testes com uma visão contínua, onde é possível acompanhar a saúde, a performance, os gargalos e até o comportamento do usuário. Para equipes modernas, especialmente em DevOps e SRE, a linha entre QA e observabilidade se mistura um pouco, pois qualidade não é mais só “testar antes de subir”, mas também garantir que o produto em execução esteja saudável, rastreável e confiável.
Portanto, observabilidade pode ser vista como complementar ao QA, não substitui testes e processos de qualidade, mas amplia a segurança e o controle. Muitas equipes de alto desempenho hoje tratam a observabilidade como parte essencial da estratégia de qualidade, especialmente quando o sistema é distribuído, usa microsserviços, filas, integrações externas ou precisa de alta disponibilidade.
No cenário acelerado de software atual, as equipes de garantia de qualidade enfrentam uma pressão intensa para entregar lançamentos de alta qualidade de forma mais rápida e econômica (impactqa.com, bugraptors.com).
Como as empresas enviam código para produção? O diagrama abaixo ilustra o fluxo de trabalho típico.
| APIs and Web Applications | App Mobile |
![]() |
![]() |
Passo 1: O processo começa com um product owner criando user stories baseadas em requisitos.
Passo 2: A equipe de desenvolvimento pega as histórias de usuário do backlog e as coloca em um sprint por um ciclo de desenvolvimento de duas semanas.
Passo 3: Os desenvolvedores commetem o código-fonte no repositório de código Git.
Passo 4: Uma build é acionada em Jenkins. O código-fonte deve passar por testes unitários, limiar de cobertura de código e portas no SonarQube.
Passo 5: Uma vez que a build é bem-sucedida, ela é armazenada no artefactory. Depois, a build é implantada no ambiente de desenvolvimento.
Passo 6: Pode haver várias equipes de desenvolvimento trabalhando em diferentes recursos. Os recursos precisam ser testados de forma independente, então são implantados no QA1 e QA2.
Passo 7: A equipe de QA adota os novos ambientes de QA e realiza testes de QA, testes de regressão e testes de desempenho.
Etapas 8: Uma vez que as builds de QA passam pela verificação da equipe de QA, elas são implantadas no ambiente UAT.
Passo 9: Se o teste do UAT for bem-sucedido, as versões se tornam candidatas a lançamento e serão implantadas no ambiente de produção conforme o cronograma.
Passo 10: A equipe SRE (Site Reliability Engineering) é responsável pelo monitoramento de produção.
A hiperautomação (Hyperautomation) surgiu como uma resposta estratégica: em vez de simplesmente executar testes roteirizados, ela une ferramentas avançadas para automatizar inteiramente fluxos de trabalho de QA de forma inteligente (dev.toimpactqa.com).
Essa abordagem utiliza IA, aprendizado de máquina (ML), workflows, automação robótica de processos (RPA) e até plataformas low-code para construir processos de teste autônomos que abrangem tudo, desde requisitos até implantação (dev.togenqe.ai).
Em outras palavras, hiperautomação em QA significa criar um ecossistema autônomo de testes onde os sistemas não apenas executam testes, mas também decidem o que testar, como se adaptar quando o código muda e quando executar testes, tudo isso com intervenção humana mínima (dev.toimpactqa.com).
A hiperautomação representa uma mudança de paradigma para o QA: de testes roteirizados executados sob comando para um ecossistema de qualidade dinâmico e autorregulado (dev.to testingxperts.com). Ao combinar IA, ML, RPA e ferramentas modernas, as equipes podem alcançar testes mais rápidos, precisos e escaláveis. Os benefícios — lançamentos mais rápidos, melhor cobertura contra defeitos e menor risco — são claros, embora realizá-los exija superar obstáculos de integração e governança. A experiência na indústria mostra que a transição é gradual: comece com pilotos e frameworks de governança, desenvolva expertise e expanda iterativamente o escopo da automação.
À medida que o desenvolvimento acelera, o QA não pode permanecer manual ou estático. Adotar a hiperautomação faz dos testes um facilitador estratégico da transformação digital. Organizações que integram com sucesso a automação inteligente ao QA entregarão software de maior qualidade em alta velocidade e navegarão com confiança pela complexidade das aplicações modernas (testingxperts.com, testingxperts.com).
Correção Automatizada de Bugs em Escala do Facebook: Se tem uma coisa que a maioria dos desenvolvedores realmente odeia, é depuração (debugging).
Embora depurar programas pequenos não seja divertido, pode ficar extremamente irritante quando você precisa depurar milhões de linhas de código numa sexta-feira à noite para encontrar aquele bug tão difícil de encontrar.
Para piorar, bugs (de software ou não) são persistentes.
Você se livra de um e mais dois aparecem. Quando você acha que finalmente resolveu o problema e começou a testar, percebe que o patch que você acabou de fazer está causando outro travamento em outro lugar dentro dessas milhões de linhas.
Antes que perceba, você já está caminhando por mais uma caçada ao inseto.
É aí que o SapFix se projeta como uma ferramenta revolucionária no campo da correção automática de bugs.
É uma nova ferramenta híbrida de IA criada pelo Facebook com o objetivo de reduzir o tempo que os engenheiros gastam com depuração.
O SapFix facilita a depuração ao gerar automaticamente correções para problemas específicos e propor essas correções aos engenheiros para aprovação e implantação em produção.
O diagrama abaixo mostra o fluxo de trabalho do SapFix em um nível geral. Em uma seção posterior, veremos todo o processo com ainda mais detalhes.
Dizer que a SapFix mostrou potencial seria pouco. Aqui estão alguns fatos que valem a pena considerar:
O SapFix tem sido usado para sugerir correções para seis aplicativos-chave do Android na família de aplicativos do Facebook.
Os aplicativos são Facebook, Messenger, Instagram, FBLite, Workplace e Workchat.
Juntos, esses aplicativos consistem em dezenas de milhões de linhas de código e são usados diariamente por centenas de milhões de usuários ao redor do mundo.
Se você pensar bem, são 6 bases de código de vários milhões de linhas e ainda estamos nos primeiros dias de desenvolvimento do SapFix!
Neste ponto, você pode se perguntar como a SapFix consegue gerar correções para tantos aplicativos diversos com usos tão variados, que vão desde comunicação até redes sociais e construção de comunidades.
O Papel de Sapienz e Infer O segredo do SapFix é a adoção de técnicas automatizadas de reparo de programas.
Essas técnicas são baseadas em algoritmos para identificar, analisar e corrigir bugs de software conhecidos sem intervenção humana. Uma das abordagens mais utilizadas depende de testes de software para direcionar o processo de reparo.
É aí que o Facebook utiliza seu sistema automatizado de design de casos de teste, conhecido como Sapienz.
A Sapienz utiliza Engenharia de Software baseada em Busca (SBSE) para projetar automaticamente casos de teste em nível de sistema para aplicativos móveis. Executar esses casos permite que a Sapienz encontre centenas de falhas por mês mesmo antes que possam ser descobertas pelos testadores humanos internos do Facebook.
Pense no SBSE como se tivesse um assistente super inteligente que analisa todas as linhas de código e tenta diferentes combinações para resolver um problema. É muito parecido com quando você tenta diferentes peças de um quebra-cabeça até que elas se encaixem perfeitamente.
Como estimativa, os engenheiros do Facebook conseguiram corrigir 75% das falhas relatadas pela Sapienz. Isso indica uma relação sinal-ruído muito alta para os relatórios de bugs gerados pelo Sapienz.
No entanto, para melhorar ainda mais esse número, o Facebook também usa o Infer.
Infer é uma ferramenta de código aberto que auxilia na localização e análise estática das correções propostas. Assim como o Sapienz, o Infer também é implantado diretamente no sistema interno de integração contínua do Facebook e tem acesso à maior parte da base de código do Facebook.
Sapienz e Infer colaboram entre si para fornecer informações aos desenvolvedores sobre possíveis bugs como:
Localização da provável causa raiz
O cenário de teste de falha que ajudou a identificar o bug
No entanto, Sapienz e Infer só podem fornecer informações e não economizam tempo para o desenvolvedor realmente corrigir o problema. Claro, a colaboração deles ajuda a identificar bugs e a localização deles no código, mas a maior parte do trabalho envolvido para corrigir esses bugs ainda cabe ao desenvolvedor.
É aí que o SapFix entra e combina três componentes importantes para fornecer um sistema automatizado de reparo de ponta a ponta:
Técnica baseada em mutação apoiada por padrões gerados a partir de correções humanas anteriores
O projeto automatizado de testes do Sapienz
Infraestrutura de análise estática e localização da Infer
Desde a escolha dos casos de teste que detectam o crash até a correção do problema e reteste, o SapFix cuida de todo o processo como parte do sistema contínuo de integração e implantação do Facebook.
O Fluxo de Trabalho SapFix Como o SapFix realmente funciona?
Existem quatro tipos de correções realizadas pela SapFix:
Correção do Modelo
Correção de Mutação
Diff Revert
Revert Parcial de Diferença
Abaixo está um diagrama que mostra todo o fluxo de trabalho de como o SapFix lida com o processo de correção de um problema com base nesses tipos.
No fundo, o processo é extremamente simples de entender.
O processo de criação de correções recebe a entrada abaixo:
A revisão com bugs e o arquivo culpado que contém o local do crash
A linha culpada onde o acidente deveria estar acontecendo
Rastreamento da pilha do crash
O id único do acidente
Autor da revisão bugada (o desenvolvedor que criou o Diff)
Expressões com bugs fornecidas pelo Infer (isso é nulo quando os dados do Infer não estão disponíveis)
Com base nessa informação, o SapFix gera uma lista de revisões que podem corrigir o crash. Esta lista é criada após o SapFix ter testado essas revisões minuciosamente.
Da entrada à saída, há várias etapas envolvidas:
Desenvolvedores submetem alterações (chamadas de 'Diffs') para serem revisadas usando o Phabricator (o sistema de integração contínua do Facebook)
O SapFix usa o Sapienz para selecionar alguns casos de teste a serem executados em cada Diff submetido para revisão.
Quando o Sapienz identifica um travamento específico para o Diff dado, o SapFix estabelece a prioridade dos tipos de correção (template, mutação, revert, etc).
O SapFix tenta gerar múltiplas possíveis correções por bug e depois avalia sua qualidade.
Para isso, ele executa testes existentes, escritos por desenvolvedores, junto com testes criados pela Sapienz nas versões atualizadas. Esse processo de validação é autônomo e isolado da base de código maior.
Em essência, o SapFix é meio que depurar a base de código, assim como os desenvolvedores fazem atualmente. Pense na abordagem de resolução de quebra-cabeças que mencionamos antes. No entanto, ao contrário dos desenvolvedores, o SapFix não pode implantar a correção em produção sozinho.
Uma vez testados os patches, o SapFix seleciona um dos corredores candidatos e solicita que um revisor humano revise a alteração pelo sistema de revisão de código Phabricator. O revisor é escolhido para ser o engenheiro de software que realmente enviou o Diff que o SapFix tentou corrigir.
Esse é o engenheiro que provavelmente tem o melhor contexto técnico para avaliar o patch. No entanto, outros engenheiros relevantes também estão inscritos em cada Diff com base nos padrões de revisão de código do Facebook. Isso significa que todos os Diffs propostos pelo SapFix têm garantia de ter pelo menos um revisor humano qualificado.
O fluxo acima pode parecer simples, mas há algumas nuances adicionais, e entendê-las torna as coisas mais claras.
Correção de Template e Correção de Mutação Como o nome sugere, as estratégias de correção modelo e correção de mutação escolhem entre correções modelo e baseadas em mutação.
Correções baseadas em templates são favorecidas quando todos os outros parâmetros são iguais.
Mas de onde vêm esses modelos?
Correções de templates vêm de outra ferramenta chamada Getafix, que gera patches semelhantes aos que desenvolvedores humanos produziram no passado. Do ponto de vista do SapFix, o Getafix é uma caixa-preta que contém vários padrões de correção modelo colhidos de correções anteriores bem-sucedidas.
No que diz respeito à estratégia de correção de mutação, o SapFix atualmente só suporta correção de travamentos de Exceção de Ponteiro Nulo (NPE). Embora o Facebook tenha um plano para cobrir mais estratégias de mutação, focar apenas no NPE também trouxe bastante sucesso.
Altos Acidentes de Disparo Se nem estratégias baseadas em templates nem em mutações produzirem um patch que passe em todos os testes, o SapFix tenta reverter Diffs que resultam em travamentos de alta execução.
Travamento de alta execução é um bug de software que ocorre com frequência ou afeta um grande número de usuários.
Existem alguns motivos para reverter o diferencial em vez de tentar fazer patch:
Quedas de alta intensidade podem bloquear o Sapienz e outras tecnologias de teste. Portanto, é importante excluí-los ou revertê-los da build principal o quanto antes.
Bugs de alta capacidade têm um impacto potencial maior na estabilidade e confiabilidade da aplicação.
As estratégias de reverter (total e parcial) basicamente apagam a mudança feita no diferencial. Na prática, reverter pode significar exclusão, adição ou substituição de código na versão atual do sistema.
Entre os dois tipos de estratégias de revert, o SapFix geralmente prefere o revert total de diff porque o revert parcial tem maior probabilidade de efeitos colaterais colaterais.
No entanto, novos Diffs são gerados a cada poucos segundos e reverts completos de diff também podem falhar devido a conflitos de fusão com outras revisões. Nesses casos, o SapFix tenta optar por reverter parcialmente por diferenças, já que as mudanças produzidas são menores e menos propensas a conflitos de fusão.
Resultados da Adoção do SapFix Ao longo de 3 meses, após a adoção do SapFix, ele foi tratado de 57 falhas relacionadas a Exceções de Null-Pointer (NPE).
Para lidar com esses travamentos, foram criados 165 patches (aproximadamente metade de template e metade de reparos baseados em mutações). Desses 165 patches, 131 foram construídos corretamente e passaram em todos os testes. Finalmente, 55 foram reportados aos desenvolvedores.
Além disso, as reações iniciais dos desenvolvedores foram bastante positivas. Ao passar pelo primeiro patch proposto pelo SapFix, os desenvolvedores tiveram a sensação de "viver no futuro".
No entanto, o tempo necessário para gerar uma correção apresentava um problema um pouco diferente.
O tempo mediano entre a detecção de falhas e a publicação da correção para o desenvolvedor foi de 69 minutos. O pior caso foi aproximadamente 1,5 hora e o mais rápido foi 37 minutos após a primeira detecção do acidente.
Como você também pode ver, a faixa geral de valores observados é bem ampla.
A principal razão para isso é a complexidade computacional de corrigir um problema e a variação das cargas de trabalho no sistema CI/CD.
Como o SapFix é implantado em um ambiente altamente paralelo e assíncrono, o tempo desde a detecção até a publicação é influenciado pela demanda atual no sistema e pela disponibilidade de recursos computacionais.
Lições Aprendidas com o SapFix A principal filosofia do Facebook por trás do SapFix foi focar na implantação industrial de um sistema automatizado de reparos, em vez de pesquisa acadêmica. Portanto, a maioria das decisões foi focada nesse objetivo.
Embora ainda haja muito a ser feito, o Facebook também aprendeu muitas lições com o SapFix que compartilhou.
Aqui estão alguns importantes:
Reparos automatizados de ponta a ponta podem funcionar em escala industrial
O papel dos desenvolvedores como o último guardião é fundamental para o sucesso do SapFix. Ainda há muito trabalho necessário para ter oráculos automatizados.
Reverter diferenciais é útil para travamentos de alta disparo na versão master do sistema
O SapFix funciona melhor com falhas ou travamentos recém-surgidos. Com travamentos pré-existentes, a relevância é reduzida porque o desenvolvedor que revisa o patch pode não ter uma visão geral suficiente do código.
A sociologia do desenvolvedor é importante de ser considerada. Apesar da SapFix fornecer patches prontos para uso, os desenvolvedores ainda podem preferir clonar e assumir as mudanças em vez de simplesmente lançar os patches sugeridos pelo SapFix.
Os desenvolvedores demonstraram um bom interesse em interagir com o bot SapFix.
O SapFix foca mais em remover o sintoma do que em tratar a causa raiz. Isso precisa de mais trabalho em termos de identificar a causa raiz das falhas e tentar corrigi-las.
Você sabia que a qualidade de um software pode impactar diretamente nos custos de uma empresa? Os Custos de controle em qualidade de software se referem aos gastos necessários para prevenir defeitos e garantir a qualidade dos produtos de software, bem como para corrigir quaisquer defeitos encontrados durante as fases de desenvolvimento e teste.
Esses custos podem incluir investimentos em treinamento, ferramentas de teste, testes manuais e automatizados, revisões de código e outras atividades de garantia de qualidade.
Ao investir em custos de controle, as organizações podem reduzir o número de defeitos encontrados nos produtos de software (custos de prevenção) e, consequentemente, minimizar os custos associados a falhas de software, como retrabalho, atrasos no cronograma, perda de receita e reputação negativa (custos de avaliação).
As falhas internas são aquelas que acontecem dentro da empresa, antes do produto final ser entregue ao cliente. Elas podem ser causadas por diversos motivos, como falta de capacitação da equipe, problemas de comunicação, dentre outros. Essas falhas podem levar a retrabalho, atrasos na entrega e insatisfação do cliente.
Já as falhas externas são aquelas que ocorrem após a entrega do produto final ao cliente. Elas podem ser causadas por problemas na codificação, falhas de segurança, dentre outros fatores. Essas falhas podem gerar custos altíssimos para a empresa, como a necessidade de retrabalho, de suporte técnico, de processos judiciais e até mesmo a perda de clientes
Warning
Para aprofundar seu conhecimento sobre os custos de qualidade de software, você pode se perguntar: quais são as principais causas de falhas internas e externas? Como a qualidade do software pode influenciar nos custos da empresa? Como é possível reduzir esses custos e melhorar a qualidade do produto final?
A Regra 10 de Myers, criada em 2004 por Glenford Myers, é uma importante diretriz para os analistas de teste de software. Essa regra afirma que toda falha no software deve ser rastreável a uma ação ou decisão específica tomada durante o desenvolvimento do software.
A ilustração mostra um gráfico que representa os custos de correção associados a cada estágio do ciclo de desenvolvimento de software. No lado esquerdo, há uma linha vertical que corresponde ao eixo Y, onde está inscrito "Custo da correção do defeito". Na parte inferior, há uma linha horizontal que se junta à linha vertical, formando uma grade para marcar os estágios do ciclo no eixo X. Abaixo dessa linha vertical está escrito da esquerda para a direita: Análise, Especificação, Construção, Testes e Produção. No interior do gráfico há uma linha vermelha com cinco círculos amarelos.
Em outras palavras, a Regra 10 de Myers enfatiza que o custo da correção de defeitos é bem mais custoso quanto mais tarde o defeito é encontrado, ou seja, um defeito encontrado em produção custa muito mais do que se fosse encontrado na fase de análise ou em modelos de dados.
Uma boa gestão de qualidade pode ser a chave para minimizar os custos relacionados às falhas em software. Investir em capacitação da equipe, em boas práticas de desenvolvimento e em testes constantes pode ajudar a evitar falhas internas e externas e, consequentemente, reduzir os custos para a empresa.
O Shift Left Testing é um conceito dentro da engenharia de software e de testes que significa trazer as atividades de teste para mais cedo no ciclo de desenvolvimento. Tradicionalmente, em modelos como o waterfall, os testes só aconteciam no fim, depois que o código já estava implementado. Isso levava a atrasos, altos custos de correção e acúmulo de defeitos. O movimento de “shift left” (deslocar para a esquerda, no cronograma) é justamente antecipar a validação de qualidade para as fases iniciais: análise de requisitos, design e até antes de escrever código com o objetivo de encontrar problemas cedo, quando ainda são baratos de corrigir.
E isso se conecta com a famosa Regra de 10 de Myers (ou Myers’ Rule of Ten). A regra, proposta por Glenford Myers, diz que:
“O custo para corrigir um defeito aumenta em uma ordem de grandeza (10x) a cada fase posterior do ciclo de desenvolvimento em que ele é encontrado.”
Na prática, shift left se traduz em várias práticas modernas. Envolve escrever critérios de aceitação e testes antes da implementação (TDD, ATDD, BDD), fazer testes unitários e de integração contínuos, usar pipelines de CI/CD para rodar automaticamente baterias de testes a cada commit, e até aplicar ferramentas de análise estática e validações de segurança durante o desenvolvimento. Também pode incluir colaboração mais próxima entre desenvolvedores, QAs e analistas de negócio logo no início, evitando que requisitos mal entendidos só sejam percebidos no final.
O grande benefício é que, quanto mais cedo um bug é descoberto, menor o impacto técnico e financeiro para o projeto. Por isso, “shift left testing” não significa apenas testar antes, mas criar uma cultura de qualidade incorporada desde o início, em vez de tratada como etapa final. É um complemento direto de metodologias ágeis e DevOps, porque apoia ciclos rápidos, entregas frequentes e feedback constante.
Important
Qualidade De Software Na Prática - Como Reduzir O Custo De Manutenção: Para aprofundar seus conhecimentos sobre avaliação de atributos de qualidade, recomendamos a leitura do livro Qualidade De Software Na Prática - Como Reduzir O Custo De Manutenção é um livro escrito por Alexandre Kherroubi e Adalberto Cavalcanti. O livro aborda a importância da qualidade de software na redução dos custos de manutenção e explora estratégias práticas para melhorar a qualidade do software, resultando em um impacto positivo nos custos de manutenção ao longo do ciclo de vida do software.
Os padrões de qualidade de software são conjuntos de práticas, regras e diretrizes que visam garantir a qualidade do software produzido. Eles são uma referência para desenvolvedores e equipes de qualidade, ajudando a garantir que o software produzido seja confiável, seguro e atenda aos requisitos do usuário.
Os padrões de qualidade de software podem ser baseados em normas internacionais , como a ISO/IEC 12207 e a ISO/IEC 9126, ou podem ser específicos para uma indústria ou empresa. Eles podem cobrir todos os aspectos do ciclo de vida do software, desde a especificação de requisitos até a manutenção e evolução do software.
Os padrões de qualidade de software podem ser baseados em normas internacionais.
VOCÊ SABE RESPONDER? Como os padrões de qualidade de software podem ajudar a melhorar a confiabilidade e segurança do software?
Um dos principais benefícios dos padrões de qualidade de software é que eles fornecem uma base sólida para a avaliação da qualidade do software (GALINAC, 2013). Os padrões de qualidade de software, como o CMM (Capability Maturity Model) e o MPS-BR (Melhoria de Processo do Software Brasileiro), foram desenvolvidos para melhorar a qualidade dos processos e produtos de software.
O CMM foi criado pelo SEI (Software Engineering Institute) nos anos 1990, com o objetivo de estabelecer um modelo de maturidade para avaliar a capacidade dos processos de software em uma organização (CMMI-DEV V2.0, 2018).
O modelo é baseado em cinco níveis, que representam um aumento gradual da maturidade do processo, desde o nível 1, que é o mais baixo, até o nível 5, que é o mais alto. Cada nível tem seus próprios objetivos, práticas e áreas de processo.
O CMM é amplamente utilizado para avaliar a maturidade dos processos de software em organizações governamentais e privadas em todo o mundo.
O conceito de self-testing em software vem da ideia de que o próprio sistema deve ter meios embutidos para validar se está funcionando corretamente, sem depender de verificações externas e ocasionais. Em essência, é a noção de que a aplicação deve trazer consigo a capacidade de se “autoavaliar”.
Isso pode se manifestar de formas diferentes dependendo do contexto: em nível de código, por meio de uma suíte de testes automatizados que é executada sempre que o sistema é construído ou implantado; em nível operacional, com health checks, métricas, logs e alertas que confirmam se o serviço está íntegro; ou ainda em processos de QA, onde o produto é constantemente validado contra critérios de aceitação definidos.
Dentro desse conceito, testes automatizados são o núcleo natural do self-testing, porque permitem verificar de forma contínua e repetível se o código ainda atende às especificações à medida que evolui. É nesse ponto que práticas como TDD se encaixam perfeitamente: ao escrever primeiro o teste e depois o código, o desenvolvedor garante que cada parte do sistema já nasce coberta por verificações, criando um software que já vem com seus próprios “exames de saúde”. Além disso, TDD gera uma rede de segurança de regressão, que torna o sistema mais resistente a falhas ao longo do tempo.
"Descobrir o inesperado é mais importante do que confirmar o conhecido." - George E.P.Box
Já os testes manuais também podem existir em um cenário de self-testing, mas não cumprem totalmente a proposta. Eles servem bem em situações exploratórias, de usabilidade ou de descoberta de casos não previstos, mas não garantem a automação e a repetibilidade que o conceito prega. Em outras palavras, um sistema pode até contar com testes manuais complementares, mas não se considera realmente “self-testing” se depende só deles, porque não há como o software se validar sozinho sem a intervenção humana.
Portanto, podemos dizer que o self-testing aceita testes manuais como complemento, mas a essência dele está nos testes automatizados, preferencialmente guiados por boas práticas como TDD ou ATDD, que dão disciplina e cobertura consistente ao ciclo de desenvolvimento.
Então, a base da pirâmide se chama unidade, são os testes de unidade, é um processo de desenvolvimento de software em que as menores partes testáveis de uma aplicação, chamadas unidades, são examinadas individual e independentemente para uma operação.
Ao utilizar este processo é possível obter testes contínuos e revisão do que acabou de ser alterado na aplicação, evitando quebrar funcionalidades que já estavam funcionando. Além disso, são normalmente rápidos e fáceis de depurar.
Estudamos os branches, os workflows e concluímos que dentro da prática da integração contínua, devemos nos afastar o mínimo possível do nosso trunk master principal.
Com as alterações que realizamos o tempo todo em nosso software, como podemos garantir a qualidade do código? Testes. No caso da integração contínua, precisaremos utilizar testes automatizados. O ideal é que a cada alteração, seja realizado um novo teste automatizado, para termos certeza de nenhum problema será gerado.
Para isso, algumas regras e padrões são necessários para manter o fluxo saudável e o caminho feliz em todas as fases:
O Build vem antes da etapa de teste/QA. Pensa assim: o build é o processo de pegar o código-fonte (em .NET, Java, etc.) e transformá-lo em um artefato executável ou implantável (um .dll, .exe, .jar, container Docker, pacote NuGet, etc.). Só depois que esse artefato é gerado é que você consegue rodar os testes de verdade, seja unitário, de integração, ou QA manual/automatizado.
A questão é não é que "sem teste, não tem build", mas sim "do que adianta build sem teste?".
Tecnicamente, dá pra fazer build sem teste, mas aí a pergunta é exatamente essa: “de que adianta gerar um binário, se eu não sei se ele funciona?”. É como fabricar um carro e não ligar o motor pra ver se anda. O artefato até existe, mas não tem valor de verdade, porque ninguém pode confiar nele.
É por isso que, na prática, no ciclo de engenharia de software moderno, teste e build se fundem. O pipeline só chama aquilo de “build válido” se os testes passaram. Tanto que em muitas empresas você vê até a nomenclatura “Build & Test” como uma única fase.
A sequência clássica em pipelines CI/CD é mais ou menos assim:
-
Code → desenvolvedor escreve o código.
-
Build → o código é compilado e empacotado.
-
Test/QA → o artefato gerado é validado (testes automatizados, estáticos, integração, QA manual).
-
Release/Deploy → se passou em QA, o build é promovido para homologação ou produção.
Claro que existem nuances: testes unitários às vezes rodam já na fase de build (porque são rápidos e podem rodar junto da compilação), mas testes de QA completos (funcionais, integração, performance) dependem do build estar pronto. Então, em termos de pipeline: Build → Test/QA → Deploy.
Mas existem dois cenários, aí entra uma confusão comum, e é legal esclarecer. O build em si — no sentido técnico de compilar e gerar um artefato executável — não depende de teste nenhum. Você pode rodar dotnet build, mvn package ou npm run build mesmo sem nenhum teste, e o código vai gerar binário, JAR ou bundle normalmente.
O que acontece é que em pipelines modernos de CI/CD (Azure DevOps, GitHub Actions, GitLab CI, Jenkins etc.), muita gente coloca testes logo após ou até durante o build. Isso dá a impressão de que “sem teste o build não roda”, mas na prática é só uma regra do pipeline que impede a entrega se os testes falharem.
Ou seja:
- Build cru = compila e empacota. Independe de teste.
- Build no pipeline CI = compila, empacota e executa testes obrigatórios (unitários, integração rápida). Se algum falhar, o pipeline cancela e o build não é promovido para as próximas fases (QA, deploy).
Então, tecnicamente: o build vem antes do teste, mas no mundo real de pipelines, eles caminham juntos, porque é mais eficiente falhar cedo se os testes não passarem.
Melhores maneiras de testar a funcionalidade do sistema - Testar a funcionalidade do sistema é uma etapa crucial nos processos de desenvolvimento e engenharia de software.
![]() |
![]() |
![]() |
![]() |
Ela garante que um sistema ou aplicativo de software funcione conforme esperado, atenda aos requisitos do usuário e opere de forma confiável.
Aqui vamos explorar as melhores maneiras:
-
Testes Unitários: Garante que componentes individuais do código funcionem corretamente isoladamente.
-
Testes de Integração: Verifica que diferentes partes do sistema funcionam perfeitamente juntas. Este teste combina várias chamadas de API para realizar testes de ponta a ponta. As comunicações e transmissões de dados dentro do serviço são testadas.
-
Testes do Sistema: Avalia a conformidade de todo o sistema com os requisitos e o desempenho do usuário.
-
Teste de Carga: Testa a capacidade de um sistema de lidar com altas cargas de trabalho e identifica problemas de desempenho. Isso testa o desempenho das aplicações simulando diferentes cargas. Depois, podemos calcular a capacidade da aplicação.
-
Teste de Fumaça Isso é feito após o desenvolvimento da API ser concluído. Basta validar se as APIs estão funcionando e nada quebra.
-
Testes Funcionais Isso cria um plano de teste baseado nos requisitos funcionais e compara os resultados com os esperados.
-
Testes de Regressão Este teste garante que correções de bugs ou novos recursos não devam quebrar os comportamentos existentes das APIs.
-
Teste de Fuzz Isso injeta dados de entrada inválidos ou inesperados na API e tenta fazer a API travar. Dessa forma, ele identifica as vulnerabilidades da API.
-
Testes de Estresse Criamos deliberadamente altas cargas nas APIs e testamos se elas conseguem funcionar normalmente.
-
Teste de Erro: Avalie como o software lida com entradas inválidas e condições de erro.
-
Testes de Segurança Isso testa as APIs contra todas as possíveis ameaças externas.
-
Teste de UI Isso testa as interações da interface com as APIs para garantir que os dados possam ser exibidos corretamente.
-
Automação de Testes: Automatiza a execução do caso de teste para eficiência, repetibilidade e redução de erros.
A sua conversa: Como você aborda a funcionalidade de sistemas de teste em seus projetos de desenvolvimento de software ou engenharia?
-
Testes fazem parte da construção do software: Num fluxo de desenvolvimento saudável, os testes não são “um passo depois que o código já está pronto”, mas um componente do próprio processo de criação. A ideia do TDD (Test-Driven Development), por exemplo, é justamente colocar o teste como motor do design do código. TDD pode ajudar neste processo, e mesmo fora do TDD, testes unitários, estáticos e até linters fazem parte do que chamamos de build saudável. É por isso que pipelines modernos acoplam testes ao build: sem eles, o artefato até compila, mas não é considerado “construído de verdade”;
-
Devem ser realizados antes do commit: Aqui você está descrevendo o que chamamos de pré-validação local. O ideal é que o desenvolvedor rode testes (unitários pelo menos) antes de commitar, garantindo que não quebrou nada básico. Isso pode ser cultural (boa prática individual) ou técnico (com pre-commit hooks no Git, que bloqueiam commit se os testes falharem). Dessa forma, o código que chega ao repositório central já tem uma garantia mínima;
-
Desempenho bom em testes: Os testes demorados podem ser uma barreira para a integração contínua, por isso precisamos ficar atentos.
Uma vez que o código é integrado, a próxima etapa é o teste automatizado (automation testing) que envolve a execução de um conjunto de testes para garantir que as alterações de código sejam funcionais, atendam aos padrões de qualidade esperados e estejam livres de defeitos. Essa etapa ajuda a identificar problemas no início do processo de desenvolvimento, permitindo que os desenvolvedores os corrijam de maneira rápida e eficiente.
Conforme o tempo passa a tecnologia segue avançando e os sistemas que são desenvolvidos por pessoas da área de TI estão cada vez mais completos. Antigamente os testes manuais eram os mais utilizados, mas eles já não suprem mais às demandas das empresas e acabam sendo suscetíveis a erros. Então, as organizações precisam desenvolver mais e com melhor qualidade, é aí que entram os testes automatizados que são programas que executam testes em softwares que estão em construção de uma forma padronizada, sem ser necessário a intervenção humana.
Pois, tais testes possuem funcionalidades capazes de testar de forma automática todos os aspectos de uma plataforma, com o intuito de assegurar um desempenho adequado. Ou seja, a automação de teste é o uso de software para controlar a execução do teste de software, a comparação dos resultados esperados com os resultados reais, a configuração das pré-condições de teste e outras funções de controle e relatório de teste.
Tal procedimento, gera muito mais eficácia e agilidade na etapa de testes, permitindo que o profissional encontre de uma maneira mais fácil as falhas de segurança, bugs e demais erros que possam comprometer o uso da aplicação.
Quando o profissional notar que está gastando muito tempo com tarefas repetitivas e quando o software está muito grande, pode ser a hora de automatizar. Mas, é necessário também questionar a viabilidade dessa ação, sendo essencial analisar se com a automação a equipe irá obter ganho de tempo e se conseguirão reduzir custos e manter a qualidade.
Testes automatizados são uma das práticas mais fundamentais no desenvolvimento de software moderno, pois garantem confiabilidade, reduzem bugs em produção, facilitam refatorações e melhoram a documentação viva do sistema.
Para construir testes automatizados realmente bons, é preciso compreender não só as ferramentas, mas também o processo como um todo, desde a fase de planejamento até a execução contínua. Tudo começa pela compreensão dos níveis de teste:
-
Testes de unidade (isolam pequenas partes do código),
-
Testes de integração (verificam a comunicação entre partes),
-
Testes funcionais (validam funções específicas do sistema a partir dos requisitos, checando o que o software deve fazer, independentemente de como é implementado),
-
Testes de sistema (validam o sistema como um todo),
-
Testes end-to-end (simulam o comportamento real do usuário).
Cada nível exige atenção diferente e ferramentas específicas: No início do ciclo, o desenho dos testes precisa ser baseado em critérios claros de cobertura: o que está sendo testado, por que está sendo testado e o que não precisa ser testado.
Bons testes não são só aqueles que passam, mas aqueles que falham quando o comportamento do código foge do esperado. Para isso, as asserções precisam ser claras, específicas e rastreáveis. Boas práticas incluem escrever testes que sejam rápidos, isolados, determinísticos e legíveis. Um teste bom é aquele que alguém consegue entender o que ele verifica só de ler o seu nome e o corpo, sem necessidade de ir até a implementação testada.
A fase de ferramentas é tão importante quanto o planejamento. Para testes de unidade, temos ferramentas como JUnit (Java), xUnit (C#), pytest (Python), Jest e Vitest (JavaScript/TypeScript), Elixir ExUnit, entre outras. Para mocks e test doubles, usamos bibliotecas como Moq, Sinon, Mockito ou NSubstitute, que ajudam a isolar dependências externas, como chamadas a APIs, bancos de dados e arquivos. Em testes de integração, frameworks como TestContainers, WireMock ou bancos de dados em memória ajudam a montar ambientes realistas. Para testes de aceitação e end-to-end, ferramentas como Cypress, Playwright, Selenium e Puppeteer são as mais utilizadas, permitindo testes que interagem com o navegador ou sistema completo, validando fluxos reais.
Na construção de um bom teste automatizado:
-
o primeiro passo é nomear corretamente o que está sendo testado, depois criar um ambiente previsível para que os testes não tenham falsos positivos ou negativos. Um teste que falha às vezes é um teste ruim.
-
Depois, seguir o padrão AAA (Arrange, Act, Assert) é uma boa prática: configurar os dados e dependências, executar o comportamento que está sendo testado, e por fim verificar o resultado.
-
Também é essencial não testar lógica interna demais (isso gera testes frágeis), mas focar no comportamento observável da função ou componente.
-
Outro pilar crucial é a integração com pipelines de CI/CD: Automatizar os testes via GitHub Actions, GitLab CI, Jenkins ou Azure DevOps garante que os testes rodam a cada push ou PR, evitando regressões. Um teste que só roda localmente é praticamente inútil em um time com múltiplos desenvolvedores.
Além disso, não se deve esquecer do relato dos testes. Ferramentas de coverage (cobertura de código) como Istanbul, Coverlet ou Codecov ajudam a visualizar o quanto do código está sendo testado, embora cobertura alta não signifique qualidade alta é possível ter 100% de cobertura e testes inúteis. O ideal é buscar cobertura útil, ou seja, testes que validam fluxos importantes, limites, erros e casos reais de uso.
Por fim, construir um teste automatizado bom exige prática, disciplina e conhecimento. Não é só sobre ferramentas, mas sobre escrever código de teste que seja confiável, fácil de manter e que reflita as regras de negócio do sistema. É preciso ter clareza sobre o que vale a pena testar, manter a suíte de testes rápida e identificar o ponto de equilíbrio entre cobertura e custo de manutenção. Testes automatizados são investimento — e como todo investimento, precisam de foco, consistência e revisão contínua para darem retorno real.
O desenvolvimento, inspeção e o teste de unidade são as três partes do teste de códigos. Numa era onde tudo é automatizado, testadores de software tem demandado cada vez mais ferramentas de automação de testes. Veja algumas ferramentas para automação de testes:
Selenium: é um framework portátil para testar aplicativos web. O Selenium fornece uma ferramenta de reprodução para a criação de testes funcionais sem a necessidade de aprender uma linguagem de script de teste. É provavelmente a ferramenta de automação de testes mais conhecida e utilizada no mundo, especialmente quando o foco são aplicações web. Diferente de soluções comerciais como Ranorex, UFT ou TestComplete, ele é open source, o que significa que não há custo de licença e ele pode ser adaptado de acordo com as necessidades do time. Esse fator, aliado à sua flexibilidade, fez do Selenium uma espécie de padrão de fato em automação de testes web, sendo adotado desde startups até grandes corporações.
Na automação Selenium, o XPath (XML Path Language) é um método poderoso usado para navegar e localizar elementos em uma página web. Ele oferece uma forma de consultar documentos XML, mas é comumente usado no Selenium para identificar elementos em páginas web HTML para tarefas de automação.
Principais Recursos do XPath em Selenium: Localizador Flexível de Elementos:
- O XPath permite encontrar elementos usando uma ampla variedade de atributos, tags e relações hierárquicas entre elementos em uma página.
- É particularmente útil quando elementos não possuem identificadores únicos (como atributos ou ou ou atributos) e exigem consultas mais complexas para serem localizados.idname
Tipos de XPath:
XPath Absoluto: Refere-se ao caminho direto do elemento raiz () até o elemento alvo. É frágil porque qualquer alteração na estrutura do HTML pode quebrar o caminho. Exemplo:
/html/body/div[1]/section[2]/ul/li[3]
XPath relativo: Localiza um elemento usando qualquer atributo, sem precisar especificar o caminho completo a partir da raiz. É mais flexível e recomendado para uso. Exemplo:
//input[@id='username']
Robot Framework: é uma estrutura genérica de automação de teste para testes de aceitação e desenvolvimento orientado a testes de aceitação. É uma estrutura de teste orientada por palavras-chave que usa a sintaxe de dados de teste tabular.
Robotium: é um framework open source de automação de testes voltado especificamente para aplicações Android. Ele surgiu como uma resposta à necessidade de se criar testes funcionais e de interface de forma mais prática e menos trabalhosa do que o que era oferecido nativamente pela API de testes do Android. Diferente de soluções que focam em testes unitários ou apenas em pequenas partes da aplicação, o Robotium foi desenhado para permitir que você crie testes que simulam a interação real de um usuário com o aplicativo, clicando em botões, digitando em campos, navegando por telas e validando os resultados exibidos. Ele funciona essencialmente como uma camada que se apoia no JUnit e traz uma API mais rica e amigável para lidar com a interface de usuário.
Cucumber: é uma ferramenta de automação de testes que se popularizou por trazer uma abordagem muito forte de BDD (Behavior-Driven Development) para o dia a dia do desenvolvimento de software. Diferente de frameworks mais técnicos, que focam em testes unitários ou funcionais a partir do código, o Cucumber tem como diferencial a ideia de que os testes devem ser escritos em uma linguagem acessível a todos do time — não apenas a desenvolvedores. Ele utiliza a sintaxe Gherkin, que é baseada em descrições de comportamento no formato de cenários, usando estruturas como Dado–Quando–Então. Isso permite que pessoas de negócio, analistas de QA e desenvolvedores conversem sobre o sistema usando a mesma forma de especificação, diminuindo ambiguidades e garantindo que todos entendam da mesma maneira o que está sendo implementado.
Playwright: é uma ferramenta criada com foco em testes automatizados de aplicações web, mas seu propósito vai um pouco além disso, porque ele também pode ser usado como uma biblioteca de automação de navegação. A origem dele está na mesma equipe que desenvolveu o Puppeteer, do Google, mas a Microsoft o criou para oferecer mais robustez, abrangência e recursos modernos. O Playwright permite em testes com Cross-browser (Chrome Firefox etc) para aplicações web, emulação Mobile para simular dispositivos móveis, e testes de API trata requisições HTTP.
TestComplete: é uma ferramenta comercial de automação de testes desenvolvida pela SmartBear que tem como foco facilitar a criação, execução e manutenção de testes funcionais em diferentes tipos de aplicações, desde desktop, web até mobile. A grande proposta dela é oferecer uma plataforma robusta que permite tanto a quem tem experiência em programação quanto a quem não programa criar testes de forma eficiente. Isso é possível porque ela oferece duas formas de trabalho: por um lado, há a possibilidade de construir scripts completos em linguagens como Python, JavaScript, VBScript e outras suportadas, o que dá liberdade total para quem tem familiaridade com código; por outro lado, ela também disponibiliza recursos de gravação e reprodução, em que o testador interage com a aplicação e o TestComplete grava essas interações para gerar um script automaticamente, que depois pode ser reutilizado e refinado.
Telerik Test Studio: é uma ferramenta de automação de testes desenvolvida pela Progress, criada para simplificar e acelerar o processo de validação de software em diferentes tipos de aplicações, desde web, desktop até aplicativos responsivos e mobile. O grande diferencial dela é o foco em ser acessível para equipes que não necessariamente têm profundo conhecimento em programação, ao mesmo tempo em que entrega recursos avançados que podem ser explorados por engenheiros de testes mais experientes. Assim como outras soluções comerciais, a proposta é unir praticidade, estabilidade e integração com o ciclo de desenvolvimento.
HPE Unified Functional Testing: anteriormente conhecido como HP QuickTest Professional (QTP), é uma das ferramentas mais tradicionais e consolidadas de automação de testes funcionais. Ele foi desenvolvido pela Hewlett-Packard (hoje Micro Focus, depois da aquisição da divisão de software da HPE) e é amplamente utilizado em grandes corporações que precisam validar aplicações complexas, que muitas vezes envolvem uma mistura de sistemas legados, aplicações web modernas e até integrações entre diferentes tecnologias. O grande diferencial do UFT sempre foi a abrangência tecnológica: ele suporta automação em aplicações desktop, web, SAP, Oracle, PeopleSoft, aplicações client-server, APIs e até mobile, tudo em uma única plataforma, reduzindo a necessidade de várias ferramentas diferentes.
Ranorex: é uma ferramenta comercial de automação de testes que ganhou bastante destaque justamente por tentar simplificar e unificar a automação em diferentes tipos de aplicações, cobrindo desde softwares desktop, sistemas web até aplicativos mobile. Ele é muito utilizado em ambientes corporativos onde existe a necessidade de automatizar aplicações complexas, que muitas vezes envolvem tecnologias legadas misturadas com plataformas modernas. Seu grande diferencial é oferecer uma interface visual poderosa, que permite a criação de testes sem exigir conhecimento avançado em programação, mas ao mesmo tempo dar liberdade para quem domina código escrever scripts mais sofisticados em linguagens como C# e VB.NET, já que o Ranorex é baseado no ecossistema .NET.
Visual Studio Test Professional: é uma edição do Visual Studio voltada especificamente para gestão e execução de testes de software dentro do ecossistema da Microsoft. Ele não é apenas um IDE, mas uma suíte de ferramentas pensada para equipes de QA e de desenvolvimento que precisam trabalhar de forma integrada em projetos com práticas ágeis e DevOps. Enquanto versões como o Visual Studio Enterprise são mais amplas e englobam todo o ciclo de desenvolvimento, o Test Professional é um produto direcionado para o ciclo de vida de testes, oferecendo recursos de planejamento, acompanhamento e execução.
TestingWhiz: é uma ferramenta de automação de testes voltada principalmente para equipes que buscam praticidade e velocidade na criação de testes, sem depender fortemente de programação. Diferente de frameworks open source como Selenium, que exigem bastante conhecimento técnico e montagem de infraestrutura, o TestingWhiz aposta em uma abordagem codeless, baseada em uma interface visual intuitiva onde o testador pode construir fluxos de teste arrastando e configurando blocos de ações pré-definidas. Isso o torna bastante atraente para equipes de QA funcionais ou para empresas que querem introduzir automação de forma rápida, sem exigir que todo o time saiba programar.
Existem muitas ferramentas de testes automatizados disponíveis para diversas linguagens de programação e tipos de testes. Aqui estão algumas das mais populares, categorizadas por seu propósito principal:
-
Frameworks de Teste Unitário:
-
JUnit: Framework de testes unitários para Java.
-
NUnit: Framework de testes unitários para .NET.
-
PyTest: Framework de testes para Python.
-
Mocha: Framework de testes para JavaScript e Node.js.
-
RSpec: Framework de testes para Ruby.
-
TestNG: Outro framework de testes para Java.
-
-
Ferramentas de Teste de Integração e Funcional:
-
Selenium: Automação de navegadores para testes de aplicações web.
-
Cypress: Ferramenta de teste de front-end para aplicações web modernas.
-
Protractor: Ferramenta de teste de end-to-end para aplicações Angular.
-
Watir: Ferramenta de automação de testes para aplicações web.
-
-
Ferramentas de Teste de Interface de Usuário (UI):
-
Appium: Framework de automação para aplicações móveis (iOS e Android).
-
TestComplete: Ferramenta de automação de testes para aplicações desktop, web e móveis.
-
Ranorex: Ferramenta de automação de testes para desktop, web e dispositivos móveis.
-
-
Ferramentas de Teste de Performance e Carga:
-
JMeter: Ferramenta para testes de carga e performance.
-
Gatling: Ferramenta de teste de carga focada em aplicações web.
-
LoadRunner: Ferramenta de teste de carga e performance da Micro Focus.
-
-
Ferramentas de Teste de Segurança:
-
OWASP ZAP: Ferramenta para testes de penetração de aplicações web.
-
Burp Suite: Ferramenta de teste de segurança para aplicações web.
-
Acunetix: Ferramenta de varredura de segurança para aplicações web.
-
-
Ferramentas de Teste de APIs:
-
Postman: Ferramenta para teste de APIs RESTful.
-
SoapUI: Ferramenta de teste para serviços web SOAP e REST.
-
RestAssured: Biblioteca para teste de APIs REST em Java.
-
-
Ferramentas de Integração Contínua:
-
Jenkins: Ferramenta de integração contínua que pode ser usada para executar testes automatizados.
-
GitHub Actions: Serviço de integração e entrega contínua integrado ao GitHub.
-
GitLab CI/CD: Ferramenta de integração contínua e entrega contínua do GitLab.
-
CircleCI: Serviço de integração contínua e entrega contínua.
-
-
Ferramentas de Análise de Código e Cobertura de Testes:
-
SonarQube: Ferramenta de análise estática de código que também mede a cobertura de testes.
-
JaCoCo: Ferramenta de cobertura de testes para Java.
-
Cobertura: Ferramenta de cobertura de testes para Java.
-
Istanbul: Ferramenta de cobertura de testes para JavaScript.
Essas ferramentas ajudam a automatizar diferentes tipos de testes, desde testes unitários básicos até testes de performance e segurança, garantindo a qualidade e a estabilidade do software durante todo o ciclo de desenvolvimento.
O DDD - Domain-Driven Design (Projeto Orientado a Domínio) é uma abordagem de desenvolvimento de software que se concentra em entender o domínio do negócio e modelar o software em torno desses conceitos e regras de negócio. É um tipo de modelagem de software e um design de software orientado a objetos (OOP) que procura reforçar conceitos e boas práticas relacionadas à OOP e surgiu como uma resposta às dificuldades enfrentadas por desenvolvedores ao lidarem com sistemas complexos, especialmente em domínios de negócio onde a lógica e os requisitos mudam frequentemente.
Isso vem em contrapartida com o uso comum do Data-Driven Design (Projeto Orientado a Dados), que a maioria dos desenvolvedores usa sem mesmo ter consciência disso. O design orientado por domínio (DDD) é uma abordagem importante de design de software, focada em modelar software para corresponder a um domínio de acordo com a contribuição dos especialistas desse domínio. A DDD é contra a ideia de ter um modelo unificado único; em vez disso, divide um grande sistema em contextos limitados, cada um com seu próprio modelo.
Sem levar em conta o DDD, as técnicas de modelagem de domínio são métodos utilizados na engenharia de software para compreender e representar o domínio de um problema específico.
O domínio (domain) refere-se à área de conhecimento, contexto ou setor de negócios em que o software está sendo desenvolvido. A modelagem de domínio tem como objetivo capturar os conceitos, regras e relacionamentos do domínio em um formato compreensível e utilizável pelos desenvolvedores. Então, o DDD (Domain-Driven Design) é uma abordagem para o desenvolvimento de software que combina conceitos de design de software e técnicas de modelagem de domínio.
Não é considerado um design pattern específico, mas sim uma abordagem geral para projetar e estruturar sistemas de software. Domain-Driven Design (DDD) é um método de design de software em que os desenvolvedores constroem modelos para entender os requisitos de negócios de um domínio. Esses modelos servem como base conceitual para o desenvolvimento de software.
O termo foi cunhado por Eric Evans em seu livro de mesmo nome, publicado em 2003. No entanto, a definição mais simples que encontrei foi ao ler o livro Fundamentals of Software Architecture de Neal Ford e Mark Richards:
O design orientado a domínio (DDD) é uma técnica de modelagem que permite a decomposição organizada de domínios de problemas complexos. - Neal Ford e Mark Richards. Fundamentos da Arquitetura de Software. 2020.
Eric Evans cunhou o termo Domain-Driven Design (DDD) como parte do título de seu livro de 2004, Domain-Driven Design: Tackling Complexity in the Heart of Software.
Important
Foi popularizado por Eric Evans em seu livro Domain-Driven Design: Tackling Complexity in the Heart of Software, publicado em 2003. Esse livro não é leve, especialmente se você ainda está no início da jornada. Ele exige uma certa base em desenvolvimento orientado a objetos (OOP), arquitetura de software e experiência prática com projetos reais. Geralmente, ele é mais proveitoso depois que você já trabalhou em sistemas mais complexos ou com arquitetura em camadas. O Design Orientado por Domínio (DDD) ganhou atenção significativa no desenvolvimento de software por seu potencial de enfrentar desafios complexos de software, especialmente nas áreas de refatoração de sistemas, reimplementação e adoção. Utilizando o conhecimento do domínio, o DDD visa resolver problemas de negócios complexos de forma eficaz.
Embora o design orientado a domínio (DDD) exista desde 2004, o conceito não foi capaz de se espalhar excessivamente em todo esse tempo. Nos últimos anos, no entanto, o termo experimentou uma segunda primavera. Onde o desenvolvimento de software não é um fim em si mesmo. Em vez disso, o software é desenvolvido para resolver problemas técnicos do mundo real. Isso requer tecnologia, mas esse não é o foco, é apenas um meio para um fim. O foco real está no assunto, o domínio (domain)! Portanto, uma boa compreensão disso é essencial para um desenvolvimento bem-sucedido e direcionado.
Sob design orientado por domínio, a estrutura e a linguagem do código de software (nomes de classes, métodos de classe, variáveis de classe) devem corresponder ao domínio de negócio. Por exemplo: se o software processa solicitações de empréstimo, pode ter classes como "solicitação de empréstimo", "clientes" e métodos como "aceitar oferta" e "retirar".
DDD só funciona plenamente quando anda alinhado com design de software, design de sistemas e arquitetura, não porque eles sejam a mesma coisa, mas porque o DDD sozinho não consegue se sustentar sem uma estrutura técnica que dê corpo às suas ideias. O ponto central é que o Domain-Driven Design é uma abordagem profundamente conceitual: ele organiza o pensamento sobre o domínio, define limites claros, introduz linguagens específicas, identifica contextos independentes e estabelece modelos consistentes.
Só que tudo isso precisa inevitavelmente se materializar em código, fluxos, integrações, decisões de infraestrutura, modularização e comunicação entre serviços. Se esse materializar não acompanha a lógica do domínio, o DDD implode, ou vira apenas documentação bonita.
O objetivo do DDD é, em primeiro lugar, adquirir conhecimento sobre o problema para identificar a solução. Em seguida, concordaremos com os vários componentes desta solução para implementá-la. Esse objetivo é alcançado por meio dos padrões fundamentais do DDD: padrões estratégicos e táticos. Os padrões estratégicos respondem à pergunta: "Por que estamos construindo este software e quais são seus componentes?". Por outro lado, os padrões táticos dão a resposta à pergunta: "Como esses componentes são implementados?"
Portanto, o design orientado por domínio baseia-se nos seguintes objetivos:
- Colocando o foco principal do projeto no domínio central e na camada de lógica de domínio;
- Basear projetos complexos em um modelo do domínio;
- Iniciando uma colaboração criativa entre especialistas técnicos e especialistas do domínio para refinar iterativamente um modelo conceitual que aborde problemas específicos do domínio.
Important
Em seu livro Clean Architecture: A Craftsman's Guide to Software Structure and Design, Uncle Bob chama uma arquitetura que informa ao leitor sobre o sistema, não as estruturas usadas no sistema, de "Arquitetura Gritante". Em A Arquitetura Limpa: O Guia do Artesão para Estrutura e Design de Software, Martin vai além de um simples catálogo de opções. Com base em mais de meio século de experiência em diversos ambientes de software, ele oferece orientações sobre as escolhas cruciais e explica por que são essenciais para o sucesso. Como esperado de Uncle Bob, o livro apresenta soluções simples e diretas para os desafios reais enfrentados pelos desenvolvedores, desafios que podem determinar o êxito ou o fracasso de seus projetos.
Então, faz sentido para mim pensar em design de software como design gritante quando fala alto e claro sobre o domínio do problema. Geralmente, o ativo mais crítico no design de uma solução é adquirir conhecimento sobre os problemas que estamos tentando resolver, o processo que queremos automatizar ou as dificuldades que queremos facilitar. Então, para nos aproximarmos da solução, tínhamos que já estar próximos do problema.
Falaremos sobre a maneira de se aproximar do problema e da solução: o caminho do Domain-Driven Design (DDD) em direção a um design gritante, o design que informa ao leitor sobre o domínio do negócio, não sobre os frameworks usados.
Warning
Quando não usar DDD? Às vezes só é necessário um CRUD (entrega de um produto funcional)! DDD não é uma solução para tudo. A maioria dos sistemas possui uma boa parte composta por cadastros básicos (CRUD) e não seria adequado usar DDD para isso. A engenharia de domínios tem sido criticada por focar demais em "engenharia para reutilização" ou "engenharia com reutilização" de recursos genéricos de software, em vez de se concentrar em "engenharia para uso", de modo que a visão de mundo, linguagem ou contexto de um indivíduo seja integrado ao design do software. Um CRUD é praticamente o estágio MVP funcional, voltado a registrar, atualizar e consultar informações, geralmente com baixo acoplamento conceitual e alto acoplamento técnico. Você descreve tabelas, cria endpoints, faz o básico para o sistema existir. Ele resolve o problema imediato, mas não escala bem quando as regras começam a se multiplicar, quando várias áreas de negócio influenciam o mesmo fluxo ou quando múltiplos times precisam trabalhar em partes diferentes da solução sem se bloquear.
Tip
Já o DDD é “nível engenharia” (entrega de um produto sustentável, capaz de sobreviver à complexidade, a mudanças constantes, a regras de negócio extensivas e a um ciclo de vida longo), porque exige que o desenvolvedor deixe de pensar apenas como codificador e passe a operar como analista de domínio, arquiteto e estrategista ao mesmo tempo. É necessário entender profundamente o negócio, descobrir limites contextuais, refinar invariantes, identificar agregados, modelar comportamentos, conversar com especialistas e traduzir tudo isso para uma arquitetura mais sólida, isolada e resiliente, geralmente integrada com abordagens como Clean Architecture, Hexagonal, Onion, EDA, CQRS, Event Sourcing e microsserviços.
Você aplica DDD quando a complexidade do domínio começa a crescer a ponto de inviabilizar soluções lineares ou meramente CRUD. Ele se torna realmente vantajoso quando você precisa que o software represente fielmente regras de negócio dinâmicas, mutáveis e distribuídas, principalmente em arquiteturas mais sofisticadas como microsserviços, BFF, EDA, Hexagonal, Clean Architecture, Onion ou Tomato. Nessas abordagens, há múltiplos contextos, integrações, fluxos assíncronos, eventos, fronteiras claras e times diferentes trabalhando sobre partes distintas do sistema; tudo isso exige linguagem ubíqua, modelagem explícita, separação de responsabilidades e decisões guiadas pelo domínio. Em sistemas simples, centralizados ou puramente transacionais, DDD vira peso morto, mas em cenários complexos ele traz clareza, previsibilidade e alinhamento entre tecnologia e negócio, evitando entropia arquitetural e facilitando evolução contínua.
Por isso, ele é usado quando a aplicação deixa de ser “só um sistema” e passa a ser um ecossistema vivo, com equipe, visão de produto, múltiplos contextos e evolução contínua. É literalmente a fronteira entre “programação” e “engenharia de software aplicada ao domínio”.
No entanto, críticos do design orientado por domínio argumentam que os desenvolvedores normalmente precisam implementar uma grande quantidade de isolamento e encapsulamento para manter o modelo como uma construção pura e útil. Embora o design orientado por domínio ofereça benefícios como manutenção, a Microsoft o recomenda apenas para domínios complexos onde o modelo oferece benefícios claros na formulação de uma compreensão comum do domínio.
A exigência de construir um entendimento profissional também se aplica aos desenvolvedores. Uma boa compreensão do assunto (domínio) surge da comunicação regular e de uma linguagem comum, o que representa um desafio, principalmente em equipes interdisciplinares: Afinal, cada disciplina tem sua linguagem técnica, por isso os mal-entendidos são inevitáveis.
Num Code Review, o time não está apenas olhando se o código “funciona”, mas se ele está legível, sustentável e alinhado ao domínio. É aí que DDD se torna um guia. Por exemplo, quando você define entidades e value objects, o revisor consegue avaliar se você está representando o domínio corretamente ou se misturou regras de negócio com detalhes de infraestrutura. Se você trabalha com bounded contexts, o code review ajuda a garantir que cada módulo está respeitando suas fronteiras e não está acoplando responsabilidades que deveriam estar separadas. E quando se usa a linguagem ubíqua, qualquer pessoa envolvida no projeto pode bater o olho no código e identificar termos familiares do negócio, reduzindo ambiguidades.
A ideia central é trazer o domínio (o sujeito, a razão) à tona...O DDD acaba aparecendo muito em code reviews (revisões de código) porque ele não é só um conjunto de padrões técnicos, mas uma maneira de organizar o raciocínio e o design do sistema a partir do domínio de negócio. Diferente de TDD e BDD, que são mais voltados ao como testar e como validar comportamento, o DDD toca no como estruturar o código para refletir a realidade do problema que a aplicação resolve.
Isso muda o tom da revisão: em vez de ser só “esse método está mal nomeado” ou “esse if pode virar um switch”, passa a ser “essa entidade realmente pertence a este contexto?” ou “essa regra deveria estar no domínio ou no serviço de aplicação?”. São discussões de mais alto nível, que evitam dívida técnica e fortalecem a coerência do sistema. Por isso, times que adotam DDD costumam ter code reviews mais ricos, que não param na forma, mas questionam se a essência do código está fiel ao problema que ele resolve.
O DDD (Domain-Driven Design) entra no fluxo do desenvolvimento de produto antes do MVP, como um alicerce. Ele não é um artefato de entrega como MVP, UAT ou Release, mas sim uma abordagem de modelagem que orienta como o software deve ser desenhado para refletir o domínio do problema.
Se você pensar em sequência, o fluxo ficaria assim: Engenharia de domínio + Engenharia de requisitos + MVP - Minimum Product Viable + Design orientado por domínio + Design Evolutivo + Arquitetura de aplicações
-
DDD – Domain-Driven Design: Aqui você faz a imersão no domínio de negócio, conversa com especialistas e stakeholders, descobre a linguagem ubíqua e modela o sistema em termos que façam sentido para o negócio. É nessa etapa que surgem conceitos como entidades, agregados, bounded contexts e eventos de domínio. O objetivo é garantir que a estrutura do software nasça aderente à realidade que ele pretende resolver.
-
MVP – Minimum Viable Product: Uma vez que o domínio esteja bem modelado, você constrói o MVP. Esse MVP já se beneficia do DDD, porque mesmo sendo mínimo, ele está fundamentado em um design que respeita a linguagem e as regras do negócio. Isso evita que o MVP vire um “protótipo descartável” e aumenta a chance de evoluir para produto de verdade.
-
QA interno → UAT → Release → Continuous Delivery: Depois disso, o fluxo segue normalmente como te expliquei antes.
Então, se o MVP é o “primeiro produto que entrega valor real”, o DDD é o mapa que garante que esse valor seja o certo, modelando desde o início com consistência e visão de futuro. Sem o DDD, corre-se o risco de o MVP ser feito de qualquer jeito e depois ser difícil ou custoso de evoluir.
Um conceito fundamental para tudo isso é a Engenharia de domínio que é todo o processo de reutilização do conhecimento de domínio na produção de novos sistemas de software. É um conceito-chave no reuso sistemático de software e na engenharia de linhas de produtos. Uma ideia-chave na reutilização sistemática de software é o domínio. A maioria das organizações atua em apenas alguns domínios. Eles constroem repetidamente sistemas semelhantes dentro de um determinado domínio, com variações para atender a diferentes necessidades dos clientes. Em vez de construir cada nova variante do zero, economias significativas podem ser alcançadas reutilizando partes de sistemas anteriores no domínio para construir novos.
O processo de identificar domínios, delimitá-los e descobrir semelhanças e variáveis entre os sistemas do domínio é chamado de análise de domínio. Essas informações são capturadas em modelos usados na fase de implementação do domínio para criar artefatos como componentes reutilizáveis, uma linguagem específica de domínio ou geradores de aplicações que podem ser usados para construir novos sistemas no domínio.
Na engenharia de linhas de produtos, conforme definido por ISO26550:2015, a Engenharia de Domínios é complementada pela Engenharia de Aplicações, que cuida do ciclo de vida dos produtos individuais derivados da linha de produtos.
A engenharia de domínio é projetada para melhorar a qualidade dos produtos de software desenvolvidos por meio do reaproveitamento de artefatos de software. A engenharia de domínios mostra que a maioria dos sistemas de software desenvolvidos não são sistemas novos, mas sim variantes de outros sistemas dentro do mesmo campo. Como resultado, por meio do uso da engenharia de domínio, as empresas podem maximizar lucros e reduzir o tempo de lançamento no mercado utilizando conceitos e implementações de sistemas de software anteriores e aplicando-os ao sistema-alvo. A redução de custos é evidente mesmo durante a fase de implementação. Um estudo mostrou que o uso de linguagens específicas de domínio permitiu que o tamanho do código, tanto em número de métodos quanto em número de símbolos, fosse reduzido em mais de 50%, e o número total de linhas de código fosse reduzido em quase 75%.
A engenharia de domínios foca em capturar o conhecimento adquirido durante o processo de engenharia de software. Ao desenvolver artefatos reutilizáveis, os componentes podem ser reutilizados em novos sistemas de software a baixo custo e alta qualidade. Como isso se aplica a todas as fases do ciclo de desenvolvimento de software, a engenharia de domínios também foca nas três fases principais: análise, design e implementação, paralelamente à engenharia de aplicações. Isso produz não apenas um conjunto de componentes de implementação de software relevantes para o domínio, mas também requisitos e projetos reutilizáveis e configuráveis.
Dado o crescimento dos dados na Web e da Internet das Coisas, uma abordagem de engenharia de domínios está se tornando relevante para outras disciplinas também. O surgimento de cadeias profundas de serviços Web destaca que o conceito de serviço é relativo. Serviços web desenvolvidos e operados por uma organização podem ser utilizados como parte de uma plataforma por outra organização. Como os serviços podem ser usados em diferentes contextos e, portanto, requerem configurações distintas, o projeto de famílias de serviços pode se beneficiar de uma abordagem de engenharia de domínio.
A engenharia de domínios, assim como a engenharia de aplicações, consiste em três fases principais: análise, projeto e implementação. No entanto, enquanto a engenharia de software foca em um único sistema, a engenharia de domínios foca em uma família de sistemas. Um bom modelo de domínio serve como referência para resolver ambiguidades mais adiante no processo, um repositório de conhecimento sobre as características e definição do domínio, e uma especificação para desenvolvedores de produtos que fazem parte do domínio.
A análise de domínio é usada para definir o domínio, coletar informações sobre o domínio e produzir um modelo de domínio. Por meio do uso de modelos de características (inicialmente concebidos como parte do método de análise de domínio orientada a características), a análise de domínio visa identificar os pontos comuns em um domínio e os pontos variáveis dentro do domínio. Por meio do uso da análise de domínio, é possível o desenvolvimento de requisitos e arquiteturas configuráveis, em vez de configurações estáticas que seriam produzidas por uma abordagem tradicional de engenharia de aplicação.
A análise de domínio é significativamente diferente da engenharia de requisitos e, como tal, abordagens tradicionais para derivar requisitos são ineficazes para o desenvolvimento de requisitos configuráveis, como estariam presentes em um modelo de domínio. Para aplicar a engenharia de domínio de forma eficaz, o reuso deve ser considerado nas fases iniciais do ciclo de vida do desenvolvimento de software. Por meio da seleção de recursos dos modelos desenvolvidos, a reutilização da tecnologia é realizada muito cedo e pode ser aplicada adequadamente durante todo o processo de desenvolvimento.
A análise de domínio é derivada principalmente de artefatos produzidos a partir de experiências passadas no domínio. Sistemas existentes, seus artefatos (como documentos de projeto, documentos de requisitos e manuais do usuário), padrões e clientes são todas fontes potenciais de entrada para análise de domínio. No entanto, ao contrário da engenharia de requisitos, a análise de domínio não consiste apenas na coleta e formalização de informações; Existe também um componente criativo. Durante o processo de análise de domínio, os engenheiros buscam ampliar o conhecimento do domínio além do que já é conhecido e categorizar o domínio em semelhanças e diferenças para aumentar a reconfigurabilidade.
A análise de domínio produz principalmente um modelo de domínio, representando as propriedades comuns e variáveis dos sistemas dentro do domínio. O modelo de domínio auxilia na criação de arquiteturas e componentes de maneira configurável, atuando como uma base sobre a qual projetar esses componentes. Um modelo de domínio eficaz não inclui apenas as características variáveis e consistentes de um domínio, mas também define o vocabulário usado no domínio e define conceitos, ideias e fenômenos dentro do sistema.
Modelos de características decompõem conceitos em suas características necessárias e opcionais para produzir um conjunto totalmente formalizado de requisitos configuráveis.
O design de domínio toma o modelo de domínio produzido durante a fase de análise de domínio e visa produzir uma arquitetura genérica à qual todos os sistemas dentro do domínio possam se conformar. Da mesma forma que a engenharia de aplicações utiliza os requisitos funcionais e não funcionais para produzir um projeto, a fase de projeto de domínio da engenharia de domínios toma os requisitos configuráveis desenvolvidos durante a fase de análise de domínio e produz uma solução configurável e padronizada para a família de sistemas. O design de domínios visa produzir padrões arquitetônicos que resolvam um problema comum entre os sistemas dentro do domínio, apesar das diferentes configurações de requisitos. Além do desenvolvimento de padrões durante o projeto de domínio, os engenheiros também devem tomar cuidado para identificar o escopo do padrão e o nível em que o contexto é relevante para o padrão. A limitação do contexto é crucial: contexto excessivo resulta em o padrão não ser aplicável a muitos sistemas, e contexto insuficiente resulta em um padrão insuficientemente poderoso para ser útil. Um padrão útil deve ser frequentemente recorrente e de alta qualidade.
O objetivo do design de domínio é satisfazer o maior número possível de requisitos de domínio, mantendo a flexibilidade oferecida pelo modelo de características desenvolvido. A arquitetura deve ser suficientemente flexível para satisfazer todos os sistemas dentro do domínio, ao mesmo tempo em que rígida o bastante para fornecer uma estrutura sólida sobre a qual basear a solução.
Implementação de domínio é a criação de um processo e de ferramentas para gerar eficientemente um programa personalizado no domínio.
Um domínio bem modelado só tem efeito se o design de software respeita a coesão do modelo, preserva invariantes, mantém consistência interna e evita que detalhes técnicos vazem para áreas onde não deveriam. Se o design de software decide misturar responsabilidades, criar acoplamentos arbitrários, espalhar regras por camadas que não conversam com o domínio ou ignorar o Ubiquitous Language, então o DDD perde força, porque o código deixa de refletir as estruturas conceituais. O resultado é um sistema que só “parece DDD no papel”, mas opera como um monólito acoplado, com entidades sem significado e casos de uso sem fronteiras.
Ao mesmo tempo, DDD também depende do design de sistemas, porque os Bounded Contexts (Contextos limitados), para existirem de verdade, precisam de fronteiras técnicas: precisam ser isolados, ter seus próprios modelos, seus próprios fluxos de dados, sua própria vida. É o design de sistemas (System design) que decide como esses contextos conversam, se por eventos, filas, APIs, contratos assíncronos ou mensagens. O DDD diz “esses dois contextos são independentes e têm linguagens diferentes”; o design de sistemas transforma essa independência em topologia real e se ele falha nisso, os contextos se fundem, o sistema fica acoplado e a proposta de modularidade do DDD desaparece.
E por fim, a arquitetura de software é o alicerce onde o DDD se apoia. É ela que escolhe padrões como microservices, monólito modular, event-driven, CQRS, event sourcing, hexagonal ou clean architecture, que definem como as partes se organizam, como as dependências fluem, como proteções ao domínio são criadas. A arquitetura é o meio técnico que permite que o domínio respire. Quando a arquitetura é mal desenhada, a equipe tenta aplicar DDD sobre um terreno instável, e o domínio acaba se adaptando aos problemas arquiteturais em vez de a arquitetura se adaptar ao domínio. DDD, nesse cenário, vira ornamento.
Então, sim: DDD precisa andar alinhado com design de software, design de sistemas e arquitetura. O DDD é a visão conceitual, estratégica e semântica; o design de software é a expressão detalhada e tática; o design de sistemas é a topologia operacional; e a arquitetura é a fundação estrutural. Quando esses quatro caminham juntos, o domínio guia o software, o sistema é coerente, a linguagem é compartilhada, os limites são respeitados, e a complexidade fica organizada. Quando andam separados, o DDD deixa de ser um modelo vivo e vira apenas teoria sem impacto real no código e no comportamento do sistema.
O design orientado por domínio articula uma série de conceitos e práticas de alto nível. De importância primordial é o domínio do software, a área de estudo à qual o usuário aplica um programa. Os desenvolvedores de software constroem um modelo de domínio: um sistema de abstrações que descreve aspectos selecionados de um domínio e pode ser usado para resolver problemas relacionados a esse domínio.
Esses aspectos do design orientado por domínio visam fomentar uma linguagem comum compartilhada por especialistas em domínio, usuários e desenvolvedores — a linguagem onipresente. A linguagem onipresente é usada no modelo de domínio e para descrever os requisitos do sistema. A linguagem onipresente é um dos pilares do DDD junto com o design estratégico e o design tático. No design orientado por domínio, a camada de domínio é uma das camadas comuns em uma arquitetura multicamadas orientada a objetos.
Os conceitos e práticas do DDD são dividos em camadas de domínio: Quando você diz que os conceitos e práticas do Domain-Driven Design são divididos em camadas, você está tocando em uma das características mais marcantes do DDD, mas que muitas vezes é mal compreendida. DDD não é exatamente uma “arquitetura em camadas”, mas ele influenciou fortemente arquiteturas que usam camadas de maneira disciplinada. O ponto central é que DDD organiza o software em torno do domínio, e essa organização naturalmente cria zonas distintas de responsabilidade, que acabam se parecendo com camadas lógicas, mesmo que o padrão em si não force isso.
Na prática, quando falamos de camadas em DDD, normalmente estamos falando do conjunto de estruturas conceituais que separa o coração do domínio (onde vivem as regras de negócio puras) das partes infraestruturais, externas ou acopladas a tecnologia. O domínio fica isolado, limpo, autocontido, enquanto o resto da aplicação orbita em volta dele, servindo-o, protegendo-o e garantindo que ele se mantenha expressivo e imutável diante das mudanças tecnológicas.
Os padrões estratégicos lidam com a obtenção de uma visão geral do domínio de negócios, o que inclui decompô-lo em regras de negócios.
Estritamente falando, as regras de negócios são regras ou procedimentos que fazem ou economizam o dinheiro da empresa. — Tio Bob, Arquitetura Limpa: Guia de um Artesão para Estrutura e Design de Software
Em outras palavras, as regras de negócios são os recursos funcionais que requerem desenvolvimento. No entanto, existem diferentes tipos de regras de negócios. Existem urgentes e importantes. Ainda assim, outros não são urgentes, mas são importantes. Coletar e reagrupar essas regras de forma significativa ajuda a decompor a complexidade do domínio de negócios em subdomínios: subdomínios principais, genéricos e de suporte.
O subdomínio principal contém as regras de negócios que oferecem vantagem competitiva e destacam a diferença entre as empresas que trabalham no mesmo domínio de negócios. Ele incorpora uma lógica de negócios altamente complexa e volátil. Portanto, ele deve estar aberto a mudanças na lógica de negócios, o que pode envolver a adição de novos requisitos, a atualização dos antigos ou até mesmo a remoção de alguns deles. Em seguida, ele deve ser cuidadosamente implementado internamente para permitir que essas alterações ocorram de forma eficiente sem causar muitos problemas em todo o software.
A conclusão das regras de negócios do subdomínio principal requer outras regras de negócios que não envolvem lógica altamente complexa nem volátil. Em vez disso, eles apenas apoiam os negócios da empresa sem invocar nenhuma vantagem competitiva, pois estão resolvendo um problema muito óbvio. Eles são desenvolvidos internamente e podem ser terceirizados. Aqui, falamos sobre o subdomínio de suporte. No entanto, o subdomínio genérico consiste em regras de negócios relacionadas a um problema já resolvido para que ele possa ser comprado ou adotado.
Ao destilar o domínio de negócios em busca de subdomínios, devemos falar a mesma língua para nos entendermos. Devemos, portanto, usar o mesmo termo para nos referirmos a um determinado objeto, comportamento ou campo. Na terminologia do DDD, é chamada de linguagem onipresente. Aqui, estamos evocando um dos ingredientes indispensáveis para construir uma solução de software: comunicação eficaz entre as diferentes partes interessadas do projeto, incluindo desenvolvedores, especialistas de domínio, analistas de negócios, gerentes de projeto, gerentes de marketing, etc.
Como desenvolvedores de software, devemos garantir que nossas suposições estejam alinhadas com o conhecimento dos especialistas do domínio. Alberto Brandolini, que é um consultor versátil na área de Tecnologia da Informação, disse uma vez:
Não é o conhecimento dos especialistas de domínio que vai para a produção, são as suposições dos desenvolvedores que vão para a produção. — Alberto Brandolini
Ele é um especialista em DDD que criou o EventStorming, uma metodologia colaborativa envolvendo todas as partes interessadas do projeto para compartilhar conhecimento sobre o domínio do problema. Ele os reuniu com post-its e canetas coloridas diante de um quadro branco para iniciar uma sessão de análise de conhecimento. Essas metodologias ajudam a encontrar limites para separar regras de negócios consistentes e reagrupá-las por contexto. Na terminologia do DDD, esses grupos são chamados de Contextos Limitados, cada um contendo regras de negócios consistentes.
Como reconhecemos regras de negócios consistentes? Regras de negócios consistentes falam uma linguagem onipresente consistente. Quando chamamos de conceito "um" usando um vocabulário diferente e começamos a tratá-lo de um ponto de vista diferente, podemos estar falando de duas responsabilidades comerciais diferentes que exigem segregação de contexto. Além disso, regras de negócios consistentes geralmente mudam pelo mesmo motivo, portanto, mantê-las juntas é melhor.
Cada contexto limitado incorpora determinados recursos de domínio para cumprir uma única responsabilidade e pode ser empacotado de forma independente. Dessa forma, nosso software será fatiado verticalmente e empacotado por recurso. Além disso, essas fatias verticais devem se comunicar para processar um fluxo de trabalho de negócios específico. Portanto, nossos contextos limitados precisam colaborar; Enquanto isso, devemos proteger a consistência das regras de negócios. Devemos escolher o tipo de comunicação que melhor se adapta aos requisitos do negócio, nem mais, nem menos.
A maneira como os contextos limitados devem se comunicar também afeta a maneira como as equipes devem se organizar e colaborar. Duas formas de comunicação são possíveis: cooperação ou comunicação cliente-fornecedor.
Como cooperamos em diferentes contextos delimitados? A cooperação é possível através de parcerias. Nesse tipo de relacionamento, as equipes são parceiras na solução de problemas que surgem durante a integração de contextos limitados; Eles devem, portanto, sincronizar com frequência para detectar pontos de bloqueio, a fim de garantir a integridade do software.
Outra maneira de cooperar entre contextos limitados é ter um kernel compartilhado. As equipes criam um modelo compartilhado que deve ser consistente em todos os contextos limitados que o utilizam. O uso desse padrão deve ser justificado pelo custo da duplicação, que é maior do que o custo da coordenação.
E quanto à relação Cliente-Fornecedor? No tipo de comunicação Cliente-Fornecedor, tínhamos dois lados. O lado do fornecedor é chamado de "upstream", enquanto os clientes são conhecidos como "downstream". O fornecedor é aquele que presta um serviço aos seus clientes. No entanto, os clientes podem interagir de forma diferente com esse serviço, dependendo do padrão de serviço conformista, anticorrupção ou de host aberto.
Conformista significa que o downstream estará em conformidade com o modelo upstream. No entanto, o downstream não estará em conformidade com o modelo upstream na camada anticorrupção. Em vez disso, ele o personalizará para se adequar ao seu modelo por meio de uma camada anticorrupção para proteger seu modelo contra corrupção e conceitos irrelevantes que podem prejudicar a consistência do contexto limitado. Outra maneira de proteger o downstream é seguir o padrão de serviço de host aberto. Nesse padrão, o fornecedor exporá um serviço que esteja em conformidade com seus clientes. Em outras palavras, o upstream pode ter versões diferentes de uma linguagem publicada para estar em conformidade com seu modelo downstream correspondente.
Após essa longa jornada de obtenção do conhecimento essencial, decomposição do domínio de negócios em subdomínios, delimitação de contextos e desenho das relações entre eles, traçamos um mapa de contexto. É uma representação visual que fornece insights, em primeiro lugar, sobre o design de alto nível, incluindo nossos subdomínios, contextos limitados, bem como o modelo que eles implementarão, em segundo lugar, sobre os padrões de comunicação que devem ser usados entre os contextos limitados e, finalmente, sobre a organização e colaboração das equipes.
O DDD nos permite planejar uma arquitetura de microsserviços decompondo o sistema maior em unidades independentes, compreendendo as responsabilidades de cada uma e identificando seus relacionamentos, ele não é um design pattern específico, mas sim uma importante abordagem de design de software, com foco na modelagem de software para corresponder a um domínio de acordo com as informações dos especialistas desse domínio. O Domain-Driven Design (DDD) surgiu como uma metodologia revolucionária para a modelagem de software, desenvolvida com o intuito de refinar e otimizar a correspondência entre o design do software e o domínio do software e o domínio do problema que ele busca resolver.
Os microsserviços são a forma mais escalável de desenvolver software. Mas você precisa de um bom design que permita que as equipes de desenvolvedores trabalhem de forma autônoma e implementem sem atrapalhar umas às outras, caso contrário, você perderá os benefícios de escalabilidade. O DDD ajuda a delimitar responsabilidades claras entre os serviços, o que permite que equipes atuem de forma independente e coordenada.
No entanto, suas raízes vêm de práticas e ideias que estavam sendo discutidas na indústria desde os anos 1990. Durante esse período, muitas empresas estavam adotando metodologias ágeis e enfrentando problemas ao construir sistemas que não apenas funcionassem, mas que também fossem fáceis de entender, modificar e expandir. Um dos grandes desafios era a chamada "lacuna semântica" entre os especialistas de domínio (pessoas que entendem o negócio) e os desenvolvedores (que implementam soluções técnicas). Essa lacuna frequentemente levava a softwares que funcionavam de forma errada ou que eram difíceis de adaptar a mudanças nos requisitos.
A ideia inicial do DDD é voltar à uma modelagem OO mais pura, por assim dizer. Devemos esquecer de como os dados são persistidos e nos preocupar em como representar melhor as necessidades de negócio em classes e comportamentos (métodos). Isso significa que em DDD um Cliente pode não ter um setter para os seus atributos comuns, mas pode ter métodos com lógica de negócio que neste domínio de negócio pertencem ao cliente, como void associarNovoCartao(Cartao) ou Conta recuperarInformacoesConta(). Em resumo, as classes modeladas e os seus métodos deveriam representar o negócio da empresa, usando inclusive a mesma nomenclatura. A persistência dos dados é colocada em segundo plano, sendo apenas uma camada complementar.
O DDD nasceu da necessidade de aproximar esses dois mundos. Eric Evans observou que o software bem-sucedido em contextos complexos era construído em torno de um modelo de domínio que capturava com precisão o conhecimento do negócio. Ele também percebeu que os sistemas mais sustentáveis utilizavam linguagens comuns entre especialistas e desenvolvedores, além de técnicas para isolar a complexidade e tornar o código mais alinhado com as regras do domínio.
Com a evolução do desenvolvimento de softwares e no aumento da complexidade dos requisitos da aplicação, é extremamente relevante definirmos uma comunicação clara entre as várias partes envolvidas em um projeto de software. É bastante comum haver conflitos entre os termos técnicos utilizados pelas diferentes áreas, seja entre analistas de negócios, desenvolvedores, especialistas financeiros ou de vendas. Nada mais natural haja visto que as equipes estão cada vez mais multidisciplinares. Para auxiliar em uma comunicação fluida, os conceitos do DDD — Domain Driven Design, propõem como um dos seus pilares a definição de uma Linguagem Ubíqua.
O DDD formalizou essas práticas ao introduzir conceitos como Ubiquitous Language (Linguagem Ubíqua), o cerne do DDD, que promove a criação de uma linguagem compartilhada entre todas as partes interessadas, e Bounded Contexts (Contextos Limitados), que ajudam a dividir sistemas grandes e complexos em partes menores e mais compreensíveis. Além disso, o DDD trouxe atenção para padrões arquiteturais que dão suporte ao domínio, como Entidades (Entity), Agregados (), Repositórios () e Serviços de Domínio (), estabelecendo um design centrado na lógica de negócios em vez de nas tecnologias subjacentes.
Para que possamos iniciar uma compreensão acerca de Linguagem Ubíqua (Ubiquitous Language), podemos nos valer da análise semântica do termo ubíquo: u-bí-quo (latim ubiquus, -a, -um), adjetivo:
- Que está ao mesmo tempo em toda a parte. =
ONIPRESENTE - Que tem dom da ubiquidade. =
ONIPRESENTE - Que está difundido em todo o lado. =
GERAL, UNIVERSAL.
De forma conceitual, a linguagem ubíqua é o conjunto de termos e inter-relações que fornecem a semântica da comunicação do domínio, que reflete a visão do negócio. E de forma prática, ao se trabalhar com DDD, entende-se como comunicação de mesma linguagem, em um único modelo, de forma que todos os envolvidos no projeto tenham a mesma compreensão acerca dos termos utilizados. Linguagem ubíqua pode parecer um termo complexo de se compreender, mas outro termo também utilizado para identificar este tipo de comunicação, nos auxilia em uma melhor compreensão: Linguagem Onipresente.
Linguagem Onipresente é essencialmente os termos, palavras e definições utilizadas por todo o domínio do projeto. É o idioma utilizado no cotidiano da empresa, as terminologias da realidade do negócio. Quando um projeto não respeita a linguagem do domínio diversos problemas de comunicação surgem, dificultando o desenvolvimento, implantação e sustentação da solução.
Quando termos utilizados no projeto vão sendo traduzidos, de acordo com o uso em cada departamento, a comunicação se torna anêmica e a assimilação do conhecimento disperso. Em seu livro Domain Driven Design — Atacando as Complexidades no Coração do Software, Eric Evans descreve de maneira clara esta problemática:
O custo de toda a tradução, além do risco de entendimento errado, é simplesmente muito alto. Um projeto precisa de uma linguagem em comum que seja mais robusta que o mínimo denominador comum.
A Linguagem Onipresente (ubiquitous language) não está limitada a diagramas em UML (Unified Modeling Languages, ou Linguagem de Modelagem Unificada), mas principalmente, define em seu vocabulário nome das classes e operações de destaque. Inclui regras para implantar um dicionário uniforme e explícito para o modelo, para que o mesmo possa ser utilizado com o máximo de eficiência possível.
O Diagram as code (Diagrama como código) é uma abordagem de criação de diagramas que utiliza código, em vez de ferramentas gráficas, para desenhar e manter diagramas. Essa abordagem permite que os diagramas sejam criados e atualizados utilizando linguagens de programação ou marcadores, em vez de ferramentas de desenho gráfico. A prática de "Diagram as code" (Diagrama como código) pode auxiliar no Domain-Driven Design (DDD), pois é uma prática útil no DDD que ajuda a manter a documentação atualizada, facilita a colaboração, fornece controle de versão e permite a geração automática de diagramas. Vantagens do "Diagram as code" no DDD:
- Melhor documentação: Com o "Diagram as code", você pode manter a documentação do seu modelo de domínio atualizada e sincronizada com o código. Isso ajuda a garantir que a documentação seja precisa e refletida nas mudanças no código.
- Modelagem colaborativa: O "Diagram as code" permite que os membros da equipe colaborativamente trabalhem no modelo de domínio, tornando mais fácil para os desenvolvedores, especialistas em domínio e outros stakeholders discutirem e refinarem o modelo.
- Versão e controle: Com o "Diagram as code", você pode usar sistemas de controle de versão (como Git) para rastrear as alterações no modelo de domínio. Isso ajuda a garantir que todas as alterações sejam documentadas e possam ser revertidas se necessário.
- Geração automática de diagramas: Muitas ferramentas de "Diagram as code" permitem que você gere diagramas automaticamente a partir do código. Isso pode economizar tempo e reduzir a chance de erros manuais.
- Integração com o ciclo de desenvolvimento: O "Diagram as code" pode ser integrado ao ciclo de desenvolvimento de software, permitindo que os desenvolvedores trabalhem no modelo de domínio em paralelo com o desenvolvimento do código.
As ferramentas permitem que você crie diagramas como código, utilizando sintaxes específicas para desenhar os diagramas. Em seguida, elas geram imagens ou diagramas a partir do código. Algumas ferramentas populares para "Diagram as code" incluem:
- Mermaid
- PlantUML
- Graphviz
- C4 (abordagem de modelagem de software)
Normalmente os especialistas de um domínio, diretores, administradores, analistas, técnicos, possuem pouca familiaridade com o jargão técnico utilizado no desenvolvimento de software, mas utilizam os jargões próprios de sua área de atuação. A partir desta realidade, os especialistas de um domínio descrevem superficialmente o que necessitam, fazendo com que desenvolvedores criem abstrações que sustentem o design da aplicação. Com isso, uma compreensão uniforme vai se deteriorando exponencialmente.
Como solução a esta dispersão na comunicação, devemos usar a linguagem baseada no modelo de forma exaustiva até que a comunicação seja fluida e compreensível entre os diversos setores envolvidos no projeto. Para que possamos alcançar esta fluência, os especialistas do domínio devem vetar termos ou estruturas que não transmitam uma compreensão clara acerca das funcionalidades envolvidas; os desenvolvedores devem se empenhar no cuidado com ambiguidades ou inconsistências que possam corromper o modelo proposto. Ou seja, é um esforço conjunto entre todos os envolvidos, mas, é essencialmente necessário que ocorra.
Como identificar os especialistas de domínio? Especialistas de domínio são os profissionais envolvidos no dia a dia da operação, nos mais diferentes setores, ou seja, são os “conhecedores” do negócio (stakeholders). Normalmente estes especialistas são analistas, técnicos, engenheiros, podendo ser todo aquele que possui a compreensão acerca do fluxo de operação da empresa. Os especialistas de domínio detém o conhecimento sobre as necessidades e requisitos necessários para o processamento das atividades organizacionais.
Em essência, o DDD surgiu para enfrentar a complexidade inerente ao desenvolvimento de software em domínios desafiadores, permitindo que os sistemas sejam projetados de forma que o código seja uma expressão direta das regras e processos do negócio. Com o tempo, o DDD ganhou popularidade e passou a ser usado em diversos contextos, especialmente em sistemas corporativos onde o domínio de negócio é complexo e sujeito a constantes mudanças.
Para o sucesso de um projeto de software, o DDD sugere que tanto especialistas de domínio quanto desenvolvedores devem falar a mesma língua.
A figura acima, ilustra a existência de termos que só os especialistas de domínio conhecem e apresentam expressões somente de caráter tecnológico, os quais são de uso apenas do time de desenvolvimento. Contudo, é necessário que exista um conjunto de termos que devem ser de conhecimento universal, no que se refere ao domínio da aplicação, formando a Linguagem Ubíqua do sistema. A definição de uma linguagem onipresente objetiva principalmente dois propósitos:
-
Possibilitar uma comunicação fluida entre os membros de equipes multidisciplinares; Nomear elementos do código da aplicação, como classes, métodos, variáveis, funções, módulos, tabelas de bancos de dados, rotas de APIs, etc.
-
Ademais a padronização na comunicação propõe elucidar o significado dos termos, de um forma simples, objetiva e compreensível para facilitar os relacionamentos e associações entre todos módulos necessários.
Qualquer pessoa técnica contribuindo para o modelo deve programar, pelo menos tocar no código, independente do papel desempenhado no projeto. Um responsável por mudar o código deve sempre aprender a expressar o modelo através do código. Todo desenvolvedor deve estar envolvido na discussão sobre o modelo e ter contato com os especialistas do domínio. (EVANS, 2016).
Em seu livro Implementando Domain Driven Design, Vaughn Vernon, pontua que um especialista de domínio tem uma forte influência sobre a linguagem utilizada, devido ao maior conhecimento acerca do negócio, que no final é o contexto imperativo de todo projeto. Estes especialistas tendem a ser influenciados pelos padrões da indústria, contudo, uma linguagem universal deve ser centrada em como o próprio negócio pensa e opera. Ou seja, cada empresa possui seu próprio domínio acerca da execução de seus processos.
Não entenda Linguagem Ubíqua como um conjunto de jargões de negócios sendo impostos ao time de desenvolvimento, e nem mesmo uma sobreposição de termos técnicos sobre o contexto de negócio, mas sim, uma linguagem real que é criada por toda a equipe e que é propagada por toda a corporação.
Compreende-se que haverá discordâncias em relação aos termos utilizados e que estão na mente dos especialistas, mas, a partir do uso aberto da linguagem, a evolução é natural e consolidada por este processo de maturação da comunicação.
O DDD enfatiza a compreensão profunda do domínio do problema e o uso de uma linguagem ubíqua compartilhada entre as equipes de desenvolvimento e especialistas do domínio. Ele propõe a organização do código em torno do domínio do problema, separando-o dos detalhes técnicos e infraestrutura.
Embora o DDD não seja um design pattern em si, ele pode ser combinado com vários design patterns e princípios de design, como Agregado, Repositório, Especificação, Event Sourcing, entre outros. O DDD fornece diretrizes e conceitos para ajudar na criação de uma arquitetura de software robusta e flexível.
Portanto, podemos dizer que o DDD é uma abordagem de design e uma metodologia de modelagem que pode ser aplicada em diferentes arquiteturas de software, como arquitetura em camadas, arquitetura hexagonal, arquitetura de microsserviços, entre outras. Ele fornece princípios e práticas para projetar e estruturar o código em torno do domínio do problema, visando um modelo de domínio rico, desacoplamento e flexibilidade.
É uma abordagem mais ampla para o design de software que abrange vários conceitos e técnicas. DDD enfatiza a modelagem do domínio, a colaboração entre especialistas do domínio e desenvolvedores, e a criação de um código baseado em um entendimento profundo do domínio do problema.
O DDD deve ajudar na modelagem das classes mais importantes e mais centrais do sistema de forma e diminuir a complexidade e ajudar na manutenção das mesmas, afinal este é o objetivo dos princípios de orientação a objetos.
Important
Outro ponto é sobre nós desenvolvedores estarmos compartilhando dados com outros sistemas, as rotinas de integração que recebem ou disponibilizam dados para outros sistemas não devem ser "inteligentes". Muitos desenvolvedores acabam modelando suas classes de negócios tentando resolver as questões internas do sistema e, ao mesmo tempo, pensando em como essas classes serão expostas para outros sistemas. Padrões como DTO (Data Transfer Object) que usam objetos "burros" são mais adequados para isso.
Portanto, o DDD não tenta resolver todos os problemas de todas as camadas de um sistema. Seu foco é na modelagem das entidades principais de negócio usando a linguagem adequada daquele domínio para facilitar a manutenção, extensão e entendimento. Particularmente, eu não seguiria à risca o padrão, até porque existem inúmeros padrões e variações de modelagem OO. Estude os princípios por detrás desses padrões, pois eles são geralmente parecidos e veja o que funciona melhor para cada projeto.
A maioria dos softwares não quebra por causa de erros de sintaxe ou lógica if-else falha.
Ele quebra porque as equipes perdem o alinhamento com o problema de negócios que deveriam resolver. Os sistemas se emaranham com suposições técnicas que envelhecem mal. Os recursos são implementados sem considerações de design adequadas. E com o tempo, cada novo requisito cria mais problemas que continuam se acumulando.
Muitas vezes, isso não é um problema de ferramentas. É um problema de modelagem.
O DDD (Design Controlado por Domínio) tenta resolver esse problema de frente. Em sua essência, o DDD é uma maneira de projetar software que mantém o domínio de negócios, não o esquema de banco de dados ou a estrutura mais recente, no centro da tomada de decisões. Ele insiste que os engenheiros colaborem profundamente com especialistas de domínio durante o ciclo de vida do projeto, não apenas para reunir requisitos uma vez e desaparecer nos tickets do Jira. Ele fornece às equipes o vocabulário, os padrões e os limites para modelar sistemas complexos sem serem enterrados na complexidade acidental.
Claro, o DDD não é uma bala de prata. Ele não gera código e não conserta magicamente um monólito legado. Mas oferece algo mais valioso a longo prazo: clareza sobre o que o sistema deve fazer e onde pode mudar.
Essa abordagem se torna especialmente valiosa quando:
- O domínio não é trivial e continua evoluindo. Pense em finanças, saúde, logística ou mercados gigantes.
- Várias equipes estão trabalhando em partes sobrepostas do sistema.
- O código precisa refletir o comportamento do mundo real, não construções técnicas abstratas.
O DDD não se importa se a arquitetura é monolítica ou baseada em microsserviços. O que importa é se o modelo reflete as regras e a linguagem do mundo real do domínio e se esse modelo pode evoluir com segurança à medida que o domínio muda.
Exploramos as ideias centrais do DDD (como Contextos Limitados, Agregados e Linguagem Ubíqua) e explicamos como eles funcionam juntos na prática. Também veremos como o DDD se encaixa nos sistemas do mundo real, onde ele brilha e onde pode falhar.
Essa divisão natural acaba sendo organizada em três grandes agrupamentos: uma camada de domínio, uma camada de aplicação e uma camada de infraestrutura. Mas, novamente, isso não é um dogma do DDD, é apenas a forma como as ideias do DDD funcionam melhor em arquiteturas limpas e separadas:
![]() |
![]() |
![]() |
No contexto do DDD, existem design patterns específicos que são frequentemente utilizados para ajudar a implementar os conceitos e princípios do DDD. Alguns desses padrões incluem:
-
Agregado: Se refere a um padrão de design que agrupa um conjunto de objetos relacionados em uma única unidade coesa. O Agregado é uma das principais construções utilizadas para modelar e organizar o domínio em DDD;
-
Repositório: Fornece uma interface para acessar coleções de objetos agregados, permitindo que o domínio permaneça livre de preocupações com persistência. Ele atua como uma camada intermediária entre o domínio e a fonte de dados (como bancos de dados).
-
Serviço de Domínio: Representa operações ou ações do domínio que não pertencem naturalmente a uma única entidade ou value object. Encapsula lógica de negócio que depende de múltiplos objetos.
-
Value Object: Objetos que não possuem identidade própria e são definidos apenas por seus atributos. São imutáveis e usados para representar conceitos como dinheiro, coordenadas ou medidas.
-
Entidade: Objetos do domínio que possuem identidade própria (geralmente um ID) e um ciclo de vida distinto. Diferente de value objects, entidades podem mudar seus atributos ao longo do tempo.
-
Factory: Padrão responsável por encapsular a lógica de criação complexa de objetos, especialmente agregados. Evita a poluição do construtor com lógica de montagem de objetos.
-
Especificação: Define regras de negócio reutilizáveis e combináveis para verificar se um objeto atende a determinados critérios. É útil para separação de responsabilidades e clareza das regras de domínio.
-
Event Sourcing: Técnica onde o estado do sistema é determinado por uma sequência de eventos (ao invés de snapshots de dados). Permite reconstruir o estado do sistema e ter um histórico detalhado das mudanças.
-
Injeção de Dependência (DI - Dependency Injection): Técnica que permite desacoplar componentes do sistema, facilitando testes, manutenção e extensibilidade. No DDD, é comum para injetar repositórios, serviços de domínio e unidades de trabalho nos agregados e serviços de aplicação.
Esses padrões, juntamente com outros conceitos e técnicas, podem ser aplicados para construir uma arquitetura que segue os princípios do DDD. O DDD, portanto, não é um design pattern em si, mas uma abordagem que pode ser implementada usando diversos padrões de design específicos.
O coração é sempre o domínio: entidades, objetos-valor, agregados, repositórios como contratos e os serviços de domínio quando algo não cabe numa entidade específica. Aqui não existe conhecimento técnico como banco de dados, HTTP, mensageria ou UI. É o código que sobrevive quando se troca tudo ao redor. É onde o vocabulário ubíquo vive, onde o modelo mental da empresa vira código executável. É a camada mais estável e a mais valiosa de todas:
Quando você fala dessas “camadas do domínio”: entidades, objetos-valor, agregados, repositórios e serviços de domínio, na verdade você está descrevendo os blocos estruturais internos do próprio Domain Model, isto é, os elementos que compõem o núcleo do DDD. Eles não são camadas no sentido arquitetural, mas sim componentes conceituais do modelo, cada um representando um papel distinto dentro da lógica de negócio. E essa distinção é crucial, porque o domínio só fica expressivo e coerente quando cada peça é usada para aquilo que foi criada.
O design orientado por domínio reconhece múltiplos tipos de modelos. Por exemplo, uma entidade é um objeto definido não por seus atributos, mas por sua identidade. Por exemplo, a maioria das companhias aéreas atribui um número único aos assentos de cada voo: essa é a identidade do assento. Em contraste, um objeto valor é um objeto imutável que contém atributos, mas não possui identidade conceitual. Quando as pessoas trocam cartões de visita, por exemplo, elas se importam apenas com as informações do cartão (seus atributos), em vez de tentar distinguir entre cada cartão único.
Dentro do domínio, as entidades representam conceitos que têm identidade persistente ao longo do tempo, mesmo que seus atributos mudem. Elas capturam comportamentos essenciais ligados a um identificador único, como um Cliente, um Pedido ou um Veículo.
Já os objetos-valor são as estruturas que descrevem características imutáveis, conceituais, que não têm identidade própria, mas têm significado. Eles existem para impedir que o domínio fique cheio de tipos primitivos sem sentido, substituindo-os por tipos ricos, como Email, CPF, Placa, Endereço ou Dinheiro. Essa relação entre entidades e objetos-valor sustenta a expressividade do vocabulário ubíquo dentro do código.
Modelos também podem definir eventos (algo que aconteceu no passado). Um evento de domínio é um evento pelo qual especialistas de domínio se importam. Modelos podem ser ligados por uma entidade raiz para se tornarem um agregado. Objetos fora do agregado podem conter referências à raiz, mas não a qualquer outro objeto do agregado. A raiz agregada verifica a consistência das mudanças no agregado. Os motoristas, por exemplo, não precisam controlar individualmente cada roda de um carro: eles simplesmente dirigem o carro. Nesse contexto, um carro é um conjunto de vários outros objetos (o motor, os freios, os faróis, etc.).
Quando o sistema cresce e você percebe que certas entidades começam a formar agrupamentos naturais de regras e invariantes, entra o conceito de agregado. Ele funciona como um limite de consistência: um conjunto de entidades e objetos-valor que sempre deve ser manipulado de forma coerente. O agregado garante que você não mexa em partes internas que não deveriam ser alteradas isoladamente e que toda modificação passe por uma única entidade-raiz. Essa raiz é quem expõe métodos públicos e controla as regras internas do agregado, protegendo sua integridade lógica.
No design orientado por domínio, a criação de um objeto frequentemente é separada do próprio objeto.
Um repositório, por exemplo, é um objeto com métodos para recuperar objetos de domínio de um armazenamento de dados (por exemplo, um banco de dados). De forma semelhante, uma fábrica é um objeto com métodos para criar diretamente objetos de domínio. Já os repositórios são contratos dentro do domínio — e isso é importante: contratos, não implementações. Eles definem as operações necessárias para persistir e recuperar agregados, mas não sabem nada sobre banco de dados, ORM, SQL ou infraestrutura. Eles expressam apenas a intenção: “preciso obter esse agregado”, “preciso persistir esse agregado”. A tecnologia que faz isso acontecer vive fora do domínio. O repositório é o ponto de contato mais importante entre o núcleo do domínio e o resto da aplicação, porque permite que o domínio permaneça independente e limpo.
Por fim, os serviços de domínio (Domain service) são como espaços auxiliares dentro do modelo. Eles só existem quando uma regra de negócio não pertence naturalmente a nenhuma entidade ou objeto-valor. São operações puramente de domínio, mas sem estado próprio, que coordenam comportamentos entre objetos.
Muitas vezes representam cálculos, validações complexas, políticas ou decisões de negócio que não cabem dentro de um único agregado. O fato de eles existirem mostra maturidade na modelagem, porque impede que entidades se tornem “deuses” com responsabilidades demais.
Esse diagrama junta vários conceitos de DDD, microsserviços, Domain Services, CQRS e Event-Driven Architecture, e por isso parece mais abstrato do que realmente é.
O contextual service não tem relação com application context no sentido técnico de frameworks, mas sim com o contexto de domínio definido pelo DDD. Ele representa um serviço que atua dentro do bounded context, carregando as regras de negócio específicas daquele domínio. É um tipo de componente que não é simplesmente uma service class de aplicação, mas uma unidade que encapsula conhecimento do domínio e age conforme as regras internas do contexto. Ele existe para isolar o significado, o vocabulário e as invariantes daquele pedaço do sistema, de modo que aquilo que faz sentido dentro daquele contexto não vaze para outro. Ou seja, é um serviço que só funciona dentro do contexto semântico onde ele pertence e é por isso que ele é “contextual”, porque depende do significado local daquele domínio, e não de uma API genérica ou do framework.
Já o managed state transition event aparece quando o domínio possui algum tipo de estado que muda com o tempo e essas mudanças têm significado de negócio. Quando um agregado ou entidade passa de um estado para outro, essa transição pode gerar um evento que registra essa mudança para que outras partes do sistema possam reagir. O termo “managed” indica que esse estado é controlado e validado pelo próprio domínio, obedecendo invariantes, regras e consistência interna. Portanto, esse tipo de evento é originado quando o domínio aceita, valida e confirma uma mudança de estado, e então emite um evento que pode acionar comportamento assíncrono, projeções, atualizações ou integrações. Não é apenas um evento técnico, mas sim a expressão de uma transformação relevante dentro da lógica da operação.
O business event, por sua vez, é o evento de mais alto nível, aquele que traduz uma ocorrência de valor para o negócio. É diferente do managed state transition event porque ele não diz apenas que um agregado mudou seu estado interno, mas que algo significativo aconteceu dentro da empresa, algo que outros serviços, contextos e até sistemas externos precisam saber. Esse tipo de evento é emitido quando o domínio reconhece que ocorreu um fato com relevância semântica, como “Pagamento Confirmado”, “Pedido Cancelado”, “Cliente Registrado”. Ele se torna um ponto de integração entre bounded contexts e é geralmente publicado em sistemas de mensageria ou barramento de eventos, servindo de gatilho para fluxos, reações automáticas ou pipelines de consumo. Em termos de EDA e CQRS, é o que permite que projeções atualizem read models, ou que outros microserviços sincronizem decisões e comportamentos sem acoplamento direto.
Assim, o diagrama articula três camadas de significado. O contextual service executa lógica de domínio situada no contexto adequado. O managed state transition event registra a mudança interna e valida dentro do domínio. E o business event expõe para o ecossistema aquele fato relevante que deve ser consumido por outros componentes, dando vida à característica distribuída de microservices com DDD e CQRS.
Quando parte da funcionalidade de um programa não pertence conceitualmente a nenhum objeto, ela normalmente é expressa como um serviço.
Existem diferentes tipos de eventos no DDD, e as opiniões sobre sua classificação podem variar. Segundo Yan Cui, existem duas categorias-chave de eventos:
-
Eventos de domínio significam ocorrências importantes dentro de um domínio de negócios específico. Esses eventos são restritos a um contexto limitado e são vitais para preservar a lógica de negócios. Normalmente, eventos de domínio têm cargas úteis mais leves, contendo apenas as informações necessárias para o processamento. Isso ocorre porque os ouvintes de eventos geralmente estão dentro do mesmo serviço, onde seus requisitos são mais claramente compreendidos.
-
Por outro lado, eventos de integração servem para comunicar mudanças entre diferentes contextos limitados. Eles são cruciais para garantir a consistência dos dados em todo o sistema. Eventos de integração tendem a ter cargas úteis mais complexas com atributos adicionais, pois as necessidades dos ouvintes potenciais podem variar significativamente. Isso frequentemente leva a uma abordagem mais completa da comunicação, resultando em excesso de comunicação para garantir que todas as informações relevantes sejam compartilhadas de forma eficaz.
O que você chamou de “camadas” é, portanto, o conjunto de padrões que estruturam o modelo de domínio internamente. Eles servem para que o núcleo do sistema represente a realidade de negócio de forma organizada, expressiva e coerente. Cada um desempenha um papel específico e todos se combinam para formar o coração do DDD. Esses elementos não vivem separados por fronteiras técnicas — eles convivem dentro do mesmo contexto, mas com funções bem definidas. E é exatamente essa organização interna que permite ao domínio permanecer sólido mesmo quando todo o resto do sistema muda.
Ao redor do domínio está a camada de aplicação, que não contém regras de negócio, mas coordena casos de uso. Ela funciona como um orquestrador, chamando o domínio e o que está fora dele para cumprir um fluxo. Esse nível também permanece relativamente estável, mas já conhece elementos mais concretos, como serviços de email, notificações ou repositórios que serão implementados no mundo técnico. Essa camada é a ponte entre a intenção de negócio e a execução tecnológica.
Por fim, a infraestrutura é a camada que toca na realidade: banco de dados, HTTP, mensageria, arquivos, serviços externos, ORM, drivers, tudo que é acoplado à tecnologia. Aqui ficam as implementações concretas dos contratos definidos nas camadas superiores. A infraestrutura depende do domínio, mas o domínio nunca depende dela — essa é a inversão fundamental que mantém a modelagem limpa. Quando se aplica arquitetura hexagonal ou Onion Architecture, o domínio fica no centro e a infraestrutura fica nas bordas, o que reforça a mesma ideia.
Então, quando se fala que DDD tem camadas, a verdade é que ele inspira a criação de camadas porque a modelagem orientada ao domínio exige separação clara entre regras de negócio e tecnologia. É por isso que tantos times que aplicam DDD acabam automaticamente usando Clean Architecture, Hexagonal Architecture ou Onion Architecture: essas estruturas preservam o modelo de domínio e permitem que ele evolua sem ser destruído por detalhes técnicos.
DDD não existe sem essa separação. Ele até poderia ser aplicado em uma bagunça de código monolítico distribuído, mas não funcionaria. O poder do DDD acontece justamente quando o domínio é posto no centro e protegido por camadas que impedem que a tecnologia engula as regras de negócio. Essa divisão natural acaba sendo percebida como “camadas do DDD”, apesar de tecnicamente elas pertencerem mais a estilos arquiteturais compatíveis com DDD do que ao DDD em si.
O AOP - Aspect-Oriented Programming pode fortalecer muito as boas práticas de DDD, TDD e BDD, e essa conexão faz bastante sentido quando entendemos o papel de cada um desses paradigmas dentro de uma arquitetura limpa e organizada. A orientação a aspectos não substitui nenhum deles, mas funciona como uma espécie de tecido estrutural que mantém o código limpo, modular e fiel aos princípios que essas abordagens defendem.
O DDD se beneficia especialmente porque a regra número um do design orientado ao domínio é manter o domínio puro, expressivo e livre de detalhes técnicos acoplados. O AOP ajuda justamente a retirar do domínio tudo aquilo que não é domínio: logs, transações, auditorias, repetição de validações estruturais, políticas de segurança, métricas e qualquer outra funcionalidade transversal. Ao deslocar essas responsabilidades para aspectos, o modelo de domínio permanece limpo e orientado exclusivamente à lógica do negócio, sem anotações excessivas, sem serviços utilitários misturados e sem ruídos que atrapalhem a clareza conceitual do código. Isso deixa o domínio mais próximo da linguagem ubíqua, mais fácil de evoluir e mais coerente com o propósito principal do DDD, que é representar conhecimento e regras do negócio de forma elegante e sustentável.
No caso do TDD, o AOP traz uma contribuição igualmente importante. Quando usamos TDD, queremos testar a lógica de forma isolada, sem que camadas externas interfiram no comportamento esperado. Se o código estiver poluído com logs, tratamentos repetitivos ou preocupações técnicas envolvendo transações ou autenticação, o ato de testar se torna mais difícil, mais lento e mais sujeito a falhas colaterais. O AOP limpa esse cenário ao remover grande parte desse ruído estrutural, permitindo que você escreva testes focados apenas na lógica essencial. Além disso, como os aspectos podem ser desativados ou simulados durante os testes, você consegue um ambiente de testes mais controlado, determinístico e alinhado com o espírito do TDD, que exige ciclos rápidos, previsíveis e com feedback imediato sobre a execução da regra de negócio.
Sobre o BDD, o AOP também se encaixa de maneira natural, porque o BDD exige clareza comportamental e foco na história do usuário ou no comportamento esperado de uma funcionalidade. Quando você descreve um comportamento no formato Given-When-Then, espera que o código reflita essa lógica de forma limpa, sem camadas técnicas misturadas. O AOP impede que esses comportamentos de alto nível fiquem escondidos ou embrulhados por detalhes de infraestrutura, preservando a naturalidade da leitura tanto no código quanto nos testes comportamentais. Isso torna os cenários mais fiéis à linguagem de negócio, diminui ruídos e deixa as tratativas técnicas centralizadas fora do fluxo principal. Em outras palavras, o BDD ganha em clareza, o TDD ganha em eficácia e o DDD ganha em pureza — tudo porque o AOP protege a regra de negócio de interferências externas.
Assim, quando você coloca tudo isso junto, percebe que AOP não é um acessório técnico, mas uma ferramenta arquitetural que sustenta a separação de responsabilidades, reduz acoplamento e melhora a manutenção do sistema como um todo. Ele permite que o domínio seja domínio, que os testes sejam testes e que o comportamento da aplicação seja descrito de maneira simples e coerente. O AOP acaba se tornando um verdadeiro aliado para sistemas que queiram adotar DDD, TDD e BDD de forma madura, escalável e profissional, principalmente em ambientes como o ecossistema Java/Kotlin, onde essa abordagem é amplamente consolidada através de frameworks como o Spring.
![]() |
![]() |
DDD - Segregação de Responsabilidade de Consulta de Comando (CQRS) os comandos são complexos, as consultas são simples, anteriormente, examinamos as Entidades DDD, que têm estado, e Eventos, onde o estado muda. Para reduzir a complexidade, podemos ser específicos sobre o que tem estado e encapsular onde ele muda. Os eventos são códigos de alto nível, situados no meio da Onion Architecture. Veremos como os comandos de nível inferior vinculam a interface do usuário ou a API a eventos para permitir que os usuários alterem o estado.
![]() |
![]() |
Os eventos de domínio existem no centro de domínio de alto nível de um diagrama Onion Architecture, coberto por Robert C. Martin em Clean Architecture. A camada externa da cebola contém detalhes de baixo nível, entradas e saídas, como armazenamento, interfaces de usuário e APIs. Os comandos estão em algum lugar no meio, vinculando as entradas ao domínio de nível superior para que os usuários possam acionar os eventos para alterar o estado.
Para que um usuário interaja com um sistema e para que ele seja útil, precisamos ser capazes de fazer duas coisas. A primeira é exibir informações para o usuário.
O Clean Architecture (CA) é a diretriz de arquitetura de sistemas proposta por Robert C. Martin (Uncle Bob) derivada de muitas diretrizes arquitetônicas como Hexagonal Architecture e Onion Architecture, entre outras. Eric Evans introduziu o conceito de Domain-Driven Design (DDD). Ele escreveu sobre isso em seu livro Domain-driven Design em 2004 (também conhecido como "The Big Blue Book"). O Design Orientado a Domínio é uma abordagem para o desenvolvimento de software que centra o desenvolvimento na programação de um modelo de domínio com uma rica compreensão dos processos e regras de um domínio.
| Hexagonal Architecture + DDD | Hexagonal Architecture + Clean Architecture + DDD |
![]() |
![]() |
Comecei com minha equipe a adotar o Domain-Driven Design (DDD) em nosso trabalho, e nossa missão era tirar o máximo proveito do DDD e do CA, se possível. À medida que a adoção cresce, estamos cada vez mais perto do negócio e do domínio que estamos abordando; começamos a falar a linguagem onipresente do domínio.
![]() |
![]() |
Note
Não existe tal arquitetura que sirva para todos. Toda arquitetura ou padrão de desenvolvimento de software tem prós e contras; Baseie sua decisão no projeto, no escopo e na equipe. Vamos descrever o caminho que tomamos.
Nos concentraremos em como estruturamos o código de acordo com DDD e Clean Architecture, para que o código também fale a linguagem onipresente do domínio com mais facilidade:
Primeiro, dividimos o sistema em partes independentes menores em torno dos subdomínios de negócios por meio de algumas iterações (ferramentas estratégicas de DDD, como tempestade de eventos, narrativa e muito mais). Idealmente, essas partes devem ser implantáveis de forma independente (microsserviços), mas nem sempre é esse o caso. Muitas vezes, podemos ter um código legado que não podemos alterar facilmente, então temos que mantê-lo por um tempo. Nesses casos, temos esses subdomínios em um único projeto (monólito), com cada subdomínio em uma pasta ou pacote separado.
Estrutura de código de contexto limitado (Bounded Contexts): Depois de dividir o domínio extenso em partes menores, também chamadas de subdomínios. Em seguida, tentamos resolver cada subdomínio; Um "contexto limitado" implementará um subdomínio. Cada contexto limitado pode ser um microsserviço separado ou um pacote separado que encapsula esse contexto limitado dentro de um serviço atual. Então, vamos falar sobre essa parte agora, como projetamos cada contexto limitado, quantas camadas de alto nível temos e como elas se comunicariam juntas.
Exemplos de contextos limitados em um sistema de comércio eletrônico
├───wallet
├───orderManagement
├───shipping
├───..
Comando vs. Consulta (CQRS): A primeira camada que temos em cada contexto limitado são comandos e consultas. O que eles significam?
Todo caso de uso em um sistema pode ser considerado um Comando ou Consulta, onde um Comando é qualquer caso de uso que altera o estado atual do sistema, enquanto uma Consulta é qualquer caso de uso que busca o estado atual SEM mudar o estado atual. Como esses dois têm preocupações diferentes, decidimos usar o padrão CQRS (Command Query Responsibility Segregation)
Então, temos pastas de nível muito alto; Cada uma contém o restante das camadas, que discutiremos mais adiante nesta página para isolar essas duas preocupações.
├───shipping
| ├───commands
| ├───queriesCamadas de Arquitetura: Ter camadas claras em nosso código onde cada camada tem responsabilidade clara torna adequado para nós identificar a direção da dependência, testar facilmente o código, trabalhar em paralelo sem esperar uns pelos outros, e muito mais.
Concordamos em ter as seguintes camadas
- Camada de domínio
- Camada de Aplicação
- Camada de Infraestrutura
├───shipping
├───commands
├───application
├───domain
├───infrastructure
├───queries
├───application
├───infrastructureVocê provavelmente percebeu que a subpasta de consultas não tem camada de domínio! A seção a seguir explicará os motivos por isso.
Camada de domínio: A camada de domínio é a parte central de um contexto limitado, contendo o domínio central e todos os invariantes de negócios e a lógica para esse contexto limitado. Não deve depender de nenhuma camada ou biblioteca ou framework de terceiros. Em vez disso, todas as camadas dependem disso. O conteúdo típico nessa camada é o seguinte.
├───shipping
├───commands
├───domain
├───models
├───Shipment
├───Order
├───ShippingCompany
├───OrderStatus
├───Receiver
├───services
├───ShippingService
├───events
├───ShipmentCreated
├───ShipmentDelievered
├───contracts
├───ShipmentRepo
├───OrderRepo
├───ShippingCompanyProviderComponentes do Domínio: Resumindo, temos modelos de domínio, serviços de domínio, eventos e contratos. Existem principalmente três tipos de modelos de domínio que envolvem a lógica de negócios:
- AggregateRoot,
- Entidade, e
- Objeto de valor.
E qualquer código que não se encaixe em nenhum desses modelos deve ir para um serviço de domínio. Também temos os eventos produzidos pelo AggregateRoots sempre que algo muda no modelo. E, por fim, os contratos/interfaces para qualquer domínio de implementação de infraestrutura que possam precisar.
Camada de aplicação: Essa camada fina atua como uma API que expõe as funcionalidades do contexto limitado por meio de casos de uso.
É o cliente direto do modelo de domínio, responsável pela coordenação de tarefas (orquestração) dos fluxos de casos de uso. Além disso, ao usar um banco de dados ACID, a camada de aplicação controla as transações, garantindo que a aplicação persista atômicamente as transições de estado do modelo.
Note
Nota: É um erro considerar o caso de uso da Aplicação igual ao dos Serviços de Domínio. Eles não são. O contraste deve ser marcante. Devemos nos esforçar para colocar toda a lógica de domínio de negócios no modelo de domínio, seja em Agregados, Objetos de Valor ou Serviços de Domínio. Mantenha os Serviços de Aplicação enxutos, usando-os apenas para coordenar tarefas no modelo. (Vaughn Vernon)
Uma camada típica de aplicação consiste em duas pastas, conforme segue:
├───shipping
| ├───commands
| ├───application
| ├───usecases
| ├───CreateShipment
| ├───OrderConfirmedEventHandler
| ├───models
| ├───CreateShipmentRequest
| ├───OrderConfirmedEvent
| ├───queries
| ├───application
| ├───usecases
| ├───ListShipments
| ├───models
| ├───ListShipmentsQueryImplementamos uma classe separada por caso de uso; Cada classe contém um único método chamado executar algo como padrão de comando
Existem três tipos de casos de uso:
- Solicitar para fazer algo (
CriarEnvio,AtualizarStatusEnvio) - Consultar algo (
GetShipments) - Manipulador de Eventos (
OrderReceivedEventHandler)
Camada de infraestrutura: O trabalho da infraestrutura é fornecer capacidades técnicas para outras partes da nossa aplicação. Ele contém qualquer implementação de banco de dados, IOs ou rede, como MongoDB, Postgres, serviços analíticos, controladores, acesso ao sistema de arquivos ou cache de memória.
É útil manter uma mentalidade de Princípio da Inversão de Dependência. Então, onde quer que as camadas de aplicação ou domínio precisem de detalhes de infraestrutura, dependemos de interfaces. Então, quando um caso de uso de aplicação consulta um repositório, ele dependerá apenas da interface do modelo de domínio, mas usando a implementação da infraestrutura.
Um diagrama simples para ilustrar como isso funciona é o seguinte.
O Serviço de Aplicação depende da interface do Repositório do modelo de domínio, mas utiliza a classe de implementação da infraestrutura. Os pacotes abrangem amplas responsabilidades.
├───shipping
| ├───commands
| ├───infrastructure
| ├───controllers
| ├───services
| ├───repositories
| ├───..
Repositórios: Repositórios são classes ou componentes que encapsulam a lógica necessária para acessar fontes de dados. Eles centralizam funcionalidades comuns de acesso a dados, proporcionando melhor manutenção e desacoplando a infraestrutura ou tecnologia usada para acessar bancos de dados a partir da camada do modelo de domínio.
Para cada agregado ou raiz agregada, você deve criar uma classe de repositório.
Criamos uma classe de repositório para cada agregado ou raiz agregada, já que a raiz agregada não permite acesso direto às suas entidades filhas para impor variantes agregadas. Precisamos puxar todo o agregado do banco de dados, realizar ações e salvá-lo.
Dito isso, tenha cuidado ao projetar seu agregado para evitar penalidades de desempenho. Por exemplo, não projete uma raiz agregada contendo uma lista de entidades que aumenta ao longo do tempo. Você precisará extrair uma parte significativa dos dados toda vez que operar com esse agregado. Como resultado, você pode acabar com uma operação muito lenta e, pior ainda, OutOfMemory!.
Algo como uma raiz agregada de carteira com uma lista de transações é um exemplo de um design ruim desse tipo; A transação de uma carteira aumenta com o tempo. Existem múltiplas soluções para esses casos; Uma delas é usar event sourcing.
O último ponto a mencionar aqui é que o repositório esconde diferentes fontes de dados usadas da camada de domínio para que possa usar MongoDB e Redis sem alterações na interface.
Controladores:
- Nos esforçamos para ter um único controlador por API. Por exemplo,
GetUserControllereSaveUserController. Um controlador único por API mantém os controladores menores e diretos ao ponto. - Um controlador recebe a solicitação, a mapeia para o modelo de Aplicação, chama o caso de uso apropriado da aplicação e mapeia o resultado para a entidade de resposta desejada.
Como é um fluxo completo? Os dois diagramas a seguir explicam a implementação de cada fluxo típico.
| Fluxo Típico de Comando: | Fluxo típico de consulta: |
![]() |
![]() |
Novamente, não existe uma arquitetura que sirva para todos. Toda arquitetura ou padrão de desenvolvimento de software tem prós e contras; Baseie sua decisão no projeto, no escopo e na equipe.
Padrões de Arquitetura de Integração Empresarial - Redações sobre arquitetura
A perspectiva: Sistemas de TI interagem e, portanto, se integram pelos mesmos motivos que as pessoas.
- Quando precisamos de informações de outras pessoas para o nosso trabalho
- Quando precisamos de alguém para fazer algo por nós
- Quando queremos socializar com alguém
Os exemplos correspondentes de TI são uma chamada de API para uma consulta, uma operação e um cheeping de keep-alive/checagem de saúde (com IA, pode surgir uma verdadeira socialização entre sistemas de TI). Eles também são os tipos de interação mais comuns para os menos comuns.
A empresa se preocupa com a interação entre sistemas tecnológicos e os humanos envolvidos nos processos de negócios. Estes últimos podem ser clientes, funcionários e parceiros. Nem tudo pode ser automatizado, e há humanos nas extremidades ou dentro de sequências de eventos, e precisamos torná-los igualmente parte da solução.
A integração de sistemas é cara e frequentemente o ponto de falha. Assim como nas pessoas, os seguintes princípios tornam a interação entre os sistemas rápida, eficiente e espaçosa.
- A especialização de responsabilidades (coesão)
- A falta de importância de saber como algo funciona ou fornece informação (acoplamento frouxo)
- Uma comunalidade de linguagem (protocolos padronizados)
Como pensar em integração: Integração não é apenas HTTP, REST e algum FTP ou SQLNet antigo em segundo plano. É uma área arquitetônica e disciplina interessante e complexa. Não existe uma única forma de encarar isso. Eu vejo isso sob seis pontos de vista que nos permitem criar requisitos sólidos de arquitetura de integração, declarações de problemas e soluções.
Esses pontos de vista de integração, dos mais amplos aos mais detalhados, são
Escopo da integração empresarial → Hierarquia de padrões de integração empresarial → Padrões de integração de aplicações → Integração horizontal das camadas dos sistemas → integração sem estado versus com estado → Segurança da integração
(Usaremos terminologia arquitetonicamente relevante do Design Orientado por Domínio, especialmente os termos específicos de DDD para camadas — UI, Aplicação, Domínio e Infraestrutura. Outros termos DDD estão em itálico. Por favor, consulte meus artigos sobre Arquitetura Orientada por Domínio Parte I e II.)
Uma vez que você entenda a integração sob os pontos de vista abaixo, poderá definir e resolver problemas de integração de forma holística, eficiente e eficaz.
Ponto de Vista 1: O escopo da integração empresarial: É útil começar com uma visão de onde as interações estão acontecendo, pois isso influencia sua natureza e soluções.
Intra-Domínio: Integrações dentro de um domínio de negócios são as mais comuns. Eles conectam sistemas relacionados com arquitetura e modelos de design semelhantes (assumindo o Domain Driven Design). Isso permite mapas de contexto mais simples entre contextos limitados e integrações mais diretas, com menos preocupações de capacidade e escalabilidade: por exemplo, integração entre um sistema de Gestão de Estoque e um Sistema de Compras.
Interdomínio: As integrações entre domínios de negócio são menores, mas apresentam mais desafios. A Linguagem Ubíqua provavelmente diferirá em cada domínio, e a carga útil de dados e os objetos de resposta frequentemente precisam ser transformados nas interações. A infraestrutura também pode ser separada um pouco ou muito, introduzindo a necessidade de várias funções de suporte (veja ponto de vista #2 abaixo) para uma integração bem-sucedida, por exemplo, integração entre um Sistema de Gerenciamento de Pedidos no domínio de vendas com um Sistema de Gerenciamento de Relacionamento com o Cliente no domínio de atendimento ao cliente.
Inter-Empresas: A integração entre empresas parceiras em cenários B2B e B2B2C está aumentando exponencialmente com os serviços modernos de web e digitais. Felizmente, os padrões de integração de serviços e serviços em nuvem acompanharam as arquiteturas emergentes da web, mobile e digital, atendendo às necessidades de serviços integrados confiáveis, rápidos e rápidos em escala, seguros e ágeis, desenvolvendo serviços integrados que abrangem de duas a dezenas de empresas em ecossistemas como eCommerce, logística, viagens, automotivo, manufatura, companhia aérea, bancos e serviços financeiros.
A imagem abaixo ilustra esse ponto de vista:
Diretrizes: Considere cada tipo de integração acima separadamente por suas características e os cinco pontos de vista abaixo para personalizar as arquiteturas de integração.
Ponto de Vista 2: Hierarquia dos padrões de integração empresarial - Hierarquia de funções de negócio
BPM — A gestão de processos de negócios coordena pessoas, sistemas, informações e coisas para produzir resultados de negócios para os domínios e subdomínios da empresa. Exemplos são design de produto, fabricação, marketing, aquisição de clientes, vendas, etc. A arquitetura BPM automatiza a integração dinâmica dos processos de negócios na máxima medida possível para fornecer resultados bem definidos, repetíveis, eficientes e confiáveis.
Fluxo de trabalho — Um fluxo de trabalho é uma sequência definida de tarefas realizadas por sistemas e humanos trabalhando juntos para entregar uma tarefa de negócio. Pode ser considerado um subconjunto de um processo de negócios. Um exemplo é o fluxo de aprovação de um empréstimo empresarial. A arquitetura de workflow integra sistemas de TI e humanos para a iniciação, roteamento, suporte analítico à decisão, etapas manuais, tarefas automatizadas, coordenação e monitoramento dos fluxos de trabalho.
Integração de sistemas — Em um cenário de TI bem projetado, a funcionalidade é dividida em sistemas coesos que mapeiam capacidades de domínio e subdomínio de negócio. Esses sistemas devem trabalhar juntos para entregar resultados úteis para fluxos de trabalho e processos de negócios. A integração de sistemas automatiza a cooperação dinâmica deles. (Leia este artigo para saber mais sobre os tipos de sistemas que precisam ser integrados → Uma Abstração Prática de Sistemas de TI Funcionais.)
Integração de componentes — Todos os sistemas de TI são segregados internamente em componentes e subcomponentes coesos e fracamente acoplados. Esses são integrados usando padrões padrão (veja ponto de vista #4 abaixo) para entregar as funções externamente úteis do sistema.
A imagem abaixo ilustra esse enquadramento:
Hierarquia técnica de funções: A integração é cara e deve ser o mais simples possível. O quão difícil e propenso a problemas será depende da maturidade dos sistemas de TI (ou seja, aplicações). Quando sistemas/serviços cliente e servidor se comunicam como parte de um processo de negócio, algumas ou todas as seguintes funções podem ser necessárias.
- Retentando — o servidor não responde na primeira tentativa
- Mensagens — o servidor não pode ser contatado diretamente
- Gerenciamento de carga (fila, balanceamento, pooling de conexão) — o servidor pode responder a um número limitado de solicitações por unidade de tempo
- Publicar-assinar — o servidor possui informações que um ou mais consumidores podem obter enquanto estão atualizadas
- Transformação de objetos — o cliente e o servidor trabalham com pacotes de dados suficientes, porém diferentes.
- Transformação de protocolo — cliente e servidor não usam o mesmo protocolo de comunicação
- Conversão sincronizada — o cliente espera uma resposta online enquanto o servidor é projetado para uma resposta offline ou espera uma resposta offline enquanto o servidor é projetado para uma resposta online.
- Fusão de funções ou dados — o cliente deve ser atendido a partir de uma combinação de mais de uma operação ou repositório de informações.
- Roteamento — o servidor correto deve ser alcançado com base em regras
- Orquestração — uma sequência de operações deve ser realizada para responder ao cliente, e a lógica está em um único lugar. (Coreografia é um conceito relacionado, onde a lógica de ação e sequência são distribuídas nos serviços participantes; no entanto, sua capacidade funcional é limitada, e geralmente há um orquestrador centralizado de algum tipo, mesmo que seja apenas um conjunto de regras de referência passivo.)
- Gerenciamento de versões — diferentes clientes precisam de versões diferentes da mesma operação
- Segurança (veja ponto de vista #6 abaixo) — o cliente e o servidor precisam ser protegidos de uma ou mais maneiras
Quanto mais dessas necessidades os sistemas cliente e servidor atendem internamente, menos o arquiteto de integração precisa fornecê-las externamente. Em outras palavras, a arquitetura da aplicação e da informação influenciam significativamente a complexidade e o custo da arquitetura de integração para construir e operar.
Normalmente, haverá algumas funções de integração externas que precisamos atender. Mas quanto mais direta e simples a integração, melhor.
Padrões e plataformas de soluções de integração intermediária: Os padrões de solução de integração externa são os seguintes, em ordem crescente de escopo. Tecnicamente, eles não são padrões arquitetônicos, e alguns se sobrepõem funcionalmente. Então, compreenda as diferenças deles e use os termos com cuidado.
-
RPC — uma chamada de procedimento remoto (RPC) é quando um programa de computador faz com que um procedimento (sub-rotina) seja executado em um espaço de endereçamento diferente (comumente em outro computador em uma rede compartilhada), codificado como se fosse uma chamada de procedimento comum (local), para simplificar a programação e não precisar lidar com detalhes de rede, protocolo e sistema operacional, etc.
-
Integração intra-app — interações entre as camadas de UI, aplicação, domínio e repositório de um sistema (termos DDD; ou UI, lógica de negócio e camadas de banco de dados em terminologia mais antiga), por exemplo, entre a interface e a camada de lógica de negócios (usando protocolos HTTP(s)) e entre a lógica de negócio e a camada de banco de dados (usando TNS/TTC sobre protocolos TCP/IP para o Oracle DB).
-
API de Leitura (DAL) — uma API de leitura ou Camada de Acesso a Dados é uma camada comum de integração em Fontes de Dados Operacionais (ODS), Bancos de Dados de Sistemas de Informação de Gestão (MIS DBs) e camadas de negócios de BI acessadas por transações de leitura OLTP e OLAP.
-
API — Uma Interface de Programação de Aplicações é uma interface de software para comunicação entre sistemas de TI. Uma API geralmente suporta múltiplas operações. O projeto é guiado pela padronização das assinaturas de funções e protocolos de rede, e um documento de especificação da API é publicado. APIs frequentemente possuem várias versões disponíveis para diferentes clientes. A descoberta dinâmica e a vinculação de APIs e operações por clientes durante a execução também podem ser suportadas.
-
API GW — um API Gateway realiza uma combinação de padrões arquitetônicos como fachada, adaptador, mediador e (reverso) proxy. Ele recebe chamadas de clientes e as roteia para os serviços que estão por detrás dele, enquanto fornece funções como roteamento, limitação de taxa, transformação de protocolo, agregação, segurança, versionamento, logs, etc.
-
Sistemas de Mensagens — o padrão de integração de mensagens permite que os sistemas sejam acoplados de forma fraca por meio da comunicação assíncrona, tornando a comunicação mais confiável porque os dois sistemas não precisam funcionar simultaneamente. O sistema de mensagens é responsável por transferir dados de um sistema para outro, para que possam focar nas informações que precisam compartilhar e não gerenciar a interação ativamente. É como um serviço de cartas postal.
-
Sistemas de Fila (de Mensagens) — o padrão de fila estende o padrão de comunicação assíncrona da Mensagem, fornecendo um pipeline onde múltiplas mensagens podem ser armazenadas sequencialmente por sistemas clientes ou servidores até que sejam consumidas, geralmente em um sistema de Primeiro Entrado, Primeiro a Sair (FIFO), ou se tornem obsoletas. Mensagens de solicitação são colocadas em filas pelos sistemas clientes e captadas e atendidas com mensagens em filas de resposta pelos sistemas de serviço. Filas aumentam a confiabilidade e o throughput líquido e reduzem a perda de dados. Esse acoplamento frouxo permite o desenvolvimento independente e ágil dos sistemas. Gerenciar as filas e seu conteúdo, envelhecimento, escalonamento, registro, etc., são funções fornecidas pela plataforma de fila de mensagens. É como fazer fila para comprar um ingresso para um filme.
-
Sistemas de Publicação-Assinatura — o padrão de publicação é um padrão de comunicação assíncrono e fracamente acoplado para que um sistema de serviço disponibilize informações ou funções às quais clientes interessados possam se inscrever. É como publicar jornais e assinar por leitores interessados.
-
ESBs — Um Barramento de Serviços Empresariais combina múltiplas funções de integração, como mensagens, fila, publicação-assinatura, roteamento, transformação de protocolo, transformação de objetos, etc., em uma única plataforma de software. Elas são uma das plataformas EAI mais comuns (veja abaixo).
-
Plataformas EAI — de modo geral, as plataformas de Integração de Aplicações Empresariais podem fornecer todas as 12 funções de integração abordadas na seção de Hierarquia de Funções Técnicas acima e os tipos de solução 2 a 9 acima.
-
Gerentes de Fluxo de Trabalho — Plataformas de fluxo de trabalho integram os sistemas e as pessoas para a iniciação, roteamento, suporte à decisão, ações manuais, tarefas automatizadas, coordenação e monitoramento da sequência de etapas realizadas por sistemas e humanos juntos para uma tarefa de negócio.
-
Gerentes de Processos de Negócios — Plataformas BPM automatizam a integração dinâmica dos processos de negócios para fornecer resultados bem definidos, repetíveis, eficientes e confiáveis. Eles geralmente cobrem serviços de descoberta, análise e otimização.
Diretrizes: O que é necessário para a integração surge organicamente da arquitetura de aplicações e informações e dos processos e fluxos de trabalho de negócios que eles atendem. Mas, às vezes, há uma decisão a ser tomada se devemos refatorar aplicações para melhor integração ou usar uma plataforma externa.
O teste de tornasol mostrado abaixo nos ajuda a identificar a necessidade de apoio externo. Se a pontuação for baixa, é recomendável refatorar as candidaturas.
Ponto de Vista 3: Padrões de integração de aplicativos: Desde o início, os padrões de design de aplicações incluíram padrões de integração. Alguns desses fatores estão alinhados com o pensamento arquitetônico; Podemos nos adaptar e adotá-los. O livro 'Gang of Four' sobre Padrões de Design de Aplicações aborda os seguintes temas arquitetonicamente relevantes, que são brevemente descrevidos. Como arquiteto de aplicações e integração, aprenda e use esses padrões.
-
Facade — Fornece uma interface unificada para um conjunto de interfaces em um subsistema. A Facade define uma interface de nível superior que torna o subsistema mais fácil de usar. Por exemplo, uma fachada de API para Order reúne as interfaces de múltiplos subsistemas subjacentes, como ManageStock, CheckPayment, FulfilOrder, etc.
-
Adapter — Converte a interface de uma classe em outra interface que os clientes esperam. O adaptador permite que classes trabalhem juntas que, de outra forma, não poderiam por causa de interfaces incompatíveis. Em termos arquitetônicos, pense em sistema, aplicação, componente ou subcomponente em vez de classe. Por exemplo, um PaymentGWAdapter preenche um campo de tipo cliente obrigatório exigido por uma interface de sistema de pagamento com um valor padrão para um cliente que não o fornece.
-
Proxy — Fornece um substituto ou substituto para outro objeto controlar o acesso a ele. Funcionalmente, eles podem ser ainda divididos em Proxy Remoto (representação local de uma API remota), Proxy Virtual (fornece cache e acesso backend sob demanda), Proxy de Proteção (acesso de controle baseado em funções e regras) e Proxy Auxiliar (fornece serviços adicionais como contagem, registro, etc.). Por exemplo, uma CDN (rede de entrega de conteúdo) usa um proxy reverso para segurança, balanceamento de carga e escalabilidade.
-
Observer (também conhecido como Publicar-Assinar) — Define uma dependência de um para muitos entre objetos para que todos os seus dependentes sejam notificados e atualizados automaticamente quando um objeto muda de estado. (O padrão de Arquitetura Orientada a Eventos é um caso de um padrão de integração de aplicação por observador ou publicação-assinatura.) Por exemplo, se o cronograma de voo de uma companhia aérea (assunto) mudar e vários portais de viagem (observadores) precisarem saber disso, um padrão de Observador ou Publicar-Assinar os integra usando notificações, solicitações, etc.
-
Mediator — Define um objeto que encapsula como um conjunto de objetos interage. Mediador promove o acoplamento frouxo ao impedir que objetos se refiram explicitamente uns aos outros e permitir que você varie sua interação de forma independente. Por exemplo, uma plataforma de comércio eletrônico se comunica com vários gateways de pagamento por meio de um mediador para que todos possam mudar de forma independente.
-
Camada Anticorrupção (esse padrão vem do DDD) — Quando sistemas baseados em diferentes modelos são combinados, a necessidade do novo sistema se adaptar à semântica do outro sistema pode levar à corrupção do modelo do novo sistema. Crie uma camada isolante para fornecer aos clientes funcionalidades em termos do modelo de domínio deles. A camada se comunica com o outro sistema por meio de sua interface existente, exigindo pouca ou nenhuma modificação no outro sistema. Internamente, a camada se desloca em ambas as direções conforme necessário entre os dois modelos. Por exemplo, um aplicativo de banco móvel comunica-se com um sistema bancário central legado por meio de uma ACL que separa a verificação de crédito das ordens combinadas em uma única função no sistema legado.
Diretrizes: Adote o Design de Arquitetura Orientado por Domínio, pois ele leva naturalmente aos contextos limitados, arquiteturas de subdomínio e mapas de contexto que orientam a escolha dos padrões de integração intra e inter-sistema.
Ponto de Vista 4: Integração horizontal das camadas dos sistemas
Integração horizontal na camada UI: As integrações horizontais na camada de UI estão aumentando, impulsionadas pela tendência de 'montar' aplicações combinando serviços prontos para uso, como buscas de produtos, portais de comércio eletrônico, gateways de pagamento, etc. Vamos considerar isso nos três tipos típicos de interfaces.
- Aplicações Estáticas de Página Única (SPAs) — São aplicativos mais simples, onde toda a funcionalidade está contida em uma única página, totalmente atendida por um ou mais sistemas backend. Por exemplo, Gmail, Google Maps, Twitter, Facebook, etc. A integração de interface horizontal é menos comum em SPAs.
- Aplicações Multi Page Estáticas (SMPAs) — Cada página nessas interfaces fornece um conjunto coeso de funcionalidades do usuário, por exemplo, uma interface ERP. Existem diferentes páginas para contas de usuário, pedidos, suporte, etc. Cada página pode ser bastante independente das outras e conectada a um componente backend separado ou a sistemas parceiros 'white label'. Eles estão integrados de forma frouxa em um portal web ou aplicativo móvel, mas o visual e a sensação são consistentes em todas as páginas.
- Aplicações Dinâmicas Multi Página (DMPAs) — Nessas aplicações, existe um conjunto central de páginas conectadas aos componentes principais do backend, mas as tarefas do usuário também percorrem páginas povoadas por sistemas em outros domínios da mesma empresa ou por sistemas de parceiros, todos os quais podem não ser 'white label'. Por exemplo, um site de comércio eletrônico. Nesses casos, a aparência e a sensação das páginas externas podem ser um pouco ou muito diferentes e mudar com o tempo. Também pode variar entre versões web e mobile da mesma tarefa.
A maior parte da integração horizontal da camada de interface é realizada pela integração horizontal da camada de aplicação (DDD) conforme abaixo, incluindo projetos como portais com uma coleção de portlets de diferentes sistemas.
Integração horizontal na camada de aplicação: Eric Evans define a camada de aplicação para DDD como 'Define os trabalhos que o software deve realizar e objetos de domínio expressivos para resolver problemas. As tarefas pelas quais essa camada é responsável são significativas para o negócio ou necessárias para a interação com as camadas de aplicação de outros sistemas. Essa camada é mantida fina. Ele não contém regras de negócio ou conhecimento, mas apenas coordena tarefas e delega trabalho para colaborações de objetos de domínio na camada seguinte. Não possui um estado que reflita a situação do negócio, mas pode ter um estado que reflita o progresso de uma tarefa para o usuário ou para o programa.'
Por sua natureza, a camada de aplicação pode se integrar horizontalmente a sistemas externos, desde que haja pouca ou nenhuma necessidade de preparar os dados para a camada de apresentação ou de persistir dados conforme as regras de negócio no backend. Esse tipo de integração horizontal da camada de aplicação (por exemplo, Javascript rodando no navegador) está aumentando, impulsionado pela tendência de 'montar' aplicações combinando serviços prontos a usar, como catálogos de produtos e serviços, eKYC, verificações de crédito, APIs de pagamento, etc. Um exemplo de padrão para isso é o CORS - Cross-Origin Resource Sharing.
Integração horizontal na camada de domínio: Esse ainda é o padrão de integração horizontal mais comum. Objetos da camada de domínio podem ser criados especificamente para chamar principalmente sistemas externos, preparar a resposta para as camadas de aplicação e UI, e atualizar o estado do negócio no armazenamento persistente na camada de infraestrutura. Objetos de negócios focados em domínio também podem precisar fazer chamadas externas como parte de seu processamento.
(Infraestrutura) Integração horizontal da camada de persistência: A integração horizontal de repositórios de dados persistentes na camada de infraestrutura é necessária e inevitável. Por favor, veja este artigo meu para entender as motivações para integrações de dados → Padrões e Progressão do Repositório de Dados Corporativos.
O arquiteto de integração deve considerar os seguintes aspectos para otimizar o design da integração e selecionar os métodos, protocolos e tecnologias apropriados.
- Os dados podem se mover em forma bruta, modificada ou enriquecida entre sistemas
- Pode ser um subconjunto (geralmente) ou todos os dados originais
- Modificações ou transformações podem ser feitas na fonte, no caminho ou no sistema receptor
- Ele pode se mover em tempo real, quase em tempo real ou com atraso (também podemos falar disso como online vs offline; ou tempo real vs lote)
- Pode ser o dado unitário ou agregado (estes últimos por tamanho ou tempo)
- Requisitos de criptografia, compressão e segurança
A imagem abaixo ilustra o ponto de vista horizontal.
Diretrizes: O arquiteto de integração deve considerar cuidadosamente como tornar a experiência do usuário intuitiva e fluida por meio dos padrões de solução nos pontos de vista #2 e #5, além da gestão de capacidade e desempenho.
Ponto de Vista 5: Padrões de integração sem estado para aplicações web e digitais: No mundo da web e dos serviços digitais, ser 'sem estado' é apresentado como uma solução mágica para componentes não persistirem dados ou não se importarem com o passado e o futuro, e magicamente alcançarem alto desempenho, escalabilidade e desenvolvimento ágil. Mas nenhum sistema ou tarefa empresarial útil pode ser sem estado. Seria informe ou preso no tempo e não teria muita utilidade. (Para mais informações, veja este excelente artigo.)
Mas a ideia por trás do equívoco de 'apatridia' é útil quando a desambiguamos. Ela permite que arquiteturas entreguem rapidamente serviços digitais de negócios ao montar serviços web. E aproveita a distribuição cada vez mais igualitária do poder de processamento entre servidores e dispositivos do usuário.
Um modelo proeminente para arquiteturas sem estado é o REST — Transferência de Estado Representacional. Funciona bem com o modelo de arquitetura em camadas do DDD. (Por favor, veja o livro de Vernon 'Implementing DDD' para mais informações sobre isso.)
Vamos analisar a justificativa por trás da apatridia em termos das três decisões arquitetônicas típicas (veja →As 3 Decisões Arquitetônicas Mais Importantes)
- Decisão sobre a Arquitetura de Colocação de Funções: Problema: Para o mundo digital, web e móvel, como podemos construir serviços backend e parceiros como enxames de unidades de servidores idênticas que crescem suavemente (de algumas para pontuações) com o número de clientes (milhares a centenas de milhões)? Pensamento Arquitetônico: Se os sistemas de serviço mantêm o estado em tempo real do usuário e da tarefa, a interface deve permanecer no mesmo backend até que a tarefa seja concluída. Isso desperdiça recursos em todo o ecossistema do ecospero, limitando o número de clientes que podem ser atendidos e escalando rapidamente com o número de clientes ativos.
E se transferirmos essa responsabilidade em tempo real sobre o conhecimento do usuário e do estado da tarefa (por exemplo, logado, check-out, etc.) para as camadas de UI e Aplicação (terminologia DDD)? Assim, podemos aliviar a camada de domínio nos sistemas primários e parceiros da necessidade de um estado. Deixe que eles gerenciem apenas o estado de negócio não em tempo real e offline (por exemplo, papel do usuário, status do inventário, etc.) e persistam na camada de infraestrutura.
Quando diferenciamos o estado do usuário/tarefa do estado de negócio e sua natureza em tempo real versus não em tempo real dessa forma, toda a vantagem da apatridia se encaixa.
Decisão: Faça da instância cliente da interface o componente fixo ou 'fixo' do ecossistema para manter o contexto da sessão e do estado. Aliviar os componentes do servidor e as aplicações/serviços do ecossistema de suporte da necessidade de persistir ou ficarem pegajosos por meio de conhecer ou ter um estado em tempo real.
-
Decisão sobre a Arquitetura do Repositório de Informação: Existem três tipos de informação envolvidos.
-
Informações de estado em tempo real — Estado do usuário e da tarefa que muda rapidamente conforme o caso de uso avança, por exemplo, logado, finalizado no pagamento, desconectado, etc. Essa é uma informação temporária e transitória. Mantenha na camada de aplicação cliente (termo DDD) perto da memória.
-
Informações de contexto em tempo real — Informações ambientais e de usuário que mudam menos rapidamente conforme o caso de uso, por exemplo, tipo de dispositivo, sistema operacional, geolocalização, usuário anônimo ou identificado, companhia aérea selecionada, etc. Mantenha isso na camada de aplicação cliente (termo DDD) em lojas locais semi-permanentes como cookies, etc.
-
Informações não em tempo real e de longo prazo do Estado dos Negócios — Informações empresariais que precisam ser mantidas por minutos a anos, por exemplo, usuários, contas, perfis, pedidos, estoque, pagamentos, histórico de serviço, mídia, etc. Mantenha em caches do lado do servidor, RDBMS, arquivos, Hadoop e outros repositórios de armazenamento apropriados. Portanto, vemos que a necessidade de informação e seu armazenamento permanece em arquiteturas focadas sem estado. Clientes e sistemas de cache gravam dados temporários na memória ou em armazenamento local em disco.
E a maioria dos serviços backend e parceiros armazena dados de longo prazo em bancos de dados centrais. Esses bancos de dados centrais ainda podem ser um ponto de disponibilidade e problemas de desempenho. No entanto, o compromisso é aceitável pela sua relação custo-benefício, já que os dados de longo prazo são volumosos e não podem ser facilmente replicados como os componentes de lógica de negócios da camada de domínio.
- Método de integração Decisão de arquitetura
Problema: (1) Como os clientes com estado devem interagir com backends sem estado e sistemas parceiros? (2) Como os sistemas backend sem estado devem interagir com outros sistemas sem estado?
Interação com estado: Essas interações ocorrem entre a camada de aplicação cliente e os componentes sem estado do backend. Informações indicadoras de estado exigidas pelo backend da camada de domínio sem estado ou componentes parceiros são incluídas na chamada de API com estado pelo cliente. Por exemplo, na chamada abaixo solicitando um token de autorização, um token de autenticação válido é necessário e incluído na forma do valor client_id.
https://MyDomainName.my.salesforce.com/services/oauth2/authorize?response_type=token&client_id=3MVG9lKcPoNINVBKV6EgVJiF.snSDwh6_2wSS7BrOhHGEJkC_&redirect_uri=http%3A%2F%2F2www.example.org%2Fqa%2Fsecurity%2Foauth%2Fuseragent_flow_callback.jsp&scope=api%20id%20webInteração sem estado: Na camada de domínio DDD, os componentes interagem sem estado com componentes de outros domínios e sistemas externos. Com base em solicitações da camada de aplicação cliente, as informações são adquiridas pelos componentes do cliente a partir de componentes do servidor na forma de recursos HyperMedia que compreendem informações e hiperlinks para escolhas de ações adicionais. As informações e escolhas são então retornadas ao cliente (aplicações DDD e camadas de UI) para apresentação e próximos passos.
Aqui está um exemplo de uma requisição e uma resposta RESTful sem estado com informações e opções válidas de URI para ação de próximo estado. Ela mostra a essência das informações de 'representação' e 'transferência de estado' que se tornam as escolhas de informação e ações da interface ao longo da camada de aplicação. Este é o projeto Hypermedia as the Engine of Application State (HATEOAS) do padrão da arquitetura REST.
GET /accounts/12345 HTTP/1.1
Host: bank.example.comHTTP/1.1 200 OK
{
"account": {
"account_number": 12345,
"balance": {
"currency": "usd",
"value": 100.00
},
"links": {
"deposits": "/accounts/12345/deposits",
"withdrawals": "/accounts/12345/withdrawals",
"transfers": "/accounts/12345/transfers",
"close-requests": "/accounts/12345/close-requests"
}
}
}Veja esta imagem para uma ilustração dessa arquitetura geral típica de integração web.
EDA - Orientado por Eventos: O aspecto de integração da arquitetura é essencialmente uma variação do padrão de integração public-subscribe que suporta o uso assíncrono do modelo REST, essencial na maioria das grandes soluções digitais. Os principais aspectos do EDA são: diferenciar eventos de notificações e tópicos, enviar em vez de sondar/puxar mudanças, catalogar eventos, classificar interações sincronizadas/assíncronas, refatorar aplicativos para publicar/assinar, linguagem Ubiquitous Orientada por Domínio para semântica de tópicos, brokers de eventos, malhas de eventos e preferência por coreografia em vez de orquestração. A ilustração abaixo mostra o conceito em um nível geral.
Diretrizes: A arquitetura de aplicações digitais/web e da informação mais adequada deve ser decidida para os componentes e repositórios de dados com estado e sem estado. Depois, as decisões sobre a arquitetura de integração virão naturalmente. Interações RESTful seriam a norma, usando cargas úteis JSON, XML e HTML.
Arquitetura do Sistema DDD REST — uma visão geral: A combinação das arquiteturas DDD e REST resulta no interessante modelo prático de aplicação e arquitetura de integração ilustrado abaixo. Estude-a com atenção e considere a web digital, arquitetura móvel e frameworks de desenvolvimento de software existentes.
Ponto de Vista 6: Padrões de Segurança de Integração: De modo geral, há três áreas que uma empresa precisa garantir:
- Informações empresariais nas lojas
- Informações do usuário nas lojas
- Qualquer tipo de informação em voo
A terceira é de particular preocupação para o arquiteto da integração. Quando pessoas ou sistemas se comunicam, precisamos assegurar cinco coisas a eles:
- Eles não podem ser ouvidos ou espionados (privacidade, fornecida por redes privadas e criptografia)
- Eles estão conversando com uma pessoa ou sistema que conhecem (autenticação, fornecida por 1/2/3 fatores)
- A pessoa ou sistema tem direito à informação ou ação solicitada (autorização, fornecida pelos papéis)
- Se algo der errado, isso pode ser investigado (não repudiação e outros detalhes fornecidos pela contabilidade)
- Ataques são prevenidos ou resistidos durante a comunicação (proteção contra vários tipos de ameaças)
Os mecanismos de integração devem atender às necessidades de segurança acima, que podemos abreviar como PAAAP.
Essas necessidades aparecem nos canais de interação humano-sistema e sistema para sistema, conforme abaixo.
- De humano para máquina — teclado, mouse, caneta stylus, toque, fala e biometria.
- Máquina a máquina — As sete camadas de rede OSI, de cima a baixo: Aplicação, Apresentação, Sessão, Transporte, Rede, Enlace de Dados e Física.
Sendo um ensaio de arquitetura, não discutiremos os mecanismos e tecnologias do PAAAP. O leitor interessado encontrará facilmente a informação em outro lugar. A tabela abaixo resume os padrões típicos de soluções de segurança para entregar PAAAP em cada camada.
(Por favor, note que as camadas OSI e as soluções de segurança não são arquitetônicas nem imutáveis. Use a tabela apenas para pensamento de aspectos e princípios.)
Diretrizes: Considere os padrões de segurança da organização, os frameworks de segurança da indústria como o SABSA, as arquiteturas de referência de segurança (por exemplo, AWS, Cisco) e a necessidade particular de cada interface no escopo da solução para decidir a arquitetura, o design e a tecnologia de segurança.
Note
Esses padrões e pontos de vista da arquitetura de integração resistirão ao teste do tempo. Gosto de estudar e descrever ideias estáveis pelo seu valor prático. Veja as formas fundamentais pelas quais as coisas interagem; Tudo flui a partir daí. Vá em frente e arquitete, arquiteto.
O SDD - Specification-Driven Development é uma abordagem de desenvolvimento de software em que o ponto de partida e o guia principal de todo o ciclo de criação é a especificação formal e clara do sistema a ser construído.
Em vez de começar direto pela implementação de código ou até mesmo pelos testes, a ideia central do SDD é produzir especificações bem definidas que podem ser documentos estruturados, contratos formais, modelos de comportamento ou até DSLs (linguagens específicas de domínio) e a partir delas orientar o design, os testes e a implementação.
No desenvolvimento tradicional, o código vem primeiro, seguido pela documentação, testes e explicação.
Mas no desenvolvimento orientado a especificações, invertemos o roteiro. A especificação se torna a fonte da verdade e o código flui a partir dela. Com a IA no circuito, esse fluxo de trabalho se torna ainda mais poderoso. Gera especificações a partir da intenção do usuário. Deriva código a partir de especificações e valida a lógica com linguagem natural.
Neste tópico, você aprenderá como fluxos de trabalho com tecnologia de IA permitem sistemas mais rápidos, seguros e modulares, especialmente para aplicações embarcadas e distribuídas na borda. O desenvolvimento orientado a especificações começa definindo o que o sistema deve fazer.
Antes de tocar no código, as especificações podem delinear o comportamento funcional, relacionamentos de entrada/saída, casos extremos (edge cases), failure cases, Temporal logic e.g. do x, y, and z.
O propósito é reduzir ambiguidades que frequentemente aparecem em métodos tradicionais, onde requisitos em linguagem natural muitas vezes dão margem a interpretações diferentes por desenvolvedores, testadores e stakeholders. No SDD, a especificação não é apenas um requisito no papel, mas sim um artefato executável ou validável, que pode ser usado para gerar código, validar regras de negócio automaticamente ou servir de referência inequívoca para testes.
Dessa forma, o SDD busca criar um fluxo em que a especificação é a fonte da verdade. Em muitas implementações dessa prática, as especificações podem ser escritas em formatos que permitam serem executadas ou verificadas, como contratos formais, modelos lógicos ou até arquivos que depois geram código de suporte, documentação ou casos de teste. Isso cria uma forte ligação entre o que o cliente espera, o que o time desenvolve e o que é testado. Diferente do TDD (Test-Driven Development), que coloca os testes como guia para a implementação, ou do BDD (Behavior-Driven Development), que enfatiza a escrita de cenários de comportamento em linguagem quase natural, o SDD é mais rígido e sistemático, focando na especificação técnica ou formal como ponto de convergência, de forma que a implementação não seja “interpretada” pelos desenvolvedores, mas derivada da especificação.
Essa abordagem é especialmente útil em domínios onde erros de interpretação podem ser críticos, como sistemas financeiros, governamentais, industriais e médicos, porque minimiza a distância entre o que foi pedido e o que é entregue. Além disso, o SDD facilita auditorias, compliance e rastreabilidade, já que a especificação pode ser usada tanto como documento de contrato entre as partes quanto como mecanismo técnico para validar que o software está aderente às regras. Por outro lado, aplicar SDD de forma efetiva exige disciplina, ferramentas adequadas e equipes acostumadas a lidar com formalismo maior do que em metodologias mais flexíveis, o que pode ser uma barreira em times ágeis que preferem rapidez de iteração.
O TDD - Test-Driven Development, ou em português "Desenvolvimento guiado por testes" ou Desenvolvimento Orientado a testes ou Desenvolvimento digirido por testes, é uma técnica de desenvolvimento de software que se relaciona com o conceito de verificação e validação e se 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. Logo, é 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.
Basicamente, ela ajuda a aumentar a produtividade a partir de testes já consolidados. O TDD (Test-Driven Development) foca em um tipo específico de teste chamado teste de unidade. No entanto, ele pode influenciar outros tipos de testes durante o ciclo de desenvolvimento.
Kent Beck, considerado o criador ou o 'descobridor' da técnica, declarou em 2003 que TDD encoraja designs de código simples e inspira confiança. Desenvolvimento dirigido por testes é relacionado a conceitos de programação de XP - Extreme Programming, iniciado em 1999, mas recentemente tem-se criado maior interesse pela mesma em função de seus próprios ideais. Através de TDD, programadores podem aplicar o conceito de melhorar e depurar código legado desenvolvido a partir de técnicas antigas.
O TDD é considerado uma técnica ou metodologia, muito adotada nos times de desenvolvimento. Isso porque ele é direcionado ao desenvolvimento de softwares. Contudo, pelo fato de inverter a ordem dos trabalhos – do teste para o código – é um pouco impopular entre os Devs. No entanto, após pegar o jeito, o desenvolvimento ganha um up, e a técnica traz muitos resultados positivos ao projeto.
Important
O livro Test-Driven Development: By Example do Kent Beck, é considerado a obra fundamental sobre TDD (Test-Driven Development). Ele é importante por alguns motivos históricos e práticos. Primeiro, porque o Kent Beck foi um dos criadores originais do TDD como prática estruturada dentro do movimento Extreme Programming (XP) e um dos autores do Manifesto Ágil. Ou seja, ele não só ajudou a popularizar o conceito, como também foi quem deu a forma mais clara e prática para sua aplicação. Segundo, porque esse livro é didático e prático: Beck explica a ideia de TDD usando exemplos pequenos, progressivos e reais. Ele mostra o famoso ciclo do TDD — Red, Green, Refactor — de maneira muito detalhada, quase como se estivesse pair programming com o leitor. Ele começa com problemas bem simples, como implementar uma calculadora de dinheiro com operações básicas, e vai aumentando a complexidade, até chegar em casos mais sofisticados.
O TDD segue a lógica do ciclo: Red, Green e Refactor. Este ciclo é uma abordagem estruturada para escrever e melhorar código de software de maneira incremental, garantindo que ele seja testável, funcional e de alta qualidade. Aqui está uma explicação detalhada de cada fase do ciclo:
🔴 Red: Escreva um teste que apresenta erros, que falhe. Ação: você começa escrevendo um teste automatizado para a funcionalidade que deseja implementar. Este teste é baseado nos requisitos e especificações do que o código deve fazer. Resultado: O teste falha, pois a funcionalidade ainda não foi implementada. A falha confirma que o teste é válido e que a funcionalidade não existe no momento.
🟢 Green: Logo após, escreva um código que passe no teste, que funcione e faça o teste passar. Ação: Escrever a quantidade mínima de código necessário para fazer o teste passar. Nesta fase, o foco está em implementar a funcionalidade de maneira rápida e simples, sem se preocupar muito com a qualidade ou elegância do código. Resultado: O teste passa, indicando que a funcionalidade básica foi implementada corretamente.
🟡 Refactor: Depois disso, "refatorar" o que foi feito, ou seja, eliminar a redundância e melhorar a qualidade do código, ou seja, melhorar e otimizar o código sem alterar sua funcionalidade, mantendo todos os testes passando. Ação: Refatorar o código escrito na fase anterior para torná-lo mais limpo com princípios de Código Limpo (Clean Code), eficiente e fácil de manter. Isso pode incluir a remoção de duplicações, melhoria da legibilidade, e conformidade com padrões de design. Resultado: O código é melhorado sem alterar seu comportamento externo. Os testes (novos e antigos) continuam passando, garantindo que a funcionalidade permanece correta após as melhorias.
A Refatoração é o processo de reestruturar o código de um software para melhorar sua qualidade interna, sem alterar seu comportamento externo. A principal finalidade da refatoração é tornar o código mais limpo, legível, e fácil de manter, otimizando aspectos como desempenho, organização e modularidade.
Ela costuma envolver a remoção de duplicação de código, simplificação de estruturas complexas, e melhoria na nomenclatura de variáveis, classes e funções, além de aplicar padrões de design e princípios como o SOLID.
Refatorar também ajuda a prevenir a "dívida técnica", que ocorre quando decisões de design ou implementação apressadas criam problemas futuros. Em metodologias ágeis, a refatoração é geralmente integrada ao processo de desenvolvimento contínuo, sendo realizada entre ciclos de implementação de novas funcionalidades. Portanto, a refatoração se encaixa como uma prática regular dentro da fase de desenvolvimento, especificamente na etapa de Integração Contínua (CI), que foca na qualidade do código e na automação de testes.
Important
Por mais de vinte anos, programadores experientes no mundo inteiro contaram com o livro Refatoração: Aperfeiçoando o Design de Códigos Existentes de Martin Fowler para aperfeiçoar o design de códigos existentes e melhorar a manutenibilidade do software, assim como para deixar o código existente mais fácil de entender. Essa nova edição ansiosamente esperada foi atualizada por completo para refletir mudanças vitais no domínio da programação. Refatoração 2ª edição contém um catálogo atualizado das refatorações e inclui exemplos de código JavaScript bem como novos exemplos funcionais que demonstram a refatoração sem classes. Assim como na edição original, este livro explica o que é refatoração, por que você deve refatorar, como reorganizar um código que precise de refatoração e como fazer isso de forma bem-sucedida, independentemente da linguagem usada. Para aperfeiçoar o design de códigos existentes e melhorar a manutenibilidade de software, assim como para deixar o código existente mais fácil de entender.
Após ler este livro, você será capaz de:
- Entenda o processo e os princípios básicos da refatoração;
- Aplique rapidamente refatorações convenientes para deixar um programa mais fácil de entender e de alterar;
- Reconheça “maus cheiros” no código (Code Smells) que sinalizam oportunidades para refatorar;
- Explore as refatorações, cada uma com suas explicações, a motivação, o mecanismo e exemplos simples;
- Escreva testes robustos para suas refatorações;
- Reconheça as contrapartidas e os obstáculos para a refatoração.
O Desenvolvimento dirigido por testes requer dos desenvolvedores criar testes automatizados que definam requisitos em código antes de escrever o código da aplicação. Os testes contém asserções que podem ser verdadeiras ou falsas. Após as mesmas serem consideradas verdadeiras após sua execução, os testes confirmam o comportamento correto, permitindo os desenvolvedores evoluir e refatorar o código. Normalmente todos os testes são efetuados de forma continua de acordo com o desenvolvimento cada funcionalidade criada deve ser acompanhada de um teste bem descrito e projetado, então deve-se escolher a área do projeto ou requisitos da tarefa para melhor orientar o desenvolvimento destes testes.
Desenvolvedores normalmente usam frameworks de testes, como xUnit, para criar e executar automaticamente uma série de casos de teste.
As empresas esperam que seus colaboradores sejam realmente muito bons em testes unitários e a melhor forma de garantir isso é pedindo TDD. Muitas pessoas aprendem testes de forma muito superficial, mas um profissional que já praticou TDD em alguma codebase real tem uma vantagem sobre os outros, pois já enfrentou diversos problemas e sabe como contorná-los.
Os testes em integração contínua são sobre feedback do software, como a maioria dos métodos ágeis. Feedback é o ponto chave para um desenvolvimento com qualidade, seja ele a nível técnico, de gestão ou pessoal. O Feedback é o ponto chave para um desenvolvimento com qualidade, seja ele a nível técnico, de gestão ou pessoal.
Bom, muito provavelmente não fui eu quem inventou o nome Ciclo de Feedback para desenvolvimento de Software mas estou adicionando o guiado a Testes.
Legal mas o que isso quer dizer? Quer dizer que, quando trabalhando no desenvolvimento de uma tarefa qualquer, que seja guiada a testes, nós temos que trabalhar em cima do feedback que os testes nos trazem e não com o pensamento de que temos apenas que codar a feature e adicionar testes para garanti-las. Realizar uma tarefa guiada a testes com esse pensamento é disperdiçar boa parte do potencial da abordagem do TDD.
Então, o DDD, TDD e BDD são três abordagens diferentes que se complementam no desenvolvimento de software:
O DDD (Domain-Driven Design), por sua vez, é mais abrangente, é uma forma de estruturar todo o sistema a partir do domínio de negócio. O ponto central é que o design da aplicação deve emergir do entendimento profundo do problema que se quer resolver, criando um modelo de domínio claro, organizado e rico, que reflete a realidade do negócio. DDD traz conceitos como entidades, agregados, value objects, bounded contexts e ubiquitous language, para garantir que o código represente fielmente as regras do domínio e que diferentes partes do sistema conversem de forma coerente. Ele não é uma técnica de testes como TDD ou BDD, mas sim uma filosofia arquitetural que ajuda a manter a complexidade sob controle e tornar o software mais alinhado às necessidades reais do negócio. Por isso, o DDD é amplamente adotado em Code Reviews.
O TDD (Test-Driven Development) é uma prática focada no ciclo de escrita de testes antes da implementação do código. A ideia central é escrever primeiro um teste que falha porque a funcionalidade ainda não existe, depois escrever o código mínimo necessário para fazê-lo passar e, em seguida, refatorar para manter a qualidade. Esse ciclo de “red, green, refactor” garante que o sistema nasça já com testes cobrindo a funcionalidade, evita sobrecarga de bugs e mantém o design do código limpo e orientado ao que realmente precisa ser implementado. Com o tempo, você ganha confiança para refatorar e evoluir a aplicação porque sabe que os testes automatizados garantem que nada quebrou.
Já o BDD (Behavior-Driven Development) nasceu como uma evolução do TDD, com foco maior na comunicação entre time de negócios e desenvolvimento. A ideia é descrever o comportamento esperado do sistema em linguagem natural, geralmente em formato de cenários (“Dado que”, “Quando”, “Então”), o que torna os testes legíveis até para quem não programa. Isso aproxima desenvolvedores, QA, product owners e até clientes, porque todos falam a mesma língua sobre o que o software deve fazer. Enquanto o TDD guia o código a partir de testes técnicos, o BDD guia o código a partir do comportamento esperado do usuário ou do negócio, trazendo clareza e diminuindo ambiguidades nos requisitos.
Quando você junta as três abordagens, tem um fluxo bastante poderoso: o DDD te ajuda a entender e modelar corretamente o domínio; o BDD garante que os comportamentos mais importantes estejam claros e validados com o negócio; e o TDD dá a base técnica para implementar cada parte com qualidade e segurança. Assim, você cobre desde a concepção do sistema até a implementação e os testes, reduzindo riscos, aumentando a clareza e facilitando a manutenção e evolução ao longo do tempo.
Esse diagrama mostra o ciclo do TDD expandido para além do “red-green-refactor” clássico, conectando os diferentes níveis de testes (caixa preta, cinza e branca) dentro do ciclo de desenvolvimento. Destrinchando o que ele está representando:
À esquerda, temos a Black Box, que é onde entram os testes de aceitação. Eles validam se o sistema, como um todo, faz o que o usuário ou o negócio espera. É aqui que entra aquele “Dado–Quando–Então” do BDD, por exemplo.
-
Esse é o primeiro passo (1): escrever o teste de aceitação que vai falhar porque o sistema ainda não implementa a funcionalidade.
-
Na sequência (2), entra a parte de testes funcionais, ainda numa visão de alto nível. Eles descrevem como cada funcionalidade deve se comportar dentro do sistema. É a ponte entre aceitação e código.
3.4 Indo para a direita, temos a White Box, com testes unitários. Esse é o núcleo do TDD clássico: para cada pequena parte do código (função, método, classe), você escreve um teste que falha (3), implementa o código mínimo para passar (4) e depois refatora (ciclo interno de unidade).
-
Depois de escrever e passar os testes unitários, você volta para os testes funcionais (5), garantindo que aquela unidade está contribuindo corretamente para o comportamento esperado.
-
Daqui em diante, surgem os testes de integração (6), que validam se as diferentes partes do sistema trabalham bem juntas (por exemplo, se um serviço conversa com outro via API, se a camada de aplicação integra corretamente com o banco).
6.7 Os testes funcionais e de integração se retroalimentam (6 e 7), porque quando você conecta módulos, é comum ajustar tanto o comportamento funcional quanto a integração.
- Por fim, quando tudo isso passa, você retorna para os testes de aceitação (8). Se eles agora passam, significa que o sistema como um todo está atendendo ao que foi pedido. Se ainda falham, o ciclo recomeça até que os critérios de aceitação estejam cumpridos.
Resumindo: o ciclo mostra que o TDD não vive só no nível unitário (teste–código–refatora), mas pode ser entendido como um encadeamento de ciclos em diferentes camadas: começa com aceitação (visão do usuário), vai para funcional (visão do sistema), integrações (módulos se falando) e finalmente unidades (blocos de código). É um processo iterativo que desce do mais abstrato até o mais concreto e depois sobe de volta, validando em todos os níveis.
Os testes unitários ou testes de unidade (unit tests) é toda a aplicação de teste nas assinaturas de entrada e saída de um sistema. Consiste em validar dados válidos e inválidos via I/O (entrada/saída) sendo aplicado por desenvolvedores ou analistas de teste (QA). O teste unitário é uma verificação feita com uma pequena porção de código, uma unidade de um software. Ou seja, é diferente do teste geral, que se dedica a testar o fluxo do sistema, com as funcionalidades principais.
Testes unitários são métodos que verificam o funcionamento de unidades de código, vulgo métodos, e seus objetos associados. O grande objetivo, por incrível que pareça, não é ter uma grande cobertura, e sim resultar em uma arquitetura melhor, menos acoplada, e de melhor manutenção. Classes com muitas depêndencias são muito difíceis de testar. Métrica utilizada: cobertura de código.
No unitário, cada parte do sistema ganha uma atenção devida e detalhada, de modo a otimizar o processo de identificação de erros. O objetivo é ajudar a rastrear os bugs e impedir que eles retornem depois que alterações forem feitas no produto.
Portanto, são testes que verificam se uma parte específica do código, costumeiramente a nível de função, está funcionando corretamente. Em um ambiente orientado a objetos (OOP) é usualmente a nível de classes e a mínima unidade de testes inclui construtores e destrutores.
🧪 Os testes de unidade verificam unidades, como métodos e classes (OOP), funções (Funcional) e componentes (Front-end) dentro do software. São os testes mais rápidos, baratos de escrever e sua manutenção é simples. Para verificar o comportamento dessas pequenas partes isoladas do sistema sem dependências externas como banco de dados, APIs, arquivos ou rede. Por isso, eles são rápidos de executar, baratos de manter e oferecem feedback imediato durante o desenvolvimento. Como testam unidades isoladas, são fundamentais para garantir a estabilidade do código à medida que ele evolui.
Uma unidade (unit) é a menor parte testável de um programa de computador, no programação procedural uma unidade pode ser uma função individual ou um procedimento do nosso código, imagine que toda função é uma pequena fábrica que fabrica alguma coisa que pode sair, sem a necessidade de entrar algo. Idealmente, cada teste de unidade é independente dos demais, o que possibilita ao programador testar cada módulo isoladamente.
Relação de conceitos de testes de unidade:
-
I/O Input-Output (Entrada e Saída): são todas as entradas e saídas existentes na programação. Portanto, os testes de unidade servem para front-end e back-end. Eles são uma prática essencial no desenvolvimento de software, pois ajudam a garantir a qualidade do código e a facilitar a manutenção. Os testes de unidade são realizados em pequenas unidades de código, como funções, componentes ou módulos. Eles são projetados para testar a funcionalidade e a lógica dessas unidades de forma isolada. Isso significa que os testes de unidade não dependem de outros componentes ou módulos para funcionar.
-
No front-end, os testes de unidade são usados para testar a funcionalidade e a lógica de componentes de interface do usuário, como botões, formulários e listas. Eles também são usados para testar a interação entre componentes.
-
No back-end, os testes de unidade são usados para testar a funcionalidade e a lógica de serviços, APIs e outros componentes de back-end. Eles também são usados para testar a integração entre componentes de back-end.
Os testes de unidade oferecem uma série de benefícios, incluindo:
-
Aumento da qualidade do código: Os testes de unidade ajudam a identificar erros e bugs no código antes que eles sejam integrados ao sistema. Isso resulta em um código mais confiável e estável.
-
Facilidade de manutenção: Os testes de unidade facilitam a manutenção do código, pois permitem verificar se as alterações não afetaram o funcionamento de outras partes do código.
-
Agilidade no desenvolvimento: Os testes de unidade permitem que os desenvolvedores tenham mais confiança ao realizar refatorações ou adicionar novos recursos. Isso permite que as equipes desenvolvam de forma mais rápida e eficiente.
Portanto, os testes de unidade são uma prática importante para qualquer desenvolvedor, independentemente da área de atuação.
Sobre os processos de desenvolvimento de software, no terceiro passo no nível de queda do modelo cascata e no quarto passo do modelo RAPID, entramos na parte de codificação e testes unitários. Ou seja, é a construção do sistema em si. Então, só depois de eu entender todo o problema, só depois de eu saber das necessidades e se é possível ou viável para começar a desenvolver. Muitas vezes, para começar a gente se pergunta se é para pegar logo no processo de codificação, ou seja, a desenvolver logo a aplicação o mais rápido possível. No entanto, percebe-se que o tanto de retrabalho que isso gerava, fazia não valer a pena. E fazia com que estourasse muito o orçamento nesse custo. Então, depois deu definir os requisitos, depois de realizar meus modelos de projetos e provar que aquilo é viável, eu então começo o desenvolvimento do meu software em si.
Após o desenvolvimento e junto com o desenvolvimento, entram os testes unitários (unit tests - testes de unidade) que são definidos pelo próprio desenvolvedor onde eles tendem a testar a menor unidade do sistema.
Por exemplo: Se eu estou desenvolvendo um sistema de cadastro de cliente, não importa o tipo do sistema (mercadinho, farmácia, padaria, comércio ou de uma grande empresa) e esse desenvolvedor que está escrevendo essas linhas de código de cadastrar um único usuário ou funcionário, por exemplo, ele vai desenvolver um caso de teste para que dado uma entrada (input), ele possa receber uma saída (output) esperada que seria: "usuário cadastrado com sucesso".
| Prós | Contras |
|---|---|
| São executados em s. ou ms. | Não simulam o uso da aplicação de ponta-a-ponta (integração) |
| Provêm feedback imediato (rápido na manutenção) | |
| Isolamento de falhas/fácil de depurar | |
| São confiáveis | |
| Guiam o desenvolvimento (TDD) | |
| Ajudam na refatoração | |
| Podem existir tanto no back-end quanto no front-end |
Os frameworks de teste de unidade mais populares para React.js são:
-
Jest: O Jest é um framework de teste de unidade JavaScript criado pelo Facebook. Ele é rápido, fácil de usar e oferece uma variedade de recursos, como testes de snapshot, mocking e asserções.
-
Testing Library: A Testing Library é uma biblioteca de utilitários para testes de componentes React. Ela fornece uma API simples e intuitiva que permite testar componentes sem depender dos detalhes de implementação.
-
Enzyme: O Enzyme é uma biblioteca de teste de componentes React que fornece uma API poderosa e flexível para manipular o DOM e testar eventos.
A escolha do framework de teste de unidade mais adequado depende das necessidades específicas do projeto. O Jest é uma boa opção para projetos simples, enquanto frameworks como a Testing Library ou o Enzyme podem ser mais adequados para projetos mais complexos.
Os frameworks de teste de unidade mais populares para Vue.js são:
-
Vue Test Utils: O Vue Test Utils é um conjunto de utilitários para testes de componentes Vue.js. Ele fornece uma API simples e intuitiva que permite testar componentes sem depender dos detalhes de implementação.
-
Jest: O Jest é um framework de teste de unidade JavaScript criado pelo Facebook. Ele também pode ser usado para testes de unidade em Vue.js.
-
Karma: O Karma é um framework de teste de unidade JavaScript que pode ser usado para executar testes em uma variedade de navegadores. Ele também pode ser usado para testes de unidade em Vue.js.
A escolha do framework de teste de unidade mais adequado depende das necessidades específicas do projeto. O Vue Test Utils é uma boa opção para projetos simples, enquanto frameworks como o Jest ou o Karma podem ser mais adequados para projetos mais complexos.
O framework Python para web back-end chamado Django fornece um framework de teste padrão, chamado de unittest. Esse framework é baseado na biblioteca padrão unittest do Python e é adequado para testes unitários e de integração.
Além do unittest, existem outros frameworks de teste de unidade disponíveis para Django. Alguns dos frameworks mais populares incluem:
-
Pytest: O Pytest é um framework de teste de unidade completo e flexível que oferece uma variedade de recursos, como assertion fixtures, parametrização de testes e testes de desempenho.
-
Mock: O Mock é um framework de mocking que permite simular o comportamento de objetos externos. Isso pode ser útil para testar a funcionalidade de componentes que dependem de outros componentes ou APIs externas.
-
Selenium: O Selenium é um framework de automação de testes que permite testar a interação com um navegador web. Isso pode ser útil para testar a funcionalidade de componentes de front-end.
A escolha do framework de teste de unidade mais adequado depende das necessidades específicas do projeto. O unittest é uma boa opção para projetos simples, enquanto frameworks como Pytest e Mock podem ser mais adequados para projetos mais complexos.
Lembre-se das seguintes diretrizes ao escrever testes para seus métodos:
- Teste se a saída esperada de um método corresponde à saída real.
- Teste se as funções chamadas dentro do método estão ocorrendo o número desejado de vezes.
- Não tente testar código que não faça parte do método que está sendo testado.
- Não faça chamadas de API, conexões de banco de dados ou solicitações de rede ao escrever seus testes.
Sobre os conceitos técnicos a respeito de testes de unidades, temos:
✅ Testes Válidos (pass): São entradas e saídas de dados comuns ao sistema e pertencem ao processo normal. Não apresentam tratamento além do normal já programado. No caso de retorno deverá seguir os padrões estabelecidos e não permitir retornos fora das regras especificadas. Em testes unitários, estamos nos referindo a casos de teste que exercitam o comportamento correto e esperado da unidade de código sob condições normais (válidas), ou seja: Situações em que tudo ocorre como deveria. São aqueles testes que usam entradas válidas e esperadas, esperam resultados corretos, sem exceções ou erros. Confirmam que o comportamento da função está conforme o esperado.
Características de testes válidos:
| Característica | Exemplo prático |
|---|---|
| Entrada no domínio esperado | CPF válido, número positivo, email formatado corretamente |
| Estado inicial válido | Usuário existente, banco conectado, produto em estoque |
| Fluxo normal do código | Sem exceções, erros, ou retornos inesperados |
| Resultado esperado | Retorno certo, estado alterado corretamente |
Exemplo: Teste manual simples em JavaScript - Aqui, 2 e 3 são valores válidos, e o retorno esperado (5) confirma o comportamento correto da função.
function somar(a: number, b: number): number {
return a + b;
}
test("soma dois números positivos", () => {
expect(somar(2, 3)).toBe(5); // ✅ teste válido
});As afirmações (em inglês, assertions) determinam se seu teste é aprovado ou reprovado. Elas comparam o valor de retorno esperado de um método com o valor real. Há uma série de afirmações que você pode fazer no final do seu teste.
A classe Assertions no JUnit consiste em métodos estáticos que fornecem várias condições para decidir se o teste é aprovado ou não. Veremos esses métodos à medida que eu o guie por cada exemplo.
❌ Testes Inválidos (fail): São entradas e saídas de dados não comuns ao sistema. Apresentam tratamento para validar o tipo de dado inválido ou situação. Pode apresentar até dois retornos, uma mensagem para um log no sistema e uma mensagem com formatação e escrita adequada ao usuário. São tão importantes quanto os testes válidos, porque ajudam a garantir que sua função se defenda bem contra entradas erradas, estados incorretos ou fluxos inesperados. São testes que usam entradas inválidas, incorretas ou fora do esperado; Esperam que o código falhe corretamente (com exceção, erro, ou retorno de falha); Verificam se o sistema é robusto contra dados errados ou uso indevido da função.
Exemplo:
Dividir (x int,y int)=z intCaso tenhamos x=1 e y=0, z será um valor com erro e deverá retornar uma mensagem ao usuário, avisando que a operação é inválida. Caso a expressão seja um dado comum do sistema, a autorização para tal validação deverá ser do usuário, pois faz parte do conjunto de regras de negócio. Não existe retorno inválido sem um tratamento. O tratamento genérico será apenas para condições não visíveis na regra e uso do sistema.
👁️🗨️ Domínio: No domínio de testes, usamos testes unitários para validar a funcionalidade de cada componente do nosso domínio de negócio, refere-se à parte do sistema que é testada para garantir que a lógica de negócio e a funcionalidade do código estão corretas, os testes unitários focam em componentes individuais desse domínio.
Important
É importante ressaltar sobre um termo muito conhecido em ciência da computação, chamado domínio (domain), cujo a diferença está no nível de abstração e no foco de cada conceito. A palavra "domínio" realmente aparece em contextos diferentes e pode causar confusão se não for bem delimitada.
No contexto de testes unitários, "domínio" pode se referir genericamente à camada de regras de negócio ou lógica principal da aplicação, que é o alvo ideal desses testes, ou seja, testar a lógica do domínio sem envolver infraestrutura, banco de dados ou interface.
Já em DDD (Domain-Driven Design), "domínio" é o conceito central: é o conhecimento do negócio que está sendo modelado, e tudo gira em torno disso, é a área de interesse do sistema, como logística, financeiro, saúde, etc.
No caso de arquiteturas como Clean Architecture ou Ports & Adapters, "domínio" é uma camada bem definida e isolada que representa as regras puras do negócio, ou seja, aquilo que não muda mesmo que a tecnologia mude; é o núcleo da aplicação.
Já em TDD e BDD, o termo "domínio" aparece implicitamente quando você escreve testes voltados para comportamentos do sistema, especialmente no BDD que foca na linguagem ubíqua e no comportamento esperado do domínio de negócio, enquanto o TDD tende a atuar mais no detalhe técnico e no design emergente.
Por fim, em design de software e design patterns, o "domínio" pode surgir como contexto onde os padrões são aplicados, mas o foco desses conceitos é mais estrutural e de solução técnica do que modelagem de negócio em si.
Então, "domínio" em DDD e arquiteturas limpas é o coração das regras do negócio, enquanto em testes e padrões, é mais o cenário onde você aplica as práticas, muitas vezes sem foco explícito em representar o negócio como um modelo coeso.
Você pode dizer:
"Nos nossos testes de unidade, verificamos se os métodos da entidade
Pedidocalculam corretamente o total do pedido."
Focado na verificação da funcionalidade de unidades isoladas de código (geralmente métodos ou funções), tem como objetivo garantir que cada parte do software funcione conforme esperado de maneira isolada. O contexto aqui é mais técnico e voltado para a qualidade do código e a prevenção de regressões.
Pode ser um campo, uma assinatura, um I/O, ou qualquer tipo de local que receba valores externos ao sistema. Todo domínio deve realizar consistências de dados válidos e inválidos. Um domínio só permite dados com a formatação igual ao que será armazenado.
Ex.: Campo DDD deverá permitir números de até quatro casas não negativas ou a base de dados deve impedir a entrada de valores inválidos. Receber e guardar o mesmo tipo de dado, o tamanho do campo que recebe os dados deve ser menor ou igual ao campo que irá armazenar os dados (em raros casos os campos de armazenamento são menores que os de exibição).
Em suma, domínio é o tipo de valor válido para cada campo. Como exemplo podemos citar:
Campo nome: Dominio = tipo: string; tamanho:50Ao aplicarmos o particionamento por equivalência e a análise por valor limite, poderemos criar as seguintes classes de testes.
Particionamento por Equivalência: campo nome:
- valor em branco (BLANK); Cenário Negativo
- valor > 50; Cenário Negativo
+ qualquer valor de 1 a 50; Cenário PositivoAnálise por Valor Limite:
campo nome: valor em branco; valores 49,50,51; Usamos um valor exatamente inferior e exatamente posterior ao valor do campo, devido ao fato dos erros aparecerem nas fronteiras da aplicação.
O domínio de testes unitários, o domínio de DDD (Domain-Driven Design) e o domínio de microsserviços podem estar inter-relacionados, mas não são exatamente o mesmo domínio. Embora o domínio de testes unitários, o domínio de DDD e o domínio de microsserviços não sejam exatamente o mesmo, eles estão inter-relacionados e podem se complementar. Testes unitários verificam a funcionalidade do código, DDD foca na modelagem do domínio de negócios, e microsserviços organizam a aplicação em componentes pequenos e independentes. Quando usados juntos, esses conceitos podem ajudar a criar sistemas robustos, bem projetados e testados. Você pode ter uma comunicação mais assertiva com o seu time falando da maneira proposta acima que eles irão entender de qual tipo de domínio se trata.
Quando você combina esses três conceitos, você pode comunicar algo como:
Tip
"No nosso sistema, utilizamos uma abordagem de Domain-Driven Design (DDD) para modelar nosso domínio de negócio. Cada parte do domínio de negócio é implementada como um microsserviço independente, permitindo escalabilidade e independência de desenvolvimento. Além disso, garantimos a qualidade e a correção da lógica de negócio com testes unitários abrangentes, que validam cada componente do nosso domínio de negócio."
Agora, imagine que você precisa validar o funcionamento de sua aplicação em um determinado cenário, mas este cenário só funciona se integrado com uma aplicação de terceiros específica, a qual você não tem total acesso.
O fato de você não ter acesso traz um dilema: ou você ignora a integração e roda o teste, inviabilizando a obtenção de resultados assertivos, ou não testa de modo algum.
Nenhuma das opções é realmente a escolha ideal, mesmo se você encarar aquele ditado “feito é melhor que perfeito”. Afinal, é muito tênue a linha entre não trazer os resultados corretos e não testar.
O ideal seria existir algo que possibilitasse que os testes trouxessem os melhores resultados, mesmo sem acesso à tecnologia necessária. A boa notícia é que existe! Estamos falando dos test doubles.
Os Test doubles são objetos usados em testes de software para substituir componentes reais que um sistema ou módulo depende, permitindo que os testes sejam mais controláveis, isolados, rápidos e confiáveis. O nome “double” vem da ideia de um “dublê” no cinema: alguém que substitui o ator em cenas arriscadas. No código, os test doubles substituem partes reais (como um banco de dados, uma API externa ou até um serviço interno) que você não quer ou não pode usar diretamente durante o teste. Termo genérico para qualquer substituição de objeto de produção em testes.
De acordo com Martin Fowler, test doubles é um conceito usado quando, para viabilizar a realização de testes, algum objeto em produção precisa ser substituído por outro. O termo vem da analogia com os “dublês de cinema”: quando você não pode (ou não deve) usar um componente real em um teste, você o substitui por uma versão simulada que cumpre o mesmo papel de forma controlada e previsível. Os test doubles podem ser aplicados a qualquer dependência externa ou componente cuja execução real atrapalhe o isolamento do teste.
Em resumo, as duplicatas de teste (test doubles) são usadas para criar testes rápidos, independentes, determinísticos e confiáveis. Eles representam componentes reais, semelhante à forma como os dublês são usados nos filmes. Um test double pode ser usado para simplificar testes, aumentar a velocidade de execução ou permitir resultados determinísticos de uma ação.
Na prática, isso inclui APIs externas, bancos de dados, requisições HTTP, filas de mensageria (como RabbitMQ), sistemas de cache (Redis, por exemplo), consultas SQL complexas, chamadas a LLMs (modelos de linguagem) e até funções, classes e métodos internos que geram efeitos colaterais ou dependem de recursos não determinísticos (como hora do sistema, aleatoriedade, IO ou threads).
Por exemplo, em um teste unitário que precisa validar a lógica de negócio de uma função que consome uma API REST, você não quer depender de uma chamada real — isso tornaria o teste lento, sujeito a falhas de rede e dependente de algo fora do seu controle. Nesse caso, você cria um mock da API, que retorna uma resposta previsível. Da mesma forma, se a função lê dados de um banco, você cria um stub ou fake para simular a consulta.
Então, resumindo: o conceito de test doubles se aplica a qualquer camada do sistema — desde uma função pura até integrações complexas com APIs, bancos ou modelos de IA — sempre que o objetivo é isolar o teste da dependência real para torná-lo determinístico, rápido e confiável.
Por exemplo, um programa que utiliza um servidor de banco de dados é relativamente lento e consome recursos significativos do sistema, o que prejudica a produtividade dos testes. Um teste pode exigir dados do banco de dados que, sob atividade normal do sistema, estão em constante alteração, fornecendo, assim, saídas não determinísticas para qualquer consulta. Um test double pode fornecer um valor estático em vez de acessar um banco de dados real, evitando tanto chamadas de rede ou do sistema quanto dados em constante mudança.
Um test double também pode ser usado para testar parte do sistema que está pronta para teste, mesmo que suas dependências não estejam.
Por exemplo, em um sistema com os módulos Login, Home e User, suponha que Login esteja pronto para teste, mas os outros dois não. As funções consumidas de Home e User podem ser implementadas como test doubles para que Login possa ser testado.
Quando falamos em Test Doubles, estamos nos referindo a objetos “substitutos” que criamos para simular comportamentos em testes, especialmente quando não queremos ou não podemos usar a implementação real de uma dependência. O nome vem de uma analogia com o “stunt double” do cinema, o dublê que substitui o ator em cenas perigosas. Em testes de software, um test double substitui um componente real para que possamos isolar o código que queremos validar.
Fazendo um paralelo simplista, eles são como dublês de atores em um filme: substituem aplicações reais durante a realização de um teste por simular sua aparência e comportamento. Isso traz menos complexidade ao teste, além de permitir verificar uma parte de um sistema sem ficar preso em todas as suas outras porções.
Advertências: Embora os test doubles sejam frequentemente usados para facilitar o teste de unidade, existem limitações em seu uso, a principal sendo que a conectividade real ao banco de dados ou outros acessos externos não é comprovada por esses testes. Para evitar erros que podem passar despercebidos por isso, são necessários outros testes que instanciem o código com as implementações "reais" das interfaces mencionadas acima. Esses riscos de integração geralmente são cobertos por testes de integração, testes de sistema ou testes de integração de sistema.
Talvez você não esteja familiarizado com o termo test double. É que, às vezes, eles são generalizados pela palavra mock, ou então pela expressão “mockar” . O fato é que mock é apenas um dos exemplos de test doubles dentro de uma família muito maior. Além dos mocks, existem, por exemplo, os fakes, os stubs, os dummies… Abaixo, explico alguns deles.
Eles são fundamentais para testes automatizados, principalmente testes unitários, e ajudam a focar apenas na lógica que você está testando, sem interferência de outras partes do sistema.
Martin Fowler classifica os test doubles em cinco tipos, que são os tipos mais comuns de Test Doubles são geralmente organizados em cinco categorias clássicas:
-
Dummy(Boneco de simulação): Passado mas nunca usado; listas de parâmetros de preenchimentos. Usado apenas para preencher parâmetros, sem comportamento algum. Objetos de preenchimento e sem funcionalidade. -
Fake(Falso): tem uma implementação funcional, mas não é adequado para produção. Que tem uma implementação funcional, mas simplificada (por exemplo, um banco de dados em memória). Implementação simplificada e funcionalidade básica. -
Stub(Simulacro): Fornece respostas prontas para chamadas feitas durante o teste. Que retorna respostas fixas ou predefinidas. Respostas pré-determinadas e imita o comportamento real. -
Spy(Espião): Um esboço que registra informações sobre como foi chamado. Que registra chamadas e argumentos para verificação posterior. Monitoramento de interações e observa o comportamento. -
Mock(Simulado): Pré-programado com expectativas, formando uma especificação das chamadas esperadas. Que define expectativas explícitas de comportamento (quantas vezes deve ser chamado, com quais parâmetros etc.). Verificação de comportamento e rastreamento de chamadas de métodos.
O primeiro é o Dummy, que é o mais simples: são objetos criados apenas para preencher parâmetros ou satisfazer assinaturas de métodos, mas que nunca são realmente usados. Eles existem para evitar nulls ou falhas de compilação, mas não participam da lógica do teste.
Os dummies são dados que substituem dados reais, mas que não chegam a ser realmente utilizados no teste. São normalmente usados para satisfazer determinados parâmetros.
Como uso de dummies é possível diminuir a complexidade durante a escrita de um teste, ignorando o que não é relevante no cenário e focando no que realmente importa.
Os fakes são test doubles que têm implementações reais diferentes àquelas que existem em produção. é uma implementação funcional simplificada de um componente real. Ele realmente executa algo, mas de maneira controlada e menos complexa.
Um exemplo clássico é um banco de dados em memória usado nos testes em vez de um banco de produção, ou um servidor HTTP falso que responde rapidamente sem precisar de rede.
Os fakes aceleram os testes e evitam dependências externas pesadas. Podemos considerá-los como um “atalho”, algo implementado para deixar a execução do teste mais dinâmica frente ao que é colocado no ar de fato.
Basicamente, não há lógica em um fake — ele retorna um valor determinado por quem o implementa e, justamente por isso, não é um elemento adequado para ir à produção. Contudo, ele elimina a necessidade de implementar uma funcionalidade real, o que seria bem mais complexo.
O Stub é um objeto que fornece respostas pré-determinadas para chamadas feitas durante o teste. Ele não tem lógica real, apenas retorna o que foi configurado. Os stubs são muito úteis quando você quer garantir que o teste receba certos dados sem depender de sistemas externos, como simular uma API retornando um JSON fixo.
São similares aos fakes e aos spies, mas, ao contrário destes, ele consegue alterar seu comportamento com base na maneira como ele foi chamado no teste.
O stub também é uma forma de teste duplo usado para fornecer uma resposta controlada das dependências de um componente. Esse tipo de teste duplo pode ser usado para fornecer uma resposta controlada sem fazer nenhuma lógica real. Os stubs geralmente não dão nenhuma resposta fora do que está programado no teste. Isso permite que mais de um cenário para uma única dependência seja representado no teste.
As dependências externas podem não se limitar apenas às classes, mas também a certos métodos. O stubbing de método deve ser feito quando sua função está chamando uma função externa em sua implementação. Nesse caso, você faz com que essa função retorne o valor que você deseja em vez de chamar o método real.
Por exemplo, o método que você está testando (A) está chamando um método externo (B) em sua implementação. B faz uma consulta ao banco de dados, buscando todos os alunos com notas maiores que 80. Fazer uma chamada real ao banco de dados não é uma boa prática aqui. Portanto, você faz um stub do método e o faz retornar uma lista fictícia de alunos que você precisa para testar.
Qual é a diferença entre Mock e Stub? A julgar pela definição de mock e stub, parece que mock e stub são a mesma coisa. Isso ocorre porque uma simulação pode ser considerada uma extensão de um esboço. Um stub é apenas uma implementação simples usada para fornecer uma resposta controlada. Enquanto isso, a simulação não funciona apenas como um stub, mas também verifica o comportamento de um componente e sua interação com o objeto que a simulação cria.
Os mocks tem expectativas sobre o jeito que deve ser chamado e, caso ele não seja chamado da forma correta, o teste deve falhar. Eles são usados para testar interações entre métodos e são úteis onde não há como verificar algumas mudanças de estado ou retornos do método testado diretamente.
Mocking (Mockado) é uma técnica usada em testes de software para simular o comportamento de dependências externas, como serviços, bancos de dados, ou APIs, dentro de uma unidade de código que você está testando. Ao invés de usar as implementações reais dessas dependências, você cria "mocks" (objetos falsos) que imitam o comportamento esperado, permitindo testar o código de forma isolada.
O principal benefício do mocking é garantir que o teste foque apenas no comportamento da unidade de código em questão, sem se preocupar com o comportamento ou estado das dependências externas. A classe cujos métodos você está testando pode ter algumas dependências externas. Como mencionado anteriormente, você não deve tentar testar código que não faça parte da função que está sendo testada.
Nos casos em que sua função usa uma classe externa, porém, é uma boa prática fazer um mock dessa classe, ou seja, ter valores de mock-up (algo como "simulações", em português) em vez dos valores reais. Usaremos a biblioteca Mockito para esse fim.
Exemplo: Se você estiver testando um serviço que depende de um repositório de dados, você pode usar um mock para simular as respostas do repositório, em vez de acessar o banco de dados real.
Mocking e testes unitários são diferentes, mas se complementam para testar unidades isoladas do código. Os testes unitários tem o objetivo de testar uma unidade de código (como uma função ou método) de forma independente, garantindo que ela funcione corretamente em diferentes cenários. Em um teste unitário, você se preocupa apenas com o comportamento interno dessa unidade.
Já o mocking é uma técnica usada nos testes unitários para simular (mockar) dependências externas da unidade que está sendo testada. Isso permite que você foque exclusivamente na lógica interna da unidade, sem se preocupar com o comportamento ou estado de serviços, bancos de dados ou APIs reais. Em resumo, os mocks ajudam a garantir que os testes unitários sejam realmente isolados e focados na unidade de código que está sendo testada, sem interferências externas.
Por fim, existe o Spy, que é uma espécie de híbrido. Ele é um objeto real, mas que tem a capacidade de registrar as chamadas que recebeu, permitindo inspecionar posteriormente como foi utilizado. Diferente do mock, o spy não necessariamente substitui a lógica real; ele executa de verdade, mas deixa rastros que podem ser verificados.
Um spy age como um espião sob a implementação real e, como o mock, consegue verificar as interações entre os métodos.
A diferença para o mock é que o spy chama a implementação real para todos os métodos da interface mockada, a não ser que para algum método este seja configurado para retornar algo específico diferente da implementação real.
Existem boas práticas bem consolidadas no uso de test doubles, e as imagens refletem parte disso. A primeira mostra uma distinção clássica: mocks (e spies) são usados mais em commands (ações que mudam estado), enquanto stubs (stubs, dummies, fakes) são usados em queries (consultas que retornam dados). Essa prática vem da ideia de Command Query Separation (CQS), que sugere que comandos devem ter efeitos colaterais e não retornar valores, enquanto queries retornam valores mas não mudam estado. Se você usa stubs para queries, você só garante dados de entrada controlados; já mocks para comandos te permitem verificar se uma ação realmente ocorreu.
Na segunda imagem, vemos os test doubles categorizados em um “universo” com sobreposições. A ideia é reforçar que Dummy, Stub, Fake, Spy e Mock são variações de substitutos que usamos em testes, mas cada um tem um propósito específico. A boa prática aqui é usar o double mais simples possível que resolva o problema do teste. Por exemplo, se você só precisa preencher um parâmetro, use um dummy. Se precisa de dados controlados, use um stub. Se quer uma implementação leve mas funcional (como um banco em memória), use um fake. Se precisa verificar chamadas, use um mock. Se precisa capturar interações sem substituir toda a lógica, use um spy. O erro comum é usar mocks para tudo, gerando testes frágeis, acoplados demais ao código interno.
Outra boa prática é manter os doubles próximos do contexto do teste e não generalizá-los cedo demais. Doubles genéricos compartilhados entre muitos testes podem virar fonte de inconsistência ou dificultar a leitura. Em TDD, por exemplo, é comum criar o double dentro do próprio teste e só refatorar para reaproveitar se realmente houver repetição.
![]() |
![]() |
![]() |
![]() |
![]() |
Também é importante lembrar que test doubles são ferramentas para testes de unidade, onde o isolamento é crucial. Em testes de integração ou end-to-end, eles devem ser usados com cautela, pois nesses níveis o objetivo é justamente validar a interação real entre componentes. Usar doubles nesses contextos pode dar uma falsa sensação de segurança, porque o código passa no teste, mas falha no ambiente real.
Por fim, existe uma máxima importante: “don’t mock what you don’t own” — ou seja, evite criar mocks para dependências externas de terceiros, como APIs de bibliotecas que você não controla. Isso acopla seus testes a detalhes que podem mudar fora do seu controle. Em vez disso, prefira abstrair essas dependências atrás de interfaces próprias e mockar essas interfaces. Isso deixa seus testes mais estáveis e mantém o acoplamento sob seu domínio.
Ou seja, as boas práticas são: usar mocks para comandos e stubs para queries, escolher o double mais simples possível para o cenário, não abusar de mocks em todos os lugares, restringir o uso a testes de unidade, e nunca mockar diretamente dependências externas que você não controla.
Pouca gente conhece bem essa distinção, embora ela seja bem importante quando o assunto é qualidade e isolamento de testes unitários. Os termos solitary tests e sociable tests vêm de uma classificação proposta por Gerard Meszaros no livro xUnit Test Patterns. A ideia é entender como os testes unitários se relacionam com outras unidades de código e até que ponto o teste realmente está isolando a unidade sob teste (o chamado System Under Test — SUT). A diferença entre sociable tests e solitary tests praticamente gira em torno de quando e como você usa test doubles.
Um solitary test é um teste totalmente isolado. Ele testa apenas uma unidade, geralmente uma classe, função ou método e substitui todas as suas dependências externas por test doubles (como mocks, stubs ou fakes). Assim, o teste verifica exclusivamente o comportamento interno da unidade, sem interações reais com banco de dados, rede, arquivos, APIs ou outros serviços. Esse tipo de teste é o ideal quando se quer garantir pureza e velocidade nos testes unitários, pois eles são rápidos, previsíveis e não dependem de contexto externo.
Por exemplo: ao testar um OrderService, você substitui o PaymentGateway por um mock que apenas simula o comportamento esperado, sem fazer chamadas reais.
Já um sociable test é o oposto: ele testa uma unidade em conjunto com suas dependências reais — ou ao menos com algumas delas. Em vez de isolar completamente a unidade, o teste permite que ela “converse” com outras classes do mesmo módulo, desde que ainda esteja dentro do escopo de uma “unidade lógica”.
Por exemplo, um teste que exercita OrderService chamando PaymentGateway e EmailNotifier reais (ou parcialmente reais) é sociável, porque o comportamento observado depende da interação entre várias unidades.
A diferença central está na abordagem de isolamento. O teste solitário foca em verificar se a unidade funciona corretamente sozinha; o sociável foca em verificar se ela funciona em conjunto com outras partes confiáveis do sistema. Ambos são considerados testes unitários, desde que mantenham o escopo pequeno e rápido, mas o solitário é mais puro e previsível, enquanto o sociável tende a ser mais próximo da realidade, embora menos determinístico.
Na prática, bons conjuntos de testes costumam mesclar os dois: solitary tests para garantir o comportamento interno de cada componente de forma isolada e rápida, e sociable tests para verificar a integração leve entre partes da aplicação que, juntas, compõem uma unidade funcional.
O Arrange–Act–Assert Pattern (AAA) é um padrão de estruturação de testes extremamente usado em TDD, unit tests e qualquer cenário onde você quer garantir clareza, previsibilidade e legibilidade nos testes. Ele não é um framework nem uma tecnologia, mas sim uma disciplina mental, uma organização lógica para que cada teste seja fácil de entender, manter e evoluir. A ideia é que, em vez de escrever um teste como um bloco monolítico confuso, você o divide em três momentos distintos que imitam o raciocínio humano ao validar um comportamento.
O padrão Arrange-Act-Assert, também conhecido como padrão AAA ou 3A, é uma abordagem amplamente reconhecida para estruturar testes. Foi originalmente proposto por Bill Wake em 2001 e depois mencionado no influente livro de Kent Beck, "Test Driven Development: By Example", em 2002.
O padrão AAA promove clareza ao recomendar que os testes sejam estruturados em três fases distintas:
-
No primeiro momento, o Arrange, você prepara o ambiente. Organize tudo o que for necessário para realizar o teste. Isso envolve criar os objetos, configurar estado, definir entradas, mocks, fakes, stubs ou qualquer dependência necessária para que a ação principal do teste aconteça de forma isolada. Aqui você estabelece as pré-condições do cenário. Em outras palavras, é onde você monta o palco antes da cena começar. Um bom Arrange não tem lógica demais; ele apenas prepara o terreno para que o teste tenha um contexto claro.
-
Depois vem o Act, que é o coração do teste. Aja com base no código alvo a ser testado executando-o. Aqui você executa exatamente uma ação, a ação que deseja validar. Esse ponto é crucial: o Act não deve ser poluído com outras chamadas ou verificações, e não deve fazer múltiplas coisas. É a chamada do método, função ou endpoint que você está testando. Num teste de unidade bem modelado, esse bloco costuma ser uma única linha. A simplicidade aqui é o segredo: quando se faz mais de uma ação, perde-se precisão e fica difícil identificar por que um teste falha.
-
Por fim, o Assert é o momento de verificar se o comportamento observado corresponde ao comportamento esperado. Afirme os resultados esperados. É onde você compara o resultado com o valor correto, valida estado final, confirma se exceções foram lançadas, ou se eventos foram registrados. É nesse bloco que você diz explicitamente qual era o contrato esperado daquele código. E, por isso, todo Assert deve ser claro e direto: um teste que não deixa explícito o que está sendo validado é um teste que não comunica nada.
O padrão AAA melhora a legibilidade e a manutenibilidade, espelhando de perto a estrutura Given-What-When-Then desenvolvida por Daniel Terhorst-North e Chris Matts como parte do BDD (Behavior-Driven Development). Hoje, quase todas as ferramentas modernas de teste unitário com sintaxe BDD incentivam o uso do padrão AAA.
A beleza do AAA é que ele espelha como pensamos quando analisamos um comportamento: primeiro preparamos, depois fazemos, depois verificamos. É tão natural que, quando bem aplicado, qualquer pessoa — até alguém que nunca viu o projeto — consegue ler e entender o teste como se estivesse lendo uma narrativa estruturada. Além disso, frameworks como xUnit, NUnit, JUnit, Jest, PyTest e tantos outros encaixam-se naturalmente nele, porque a ideia é totalmente agnóstica de linguagem.
O padrão AAA também ajuda a evitar testes frágeis e difíceis de manter, porque desencoraja misturar lógica dentro dos asserts, evita efeitos colaterais no Act e obriga você a ser explícito sobre as condições iniciais no Arrange. Isso leva a testes mais puros, mais isolados e menos vulneráveis ao tempo.
No fim das contas, o AAA transforma testes em pequenas histórias de causa e efeito. Isso é fundamental em um ambiente profissional, especialmente no desenvolvimento orientado a testes, porque permite que o teste seja mais do que um instrumento técnico — ele se torna documentação viva, alinhando comportamentos esperados com a intenção original do desenvolvedor.
Os testes de integração (integration testing) são de um nível mais alto, e testam a relação de elementos, como por exemplo um banco de dados e o software. A realização destes testes é mais lenta, afinal possuem um outro grau de complexidade. É um teste em grupos que valida a integração de um sistema com outros sistemas ou banco de dados, é feito pelo desenvolvedor para validar se existe falha de dados entre integrações nos sistemas e se está com o comportamento correto.
São testes que verificam se módulos diferentes do sistema funcionam corretamente juntos, por exemplo: back-end + banco de dados, API + autenticação, ou microsserviços se comunicando via fila ou HTTP.
No contexto de testes de integração, módulos são partes distintas de um sistema que têm responsabilidade própria (ex: autenticação, pagamentos, cadastro), podem ou não ser executadas separadamente e precisam se comunicar entre si para o sistema funcionar corretamente. Módulo, em testes de integração, se refere a qualquer parte autônoma do sistema (seja uma classe, serviço, camada ou microserviço) que precisa se comunicar com outras partes para o sistema funcionar. Testes de integração são o que garantem que essas partes realmente funcionam bem juntas, usando dados reais, banco de dados, APIs, filas, etc. Os testes de integração avaliam se a comunicação entre esses módulos está funcionando como esperado.
Em testes de integração, módulos são as unidades de software intermediárias, partes funcionais do sistema que já foram testadas isoladamente (em testes de unidade) e agora precisam ser verificadas em conjunto para garantir que interajam corretamente umas com as outras. Em outras palavras, quando falamos em módulos, estamos nos referindo a componentes do sistema que possuem fronteiras bem definidas e trocam dados entre si, como classes, serviços, APIs, repositórios, adaptadores, filas, ou até microservices.
Por exemplo, imagine uma aplicação ASP.NET Core com RabbitMQ. Você pode ter:
- Um módulo de Producer, responsável por publicar mensagens.
- Um módulo de Consumer, responsável por ler e processar essas mensagens.
- Um módulo de persistência, que grava os dados no banco. Cada um desses módulos pode ser testado separadamente (testes unitários), mas o teste de integração se preocupa em verificar o comportamento entre eles, por exemplo, se o producer realmente publica no RabbitMQ e se o consumer consome e processa corretamente a mensagem publicada.
O conceito de módulo em integração é relativo: em um projeto monolítico, um módulo pode ser um conjunto de classes coesas; em uma arquitetura de microsserviços, um módulo pode ser um serviço inteiro. O importante é que o módulo tenha interfaces de comunicação claras (como endpoints HTTP, filas, métodos públicos, etc.), porque os testes de integração se baseiam exatamente nessas fronteiras.
No fundo, o teste de integração responde à pergunta:
“Esses módulos, quando combinados, ainda funcionam corretamente como um todo?” — garantindo que o comportamento do sistema emergente seja coerente, e que os dados fluam corretamente entre as partes.
Portanto, testes de integração são uma fase do processo de teste de software em que módulos ou componentes são combinados e testados em grupo. Ela sucede o teste de unidade, em que os módulos são testados individualmente, e antecede o teste de sistema, em que o sistema completo é testado num ambiente que simula o ambiente de produção. Os testes de integração têm como objetivo verificar a funcionalidade e a comunicação entre módulos. Eles são projetados para identificar erros de integração, que são erros que ocorrem quando dois ou mais módulos são combinados.
Exemplo em um sistema web: Suponha que você tenha um sistema de e-commerce com:
-
Módulo A: Autenticação -
Módulo B: Catálogo de produtos -
Módulo C: Carrinho de compras -
Módulo D: Pagamento
No teste de integração, você testaria coisas como:
-
✅ Se um usuário autenticado consegue adicionar produtos ao carrinho (A + C).
-
✅ Se o sistema só libera o pagamento se os produtos forem válidos (B + D).
-
✅ Se o pedido final é registrado no banco com todas as dependências funcionando (A + B + C + D).
Dependendo do contexto, módulo pode significar:
| Contexto | O que é o "módulo"? | Exemplo prático |
|---|---|---|
| Back-end (monolito) | Um componente separado (serviço, classe, camada) | Módulo de usuários vs módulo de produtos |
| Microserviços | Um serviço inteiro com banco próprio e API | Serviço de checkout vs serviço de catálogo |
| Front-end modularizado | Um conjunto de funções ou hooks reutilizáveis | Módulo de autenticação + módulo de requisições |
| Arquitetura em camadas | Uma camada da aplicação (Controller, Service, Repository) | Controller depende do Repository funcionando corretamente |
Então, nos testes de integração, o objetivo é:
- Testar a colaboração entre módulos reais, sem mocks.
- Verificar se integrações entre módulos distintos (por código, HTTP, fila, banco) estão funcionando.
- Detectar problemas de acoplamento, dependência ou contrato entre partes do sistema.
Se você quer conhecer todas as principais ferramentas e frameworks de testes de integração, aqui vai um apanhado completo, organizado e atualizado, abrangendo múltiplas linguagens e stacks modernas — incluindo ferramentas de propósito geral, específicas para APIs, banco de dados, E2E (fim a fim), e ambientes complexos como microserviços.
JavaScript / TypeScript:
-
Jest embora seja popular para testes de unidade, também permite testes de integração. Suporte a mocking, spies, e
supertestpara testes de API. -
Supertest testa endpoints HTTP diretamente em apps Node.js/Express/Koa. Muito usado com Jest ou Mocha.
-
Mocha + Chai flexível e modular. Excelente para testes de integração com
chai-httpesupertest. -
Playwright / Puppeteer usados para testes de integração em aplicações web (navegador). Permitem verificar o fluxo do usuário entre frontend e backend.
-
Vitest suporte moderno e rápido a testes de integração com foco em performance. Alternativa moderna ao Jest com suporte a ES Modules.
Deno com deno test embutido no runtime. Suporte nativo a testes de integração (com fetch, banco, etc.). Test libraries auxiliares: assert (de std) e não precisa de Supertest: pode usar fetch diretamente no servidor rodando em teste.
Python:
-
Pytest um dos frameworks mais completos. Com plugins como
pytest-django,pytest-flask,pytest-asyncio, cobre testes de integração completos. -
Requests / HTTPX combinados com
pytest, são ótimos para testar APIs REST ou FastAPI/Flask/Django. -
Behave / Lettuce estilo BDD com testes de integração escritos em Gherkin.
Java / Kotlin:
-
JUnit (5+) com Spring Boot Test: realiza testes de integração com o contexto da aplicação.
-
Testcontainers executa serviços reais (como PostgreSQL, Kafka, Redis) em containers Docker nos testes.
-
RestAssured framework fluente para testar endpoints REST em Java.
Go:
-
testing + httptest padrão da linguagem. Cria servidores HTTP fake para testar integração de APIs.
-
GoConvey framework para escrever testes BDD e de integração.
-
Testcontainers-Go integra serviços externos com Docker no teste (como bancos, filas, etc).
Rust:
-
#[tokio::test],reqwest,warp::testferramentas nativas para rodar testes async com HTTP, banco etc. -
Testcontainers-rs similar ao de outras linguagens: banco de dados real em Docker.
PHP:
-
PHPUnit com Laravel ou Symfony, permite testes de integração completos com banco de dados e API.
-
Codeception específico para testes de integração e E2E. Robusto e com suporte a múltiplos módulos (HTTP, DB, etc).
Ruby:
-
RSpec + Capybara usado em Rails para testes de integração web.
-
Minitest leve e nativo do Ruby. Com suporte a integração com bancos e HTTP.
Ferramentas genéricas de integração / multi-linguagem:
-
Postman + Newman ideal para testar APIs REST/GraphQL com fluxo, autenticação, tokens, etc.
-
Insomnia Tests alternativa ao Postman com foco em GraphQL e REST.
-
🧪 Cypress (E2E com integração real) testa interface, mas também valida backend real.
-
🐳 Testcontainers (multi-language: Java, Node, Go, Rust, .NET, etc) executa serviços reais (Redis, MySQL, RabbitMQ) em Docker, durante os testes.
-
WireMock / MockServer: Simulam serviços externos para testar integração sem dependências reais.
-
Docker Compose (para orquestrar múltiplos serviços nos testes), muito usado para testes de integração entre microsserviços.
Em casos especiais, testes com fila e mensageria (Kafka, RabbitMQ):
Testcontainers(com RabbitMQ/Kafka)docker-composepara subir o ambiente- Bibliotecas nativas da linguagem para consumir/produzir
Para testes de integração, você pode optar por:
| Categoria | Ferramentas-chave |
|---|---|
| Node.js | Jest + Supertest, Mocha, Vitest |
| Deno | deno test + fetch/assert |
| Python | Pytest + HTTPX + Testcontainers |
| Java | JUnit + SpringBootTest + Testcontainers |
| Multi | Postman/Newman, Testcontainers, Docker Compose |
| E2E web | Playwright, Cypress, Puppeteer |
| APIs | RestAssured, Insomnia, HTTPX, Supertest |
Testes de um nível ainda maior, são os functional tests, que testam o sistema completo e garante a correção de funcionalidades no ponto de vista do cliente. Teste em que não é necessário conhecer a estrutura interna de como o código fonte foi implementado. É basicamente baseado em valores de entrada e saída. É a técnica mais utilizada no dia a dia. Responsável: Analista de Qualidade
Isso verifica se a API executa corretamente suas funções pretendidas. Testes funcionais garantem que a API entregue os resultados esperados para as entradas específicas. Ele foca em verificar funcionalidades centrais sem entrar em desempenho ou segurança.
Functional Testing e Black-box Testing não são a mesma coisa, embora estejam intimamente relacionados. A melhor forma de entender é pensar que um deles é um tipo de teste, enquanto o outro é uma abordagem que pode ser aplicada a vários tipos de testes. Explicando em um textão fluido.
Quando você fala de Functional Testing, você está se referindo à prática de testar o que o software faz, ou seja, suas funcionalidades, requisitos, comportamentos esperados, regras de negócio, entradas e saídas. Nesse tipo de teste, o objetivo não é saber como o código foi escrito, mas sim verificar se o sistema cumpre aquilo que promete. Functional Testing é, portanto, uma categoria de testes, englobando vários métodos como testes de sistema, testes de integração funcional, testes de aceitação, testes E2E e até cenários automatizados que validam fluxos completos. Ele sempre olha o software de fora para dentro, garantindo que cada ação realizada devolva o resultado esperado.
Black-box Testing, por outro lado, é um método, uma forma de enxergar o sistema. É uma abordagem onde você não olha o código interno, não vê classes, funções, bancos de dados, nem algoritmos. Você apenas observa entradas e saídas, como uma caixa preta. Vários tipos de teste podem ser feitos como caixa preta: testes funcionais, testes não funcionais, testes de usabilidade, testes de performance em nível de comportamento e até testes exploratórios. A caixa preta significa ignorar o interior e se concentrar apenas na interação externa. Portanto, Black-box é um estilo de avaliação que pode ser aplicado a várias categorias de teste, e não um tipo de teste em si.
O ponto de união entre os dois é que, na prática de mercado, quase todo teste funcional é executado como Black-box, porque testar funcionalidades sem olhar o código faz parte da natureza desses testes. Contudo, isso não significa que eles sejam sinônimos. Todo Functional Testing utiliza a abordagem Black-box, mas nem todo Black-box Testing é necessariamente funcional. Por exemplo, um teste de performance que mede tempo de resposta sem olhar o código é caixa preta, mas não é funcional. Da mesma forma, um teste de usabilidade, um teste de segurança black-box ou um teste de carga são todos caixa preta sem serem testes funcionais. Pense em Functional Testing como o “o que testar” e Black-box como o “como testar”.
Assim, eles caminham juntos, se alimentam um do outro, mas ocupam categorias diferentes dentro da engenharia de software.
O que é importante pensarmos é no tempo de execução de testes que teremos. Os testes de unidade existem desde o início do projeto, qualquer commit deveria ser acompanhada por um teste.
É comum que o desenvolvedor que queria concluir um projeto rapidamente deixe de fazer testes para otimizar o tempo. Como resolver esse impasse? Antes do commit, devemos executar todos os testes, embora saibamos que isso é em um plano ideal, e muitas vezes desnecessário dependendo da modificação que foi realizada. Até mesmo executar todos os testes unitários pode ser complicado.
Uma técnica comum é executar o que chamamos de smoke tests. Na prática, trata-se de uma seleção de testes que garantem que as funcionalidades mais importantes do sistema estejam operando corretamente. Esses testes avaliam um conjunto menor de elementos, por isso são mais rápidos, e dessa maneira teremos a garantia de que o software está operante em sua estrutura básica. Depois disso, podemos aplicar todos os testes e garantir uma varredura maior de erros.
Em resumo, devemos observar a categoria de cada teste; em ambientes diferentes fazer escolhas de desempenho e que melhor atendam nossa demanda; aplicar boas práticas de testes ( testes isolados, legíveis, expressivos); realizar testes na parte de build e adquirir feedbacks o mais rápido o possível.
APIs são a espinha dorsal das aplicações modernas, permitindo a comunicação entre diferentes sistemas de software. Para garantir que sejam confiáveis, seguros e de alto desempenho, testes de API são essenciais. Aqui está uma visão abrangente dos vários tipos de testes de API, com exemplos e explicações ampliadas para destacar sua importância.
Exemplo de API:
Endpoint:
Corpo da Requisição: POST /users/create
{
"name": "John Doe",
"email": "john.doe@example.com",
"password": "password123"
}Resposta esperada:
{
"id": 101,
"name": "John Doe",
"email": "john.doe@example.com"
}
O Teste da Caixa Branca, também conhecido como Teste de Caixa de Vidro ou Estrutural, é uma abordagem de teste de software que examina as entranhas do sistema, sua estrutura interna, sua lógica de programação e seu código-fonte. Diferente do Teste da Caixa Preta, onde o testador enxerga o software apenas como um usuário final, sem conhecimento da implementação interna, o teste de caixa branca exige que o profissional tenha acesso e compreensão profunda do código, dos algoritmos, dos fluxos de dados e da arquitetura do sistema.
O seu nome é uma analogia direta: se na caixa preta você testa sem ver o que há dentro, na caixa branca a caixa é transparente, e você analisa minuciosamente os mecanismos internos para garantir que cada engrenagem funcione corretamente.
Testa tendo conhecimento do código fonte, os resultados esperados de acordo com os requisitos acordados, padrões adotados. Deve-se passar por todos os fluxos esperados. Responsável: Desenvolvedor
O objetivo central deste teste é validar a qualidade interna do software. Ele busca encontrar erros em estruturas de controle, em loops infinitos, em condições de borda não tratadas, em caminhos lógicos nunca executados, em vazamentos de memória e em más práticas de codificação. Para isso, o testador projeta casos de teste baseados diretamente no código-fonte, garantindo que todos os caminhos possíveis dentro da aplicação sejam exercitados. As métricas de cobertura são fundamentais nesse processo, pois elas quantificam o quanto do código foi testado. A cobertura de instruções mede a porcentagem de linhas de código executadas, a cobertura de decisão verifica se todos os resultados possíveis de uma condição booleana foram testados, a cobertura de caminhos é mais complexa e visa exercitar todos os caminhos lógicos independentes dentro de um módulo, e a cobertura de condição vai além, avaliando todas as combinações possíveis de subcondições dentro de uma decisão.
A execução do teste de caixa branca é geralmente realizada em fases iniciais do ciclo de desenvolvimento, frequentemente integrada à fase de codificação. Os próprios desenvolvedores costumam realizá-lo, ou engenheiros de qualidade de software com fortes habilidades de programação, utilizando ferramentas especializadas para análise estática e dinâmica. As ferramentas de análise estática examinam o código sem executá-lo, procurando por violações de padrões de codificação, possíveis bugs e vulnerabilidades de segurança. Já as ferramentas de análise dinâmica exigem que o código seja executado, permitindo a medição da cobertura de testes e a identificação de problemas de desempenho e vazamento de recursos em tempo de execução.
As vantagens desta abordagem são profundas. Ela permite a identificação precoce de defeitos, o que reduz significativamente o custo de sua correção, e otimiza o código, eliminando trechos mortos ou redundantes. No entanto, os desafios são igualmente significativos. O teste é complexo e requer um testador altamente qualificado, que seja tanto um engenheiro de qualidade quanto um programador competente. Além disso, testar exaustivamente todos os caminhos de um software minimamente complexo pode ser uma tarefa praticamente impossível devido à explosão combinatória de possibilidades, tornando-o uma atividade que busca a máxima cobertura dentro de limites de tempo e orçamento realistas. Em essência, o Teste da Caixa Branca é um exercício de engenharia de precisão, focado na saúde interna do software, assegurando que a fundação sobre a qual a aplicação é construída seja sólida, eficiente e segura.
O teste de regressão é uma técnica do teste de software que consiste na aplicação de versões mais recentes do software, para garantir que não surgiram novos defeitos em componentes já analisados. Regression testing, ou teste de regressão, é um tipo de teste de software realizado para garantir que mudanças recentes no código, como correções de bugs, novas funcionalidades ou outras modificações, não introduzam novos defeitos ou causem falhas em partes já existentes do software. Este processo é essencial para manter a integridade do software após qualquer tipo de alteração.
Em resumo, regression testing é uma prática crucial no desenvolvimento de software que visa assegurar que novas mudanças não comprometam funcionalidades existentes, contribuindo para a estabilidade e qualidade contínua do sistema.
Objetivos do Regression Testing:
-
Verificar Estabilidade: Assegurar que as novas mudanças não impactaram negativamente o comportamento existente do software.
-
Identificar Regressões: Detectar rapidamente qualquer falha que possa ter sido introduzida devido a mudanças recentes.
-
Manter Qualidade: Garantir que o software continua funcionando conforme esperado, mantendo a qualidade e a confiabilidade.
Quando Realizar Regression Testing:
-
Após Correções de Bugs: Sempre que um bug é corrigido, é importante garantir que a correção não tenha introduzido novos problemas.
-
Após Adição de Novas Funcionalidades: Novas funcionalidades podem afetar o funcionamento existente, por isso testes de regressão são necessários.
-
Durante Refatoração de Código: A refatoração melhora o design interno do código, mas pode inadvertidamente introduzir erros.
-
Em Atualizações de Dependências: Mudanças em bibliotecas ou frameworks subjacentes podem afetar o comportamento do software.
Métodos de Regression Testing:
-
Reexecução Completa: Executar todos os testes existentes para garantir que o software inteiro funcione corretamente. Isso é muitas vezes impraticável para grandes sistemas devido ao tempo e recursos necessários.
-
Seleção de Casos de Teste: Escolher um subconjunto de testes que são mais relevantes para as áreas do código que foram alteradas.
-
Teste de Prioridade: Focar nos testes mais críticos e mais propensos a serem afetados pelas mudanças.
-
Automação de Testes: Usar ferramentas de automação para executar testes de regressão de forma eficiente e repetível.
Ferramentas Comuns para Regression Testing:
-
Selenium: Para automação de testes de interface de usuário em aplicações web.
-
JUnit/NUnit/PyTest: Para testes unitários automatizados.
-
Jenkins/GitLab CI: Para integração contínua e execução automatizada de testes.
-
Robot Framework: Para testes automatizados em diversos contextos.
-
TestNG: Para organização e execução de testes em Java.
Benefícios do Regression Testing:
-
Detecção Precoce de Defeitos: Permite a identificação e correção rápida de novos problemas.
-
Redução de Riscos: Minimiza o risco de introdução de erros ao modificar o software.
-
Qualidade Contínua: Mantém a qualidade do software ao longo de todo o ciclo de desenvolvimento.
Desafios do Regression Testing:
-
Manutenção de Testes: Manter um conjunto de testes atualizado pode ser desafiador à medida que o software evolui.
-
Tempo e Recursos: A execução de um grande conjunto de testes pode ser demorada e consumir muitos recursos.
-
Falsos Positivos/Negativos: Pode haver casos onde testes falham ou passam erroneamente, exigindo investigação adicional.
Testes de performance são uma categoria de testes cujo foco não está na lógica funcional do software, mas na sua capacidade de operar sob condições reais ou extremas de uso, revelando como o sistema se comporta quando é pressionado, forçado, saturado ou submetido a cargas crescentes de usuários, requisições, dados ou processamento. Eles não buscam apenas descobrir se algo funciona, mas se funciona bem, rápido, de forma estável e consistente mesmo quando o mundo real faz força contra ele.
A essência desse tipo de teste é observar o comportamento sistêmico, medindo tempos de resposta, consumo de recursos, throughput, concorrência, latência, estabilidade e capacidade de absorver picos sem degradação crítica. Ao contrário dos testes funcionais, que respondem “o sistema faz o que deveria?”, os testes de performance respondem “ele continua fazendo o que deveria quando é estressado, pressionado e exigido no limite?”.
Responsável: Analista de Qualidade com acompanhamento do time de Infra/ Banco/ Desenvolvimento e em alguns casos DevOps
Esse tipo de teste investiga nuances invisíveis no dia a dia do desenvolvimento, mas extremamente importantes quando sistemas entram em produção. Imagine uma API que responde perfeitamente em ambiente de testes, mas colapsa quando recebe cinquenta requisições simultâneas. Ou um fluxo de pagamento que funciona sem falhas durante simulações, mas se torna lento e inconsistente quando mil usuários clicam ao mesmo tempo em “finalizar compra” durante uma promoção. A performance, nesse sentido, não é apenas velocidade, mas previsibilidade, estabilidade e capacidade de manter a mesma qualidade enquanto a carga aumenta. O software robusto não é aquele que funciona só no cenário ideal, mas o que continua funcionando no cenário real.
Quando falamos de testes de performance em profundidade, entramos em conceitos como teste de carga, teste de estresse, teste de volume, teste de pico e teste de resistência. Ainda que cada um tenha sua nuance, todos fazem parte da mesma família cujo propósito é revelar gargalos e limites. Testes de performance ajudam a descobrir, por exemplo, que uma consulta ao banco degrada exponencialmente com mais usuários, que o pool de conexões não está dimensionado, que um microserviço fica saturado porque outro envia requisições em rajadas, que um evento é processado mais lentamente do que o throughput esperado ou que um único ponto de estrangulamento coloca toda a arquitetura sob risco. A performance revela a verdade estrutural do software, expondo tudo o que o uso real amplifica.
Em arquiteturas distribuídas, especialmente quando você trabalha com mensageria, filas, pipelines, APIs, serviços de alto throughput, ingestão de eventos ou microserviços em cadeia, testes de performance se tornam ainda mais cruciais. Você mede não apenas o tempo individual de resposta, mas o fluxo completo: a latência entre serviços, a velocidade de propagação de eventos, o consumo simultâneo de mensagens no RabbitMQ, o comportamento do cluster sob saturação, o tempo de estabilização dos pods depois de um pico, o impacto da serialização e desserialização, a saúde do garbage collector, o efeito de caches, o custo de I/O, entre dezenas de variáveis que só emergem quando tudo está funcionando ao mesmo tempo. É nesse momento que o software revela se a arquitetura foi bem dimensionada ou se apenas parecia bonita no papel.
Ferramentas como JMeter, k6, Gatling, Locust e Artillery entram justamente nesse contexto, permitindo simular cenários realistas, configurar ramp-ups de usuários, gerar tráfego intenso, acompanhar métricas e observar o comportamento do sistema enquanto é pressionado. Esses testes produzem relatórios com tempos de resposta, percentis, falhas, saturação de CPU e memória, filas acumuladas, concorrência atingida e outros indicadores que ajudam o time a tomar decisões técnicas fundamentadas, seja otimizando queries, refatorando partes críticas, ajustando parâmetros de infraestrutura, redistribuindo carga ou adicionando mecanismos de escalabilidade.
Testes de performance, portanto, não são apenas uma atividade complementar, mas uma forma de garantir que o software sobreviva ao mundo real. Eles antecipam falhas antes que os usuários as vivam, ajudam a dimensionar infraestruturas, revelam gargalos invisíveis no desenvolvimento, evitam que sistemas caiam em momentos críticos, fortalecem a confiabilidade e tornam previsível o comportamento de uma aplicação quando a demanda aumenta. No final, mais do que medir números, eles medem maturidade, robustez e resiliência de um sistema.
-
Teste de Carga: Testa o software sob as condições normais de uso. Ex.: tempo de resposta, número de transações por minuto, usuários simultâneos, etc.
-
Teste de Stress: Testa o software sob condições extremas de uso. Grande volume de transações e usuários simultâneos. Picos excessivos de carga em curtos períodos de tempo.
-
Teste de Estabilidade: Testa se o sistema se mantém funcionando de maneira satisfatória após um período de uso.
Teste de sistema é uma etapa da qualidade de software em que você avalia o produto inteiro funcionando como um todo, exatamente como um usuário ou outro sistema o utilizaria, verificando se todos os componentes front-end, back-end, banco de dados, integrações, filas, APIs externas, autenticação, permissões, serviços…estão cooperando corretamente.
É o primeiro momento em que o software é testado de ponta a ponta, já integrado, e não mais isolado em módulos individuais.
Num textão contínuo, como você prefere, a ideia central do teste de sistema é garantir que o comportamento observado corresponde ao comportamento esperado quando todos os elementos estão reunidos. Em contraste com o teste unitário, que valida partes pequenas e isoladas, e o teste de integração, que valida dois ou mais módulos conversando entre si, o teste de sistema procura validar o fluxo completo. Isso inclui desde a interface visual até o armazenamento de dados e a comunicação com serviços externos.
Ele é executado em um ambiente que simula o mais fielmente possível o ambiente de produção, com configurações reais de rede, segurança, dependências e infraestrutura. Seu objetivo não é apenas encontrar defeitos funcionais, mas também falhas de usabilidade, desempenho, estabilidade e comportamento sob carga, além de verificar regras de negócio em contextos complexos, edge cases que só aparecem quando vários módulos se combinam e dependências externas que podem falhar ou se comportar de forma inesperada.
O teste de sistema é essencial porque confirma que o produto final atende aos requisitos especificados — requisitos que, muitas vezes, só fazem sentido quando vistos como um fluxo contínuo. Ele também serve como uma última proteção antes do teste de aceitação do usuário, onde o cliente ou área de negócio valida se aquilo corresponde ao necessário. Em suma, teste de sistema é a verificação holística do software funcionando completamente integrado, garantindo que o resultado global do sistema seja confiável, coerente e pronto para ser apresentado ao usuário real.
Ferramentas de teste de sistema variam conforme o tipo de aplicação (web, desktop, mobile, APIs, microserviços, filas, integrações), mas existem plataformas amplamente usadas para validar o sistema completo, ponta a ponta, simulando exatamente o que um usuário real ou outro sistema faria. Abaixo vai um textão contínuo, sem tópicos, como você prefere.
Quando falamos de teste de sistema, entramos no território de ferramentas capazes de orquestrar fluxos completos, interagir com interfaces reais, executar chamadas autenticadas, validar integrações, lidar com filas como RabbitMQ ou Kafka, e observar o comportamento do sistema como um todo. No universo de aplicações web, por exemplo, Selenium se tornou um clássico porque permite controlar navegadores reais e validar o comportamento da aplicação como um usuário. Hoje, ferramentas modernas como Cypress oferecem testes mais rápidos, estáveis e com integração profunda com JavaScript, além de uma experiência visual forte, sendo amplamente usadas para testes End-to-End que também funcionam como testes de sistema.
Playwright é outra evolução desse conceito, permitindo testar múltiplos navegadores com mais confiabilidade, além de suportar testes em APIs, mobile web e fluxos completos. Quando o sistema possui APIs e múltiplos serviços, ferramentas como Postman e sua versão automatizada, o Newman, permitem rodar coleções completas de chamadas simulando sequências reais do sistema, sendo muito usadas em pipelines CI/CD. Para validar comportamento distribuído, ferramentas como K6 permitem simular usuários simultâneos e comportamentos sob carga, atuando tanto como teste de sistema quanto de performance. Em ambientes corporativos e integrações pesadas, SoapUI e ReadyAPI são fortes, pois fazem testes completos de SOAP, REST, GraphQL, autenticação complexa e cenários de orquestração. Para aplicações mobile, Appium permite testar o sistema inteiro interagindo com apps Android e iOS reais.
Quando há robôs de interface que automatizam fluxos completos, RPA como UiPath podem executar testes de sistema em nível empresarial. No mundo .NET, é comum usar Playwright, Selenium ou Cypress integrados ao pipeline do Azure DevOps, GitHub Actions ou GitLab CI, validando a aplicação como um todo após o deploy em ambiente de staging. Para microserviços orquestrados com Docker Compose ou Kubernetes, a combinação de testes em Postman/Newman, Karate DSL e ferramentas de workflow como Robot Framework fornece um arsenal capaz de simular fluxos complexos de ponta a ponta, incluindo eventos em fila, mensagens assíncronas e APIs encadeadas. Finalmente, para sistemas que exigem automação de tela desktop, existem soluções como WinAppDriver ou TestComplete.
Cada uma dessas ferramentas atende a cenários diferentes, mas todas compartilham a mesma essência: validar que o sistema completo funciona integrado, como um organismo vivo, garantindo que a experiência final seja coerente, confiável e alinhada ao que o usuário espera.
O E2E - End-to-end refere-se a um tipo de teste ou processo que envolve a verificação de um sistema ou fluxo de trabalho em sua totalidade, desde o início até o fim, simulando as condições reais de uso pelo usuário final. Em resumo, "end-to-end" se refere à abordagem de teste que abrange todo o sistema ou processo, do início ao fim, para garantir seu funcionamento correto e eficaz.
O E2E verifica se todos os componentes de um sistema (front-end, back-end, bancos de dados, APIs externas, etc.) funcionam juntos conforme esperado, do início ao fim. Em um contexto de desenvolvimento de software, os testes end-to-end são realizados para garantir que todas as partes do sistema estejam funcionando corretamente juntas, desde a interface do usuário até o backend, incluindo integrações com outros sistemas, se aplicável. Isso é feito para garantir que o sistema esteja se comportando conforme o esperado e atendendo aos requisitos do usuário final.
Os testes end-to-end são frequentemente usados para validar fluxos de trabalho completos em um aplicativo ou site, simulando a interação do usuário final com o sistema. Eles podem envolver a automação de cliques de mouse, preenchimento de formulários, navegação entre páginas e verificação de resultados. No entanto, a validação é um conceito que se aplica a múltiplos níveis de teste, incluindo o E2E, mas não é exclusiva dele.
As principais ferramentas para testes E2E (End-to-End) e soluções complementares (como Zod) para validação de dados, organizadas por contexto de uso:
Ferramentas Especializadas em E2E:
-
Para Aplicações Web
-
Cypress: Framework completo para testes E2E em navegadores, com suporte a simulação de interações (cliques, formulários) e debug em tempo real. Inclui Cypress Testing Library para boas práticas de seleção de elementos.
-
Playwright: Suporta múltiplos navegadores (Chromium, Firefox, WebKit) e linguagens (JS/TS, Python, .NET). Recursos como auto-wait, gravação de testes e testes em paralelo. O Playwright é uma biblioteca de automação de código aberto para testes de navegador e web scraping desenvolvida pela Microsoft e lançada em 31 de janeiro de 2020, que desde então se tornou popular entre programadores e desenvolvedores web.
-
Selenium: Mais antigo, mas ainda usado em projetos legados. Requer mais configuração (WebDriver).
-
-
Para APIs
- Supertest (Node.js): Biblioteca para testar APIs HTTP integrada ao Jest/Mocha. Valida status codes, responses e headers.
- Postman/Newman: Coleções de requisições podem ser automatizadas como testes E2E (com scripts em JavaScript).
-
Para Mobile
- Appium: Framework open-source para testes E2E em aplicativos Android/iOS.
- Detox: Focado em React Native e aplicações nativas, com suporte a sincronização automática.
-
Para Desktop
- Spectron (para Electron): Integra Selenium com o Electron para testar aplicações desktop.
Ferramentas Complementares (como Zod):
- Validação de Dados em Testes E2E
Zod: Valida esquemas de respostas de API ou estados da UI durante testes. Exemplo:
const LoginResponseSchema = z.object({ token: z.string() });
const data = LoginResponseSchema.parse(await response.json()); Joi: Similar ao Zod, mas mais usado em back-end (Node.js) para validar objetos.
-
Mock de Dados/APIs
-
MSW (Mock Service Worker): Intercepta requisições HTTP em testes E2E para simular APIs sem depender do back-end real.
-
JSON Server: Cria uma API fake baseada em um arquivo JSON para testes iniciais.
-
-
Asserções Avançadas
-
Jest/Vitest: Oferecem matchers (como
.toMatchObject()) para validar estruturas de dados em testes. -
Chai: Biblioteca de asserções para Mocha, com sintaxe legível (ex.:
expect(user).to.have.property('name')).
-
-
Monitoramento e Relatórios
-
Allure Report: Gera relatórios visuais detalhados de testes E2E.
-
Sentry: Captura erros em tempo real durante testes (útil para debug em CI/CD).
-
Exemplo de Fluxo com Ferramentas Combinadas
- Playwright simula um usuário fazendo login.
- MSW mocka a API de login (opcional).
- Zod valida se a resposta da API contém
{ token: string }. - Allure Report gera um dashboard com os resultados.
Quando Usar Cada Uma?
- Testes de UI Completa: Cypress/Playwright.
- APIs: Supertest + Zod/Joi.
- Mobile: Appium/Detox.
- Validação de Dados: Zod (TypeScript) ou Joi (JavaScript).
- Mock: MSW ou JSON Server.
Essas ferramentas podem ser combinadas para cobrir todos os aspectos de testes E2E, desde a interação do usuário até a integridade dos dados.
Como Executar Testes de Ponta a Ponta em Escala: Rodar testes E2E de forma confiável e eficiente é uma peça crítica do quebra-cabeça para qualquer organização de software.
Existem principalmente duas expectativas que as equipes de software têm quando se trata de testes:
- Envie o mais rápido possível sem introduzir (ou reintroduzir) bugs
- Realize os testes o mais barato possível, sem comprometer a qualidade.
Na edição de hoje, temos a sorte de receber o autor convidado John Gluck, Principal Defensor de Testes da QA Wolf. Ele compartilhará insights sobre a infraestrutura especializada da QA Wolf, capaz de executar milhares de testes E2E simultâneos em apenas alguns minutos e atender às expectativas de seus clientes.
QA Wolf é uma solução de serviço completo para equipes de produto de médio a grande porte que desejam acelerar seus ciclos de QA e reduzir o custo de construir, executar e manter uma cobertura abrangente de testes de regressão.
Além disso, Mufav Onus, da QA Wolf, falou no Kubecon 2024 em Paris sobre como eles retomam automaticamente os pods em momentos pontuais após paralisações inesperadas. Dá uma olhada.
O Desafio de Realizar Testes E2E Rodar testes E2E de forma eficiente é um desafio para qualquer organização. Os runners tendem a causar picos de recursos, o que faz com que testes e aplicações se comportem de forma imprevisível. Por isso, é bastante comum que grandes equipes de produto programem estrategicamente seus testes. À medida que o número de testes e de execuções aumenta, os desafios se tornam exponencialmente mais difíceis de superar.
Enquanto as maiores empresas do mundo podem realizar 10.000 testes de ponta a ponta por mês, e algumas poucas executam 100.000, a QA Wolf administra mais de 2 milhões. Em nossa escala, para suportar o número de clientes que atendemos, nossa infraestrutura precisa abordar três grandes preocupações:
Disponibilidade - Os clientes podem executar seus testes a qualquer momento, sem restrições quanto ao número ou frequência das execuções paralelas. O sistema deve estar altamente disponível. Não podemos usar truques de agendamento para resolver isso.
Velocidade - Os testes precisam rodar rápido. A DORA recomenda 30 minutos como tempo máximo para execução do conjunto de testes, e os clientes querem seguir esse princípio.
Confiabilidade - Problemas de contenda de nós, sequestro de instâncias e falhas na execução do sistema de teste (as coisas que arquitetos de teste internos lidam regularmente) simplesmente não são toleráveis quando as pessoas estão pagando para executar seus testes.
Para o bem ou para o mal, o StackOverflow não tinha projetos para o tipo de infraestrutura de execução de testes que precisávamos construir. O sucesso veio de muita experimentação e refinamento constante.
Neste post, discutimos os problemas que enfrentamos e as decisões que tomamos para que pudéssemos resolvê-los por meio de experimentação.
A Quebra da Stack Tecnológica
Para contextualizar, somos totalmente nativos da nuvem e construímos nossa infraestrutura na Google Cloud Platform (GCP).
Optamos pelo GCP por sua implementação GKE (Kubernetes) e capacidades de autoscaling de cluster, que são críticas para lidar com a demanda por nós de execução de testes. Existem ferramentas semelhantes por aí, mas nossos engenheiros também tinham experiência prévia com GCP, o que nos ajudou a começar.
Adotamos uma abordagem GitOps para poder executar muitos experimentos de configuração em nossa infraestrutura de forma rápida e segura, sem interromper as operações em andamento.
O Argo CD foi uma boa escolha por seu suporte a GitOps e Kubernetes. Uma combinação dos fluxos de trabalho Helm e Argo ajuda a tornar o processo de implantação consistente e organizado. Usamos os Conjuntos de Aplicações de CD Argo e os padrões App of Apps, que são considerados melhores práticas.
Para o IaC, escolhemos o Pulumi porque é open source e, ao contrário do Terraform, ele não obriga os desenvolvedores a adotarem outra DSL (Linguagem Específica de Domínio)
Por fim, usamos o Typescript para escrever os testes. Nossos clientes analisam o código de teste escrito para eles, e o Typescript facilita a compreensão. Escolhemos o Playwright como executor de testes e framework de teste por vários motivos, tais como:
A Playwright pode lidar com os testes complexos que os clientes podem precisar automatizar.
APIs mais simples e uma instalação mais fácil impedem que os clientes fiquem presos à nossa solução.
Ele conta com o apoio da Microsoft, e o desenvolvimento mais ativo está ampliando a lista de capacidades nativas.
O Ecossistema Para o ecossistema de infraestrutura, optamos por um VPC e três clusters principais de aplicações.
Cada um dos três clusters tem um papel específico:
O cluster de aplicações
A instância de teste ou cluster runner
Cluster de operações
O cluster de operações é o cluster principal e gerencia os outros dois clusters. O Argo CD roda dentro desse cluster.
Veja o diagrama abaixo que mostra essa disposição.
No momento da inicialização, o cluster de operações cria tanto o cluster de aplicação quanto o de runner. Ele fornece nós quentes no cluster runner, cada um contendo dois pods, e cada pod é construído sobre uma única imagem de container.
Veja o diagrama abaixo para referência:
Essa estrutura é totalmente descartável. Nossos desenvolvedores podem desmontar todo o sistema e reconstruí-lo do zero com o toque de um botão, o que aumenta a previsibilidade para os desenvolvedores e também é ótimo para apoiar a recuperação de desastres.
O autoescalonador de cluster da GKE escala os nós quentes do cluster runner para cima e para baixo conforme a demanda.
A Aplicação Voltada para o Cliente A aplicação voltada para o cliente é um IDE especializado onde nossos engenheiros de QA podem escrever, rodar e manter testes Playwright. Possui views para gerenciar configurações e integrações de terceiros com dashboards de visualização.
Testes de Escrita Os testes construídos e mantidos pelos nossos engenheiros internos de QA são autônomos, isolados, idempotentes e atômicos, podendo rodar de forma previsível em um contexto totalmente paralelizado.
Quando um engenheiro de QA salva um teste, a aplicação persiste o código do teste no GCS com seus assistentes correspondentes e qualquer configuração de análise sintética necessária para executá-lo no Playwright. Este é o Arquivo de Dados de Execução do teste. Caso você não saiba, GCS é o equivalente GCP ao AWS S3.
Na implementação inicial, tentamos passar o Arquivo de Dados Executados como um payload em HTML, mas o payload contendo o código de teste de todos os testes em uma execução era grande demais para Kubernetes etc. Para contornar isso, escolhemos o caminho de menor resistência escrevendo todo o código em um arquivo central e dando ao cliente uma referência à localização do arquivo para repassar ao aplicativo.
O Fluxo de Execução Como mencionado anteriormente, orquestramos execuções com fluxos de trabalho Argo porque ele pode rodar em um cluster Kubernetes sem dependências externas.
Clientes ou engenheiros de QA podem usar um agendador na aplicação ou uma chamada de API para iniciar um teste. Quando uma execução de teste é acionada, a aplicação reúne as localizações de todos os Arquivos de Dados de Execução necessários. Também cria um novo registro de banco de dados para cada execução de teste, incluindo um número de compilação único que atua como identificador para a solicitação de execução de teste. A aplicação usa o número de compilação posteriormente para associar logs do sistema e localizações de vídeo.
Por fim, ele passa a lista de localizações de arquivos Run Data para o serviço Run Director.
O diagrama abaixo mostra todo o fluxo de execução em um nível geral.
O Diretor de Corrida O Run Director é um serviço HTTP simples, duradouro e escalável horizontalmente.
Quando invocado, o Run Director reporta o status inicial da execução de teste para a aplicação via webhook e o número da compilação. Para cada local na lista, o Run Director invoca um template de Workflows do Argo e o hidrata com o arquivo Run Data nesse local. Ao realizar ambas as ações simultaneamente, os testes individuais podem ser iniciados mais rapidamente, permitindo que todos os testes na execução sejam concluídos mais rapidamente.
O Fluxo de Trabalho Argo então fornece um pod Kubernetes para cada execução de teste solicitada dos nós quentes disponíveis. Ele anexa o código de cada teste a um volume em um recipiente correspondente no pod. Essa abordagem nos permite usar a mesma build de contêiner para cada execução de teste. Se não houver pods suficientes para rodar em nós quentes, a GKE usa autoscaling de cluster para atender à demanda.
Cada teste roda em seu próprio pod e container, o que isola os testes e facilita para os desenvolvedores solucioná-los. Executar testes assim também limita os problemas de consumo de recursos ao nó onde os testes específicos estão tendo dificuldades.
O código de teste é executado a partir do ponto de entrada do contêiner. O Argo Workflow conduz o processo de provisionamento e inicia cada container com a ajuda do Kubernetes
O aplicativo executa todos os testes em navegadores com cabeçalho. Isso é importante porque o contêiner é destruído após o término do teste, e o navegador com cabeçalho possibilita capturar vídeos dos testes. Os vídeos são uma ferramenta essencial de depuração para saber o que aconteceu no momento, especialmente em casos em que é difícil recriar uma falha específica.
Devido ao alto padrão de autoria de testes e confiabilidade da infraestrutura, a principal causa de falha nos testes ocorre quando o sistema em fase de teste (SUT) não está otimizado para testes. Faz sentido quando você pensa bem. Quanto mais lento for o SUT, mais o teste é necessário para sondar, aumentando a demanda sobre o processador que executa o teste. Embora não possamos dizer ao cliente como construir sua aplicação para melhorar o desempenho dos testes, podemos isolar o consumo de recursos de cada teste para evitar que ele impacte outros testes.
A Detecção de Flocos Mantemos um padrão muito alto de autoria em testes, o que nos permite fazer certas suposições.
Como espera-se que os testes passem, podemos assumir com segurança que uma falha ou erro de teste é devido a uma anomalia, como um SUT temporariamente indisponível. A aplicação agenda essas falhas para retentativas automáticas. Ele sinaliza qualquer outra falha – como suspeita de problema de infraestrutura – para investigação e não tenta novamente. O Argo Workflows tentará executar um teste falhado três vezes.
Se os testes passarem na tentativa novamente, a aplicação é retomada normalmente e assume que a falha foi anômala. Caso todas as tentativas falham, o sistema cria um relatório de defeito, e um engenheiro de QA investiga para confirmar se a falha se deve a um bug na aplicação ou a algum outro problema.
Os Agrupamentos de Fragmentos de Corrida Uma das vantagens mais significativas do serviço Run Director é o conceito de Run Shard Clusters.
A estratégia de fragmentação nos permite distribuir os vários testes entre clusters localizados ao redor do mundo. Temos um VPC global do GCP com várias sub-redes diferentes em diferentes regiões. Isso possibilita provisionar clusters de fragmentação em diferentes regiões que podem ser acessados de forma privada via o serviço Run Director.
Agrupamentos de fragmentos oferecem várias vantagens, tais como:
Alta disponibilidade replicada - Se uma região cair, nem tudo para.
Testes mais próximos de casa - A capacidade de realizar testes de clientes próximos à região de origem resulta em um desempenho mais preciso de suas aplicações e sistemas.
Experimentação - Podemos experimentar diferentes versões da nossa implementação do Argo Workflows ou diferentes motores de execução sem reduzir o tráfego total para a mesma versão. Isso também nos permite experimentar medidas de economia de custos, como instâncias localizadas.
Relatórios
Claro, nossos clientes também querem ver os resultados dos testes, então precisávamos criar um sistema confiável que permitisse isso.
Quando o teste termina de rodar e tentar novamente (se necessário), o template do Argo Workflow faz upload de quaisquer artefatos de execução salvos pelo Playwright de volta para o GCS usando o número da compilação. Algumas dessas informações serão agregadas e aparecerão no painel da nossa aplicação. Outras informações desses artefatos são exibidas no nível de teste, como registros e histórico de execuções.
No lado da infraestrutura, o Fluxo de Trabalho Argo aciona o Kubernetes para desligar o container e desanexar o volume, garantindo que o sistema não deixe recursos desnecessários rodando. Isso ajuda a manter os custos operacionais baixos.
Conclusão Nossa abordagem única foi desenvolvida para atender às necessidades dos clientes em termos de velocidade, disponibilidade e confiabilidade. Somos uma das poucas empresas rodando testes e2e nessa escala, então precisávamos descobrir como criar um sistema que suportasse isso por meio de tentativa e erro; Por isso, projetamos nosso sistema para também suportar iterações rápidas. Nossa execução de testes paralelos completos e econômica é a espinha dorsal da nossa aplicação, e vemos que ela entrega valor aos nossos clientes diariamente.
Se você quiser saber mais sobre a infraestrutura de testes do QA Wolf ou como ela pode ajudar a lançar mais rápido com menos escapes, visite o site deles para agendar uma demonstração.
O BDD - Behavior-Driven Development (Desenvolvimento Orientado a Comportamento), é uma metodologia de desenvolvimento ágil que tem como foco a colaboração entre desenvolvedores, QA (Quality Assurance) e partes interessadas não técnicas para criar uma compreensão compartilhada do comportamento desejado de um software. O BDD é uma evolução do TDD (Test-Driven Development) e adiciona uma ênfase maior na comunicação e na clareza dos requisitos.
Em resumo, o BDD promove uma abordagem colaborativa para o desenvolvimento de software, focando em comportamentos e resultados esperados do sistema, o que ajuda a garantir que o software entregue atenda às necessidades reais dos usuários e stakeholders.
Aqui estão os componentes chave do BDD:
-
Foco no Comportamento: Em vez de se concentrar apenas na implementação técnica e nos testes de unidade, o BDD foca em como o software deve se comportar sob várias condições, incluindo o comportamento do usuário final.
-
Linguagem Ubíqua (Ubiquitous Language): Utiliza uma linguagem comum (frequentemente baseada em linguagens naturais como o inglês) que pode ser compreendida por todos os membros da equipe, incluindo desenvolvedores, QA, e stakeholders não técnicos. Isso ajuda a reduzir ambiguidades e garantir que todos tenham a mesma compreensão dos requisitos. A Linguagem Ubíqua (Ubiquitous Language) é um conceito central no Design Orientado a Domínio (DDD) que visa criar uma linguagem comum entre todos os envolvidos em um projeto, seja para os especialistas no domínio, desenvolvedores, ou mesmo os usuários finais. Essa linguagem comum facilita a comunicação e colaboração, reduzindo a possibilidade de mal-entendidos e melhorando a qualidade do desenvolvimento.
-
Especificações Executáveis: No BDD, os requisitos são escritos em forma de especificações que podem ser executadas como testes. Essas especificações geralmente seguem um formato estruturado, como Gherkin que é uma linguagem de domínio específico usada para descrever comportamentos esperados de um sistema de forma clara e compreensível por todos os envolvidos no desenvolvimento de software, incluindo pessoas não técnicas, que usa palavras-chave como "
Given" (Dado), "When" (Quando), e "Then" (Então) para descrever cenários de teste:-
Given(Dado): Descreve o contexto inicial ou o estado do sistema antes de uma ação específica. -
When(Quando): Descreve a ação ou evento que ocorre. -
Then(Então): Descreve o resultado esperado ou o comportamento do sistema após a ação.
-
Exemplo: Login no Sistema
Feature: Login no Sistema
Scenario: Login com credenciais válidas
Given: o usuário está na página de login
When: o usuário insere suas credenciais válidas
Then: o usuário é redirecionado para a página inicialExemplo 2: Pesquisar produto
Funcionalidade: Pesquisar produto
Eu como cliente
Quero fazer pesquisas no site da OLX
Para buscar por produtos
Cenário: Buscar produto com sucesso
Dado que estou no site da OLX como um comprador de SP
Quando eu fizer uma busca por um produto
Então serão exibidos os resultados de busca para o produto em SP- Ferramentas de BDD: Existem várias ferramentas que suportam BDD, ajudando a automatizar as especificações executáveis. Algumas das ferramentas populares incluem Cucumber (para várias linguagens como Java, Ruby), SpecFlow (para .NET), Behave (para Python), entre outras.
-
Benefícios do BDD: Uma das maiores virtudes do BDD - Behavior-Driven Development é unir os dois mundos TDD e DDD, de fato é representar uma interseção entre TDD (Test-Driven Development) e DDD (Domain-Driven Design), pois ele nasce da necessidade de alinhar o desenvolvimento técnico com o entendimento do negócio, garantindo que o software reflita o comportamento esperado do sistema a partir da perspectiva do usuário ou domínio. BDD pode ser visto como a interseção onde a clareza de intenção do domínio (trazida pelo DDD) se encontra com a prática de testar antes de desenvolver (como propõe o TDD).
-
Melhor Comunicação: Facilita a comunicação entre todos os membros da equipe, garantindo que todos entendam os requisitos de maneira clara e compartilhada.
-
Desenvolvimento Orientado a Valor: Foca no que realmente importa para os usuários finais e stakeholders, ajudando a priorizar o desenvolvimento de funcionalidades de maior valor.
-
Menos Retrabalho: Reduz ambiguidades nos requisitos, diminuindo o risco de desenvolvimento de funcionalidades incorretas ou desnecessárias.
-
Documentação Viva: As especificações atuam como uma documentação viva que está sempre em sincronia com o comportamento atual do sistema.
-
O ATDD - Acceptance Test-Driven Development é uma prática e uma variação do desenvolvimento orientado a testes (TDD) que coloca o teste de aceitação como ponto central do ciclo. Enquanto no TDD tradicional o desenvolvedor escreve primeiro testes unitários para depois implementar o código que os satisfaz, no ATTD o processo começa com a definição dos testes de aceitação, geralmente descritos em linguagem mais próxima do negócio, representando os critérios que o sistema precisa cumprir para ser aceito pelo cliente ou pelo usuário final.
O User Acceptance Testing (UAT), ou Teste de Aceitação do Usuário, é uma etapa final e crucial dentro do ciclo de testes de software. Ele acontece quando o sistema já passou por testes internos como unitários, de integração, funcionais e de sistema e está teoricamente pronto para ser colocado em produção.
A ideia do UAT é validar se o software realmente atende às necessidades do usuário final e aos requisitos de negócio. Aqui, o foco não é tanto verificar se o código funciona sem erros técnicos, mas se a solução é útil, intuitiva e cumpre aquilo que foi solicitado e esperado. Por isso, normalmente é realizado pelos próprios usuários, clientes, stakeholders ou representantes do negócio, com o suporte da equipe de QA.
Dentro do ecossistema de QA, o UAT se integra como a camada de validação final que coroa todo o pipeline de testes. Em um fluxo típico, o software passa por testes unitários para verificar pequenos blocos de código isolados, depois vai para testes de integração para garantir que os módulos funcionem em conjunto, segue para testes funcionais e de sistema que avaliam requisitos e comportamento completo, pode ainda passar por testes não funcionais como performance, segurança e usabilidade. Só depois desse ciclo é que entra o UAT, pois não faria sentido colocar o usuário para validar algo que ainda tem falhas básicas de funcionamento ou de estabilidade.
O UAT funciona quase como um “filtro de realidade”: ele conecta a linguagem técnica do QA com a linguagem de negócio do cliente. Muitas vezes, mesmo um sistema tecnicamente correto pode falhar no UAT se não atender ao fluxo real que o usuário precisa seguir no dia a dia. É comum, por exemplo, que durante o UAT se descubra que um processo está tecnicamente certo, mas exige muitos cliques desnecessários, ou não reflete a forma como o trabalho é feito na prática. Assim, ele serve para alinhar expectativas, corrigir ajustes de usabilidade e garantir que, no momento do go-live, o software não só funcione, mas também seja aceito pelo público a que se destina.
Em termos de QA, você pode pensar no UAT como o elo entre testes de qualidade técnica e testes de valor de negócio. Ele não substitui os outros, mas se apoia neles: se os testes anteriores garantem que o software é confiável, o UAT garante que ele é útil e aprovado pelo cliente.
Muita gente acaba confundindo os papéis de UAT, MVP, Release e Continuous Delivery, porque todos estão ligados à entrega e validação do software, mas cada um tem uma posição e função distintas dentro do ciclo de desenvolvimento. Para não misturar os conceitos, vale enxergar como uma sequência natural, onde cada etapa se apoia na anterior.
O ciclo começa antes mesmo do UAT, com a concepção do MVP (Minimum Viable Product). Aqui o objetivo é construir a versão mínima do produto que já entrega valor real para o usuário, mesmo que com funcionalidades limitadas. Esse MVP é fruto de testes técnicos e de negócio, mas ainda não é a versão que vai para produção de forma plena — ele serve como uma forma de validar hipóteses, ganhar feedback rápido e evitar investimentos pesados em algo que o usuário não queira.
Uma vez que o MVP esteja pronto em termos técnicos, entra o UAT (User Acceptance Testing). É nessa fase que o usuário final, ou representantes de negócio, testam se o que foi construído atende de fato às suas necessidades. É o momento de validar se o software não só funciona, mas se está apto a ser aceito. Esse ponto é delicado: não se deve liberar uma Release ou acionar um pipeline de Continuous Delivery antes de o UAT aprovar. O UAT funciona como um “checkpoint” de aceitação.
Com o UAT aprovado, abre-se espaço para a Release, que é a entrega oficial de uma versão do software. A Release pode ser do próprio MVP (caso aprovado), ou de incrementos posteriores. Aqui o software deixa o ambiente de homologação/teste e vai para produção. É importante diferenciar: Release não é sinônimo de deploy. O deploy pode ser frequente (parte do CD), mas a Release é uma decisão de negócio, de colocar uma versão nas mãos do cliente com um pacote de funcionalidades definido.
O Continuous Delivery (CD) é a engrenagem que sustenta tudo isso. Ele garante que o software esteja sempre em condição de ser liberado: builds automatizados, testes automatizados, pipelines de integração e entrega prontos. O CD assegura que qualquer mudança de código possa ser validada e empacotada rapidamente para uma possível Release, mas não substitui a decisão de liberar, que continua dependendo de critérios como UAT e estratégia de produto.
O passo a passo adequado, sem bagunça, pode ser entendido assim: primeiro você constrói o MVP para validar hipóteses → depois submete esse MVP (ou incrementos subsequentes) ao UAT para validação de negócio → com o UAT aprovado, gera uma Release oficial que pode ser entregue aos usuários finais → e todo esse processo é sustentado pelo Continuous Delivery, que dá agilidade e confiabilidade ao ciclo.
Ou seja, MVP é o “o quê” inicial, UAT é o “pode ser aceito?”, Release é o “vai para produção”, e Continuous Delivery é o “como fazer isso de forma contínua e confiável”.
Nos diferentes ecossistemas de linguagens, o conceito de testes de aceitação varia bastante, mas em geral envolve frameworks que permitem validar o comportamento da aplicação do ponto de vista do usuário final, muitas vezes com suporte a BDD (Behavior Driven Development) ou integração com navegadores. Vou listar os mais conhecidos em cada um dos ecossistemas:
C/C++ Como o foco dessas linguagens é mais baixo nível, há menos ferramentas prontas para testes de aceitação de aplicações web. Ainda assim, existem opções:
- CppUnit – mais voltado para testes de unidade, mas pode ser usado em cenários de aceitação.
- Cucumber-Cpp – integração do Cucumber com C++, permite escrever testes de aceitação em Gherkin.
- GoogleTest (gtest) – é mais unitário, mas com a disciplina certa pode ser usado para aceitação.
C# (.NET) O ecossistema .NET tem um conjunto forte de ferramentas de testes de aceitação:
- SpecFlow – equivalente ao Cucumber, usa Gherkin para BDD.
- Selenium WebDriver – para testes de aceitação de interfaces web.
- Playwright for .NET – automação de browsers moderna, usada também em testes de aceitação.
- xBehave.net – extensão do xUnit para BDD.
Ruby É um dos ecossistemas mais ricos nesse aspecto:
- Capybara – padrão de fato em Rails para testes de aceitação.
- Cucumber – BDD com escrita em Gherkin.
- RSpec + Capybara – combinação clássica para acceptance testing.
- Turnip – DSL alternativa integrada ao RSpec para cenários de aceitação.
PHP A comunidade PHP tem boas ferramentas para BDD e acceptance testing:
- Behat – inspirado no Cucumber, usa Gherkin.
- Codeception – framework poderoso que cobre testes unitários, funcionais e de aceitação.
- Laravel Dusk – no ecossistema Laravel, usado para testes de aceitação com browser automation.
JavaScript (Node.js e frontend) Um dos ecossistemas mais ativos em testes de aceitação:
- Cypress – muito popular para testes end-to-end e aceitação de aplicações web.
- Playwright – concorrente moderno do Cypress, altamente estável.
- Puppeteer – automação do Chrome/Chromium, usado para acceptance testing.
- Nightwatch.js – framework baseado em Selenium.
- Jest + Testing Library (React Testing Library, por exemplo) – pode ser usado para acceptance em SPAs.
Python A comunidade Python também é forte em BDD e testes de aceitação:
- Behave – equivalente ao Cucumber, usa Gherkin.
- Lettuce – BDD inspirado no Cucumber.
- Robot Framework – muito popular para acceptance testing, com DSL própria.
- pytest-bdd – extensão do pytest para BDD.
- Selenium / Playwright Python – para automação de browser em testes de aceitação.
Go (Golang) Go tende a ser mais pragmático, mas há bibliotecas para acceptance/BDD:
- Godog – equivalente ao Cucumber, com Gherkin.
- Agouti – para acceptance testing com integração a Selenium e PhantomJS.
- Ginkgo – framework BDD muito usado, ainda que mais comum em unit/integração.
Rust Rust ainda está amadurecendo em testes de aceitação, mas já há ferramentas:
- Cucumber-rs – implementação do Cucumber para Rust.
- cucumber (new) – projeto moderno para BDD em Rust.
- assert_cmd + predicates – muitas vezes usados juntos para acceptance testing de CLIs.
- Thirtyfour – WebDriver client para Rust (similar ao Selenium).
Elixir O ecossistema Elixir tem boas ferramentas alinhadas ao Phoenix (framework web):
- Wallaby – biblioteca para acceptance testing de aplicações web com browser automation.
- Hound – wrapper em Elixir para Selenium/WebDriver.
- ExUnit + BDD-style DSLs – muitas vezes usado diretamente com helpers de alto nível.
Se você reparar, há um padrão: quase todos os ecossistemas têm algum tipo de integração com Cucumber/Gherkin para cenários de aceitação e ferramentas ligadas a browser automation (Selenium, Playwright, Cypress etc.), e depois cada comunidade cria bibliotecas mais idiomáticas para seu ambiente.
A ideia é alinhar desde o início o que será construído com o que realmente tem valor para o negócio. Os testes de aceitação funcionam como especificações executáveis: descrevem cenários, entradas, saídas e comportamentos esperados do sistema em termos que os stakeholders entendem. Depois disso, os desenvolvedores implementam o código necessário para fazer esses testes passarem. Em muitos casos, ferramentas como Cucumber, SpecFlow ou Behave são usadas para escrever cenários em Gherkin (“Given, When, Then”), permitindo que as próprias partes interessadas consigam validar e até revisar os testes.
Na prática, o ATTD acaba funcionando como uma ponte entre BDD e TDD. Ele compartilha com o BDD a preocupação de usar exemplos de negócio como base para o desenvolvimento, mas mantém a disciplina do TDD de usar testes automatizados como motor do ciclo. Isso ajuda a garantir que o software entregue não só funcione tecnicamente, mas também atenda ao valor esperado pelos usuários, reduzindo retrabalho e mal-entendidos.
O ATDD (Acceptance Test-Driven Development) é uma prática de desenvolvimento guiada por testes de aceitação, escritos antes do código, em colaboração entre desenvolvedores, QA e stakeholders. O foco dele é garantir que o software atenda aos critérios de aceitação do negócio. Em essência, ele antecipa o que seria validado lá na frente pelo UAT (User Acceptance Testing), mas de forma automatizada e contínua, desde o início do ciclo.
O TDD (Test-Driven Development) é mais granular: foca no nível do código. Primeiro você escreve um teste unitário que falha, depois escreve o mínimo de código para passar no teste e, por fim, refatora. Ele garante qualidade técnica, mas não necessariamente que o software atenda ao que o usuário quer.
Já o UAT (User Acceptance Testing) é uma fase final, manual ou semi-manual, em que os usuários ou representantes de negócio validam se o sistema está de acordo com suas expectativas. Ele é mais subjetivo e humano, enquanto ATDD tenta trazer essa visão de aceitação para dentro do ciclo técnico.
Dá pra pensar assim:
- O ATDD cria um ciclo colaborativo (negócio + dev + QA) para escrever critérios de aceitação como testes automatizados.
- O TDD atua no detalhe técnico para garantir que cada unidade de código funciona.
- O UAT é a validação real pelo usuário antes da release.
Ou seja, o ATDD não é o UAT implementado dentro do ciclo TDD, mas ele faz uma ponte: traz os critérios de aceitação (que seriam verificados no UAT) para dentro do desenvolvimento, lado a lado com o TDD. Em um fluxo maduro, você teria TDD para o nível de código, ATDD para o nível de aceitação de requisitos, e UAT no fim para validar com pessoas reais.
































































