Como ter certeza que meu código esta bem testado
Tags: testing, mcdc, coverage, boundary
Published: 2023-03-18 17:35:30

## Você testa bem seu código?

Geralmente, a métrica que utilizamos para cobertura de testes é o _coverage_, normalmente calculado como parte de automações através de serviços como codecov ou CircleCI, essa métrica se dá normalmente através de linhas de código executadas em algum arquivo de teste, ou seja, se o código foi executado em algum teste ele é contabilizado como "testado".

Para os mais entendidos, sabemos que execução puramente não nos trás segurança para dizer se algo está satisfatoriamente coberto ou não, existem diversos outros fatores que influenciam na **qualidade** dessa cobertura, entre eles podemos citar: 
- Tipos de testes.  
_A famosa pirâmide de testes, unitários, integração, E2E..._
- Caminhos do código e valores de input/output.  
_Condicionais, intervalos de valores, valores **null**, tipos, etc..._
- Estado dos componentes que envolvem o teste. 
_Normalmente em testes de integração ou E2E, temos que ter muito cuidado para não gerarmos testes com falso positivo ou intermitentes (**flaky**, que passam arbitrariamente dependendo do estado, seja por não "limpar" o banco de dados, fila de mensagens, etc...)_

Dito isso, hoje quero trazer um pouco de minha experiência falando de "caminhos de código, valores de input/output"

## Apresentando o MC/DC

MC/DC ou "Modified Condition Decision Coverage" é uma técnica de cobertura de testes para condições lógicas, ou seja, `A > 18 AND B < 60`.

A vantagem do MC/DC é que ele consegue reduzir drasticamente o número de casos que vamos implementar em nosso código de teste, sendo a redução de 2^n casos para (n+1).

Vou tentar ilustrar a técnica com a seguinte condição lógica `A AND (B OR C)`
A, B ou C seriam condições booleanas quaisquer, do dia a dia do seu código, por exemplo:
```ruby
A = FeatureFlag.on()
B = order.is_enterprise()
C = order.shipping <= SHIPPING_COST_LIMIT
```

Pela tabela verdade, teriámos 2^3 casos, ou seja, 8 combinações possivels. Porém conseguimos reduzir este número para 4 utilizando o MC/DC.

```
--+---+---+---+---+--
  | R | A | B | C |
--+---+---+---+---+--
  | 1 | 1 | 1 | 1 |     A - é relevante (1, 7), (2, 8)
--+---+---+---+---+--
  | 1 | 1 | 1 | 0 |     B - é relevante (2, 3)
--+---+---+---+---+--
  | 0 | 1 | 0 | 0 |     C - é relevante (3, 4)
--+---+---+---+---+--
  | 1 | 1 | 0 | 1 |
--+---+---+---+---+--
  | 0 | 0 | 0 | 0 |
--+---+---+---+---+--
  | 0 | 0 | 0 | 1 |
--+---+---+---+---+--
  | 0 | 0 | 1 | 1 |
--+---+---+---+---+--
  | 0 | 0 | 1 | 0 |
--+---+---+---+---+--

# R = Resultado da expression A AND (B OR C)
```

Resumidamente, com MC/DC precisamos achar os pares de expressão onde mantendo o resto constante e alterando somente a booleana analisada, alteramos o valor do resultado da expressão inteira.

Para exemplificar:
Vamos começar olhando pra booleana A.
Na linha 1, temos A = 1, B = 1 e C = 1.
Na linha 7, temos A = 0, B = 1 e C = 1.
Nesse exemplo, alteramos o valor de A mantendo B e C constante, logo, essa dupla de linhas é relevante para nosso caso.
Um outro exemplo de pares onde o A é decisivo é o (2, 8)
Fazendo o mesmo raciocínio para o B e C, achamos os pares (1, 2) e (3, 4) respectivamente.

Agora, dado que achamos os pares, precisamos achar a combinação entre esses pares que devemos implementar.
Para achar essa combinação, devemos pegar os pares que possuem números *em comum* entre si, nesse caso escolheríamos o par (2,8) pra booleana A. Ficaríamos com os pares (2,8), (2,3) e (3,4).
Dos pares selecionados, excluindo as linhas repetidas, ficamos com o resultado (2, 3, 4 e 8).

Perceba como reduzimos o número de casos de teste de 2^n = 8 para (n+1) = 4.

Com esses casos em mãos podemos agora simplesmente implementá-los garantindo uma ótima cobertura sem de fato ter que implementar todos as possíveis combinações.

OBS: Cudos ao "@r_chicarelli" (no Twitter) por ter achado um pequeno erro no texto que já foi corrigido ;)