Modified Condition / Decision Coverage (MC/DC) ou Cobertura de condição/decisão modificada é uma técnica que pode ser decisiva para apoiar na otimização da realização de testes sem deixar a cobertura do código de lado.
Neste artigo, vamos conferir MC/DC na prática nesse contexto!
Testes
Você pode aplicar diferentes tipos de testes em seu projeto de software, testes de unidade, integração, performance, dentre outros…
Neste conjunto de técnicas, os testes de unidade são responsáveis por validar pontos de entrada e saída da sua aplicação, sendo normalmente bem aceitos por terem um baixo custo de execução e por poderem ser implementados em um tempo razoável.
Pode-se utilizar diferentes técnicas na construção de testes de unidades, inclusive técnicas que não olham para o código da aplicação (caixa preta), porém, dentre essas técnicas, existem os testes estruturais, onde se orienta a escrita dos testes a partir da análise do código desenvolvido (caixa branca), trazendo uma verificação mais precisa do funcionamento da aplicação, diminuindo o número de erros no sistema e contribuindo com uma maior qualidade e confiabilidade do produto entregue.
Para Aniche, em seu livro Effective Software Testing, em poucas palavras, precisamos de testes estruturais por dois motivos:
- Derivar sistematicamente testes do código-fonte;
- Saber quando parar de testar.
Cobertura de testes
Para que isso seja possível, os testes estruturais têm a responsabilidade de garantir que critérios de cobertura de testes estejam presentes em sua aplicação, ou seja, garantir métricas que possam lhe ajudar a entender até que ponto o seu código fonte está sendo testado.
A cobertura de testes é uma métrica muito útil que pode lhe ajudar a avaliar a qualidade do conjunto, evitando que você receba bugs no seu projeto de surpresa.
Para o pesquisador Paul Ammann, você pode e deve utilizar um conjunto de regras para medir a porcentagem de cobertura de testes no código que foi desenvolvido, onde uma ou mais dessas regras podem ser utilizadas para lhe ajudar a entender o que já está sendo “coberto”, logo testado; e o que está “descoberto”, logo, precisando de testes.
Critérios básicos de cobertura de testes
Ao analisar a cobertura de código, pode-se utilizar um ou mais critérios para determinar como seu código foi coberto ou não durante a execução de seu conjunto de testes.
As métricas mais comumente utilizadas e geradas por ferramentas como JUnit, JaCoCo, PHPUnit e Coverage.py, são:
- Cobertura de função: quantas das funções definidas foram chamadas;
- Cobertura de instrução: quantas das instruções no programa foram executadas;
- Cobertura de ramificações: quantas ramificações das estruturas de controle (instruções if, por exemplo) foram executadas;
- Cobertura de condição: quantas das subexpressões booleanas foram testadas para um valor verdadeiro e falso;
- Cobertura de linha: quantas linhas de código-fonte foram testadas.
Para o cálculo dessas métricas geralmente se leva em consideração: (i) o número de itens já testados; (ii) o número de itens encontrados em seu código e não testados; e (iii) a identificação da porcentagem de cobertura baseada nestes dois fatores, dessa forma:
Porcentagem de cobertura = (itens testados/itens encontrados)*100
Há um paradigma na área de testes de software que orienta que quanto mais testes e quanto maior a cobertura de testes na sua aplicação, maior a chance do seu projeto ter uma boa qualidade.
Porém, percebe-se que principalmente em sistemas complexos, com extensas regras de negócio e várias linhas de código, a escrita dos testes pode se tornar algo bem trabalhoso.
Desta forma, a definição de saber bem o que deve ser testado para escrever menos testes e mesmo assim garantir a qualidade da cobertura do seu código pode ser determinante para o equilíbrio de uma entrega de qualidade e dentro do prazo de uma aplicação.
Neste contexto, vamos apresentar e discutir mais sobre o Modified Condition / Decision Coverage (MC/DC) ou Cobertura de condição/decisão modificada, como uma técnica que pode ser decisiva para lhe apoiar nestas situações.
MC/DC
O MC/DC cobre os pontos de entrada e saída do programa, verificando se foram invocados pelo menos uma vez e se cada decisão no programa assumiu todos os resultados possíveis pelo menos uma vez.
Esse detalhe, esse pequeno detalhe, faz muita diferença no quantitativo de testes necessários para garantir a cobertura do código.
O MC/DC em sua literatura original requer que todos os itens abaixo sejam contemplados durante a escrita dos testes:
- Cada ponto de entrada e saída deve ser invocado;
- Cada decisão deve levar a todos os resultados possíveis;
- Cada condição em uma decisão deve levar todos os resultados possíveis;
- Cada condição em uma decisão deve afetar independentemente o resultado da decisão.
A independência de uma condição é mostrada provando que apenas uma condição muda por vez, logo, determinando as independências pode-se gerar testes mais assertivos, sem ter que testar o que já foi testado.
Inclusive, temos uma trilha especial sobre testes no Zup Decodifica e, em um dos episódios, citamos o MC/DC. Vale acompanhar:
Cálculo de quantidade de testes
Você pode se perguntar: como normalmente é calculado quantos testes preciso escrever para cenários condicionais? Para compreender, vamos analisar este exemplo:
if(A && B && C) {
return D;
}
Seguindo a lógica teríamos que escrever 8 testes para cobrir os cenários possíveis deste trecho de código, gerando combinações de valores verdadeiros e falsos para cada condição (gerando uma tabela verdade), logo, seria preciso escrever testes que contemplassem:
Teste | A | B | C | Resultado |
1 | Verdadeiro | Verdadeiro | Verdadeiro | Verdadeiro |
2 | Verdadeiro | Verdadeiro | Falso | Falso |
3 | Verdadeiro | Falso | Verdadeiro | Falso |
4 | Verdadeiro | Falso | Falso | Falso |
5 | Falso | Verdadeiro | Verdadeiro | Falso |
6 | Falso | Verdadeiro | Falso | Falso |
7 | Falso | Falso | Verdadeiro | Falso |
8 | Falso | Falso | Falso | Falso |
Vemos que conseguimos chegar no número total de testes necessários para este cenário pela fórmula:
Nº de testes = 2 ^ Nº de condições
Nesse caso:
2 ^ 3 = 8 testes
Mas como calculamos isso usando o MC/DC?
Agora vem a mágica de se utilizar o MC/DC nesses cenários. O MC/DC direciona que os testes que serão escritos devem focar em análise dos pares de independência, ou seja, remover testes desnecessários que levariam a resultados já cobertos em outros cenários testados anteriormente. Então, considerando a mesma implementação:
if(A && B && C) {
return D;
}
Precisamos agora gerar um cenário verdadeiro e um cenário falso para a condição A, só alterando esta condição em específico (gerando assim os pares de independência):
Teste | A | B | C | Resultado |
1 | Verdadeiro | Verdadeiro | Verdadeiro | Verdadeiro |
5 | Falso | Verdadeiro | Verdadeiro | Falso |
A mesma coisa para a condição B:
Teste | A | B | C | Resultado |
1 | Verdadeiro | Verdadeiro | Verdadeiro | Verdadeiro |
3 | Verdadeiro | Falso | Verdadeiro | Falso |
E para a C:
Teste | A | B | C | Resultado |
1 | Verdadeiro | Verdadeiro | Verdadeiro | Verdadeiro |
2 | Verdadeiro | Verdadeiro | Falso | Falso |
Repare que só precisamos de 4 cenários de testes para cobrir todos os casos possíveis, precisaremos dos testes 1, 5, 3 e 2:
Teste | A | B | C | Resultado |
1 | Verdadeiro | Verdadeiro | Verdadeiro | Verdadeiro |
5 | Falso | Verdadeiro | Verdadeiro | Falso |
3 | Verdadeiro | Falso | Verdadeiro | Falso |
2 | Verdadeiro | Verdadeiro | Falso | Falso |
Desta forma, podemos chegar a conclusão que conseguimos determinar o número de casos de testes necessários em cenários condicionais usando o MC/DC com base nessa fórmula:
Nº de testes = Nº de condições + 1
Nesse caso:
3 + 1 = 4 testes
Uso em sistemas complexos
Perceba que para sistemas complexos isso começa a fazer ainda mais sentido, em cenários com 4 condições, pelo método tradicional, seriam 16 testes, pelo MC/DC, 5 testes.
Em cenários com 5 condições, 32 testes e pelo MC/DC, 6. Logo, o MC/DC pode atuar como uma ferramenta para lhe ajudar a escrever menos testes, garantindo ainda assim uma cobertura do seu código e que seu projeto seja entregue com qualidade de forma mais rápida.
Por isso, para aplicações críticas de segurança (como software de aviônicos), muitas vezes é necessário que o MC/DC seja satisfeito quase que obrigatoriamente. Por exemplo, a NASA exige 100% de cobertura MC/DC para qualquer componente de software crítico de segurança na sua Seção 3.7.4 da NPR 7150.2D.
Beleza, mas se é tão legal, por que não ouvi falar antes?
O MC/DC é um método mais popular em testes de software para sistemas altamente críticos. Em aplicações mais simples, o processo de gerar a tabela verdade e descobrir os pares de independência a serem testados pode ser demorado e complexo, inviabilizando o uso correto da técnica.
Por este motivo, gostaríamos de compartilhar uma ferramenta que pode lhe ajudar a determinar quantos e quais testes você precisa escrever na sua aplicação.
Conclusões
Embora seja necessária uma curva de aprendizagem significativa para entender a metodologia do MC/DC e começar a aplicá-la em seus projetos de software, os benefícios são vários.
Como foi visto, o MC/DC pode ser um investimento valioso para o processo de geração de testes, permitindo garantir uma boa cobertura do código mesmo escrevendo menos testes.
Nesta postagem ainda foi dado como exemplo um script que pode ajudar seu time e você a identificar de forma mais ágil quantos testes devem ser realizados e o que deve ser testado em seus cenários condicionais.
Por fim, gostaria de reforçar a importância de se escrever testes para a sua aplicação. Garantir a cobertura do seu código é tão importante quanto desenvolver as suas funcionalidades e lembre-se: uma boa cobertura não significa ter bons testes! Então atenção à escrita!
Ficou com alguma dúvida? Tem alguma sugestão? Quer falar com a gente? Então é só me procurar no Linkedin ?.