Específicos · Engenharia de Software
Testes: pirâmide, unitário, integração, TDD, BDD (Gherkin); verificação × validação
Todo teste se define pelo padrão contra o qual compara — especificação é verificação, necessidade do cliente é validação —, e quase todo item errado do tópico descreve um tipo de teste…
Altíssima86 itens no tópico
A ideia que organiza o assunto
Software não falha porque alguém digitou errado. Falha porque alguém entendeu errado — o requisito, a fronteira de um intervalo, o contrato entre dois módulos — e o erro atravessou meses até aparecer em produção, onde custa cem vezes mais para consertar. Teste é a disciplina que tenta antecipar esse encontro.
Cada teste se define por duas perguntas, e responder a elas resolve a maior parte dos itens sem precisar de mais nada.
A primeira é contra o que ele compara:
- contra a especificação → é verificação: estamos construindo o produto corretamente? É interna, técnica, feita pela equipe.
- contra a necessidade real do cliente → é validação: estamos construindo o produto certo? É externa, e quem tem a palavra final é o usuário.
Um sistema pode passar em toda a verificação e ser inútil: implementa perfeitamente a especificação errada. É por isso que as duas existem.
A segunda pergunta é de onde vêm os casos de teste:
- da especificação, sem olhar o código → caixa-preta (funcional).
- da estrutura interna do programa → caixa-branca (estrutural).
Dessas duas perguntas sai o método de prova deste tópico, e ele é mecânico: leia a descrição primeiro, decida que teste é aquele, e só depois olhe o nome que o item escreveu. O molde dominante da banca aqui é definir um tipo de teste com precisão e assinar outro — “em um teste de integração, cada unidade é testada separadamente” é a descrição perfeita do teste de unidade. Quem julga pelo nome cai; quem julga pela descrição não precisa decorar item nenhum.
Por que se usa (e o que custa)
O ganho óbvio é encontrar defeito cedo. O menos óbvio, e mais cobrado, é que teste é também projeto: escrever o caso antes obriga a decidir o que o componente faz, qual é sua interface e o que conta como sucesso, antes de haver código para defender. É daí que saem o TDD e o BDD.
O ganho tem dois preços. O primeiro é manutenção: suíte automatizada é código, e código apodrece. Quando a interface do usuário muda muito e rápido, automatizar sai mais caro do que testar à mão — automação compensa no que é estável e repetido, não no que ainda muda de forma toda semana. O segundo é que teste não prova ausência de defeito: ele revela presença. Cobertura total do domínio de entrada é inviável, e é por isso que existem técnicas de seleção — partição de equivalência, valor limite, diretrizes de erros comuns — em vez de força bruta.
Como funciona
Estático × dinâmico. O teste dinâmico executa o programa com entradas escolhidas e compara a saída com a esperada. O estático examina o artefato sem executá-lo: revisão por pares, inspeção, walkthrough, análise estática de código. O estático acha o que o dinâmico nunca acha — complexidade excessiva, código morto, violação de padrão — e acha mais cedo e mais barato, porque não precisa de ambiente montado. Revisão por pares, portanto, não exige execução.
Os níveis, de dentro para fora. Unidade exercita o menor elemento com comportamento próprio, método ou classe, isolado das demais partes. O nível de componente trata o componente já montado e olha suas interfaces. Integração combina componentes já testados e ataca os erros que só existem entre eles: parâmetro trocado, contrato mal entendido, suposição incompatível. Sistema testa o todo montado contra a especificação. Aceitação confronta o sistema com a necessidade real, com o cliente decidindo.
Para isolar uma unidade é preciso substituir suas dependências pelos dublês. Um stub devolve resposta pronta e apoia a verificação de estado; um mock carrega expectativas sobre quais chamadas deveriam ocorrer e apoia a verificação de comportamento. Nenhum é genérico: cada um é construído para o cenário daquele teste.
A pirâmide. Base larga de testes de unidade — baratos, rápidos, muitos; meio de integração/serviço, mais caros e em menor número; topo estreito de interface/ponta a ponta, lentos e frágeis. Inverter a proporção produz a figura que a literatura chama de cone de sorvete: suíte lenta, que quebra por qualquer motivo e não diz onde está o defeito. Existe uma segunda pirâmide que a banca já usou, a de teste de aplicações web, e ela descreve outra coisa: a ordem do processo, que começa pelo imediatamente visível ao usuário (conteúdo e interface), segue para navegação, projeto e componentes e só ao final chega a infraestrutura, configuração, desempenho e segurança — da esquerda para a direita e de cima para baixo.
As técnicas de caixa-preta. O particionamento de equivalência divide o domínio de entrada em classes cujos elementos o programa deve tratar de forma equivalente, e testa um representante de cada — classes válidas e inválidas. A análise do valor limite vai às bordas, onde o defeito mora: o valor no limite e os imediatamente vizinhos, com casos derivados tanto do domínio de entrada quanto do de saída. A caixa-branca deriva casos de caminhos, condições, laços e fluxo de dados, e é a única que enxerga o código.
TDD. Ciclo de três estados: vermelho (escreve-se o teste, que falha), verde (escreve-se o mínimo de código que o faz passar), refatorar (melhora-se a estrutura interna sem alterar o comportamento externo). A regra operacional é seca: só se escreve código novo quando um teste automatizado falha. A suíte acumulada é o que torna a refatoração segura.
BDD. O TDD movido para a linguagem do negócio. Em vez de identificadores técnicos, nomes de teste que são frases com significado real, no registro das histórias de usuário; os cenários se escrevem em Gherkin, com as palavras-chave given, when, then. Quem fala a linguagem do domínio do negócio é o BDD; o TDD é escrito na linguagem de programação, pelo desenvolvedor.
Automação e JUnit. Os princípios FIRST governam o teste automatizado de
unidade: fast, independent, repeatable, self-validating, timely. No
JUnit os testes são independentes e não devem depender da ordem — mas a
ferramenta permite fixá-la (@FixMethodOrder, @TestMethodOrder/@Order).
@BeforeClass roda uma única vez antes de todos os testes da classe, e é onde se
põe o que custa caro (abrir conexão, carregar massa). @EnabledOnOs
restringe a execução aos sistemas operacionais listados. Testes
parametrizados existem: dizer que o JUnit não aceita parâmetros é falso.
O que decide os itens
Verificação × validação — a distinção que decide mais itens que todas as outras somadas:
| compara com | pergunta | quem faz | |
|---|---|---|---|
| verificação | a especificação | construímos certo o produto? | a equipe |
| validação | a necessidade do cliente | construímos o produto certo? | o usuário/cliente |
Nível → foco → responsável:
| nível | exercita | quem decide |
|---|---|---|
| unidade | método, função, classe isolada | desenvolvedor |
| componente | interfaces do componente montado | desenvolvedor |
| integração | interfaces entre módulos já testados | equipe de teste |
| sistema | o sistema inteiro contra a especificação | equipe de teste |
| aceitação | o sistema contra a necessidade real | cliente/usuário |
Nome do teste → objetivo. É aqui que a banca mais troca etiquetas:
| nome | objetivo |
|---|---|
| regressão | a alteração quebrou o que já funcionava? (causa: mudança) |
| fumaça (smoke) | o build integrado sobe e as funções principais rodam? rápido e frequente |
| carga | comportamento sob demanda esperada |
| estresse | condições anormais — recursos em quantidade, frequência ou volume acima do previsto, até a ruptura |
| desempenho | tempo de resposta e vazão sob carga definida |
| lançamento (release) | libera a versão — inclui regressão obrigatoriamente |
Caixa-preta × caixa-branca:
| fonte dos casos | técnicas | no pentest | |
|---|---|---|---|
| caixa-preta | especificação, comportamento externo | partição de equivalência, valor limite | sem informação prévia |
| caixa-branca | estrutura interna, código-fonte | cobertura de comando, decisão, condição, caminho | com topologia, código e credenciais |
TDD × BDD × refatoração:
| o que é | |
|---|---|
| TDD | teste antes do código, na linguagem de programação; vermelho, verde, refatorar |
| BDD | cenários na linguagem do negócio, em Gherkin: given, when, then |
| refatoração | melhorar a estrutura interna sem alterar o comportamento externo — é a 3ª fase do TDD, não o TDD |
Stub × mock — stub verifica estado, mock verifica comportamento.
Qualidade interna × externa — unidade informa a interna (projeto do código, acoplamento); aceitação informa a externa (o que o usuário percebe).
Bottom-up × top-down na integração — bottom-up parte da infraestrutura e precisa de drivers; top-down parte do topo e precisa de stubs.
Estático × dinâmico — revisão, inspeção e análise estática não executam; unidade, integração, sistema e aceitação executam.
Números que caem
| princípios FIRST | 5: fast, independent, repeatable, self-validating, timely |
| estados do ciclo TDD | 3: vermelho, verde, refatorar |
| blocos do cenário Gherkin | 3: given, when, then |
partições de um intervalo fechado [a, b] | 3: abaixo de a, dentro, acima de b |
| casos mínimos para cobrir essas partições | 3, desde que um em cada faixa (um abaixo, um dentro, um acima) |
| fronteiras de n faixas contíguas | n − 1 |
| casos mínimos de valor limite por fronteira | 2 (o valor no limite e o imediatamente fora) |
| logo, 3 faixas contíguas em valor limite | no mínimo 4 casos, não 3 |
@BeforeClass | executa 1 vez por classe; @Before, 1 vez por método de teste |
Como a CEBRASPE derruba você aqui
Inversão — mais de um terço dos itens errados, e o tópico é feito de pares.
A banca gira metade de um par e deixa o resto intacto. Antes × depois: no TDD, a
especificação de teste escrita “logo após o código”. TDD × BDD: no TDD
“utiliza-se uma linguagem derivada do domínio do negócio para a definição dos
casos de testes” — essa é a linguagem do BDD. Stub × mock: “os stubs,
diferentemente dos mocks, são mais apropriados para a verificação do
comportamento da aplicação” — stub devolve estado, mock verifica
comportamento. Unidade × aceitação: “os testes de unidade propiciam a qualidade
externa” — unidade informa qualidade interna. Sentido do fluxo: um teste de
aplicação web com “um fluxo de processo da direita para a esquerda e adota uma
estratégia bottom-up”, quando se começa pelo que o usuário vê. E a propriedade
definidora negada: o JUnit que “não permite que se interfira na ordem de
execução de métodos de teste”; “a impossibilidade de definição de parâmetros
para construtores e métodos”, quando testes parametrizados existem;
@EnabledOnOs que “permite que testes sejam executados em qualquer sistema
operacional”, quando ele restringe; testes que “mantêm uma relação de
dependência entre si”; o singleton que “facilita o teste unitário”, quando
compromete o isolamento; o teste de lançamento “sem a necessidade de reexecução
de testes em funcionalidades já existentes”, quando regressão é exatamente isso.
Monte o par correto na cabeça antes de julgar; não deixe o item montá-lo para
você.
Definição certa, nome errado — quase um terço. A frase descreve com precisão um tipo de teste e assina outro. “Testes de aceitação buscam garantir que o sistema opere com cargas de trabalho variáveis” (é estresse); testes de regressão que servem a “verificar se o sistema pode operar na carga necessária” (é estresse); testes de integração que “buscam assegurar que o sistema opere com a carga necessária” (é estresse de novo); “Em um teste de integração”, quando o item descreve cada unidade avaliada isoladamente (é unidade); “Os testes de integração” que verificam conformidade com os requisitos do cliente (é aceitação); a “verificação de aceitação do servidor de aplicação” para o que é teste de unidade. Atalho que funciona quase sempre: a palavra carga elimina unidade, integração e aceitação como resposta. O mesmo molde atinge outros pares: “O TDD (test-driven development)” recebendo a definição de refatoração; refatorar descrito como “a simulação do comportamento dos componentes que interagem com a unidade de teste que está falhando”, que é dublê de teste; “No teste de penetração de caixa branca não são fornecidas informações prévias à equipe de testadores”, que é caixa-preta; e o FIRST recebendo um sexto princípio inventado, “flexíveis na aplicação em diferentes contextos”.
Fase, ator ou processo errado — um em cada sete. A ação é real, o dono ou o momento não. “Na etapa de projeto do ciclo de desenvolvimento de um software” realiza-se o aceite — o aceite fecha o ciclo. A equipe de desenvolvimento é quem “valida o uso do software e o libera para utilização pelo usuário final” — a aceitação é validação e cabe ao cliente. A revisão por pares apresentada como algo “que exige que o programa seja colocado em condições de execução próximas ao ambiente real de operação” — revisão é teste estático e não executa o programa. Vale também para nível de maturidade: “No nível F do MPS/BR”, quando verificação e validação são processos do nível D.
Absolutos. “Apenas as partições válidas.” Mocks como “objetos genéricos que atendem a todas as necessidades de testes”. No TDD, “é necessário incluir nos métodos elementos que possibilitem a captura dos dados durante o processo de testes” — o TDD não instrumenta código de produção. Achado o apenas, todas, impossível ou necessário, procure o caso que a palavra exclui.
Números. Só dois itens, e os dois na mesma ideia: oferecer valores que não cobrem o que a técnica exige. “21, 48 e 75” como conjunto mínimo para cobrir todas as partições de entrada de idades válidas entre 21 e 75 — falta um valor inválido de cada lado. E “três casos de teste” para 100% de cobertura de análise de valor-limite sobre três faixas — valor-limite conta fronteiras, não faixas.
Causa inventada. “Os testes no JUnit são realizados em sequência, por isso mantêm dependência entre si.” Ou, fora do laboratório, a solução “com efeitos mais imediatos será a contratação, com urgência, de mais programadores” para um projeto atrasado — a Lei de Brooks diz o contrário. A oração explicativa soa técnica e carrega a mentira; item que explique um porquê merece segunda leitura.
Erros clássicos
Achar que verificação e validação são sinônimos. Verificação olha a especificação; validação olha a necessidade. O processo de V&V precisa demonstrar as duas coisas — que o sistema atende à sua especificação e que seu comportamento sustenta os requisitos do cliente.
Trocar qual nível informa qual qualidade. Unidade dá retorno sobre a qualidade interna (projeto dos módulos, acoplamento, custo de manutenção); aceitação, sobre a externa. É contraintuitivo e por isso é cobrado.
Esquecer as classes inválidas. Particionamento sem entrada inválida testa metade do programa, justamente a metade em que o tratamento de erro mora.
Tratar caixa-preta como “teste de função” e nada mais. Ela também é o caminho para requisitos não funcionais — desempenho, valores-limite, compatibilidade —, porque avalia o sistema pelo lado de fora, que é onde esses requisitos se manifestam.
Achar que o Singleton facilita o teste. Instância única é estado global compartilhado entre testes: quebra o isolamento, impede substituir a dependência por dublê e derruba o I de FIRST.
Supor que TDD exige instrumentar o código de produção. Não exige. O teste exercita a interface pública; incluir nos métodos elementos para “capturar dados durante o teste” contamina o código com responsabilidade que não é dele.
Tratar teste de lançamento como “só o que é novo”. Toda versão nova reexecuta regressão sobre o que já existia — é aí que moram os defeitos introduzidos pela mudança.
Confiar que herança herda o teste. Método definido na superclasse executa em contexto diferente em cada subclasse, e precisa ser reexecutado em cada uma.
LidoPraticado