Análise Combinatória: Transformando Testes de Software com Matemática!
Aprofunde-se o suficiente em qualquer coisa e você encontrará a matemática. - Dean Schlicter
Na dinâmica e complexa paisagem da engenharia de software, a precisão e abrangência dos testes são fundamentais para garantir a entrega de soluções de alta qualidade. Como programadores e engenheiros de software, enfrentamos diariamente o desafio de escrever testes que abordem efetivamente a vasta gama de cenários possíveis em nossos sistemas. Nesse contexto, ferramentas e abordagens tradicionais podem, às vezes, deixar de cobrir todas as nuances, levando a possíveis lacunas na nossa cobertura de testes.
Por isso a aproximadamente 8 meses atrás iniciei uma série de estudos sobre como melhorar meu dia a dia ao desenvolver software, utilizando algo que sempre podemos confiar devido a sua lógica e muitas vezes precisão, a matemática!
E foi a partir desses estudos que notei algo curioso, a análise combinatória e sua lógica matemática emergem como aliadas poderosas, transformando nossa abordagem ao lidar com testes de software. Ao aplicar princípios fundamentais da contagem, combinações, agrupamentos, arranjos e permutações, podemos estruturar nossos testes de maneira mais sistemática e abrangente. Essas técnicas matemáticas nos permitem quantificar e explorar o universo de cenários de teste, garantindo que nenhum caso seja negligenciado.
Neste artigo, exploraremos como a análise combinatória pode ser aplicada na engenharia de testes de software, transformando a maneira como desenvolvemos, organizamos e priorizamos nossos testes. Com exemplos práticos e relevantes para o nosso dia a dia como desenvolvedores, ilustraremos como a matemática não é apenas um pilar teórico, mas uma ferramenta prática que pode guiar nossos esforços para alcançar a precisão em testes de software. O objetivo não é impor algo ou dizer que essa técnica é melhor, mas apenas fornecer insights e estratégias que você possa aplicar caso deseje, elevando a qualidade e a confiabilidade do software.
Se gostar do conteúdo, por favor, compartilhe e deixe seu like no post! Isso me ajuda a continuar a trazer conteúdos em forma de texto!😄
A matemática pura é, à sua maneira, a poesia das ideias lógicas.
- Albert Einstein, físico teórico alemão
Análise Combinatória: Fundamentos e Aplicações
O Que é Análise Combinatória?
A análise combinatória é um ramo da matemática pura que lida com a contagem, arranjo e combinação de objetos dentro de um conjunto definido, de acordo com regras específicas. Essencialmente, ela nos ajuda a calcular a quantidade de possíveis configurações ou agrupamentos que podem ser formados a partir de um conjunto de itens, onde a ordem dos itens pode ou não importar. Este campo da matemática é fundamental para resolver problemas de probabilidade e estatística, e tem suas raízes na matemática antiga, evoluindo significativamente ao longo dos séculos.
Origens e Desenvolvimento
A análise combinatória não é um conceito moderno; suas origens podem ser rastreadas até os estudos antigos sobre permutações e combinações na Índia e na Grécia antiga. Ao longo dos anos, matemáticos como Blaise Pascal e Pierre de Fermat expandiram sua aplicabilidade, especialmente no contexto do cálculo de probabilidades. No entanto, foi no século 20 que a análise combinatória floresceu como um campo de estudo próprio dentro da matemática, com aplicações que se estendem por várias disciplinas, da física à ciência da computação.
Aplicações no Dia a Dia
Na vida cotidiana, a análise combinatória pode ser vista em ações simples, como decidir a ordem de leitura de livros em uma estante, organizar uma equipe de projeto a partir de um grupo de colegas, ou até mesmo ao escolher ingredientes para uma receita. Em cada uma dessas situações, estamos inconscientemente realizando cálculos combinatórios.
E como se Encaixa nos Testes de Software?
Na engenharia de software, a análise combinatória assume um papel crucial ao ajudar a identificar todos os possíveis estados ou entradas que um sistema pode receber, isso pode nos ajudar em uma cobertura de teste abrangente. Em um ambiente onde os sistemas estão se tornando cada vez mais complexos, com inúmeras variáveis e estados interdependentes, aplicar princípios combinatórios permite que os engenheiros de teste criem planos de teste mais eficientes e eficazes, minimizando a probabilidade de bugs não detectados.
Princípios e Técnicas para Testes de Software
Princípio Fundamental da Contagem: Essa regra básica nos ajuda a determinar o número total de cenários de teste possíveis quando múltiplas variáveis independentes estão envolvidas, oferecendo uma base sólida para a estruturação de testes.
Combinações e Agrupamentos: Esses conceitos nos permitem considerar diferentes agrupamentos de inputs sem levar em conta a ordem, essenciais para testar funcionalidades que dependem de múltiplas seleções ou configurações.
Permutações: A análise de permutações é relevante quando a ordem dos elementos afeta o resultado. Em testes de software, isso é particularmente útil para testar sequências de operações ou transações.
Ao explorar e aplicar esses princípios da análise combinatória, podemos transformar a maneira como abordamos os testes de software, desde a concepção até a execução, garantindo sistemas mais robustos e confiáveis. Nos próximos tópicos, mergulharemos em cada uma dessas técnicas, destacando sua aplicabilidade prática em cenários de teste de software, com exemplos concretos que ilustram sua eficácia.
Princípio Fundamental da Contagem: O Pilar na Construção de Testes Eficientes
Este princípio é uma pedra angular para entender como podemos estruturar nossos testes de unidade de forma mais inteligente e eficaz.
Imagine que você está em uma sorveteria, decidindo sobre as camadas de um delicioso sundae. Primeiro, você escolhe entre 3 sabores de sorvete. Em seguida, decide se quer cobertura de chocolate, caramelo ou morango. E, por fim, escolhe entre adicionar ou não granulado. Quantas combinações de sundae diferentes você pode criar? O Princípio Fundamental da Contagem nos diz que basta multiplicarmos as opções de cada decisão:
3 sabores X 3 cobertura X 2 opções de granulado = 18 combinações únicas.
Matematicamente, se temos uma tarefa que pode ser realizada de n maneiras e outra tarefa subsequente que pode ser realizada de m maneiras, então juntas, essas tarefas podem ser realizadas de n×m maneiras. Este princípio pode ser estendido a qualquer número de tarefas sequenciais, proporcionando uma base poderosa para contagem em cenários com múltiplas etapas ou decisões.
Então como utilizar em testes de software? Bom a primeira coisa que você precisa é compreender todos os inputs que possui em um método, além disso precisamos levar em conta as ramificações dentro desse método. Vamos conversar sobre isso em breve, quero mostrar um exemplo mais simples do PFC aplicado na prática.
Imagine que estamos testando uma função de configuração de perfil em uma aplicação, onde os usuários podem escolher um idioma (Inglês, Espanhol, Francês), um tema (Claro, Escuro) e ativar ou desativar notificações. Seguindo o Princípio Fundamental da Contagem, temos 3×2×2 = 12 cenários de teste possíveis.
Vamos traduzir isso para código? Acompanhe comigo o exemplo abaixo:
Considere uma classe PerfilUsuario
com um método configurarPerfil
que aceita idioma, tema e status de notificação como parâmetros. Vamos implementar essa classe e seus testes de unidade usando JUnit 5.
public class PerfilUsuario {
public String configurarPerfil(String idioma, String tema, boolean notificacoesAtivas) {
// Lógica de configuração do perfil
return "Perfil configurado com " + idioma + ", " + tema + " e notificações " + (notificacoesAtivas ? "ativas" : "desativadas");
}
}
Para nossos testes, vamos cobrir todos os 12 cenários possíveis, aplicando o Princípio Fundamental da Contagem para garantir uma cobertura completa.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class PerfilUsuarioTest {
PerfilUsuario perfil = new PerfilUsuario();
@Test
void configurarPerfil_English_Light_NotificacoesAtivas() {
assertEquals("Perfil configurado com English, Light e notificações ativas", perfil.configurarPerfil("English", "Light", true));
}
@Test
void configurarPerfil_English_Light_NotificacoesDesativadas() {
assertEquals("Perfil configurado com English, Light e notificações desativadas", perfil.configurarPerfil("English", "Light", false));
}
@Test
void configurarPerfil_English_Dark_NotificacoesAtivas() {
assertEquals("Perfil configurado com English, Dark e notificações ativas", perfil.configurarPerfil("English", "Dark", true));
}
@Test
void configurarPerfil_English_Dark_NotificacoesDesativadas() {
assertEquals("Perfil configurado com English, Dark e notificações desativadas", perfil.configurarPerfil("English", "Dark", false));
}
@Test
void configurarPerfil_Espanhol_Light_NotificacoesAtivas() {
assertEquals("Perfil configurado com Espanhol, Light e notificações ativas", perfil.configurarPerfil("Espanhol", "Light", true));
}
@Test
void configurarPerfil_Espanhol_Light_NotificacoesDesativadas() {
assertEquals("Perfil configurado com Espanhol, Light e notificações desativadas", perfil.configurarPerfil("Espanhol", "Light", false));
}
@Test
void configurarPerfil_Espanhol_Dark_NotificacoesAtivas() {
assertEquals("Perfil configurado com Espanhol, Dark e notificações ativas", perfil.configurarPerfil("Espanhol", "Dark", true));
}
@Test
void configurarPerfil_Espanhol_Dark_NotificacoesDesativadas() {
assertEquals("Perfil configurado com Espanhol, Dark e notificações desativadas", perfil.configurarPerfil("Espanhol", "Dark", false));
}
@Test
void configurarPerfil_French_Light_NotificacoesAtivas() {
assertEquals("Perfil configurado com French, Light e notificações ativas", perfil.configurarPerfil("French", "Light", true));
}
@Test
void configurarPerfil_French_Light_NotificacoesDesativadas() {
assertEquals("Perfil configurado com French, Light e notificações desativadas", perfil.configurarPerfil("French", "Light", false));
}
@Test
void configurarPerfil_French_Dark_NotificacoesAtivas() {
assertEquals("Perfil configurado com French, Dark e notificações ativas", perfil.configurarPerfil("French", "Dark", true));
}
@Test
void configurarPerfil_French_Dark_NotificacoesDesativadas() {
assertEquals("Perfil configurado com French, Dark e notificações desativadas", perfil.configurarPerfil("French", "Dark", false));
}
}
Cada um desses testes garante que o método configurarPerfil
da classe PerfilUsuario
funcione conforme esperado para as diferentes combinações de idioma, tema e status de notificação, cobrindo todos os 12 cenários possíveis.
Mas a dúvida que alguns podem ter é a seguinte, podemos confiar? O cenário mudaria se tivessemos ramificações?
Quando temos 12 cenários claramente definidos, como no exemplo que trabalhamos, há uma base sólida para confiar que cobrimos todas as combinações possíveis das variáveis em questão. Isso é especialmente verdadeiro quando essas variáveis são independentes entre si, como os idiomas, temas e configurações de notificação do nosso exemplo. Nessas situações, o Princípio Fundamental da Contagem fornece uma metodologia confiável para garantir que cada combinação única foi considerada.
A complexidade pode aumentar quando introduzimos ramificações no cenário. Ramificações ocorrem quando a escolha em uma decisão afeta as opções disponíveis em decisões subsequentes. Nesses casos, não podemos simplesmente aplicar o Princípio Fundamental da Contagem de forma direta, pois as decisões não são mais independentes.
Por exemplo, se adicionar a condição de que certas configurações de notificação só estão disponíveis para determinados temas (digamos, notificações "ativas" estão disponíveis apenas para o tema "Escuro"), então o número de cenários válidos pode ser reduzido, e algumas combinações se tornariam inválidas.
Vamos mudar nosso exemplo para entender isso melhor:
public class ConfiguradorDePerfil {
// Método com ramificações
public String configurarPerfil(String tema, boolean notificacoes, boolean economiaEnergia) {
if ("Escuro".equals(tema) && economiaEnergia) {
return "Tema Escuro com Economia de Energia";
} else if ("Escuro".equals(tema)) {
return "Tema Escuro sem Economia de Energia";
} else if ("Claro".equals(tema) && notificacoes) {
return "Tema Claro com Notificações";
} else {
return "Tema Claro sem Notificações";
}
}
}
Para o método configurarPerfil
que apresenta ramificações, a abordagem tradicional utilizada para determinar o número total de cenários de teste não segue uma única fórmula direta do Princípio Fundamental da Contagem, devido à dependência condicional entre as variáveis. Em vez disso, a análise é feita em partes, considerando as ramificações específicas do método.
Variáveis de Entrada e Seus Valores Possíveis:
Tema: Tem dois valores possíveis ("Escuro" e "Claro").
Notificações: Como é um booleano, tem dois valores possíveis (true e false).
Economia de Energia: Também sendo um booleano, tem dois valores possíveis (true e false).
Análise das Ramificações:
Tema Escuro com Economia de Energia: Esta condição é específica e requer
tema
sendo "Escuro" eeconomiaEnergia
sendo true. Não depende do estado denotificacoes
.Tema Escuro sem Economia de Energia: Esta condição também é específica para o
tema
sendo "Escuro", maseconomiaEnergia
precisa ser false. Mais uma vez, não depende denotificacoes
.Tema Claro com Notificações: Requer
tema
sendo "Claro" enotificacoes
sendo true. Não depende deeconomiaEnergia
.Tema Claro sem Notificações: Este é o caso padrão quando nenhum dos outros if's é atendido. Acontece quando o
tema
é "Claro" enotificacoes
é false, independentemente deeconomiaEnergia
.
Cálculo dos Cenários de Teste:
Para Tema Escuro:
Com economia de energia: 1 cenário (Tema Escuro + Economia de Energia ativada).
Sem economia de energia: 2 cenários (Tema Escuro + Economia de Energia desativada, com
notificacoes
podendo ser true ou false).
Para Tema Claro:
Com notificações: 2 cenários (Tema Claro + Notificações ativadas, com
economiaEnergia
podendo ser true ou false).Sem notificações: 1 cenário específico (Tema Claro + Notificações desativadas, e economia de energia não influencia).
Totalizando:
Tema Escuro: 1 (economia de energia) + 2 (sem economia de energia) = 3 cenários.
Tema Claro: 2 (com notificações) + 1 (sem notificações, economia de energia não influencia) = 3 cenários.
Mas fica claro que não aplicamos nada de matemática, apenas lógica e separação do que vamos testar. Como então ficaria para aplicar o PFC diretamente no método?
Para aplicar o PFC, consideramos cada escolha independente nas entradas do método e multiplicamos o número de opções disponíveis para cada uma.
Tema: 2 opções ("Escuro", "Claro").
Notificações: 2 opções (true, false).
Economia de Energia: 2 opções (true, false).
O número total de cenários de teste é dado pelo produto do número de opções para cada entrada, o que reflete a ideia central do PFC de que cada escolha é independente.
Total de Cenários = Opcões de Tema × Opcões de Notificacões × Opcões de Economia de Energia
Total de Cenários = 2 × 2 × 2 = 8
Este número inclui todas as combinações possíveis de tema
, notificações
e economiaEnergia
, independentemente das ramificações específicas no código. Esse cálculo assume uma abordagem puramente combinatória, onde cada combinação de entradas é considerada um cenário de teste potencial, antes de considerar as lógicas condicionais específicas que reduziriam esse número com base nas regras de negócio implementadas no método.
Vamos identificar quais seriam esses 8 cenários, incluindo aqueles que não foram explicitamente considerados na análise anterior:
Cenários para "Tema Escuro":
Escuro + Notificações ativadas + Economia de Energia ativada.
Escuro + Notificações ativadas + Economia de Energia desativada.
Escuro + Notificações desativadas + Economia de Energia ativada.
Escuro + Notificações desativadas + Economia de Energia desativada.
Cenários para "Tema Claro":
Claro + Notificações ativadas + Economia de Energia ativada.
Claro + Notificações ativadas + Economia de Energia desativada.
Claro + Notificações desativadas + Economia de Energia ativada.
Claro + Notificações desativadas + Economia de Energia desativada.
Nos cenários de 1 a 4 com o tema "Escuro", as ramificações específicas do código são aplicadas diretamente, considerando a ativação ou não da economia de energia, independentemente do estado das notificações.
Nos cenários de 5 a 8 com o tema "Claro", a diferenciação ocorre principalmente com base no estado das notificações, enquanto a economia de energia, em teoria, não afetaria o resultado, de acordo com as condições especificadas no método configurarPerfil
.
Cenários Adicionais Identificados:
A análise anterior considerou principalmente as ramificações explícitas do código e pode ter omitido os seguintes cenários específicos, que estão incluídos na contagem de 8 derivada do PFC:
Cenário 5 e 7 (Tema Claro + Economia de Energia ativada): Esses cenários incluem a ativação da economia de energia com o tema claro, que, embora não alterem o resultado diretamente segundo a lógica do método, são combinações válidas de entrada que devem ser testadas para garantir que o sistema se comporte conforme esperado em todas as situações possíveis.
Cenário 6 (Tema Claro + Notificações ativadas + Economia de Energia desativada): Este cenário é similar ao 5, mas com a economia de energia desativada, que também é uma combinação distinta de entradas que precisa ser verificada.
Ao incluir esses cenários adicionais nos testes, garantimos uma cobertura completa, abordando todas as combinações possíveis de entradas, o que está alinhado com a abordagem abrangente sugerida pelo PFC. Assim evitamos que no futuro, se outro programador alterar esse código, ele não cometa um erro de lógica, se ele errar e alterar o comportamento sem que os requisitos de négocios exigam essa alteração, os testes irão falhar alertando que algo foi quebrado.
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith({SpringExtension.class})
class ConfiguradorDePerfilTest {
ConfiguradorDePerfil configurador = new ConfiguradorDePerfil();
@Test
@DisplayName("Tema Escuro com Notificações ativadas e Economia de Energia ativada")
void temaEscuro_NotificacoesAtivas_EconomiaEnergiaAtiva() {
assertEquals("Tema Escuro com Economia de Energia", configurador.configurarPerfil("Escuro", true, true));
}
@Test
@DisplayName("Tema Escuro com Notificações ativadas e Economia de Energia desativada")
void temaEscuro_NotificacoesAtivas_EconomiaEnergiaDesativada() {
assertEquals("Tema Escuro sem Economia de Energia", configurador.configurarPerfil("Escuro", true, false));
}
@Test
@DisplayName("Tema Escuro com Notificações desativadas e Economia de Energia ativada")
void temaEscuro_NotificacoesDesativadas_EconomiaEnergiaAtiva() {
assertEquals("Tema Escuro com Economia de Energia", configurador.configurarPerfil("Escuro", false, true));
}
@Test
@DisplayName("Tema Escuro com Notificações desativadas e Economia de Energia desativada")
void temaEscuro_NotificacoesDesativadas_EconomiaEnergiaDesativada() {
assertEquals("Tema Escuro sem Economia de Energia", configurador.configurarPerfil("Escuro", false, false));
}
@Test
@DisplayName("Tema Claro com Notificações ativadas e Economia de Energia ativada")
void temaClaro_NotificacoesAtivas_EconomiaEnergiaAtiva() {
assertEquals("Tema Claro com Notificações", configurador.configurarPerfil("Claro", true, true));
}
@Test
@DisplayName("Tema Claro com Notificações ativadas e Economia de Energia desativada")
void temaClaro_NotificacoesAtivas_EconomiaEnergiaDesativada() {
assertEquals("Tema Claro com Notificações", configurador.configurarPerfil("Claro", true, false));
}
@Test
@DisplayName("Tema Claro com Notificações desativadas e Economia de Energia ativada")
void temaClaro_NotificacoesDesativadas_EconomiaEnergiaAtiva() {
assertEquals("Tema Claro sem Notificações", configurador.configurarPerfil("Claro", false, true));
}
@Test
@DisplayName("Tema Claro com Notificações desativadas e Economia de Energia desativada")
void temaClaro_NotificacoesDesativadas_EconomiaEnergiaDesativada() {
assertEquals("Tema Claro sem Notificações", configurador.configurarPerfil("Claro", false, false));
}
}
Portanto, a abordagem aqui é saber quando segmentar a análise com base nas ramificações lógicas do método, em vez de aplicar diretamente a multiplicação n×m para todas as variáveis. Esta estratégia é essencial quando as opções disponíveis para uma variável dependem do valor de outra, como é o caso da opção de economia de energia que só se aplica ao tema "Escuro".
Agrupamentos na Análise Combinatória
Agrupamentos, conhecidos na matemática de análise combinatória como combinações, referem-se ao processo de selecionar itens de um conjunto de forma que a ordem dos itens selecionados não importe. Em outras palavras, estamos interessados nos subconjuntos possíveis de um conjunto maior, sem levar em conta a sequência em que os elementos são escolhidos.
O conceito central por trás dos agrupamentos é a ideia de escolher um número específico de itens de um conjunto maior, onde a única coisa que importa é quais itens são escolhidos, não a ordem em que aparecem. Por exemplo, se temos um conjunto de letras {A, B, C}, e queremos escolher 2 delas, os agrupamentos possíveis seriam {A, B}, {A, C}, e {B, C}. Note que {A, B} é considerado o mesmo agrupamento que {B, A}, porque a ordem não é relevante.
Vamos para um exemplo em código:
import java.util.Set;
public class PermissionValidator {
public boolean validatePermissions(Set<String> permissions) {
// Regra 1: Deve incluir a permissão "ler"
if (!permissions.contains("ler")) {
return false;
}
// Regra 2: Não pode ter "executar" sem "escrever"
if (permissions.contains("executar") && !permissions.contains("escrever")) {
return false;
}
// Se passar por todas as verificações, o conjunto de permissões é válido
return true;
}
}
Para o caso da função validatePermissions
, que verifica um conjunto de permissões com base em regras específicas, o conceito de agrupamentos (ou combinações) nos ajuda a entender o número de maneiras distintas pelas quais as permissões podem ser agrupadas, independentemente da ordem. No entanto, devido às regras específicas impostas pela função, nem todos os agrupamentos teóricos são válidos.
Quando falamos sobre agrupamentos em relação ao exemplo da função validatePermissions
, estamos nos referindo aos conjuntos distintos de permissões que podem ser formados, independentemente de eles cumprirem ou não as regras estabelecidas na função. Cada agrupamento é uma combinação de permissões onde a ordem não importa.
Permissões Disponíveis:
Ler
Escrever
Executar
Cálculo dos Agrupamentos:
Para calcular o número de agrupamentos possíveis com estas 3 permissões, consideramos que cada permissão pode estar presente ou ausente, levando a
combinações possíveis. Isso vem do Princípio Fundamental da Contagem, onde temos 2 opções (incluir ou não incluir) para cada uma das 3 permissões.
Agrupamentos Possíveis:
Vamos listar todos os 8 agrupamentos possíveis gerados por este cálculo:
{} - Nenhuma permissão
{"ler"}
{"escrever"}
{"executar"}
{"ler", "escrever"}
{"ler", "executar"}
{"escrever", "executar"}
{"ler", "escrever", "executar"}
Mas existe algo IMPORTANTE que precisamos esclarecer, por isso leia o tópico abaixo:
Total de Agrupamentos Possíveis:
Inicialmente, considerando todas as combinações possíveis das permissões "ler", "escrever" e "executar" sem levar em conta as regras, temos um total de:
Então são agrupamentos possíveis. Isso inclui todos os conjuntos possíveis, desde o conjunto vazio até o conjunto completo com todas as permissões.
Agrupamentos Válidos Segundo as Regras:
No entanto, quando aplicamos as regras específicas da função validatePermissions
:
O conjunto de permissões deve incluir "ler".
O conjunto não pode incluir "executar" sem incluir "escrever".
Identificamos que nem todos os 8 agrupamentos iniciais são válidos. Os agrupamentos que cumprem ambas as regras e, portanto, são considerados válidos para a função, são:
{"ler"} - Apenas "ler", cumpre a regra 1.
{"ler", "escrever"} - "Ler" e "escrever", cumpre ambas as regras.
{"ler", "escrever", "executar"} - Todas as permissões, cumpre ambas as regras.
Portanto, dos 8 agrupamentos possíveis, apenas 3 são válidos de acordo com as regras estabelecidas na função validatePermissions
.
Esclarecimento:
8 Agrupamentos Possíveis: Refere-se ao número total de combinações que podemos formar com as permissões disponíveis, independentemente das regras.
3 Agrupamentos Válidos: Refere-se ao número de combinações que cumprem as regras específicas da função
validatePermissions
.
Saber distinguir entre o número total de agrupamentos possíveis e o número de agrupamentos válidos, que cumprem regras específicas, é fundamental por algumas razões que eu descrevo abaixo:
Cobertura dos Testes
Ao identificar todos os 8 agrupamentos possíveis, garantimos que estamos considerando todas as combinações de permissões que um usuário poderia potencialmente ter. Isso nos permite projetar nossos testes para cobrir cada cenário único, incluindo aqueles que a função deveria invalidar. Isso é crucial para uma cobertura de teste completa, assegurando que o software se comporte conforme esperado em todas as situações possíveis.
Validação de Regras de Negócio
Ao focar nos 3 agrupamentos válidos que cumprem as regras da função validatePermissions
, estamos diretamente testando a lógica de negócio implementada na função. Isso nos ajuda a verificar se a função está corretamente impondo as regras de negócio pretendidas, como a necessidade de ter a permissão "ler" e a regra de que "executar" requer "escrever".
Identificação de Casos de Borda
Ao diferenciar entre todos os agrupamentos possíveis e aqueles que são válidos segundo as regras da função, podemos identificar casos de borda que podem não ser imediatamente óbvios. Por exemplo, um agrupamento que inclui "executar" sem "escrever" é um caso de borda importante que testa a robustez da função em lidar com entradas inválidas.
Compreensão Profunda da Aplicação
Este exercício de distinguir entre diferentes tipos de agrupamentos ajuda nós e testadores (QA’s) a terem uma compreensão mais profunda da aplicação e de suas regras de negócio. Isso promove uma melhor comunicação dentro da equipe e uma abordagem mais consciente e metódica para o desenvolvimento e teste de software.
Na matemática, a arte de propor uma questão deve ser considerada de maior valor do que resolvê-la. - Georg Cantor
Por que o PFC é o Pilar?
Permutação simples, arranjos simples e agrupamentos (combinações) são conceitos fundamentais da análise combinatória que ajudam a contar e organizar elementos em conjuntos de maneiras específicas. Cada um desses conceitos tem uma aplicação única, mas todos estão intrinsecamente conectados ao Princípio Fundamental da Contagem (PFC).
Vamos entender melhor de uma maneira mais simples, mas na nota do rodapé vai estar a maneira matemática também de entender com as formulas aplicadas.
Arranjos Simples
Imagine que você tem um chaveiro com espaços para exatamente 3 chaves, e você tem 5 chaves diferentes para escolher. Arranjos simples seriam todas as diferentes maneiras de escolher e pendurar 3 dessas chaves no chaveiro, considerando que a ordem em que você as pendura importa (por exemplo, a chave da casa no primeiro espaço, a do carro no segundo e a do escritório no terceiro é diferente de ter a chave do carro primeiro, a do escritório segundo, e a da casa no terceiro).1
Agora vamos trazer isso para algo que nós programadores vemos no nosso dia a dia, por exemplo, pense que você está testando um sistema de e-commerce e precisa verificar o processo de checkout. Especificamente, você quer testar a sequência de adicionar produtos ao carrinho, aplicar um cupom de desconto e selecionar um método de envio. Arranjos simples são úteis aqui porque a ordem em que essas ações ocorrem pode afetar o resultado final do checkout. Por exemplo, aplicar um cupom antes de adicionar todos os produtos pode não ter o mesmo resultado que aplicá-lo depois. Testar diferentes sequências dessas ações garante que o processo de checkout é robusto e funciona corretamente em várias situações.
Permutações Simples
Agora, imagine que você quer organizar seus 5 livros favoritos em uma prateleira que tem espaço para todos eles. Permutações simples são todas as maneiras diferentes de dispor esses livros na prateleira, onde a ordem dos livros é importante. Se você trocar dois livros de lugar, isso conta como uma nova disposição.2
Trazendo isso para a engenharia de software também, vamos considerar que você está testando uma aplicação que permite aos usuários personalizar um dashboard arrastando e soltando widgets. Permutações simples entram em jogo quando, por exemplo, queremos garantir que qualquer configuração possível de widgets funcione sem problemas. Isso significa testar todas as maneiras possíveis de organizar os widgets. Se houver 3 widgets, você testaria todas as maneiras de organizá-los para garantir que o dashboard sempre exiba as informações corretamente, independentemente de como os widgets são ordenados.
Para um exemplo mais próximo do dia a dia de programadores, podemos considerar a ordem de execução de funções em um script. Suponha que você tenha 3 funções críticas em seu script: validarDados()
, processarDados()
e salvarDados()
. A ordem em que essas funções são chamadas é crucial para o funcionamento correto do script. Permutações simples entram em jogo quando queremos explorar todas as maneiras possíveis de organizar a chamada dessas funções para garantir a robustez do script.
Agrupamentos
Vou reforçar os agrupamentos, sei que já falamos, mas é bom reforçar, pois isso pode ser confuso quando lemos pela primeira vez.
Suponha que você tem um buquê com 5 flores diferentes e deseja selecionar apenas 3 para colocar em um vaso menor. Agrupamentos, ou combinações, referem-se às diferentes seleções de flores que você pode fazer, mas, diferentemente dos arranjos, a ordem das flores no vaso não importa. Se você escolher uma rosa, um lírio e uma margarida, é a mesma combinação que escolher um lírio, uma margarida e uma rosa.3
E isso pode ser aplicado se você está trabalhando em um sistema de gerenciamento de projetos e quer testar as permissões de acesso dos usuários. Alguns usuários podem criar tarefas, outros podem apenas visualizá-las, e alguns podem fazer ambos. Usando o conceito de agrupamentos, você criaria testes para todas as combinações possíveis de permissões para garantir que o sistema de permissões esteja funcionando como esperado. Isso significa testar não apenas os usuários com todas as permissões, mas também aqueles com um subconjunto específico de permissões, garantindo que cada grupo de permissões seja validado.
Princípio Fundamental da Contagem (PFC)
Imagine que você está se vestindo pela manhã e tem 2 camisas e 3 calças para escolher. O PFC é como você calcularia todas as combinações possíveis de camisas e calças. Você multiplica o número de escolhas de camisas pelo número de escolhas de calças (2 camisas x 3 calças), dando a você o total de combinações de roupas possíveis.
Como Tudo se Apoia no PFC
Arranjos Simples: Eles se baseiam na ideia do PFC, onde cada escolha (cada espaço no chaveiro, por exemplo) tem um número específico de opções, e você multiplica essas opções para encontrar todas as maneiras possíveis de fazer essas escolhas em sequência.
Permutações Simples: São um caso especial do PFC onde você usa todas as opções disponíveis (todos os livros, por exemplo), e a multiplicação dessas opções (escolhas para o primeiro, segundo, terceiro livro, etc.) te dá o número total de maneiras de organizar os itens.
Agrupamentos: Embora a ordem não importe aqui, a ideia inicial de selecionar itens de um conjunto maior (escolher 3 flores de 5, por exemplo) ainda se baseia na lógica do PFC de fazer escolhas sequenciais. No entanto, ajustamos a contagem final para remover a importância da ordem das escolhas.
Portanto, o Princípio Fundamental da Contagem é o alicerce que nos permite entender e calcular o número de maneiras de fazer escolhas sequenciais, seja considerando a ordem dessas escolhas (arranjos e permutações) ou não (agrupamentos). Ele nos ajuda a estruturar e quantificar as possibilidades em várias situações, desde tarefas diárias simples até problemas complexos de teste de software.
Mas existem alguns pontos de alerta a se considerar. ⚠️
Independência das Escolhas
O PFC assume que cada escolha ou decisão é independente das outras. No entanto, em muitos sistemas de software, as ações podem ser interdependentes. Por exemplo, um saque bem-sucedido em um sistema bancário depende de haver saldo suficiente, que por sua vez pode depender de depósitos anteriores. É crucial identificar e considerar essas dependências ao aplicar o PFC para garantir a validade dos cenários de teste gerados.
Cobertura de Casos de Borda
Embora o PFC possa ajudar a calcular o número total de cenários de teste, é importante garantir que os casos de borda sejam adequadamente representados. Casos de borda são situações que ocorrem nos limites dos requisitos ou das regras de negócio e frequentemente revelam bugs ou comportamentos inesperados. Certifique-se de que os cenários de teste incluam esses casos importantes.
Complexidade e Viabilidade
A aplicação do PFC pode levar a um número muito grande de cenários de teste, especialmente em sistemas complexos com muitas variáveis independentes. Embora seja importante ter uma cobertura abrangente, também é essencial considerar a viabilidade e a eficiência dos testes. Pode ser necessário priorizar e selecionar um subconjunto representativo de cenários para testar, especialmente em situações com recursos limitados.
Sequências e Ordem de Operações
Em muitos sistemas, a ordem das operações é crucial. O PFC pode ajudar a identificar diferentes sequências de ações, mas é importante analisar cuidadosamente se todas as sequências geradas são válidas dentro do contexto do sistema. Algumas sequências podem não fazer sentido do ponto de vista da lógica de negócios ou podem ser inatingíveis devido a restrições do sistema.
Estados Iniciais e Condições
O estado inicial do sistema e as condições sob as quais os testes são executados podem afetar significativamente os resultados. Ao aplicar o PFC, considere como diferentes estados ou condições iniciais podem influenciar as escolhas possíveis e os resultados dos testes.
Tratamento de Exceções e Erros
Quando eu estava estudando e escrevendo esse artigo, pensei em cenários de exceptions, como podemos planejar testes baseados no PFC que não esquecem de incluir cenários que devem resultar em exceções ou erros?
É tão importante testar a capacidade do sistema de lidar com entradas inválidas ou situações de erro quanto testar seu comportamento sob condições ideais. Bom vou tentar explicar meu ponto de vista e o que aprendi estudando e tentando aplicar.
Sobre as exceções vamos considerar o exemplo abaixo:
public class ContaBancaria {
private double saldo;
public ContaBancaria(double saldoInicial) {
if (saldoInicial < 0) {
throw new IllegalArgumentException("O saldo inicial não pode ser negativo.");
}
this.saldo = saldoInicial;
}
public void depositar(double valor) {
if (valor <= 0) {
throw new IllegalArgumentException("O valor do depósito deve ser positivo.");
}
saldo += valor;
}
public void sacar(double valor) {
if (valor <= 0) {
throw new IllegalArgumentException("O valor do saque deve ser positivo.");
}
if (valor > saldo) {
throw new IllegalArgumentException("Saldo insuficiente para saque.");
}
saldo -= valor;
}
public double getSaldo() {
return saldo;
}
}
Aplicando o PFC para Planejar Testes de Unidade
Para planejar nossos testes de unidade, consideramos as operações e as condições que podem lançar exceções:
Construtor: Lança exceção se o saldo inicial for negativo.
Depósito: Lança exceção se o valor do depósito for negativo ou zero.
Saque: Lança exceção se o valor do saque for negativo, zero ou maior que o saldo.
Aplicando o PFC, consideramos cada método e as condições que podem levar a uma exceção:
Construtor: 1 condição de falha (saldo inicial negativo) → 1 teste.
Depósito: 1 condição de falha (valor negativo ou zero) → 1 teste.
Saque: 2 condições de falha (valor negativo ou zero, valor maior que o saldo) → 2 testes.
Totalizando, temos 4 testes distintos focados em exceções.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class ContaBancariaTest {
@Test
void construtorComSaldoInicialNegativo_DeveLancarExcecao() {
Exception excecao = assertThrows(IllegalArgumentException.class, () -> new ContaBancaria(-100));
assertEquals("O saldo inicial não pode ser negativo.", excecao.getMessage());
}
@Test
void depositarComValorNegativo_DeveLancarExcecao() {
ContaBancaria conta = new ContaBancaria(100);
Exception excecao = assertThrows(IllegalArgumentException.class, () -> conta.depositar(-50));
assertEquals("O valor do depósito deve ser positivo.", excecao.getMessage());
}
@Test
void sacarComValorNegativo_DeveLancarExcecao() {
ContaBancaria conta = new ContaBancaria(100);
Exception excecao = assertThrows(IllegalArgumentException.class, () -> conta.sacar(-50));
assertEquals("O valor do saque deve ser positivo.", excecao.getMessage());
}
@Test
void sacarValorMaiorQueSaldo_DeveLancarExcecao() {
ContaBancaria conta = new ContaBancaria(100);
Exception excecao = assertThrows(IllegalArgumentException.class, () -> conta.sacar(150));
assertEquals("Saldo insuficiente para saque.", excecao.getMessage());
}
}
Mas fica claro que para calcular o número exato de cenários inválidos na situação da ContaBancaria
não foi totalmente direta ou simples como em outros exemplos. Isso ocorre devido à natureza interdependente e condicional das operações envolvidas (criar conta, depositar, sacar) e das regras específicas para cada uma dessas operações.
Desafios na Aplicação do PFC:
Dependência de Estado: O resultado e a validade de uma operação (especialmente saques) dependem do estado atual da conta, que é afetado por operações anteriores. Isso introduz uma complexidade que vai além de uma aplicação simples do PFC, onde cada escolha é independente das outras.
Variação Condicional: A quantidade de cenários inválidos para o saque pode variar dependendo do saldo atual, que é influenciado por depósitos anteriores e pelo saldo inicial. Isso cria uma situação onde o número de cenários possíveis não é simplesmente o produto das opções disponíveis.
Aplicação Indireta do PFC:
Embora a aplicação direta do PFC para calcular o número total de cenários válidos e inválidos tenha sido desafiadora devido a esses fatores, o princípio ainda forneceu uma base lógica para estruturar o pensamento e iniciar a análise:
Identificamos operações individuais e as condições que levariam a resultados inválidos.
Reconhecemos a necessidade de considerar a sequência de operações e seu impacto no estado da conta.
Podemos, sim, criar casos de teste para cenários inválidos com base na matemática do PFC, especialmente quando estamos lidando com uma série de escolhas independentes que levam a estados inválidos. O desafio surge quando as escolhas não são totalmente independentes ou quando a lógica do sistema introduz dependências entre as escolhas, como no exemplo da ContaBancaria
.
Aplicando o PFC em Cenários Inválidos:
Para cenários inválidos que envolvem escolhas independentes, o PFC pode ser aplicado da seguinte forma:
Liste todas as escolhas independentes que podem levar a um estado inválido. Por exemplo, na
ContaBancaria
, escolhas como iniciar com saldo negativo, depositar valor negativo ou zero, e sacar valor negativo ou zero são independentes entre si e cada uma pode levar a um estado inválido.Calcule o número de maneiras que cada escolha pode ser feita. Se cada escolha inválida é única (por exemplo, apenas uma maneira de depositar um valor negativo), então o cálculo é direto.
Combine as escolhas para formar cenários inválidos. Se as escolhas são independentes (uma não afeta a possibilidade da outra ocorrer), então você pode multiplicar o número de opções para cada escolha para obter o total de cenários inválidos. Por exemplo, se há 2 maneiras de criar uma conta que resultariam em exceções (saldo muito negativo e levemente negativo) e apenas 1 maneira de depositar um valor inválido (valor zero), então o total de cenários inválidos seria o produto dessas opções.
Desafios com Dependências:
O desafio surge quando há dependências, como no caso de um saque que excede o saldo disponível. Nesse cenário, a validade do saque depende das operações anteriores (criação da conta e depósitos), tornando as escolhas interdependentes. Nesses casos:
Analise as dependências para entender como as escolhas anteriores afetam as opções disponíveis em escolhas subsequentes.
Agrupe escolhas relacionadas e trate-as como uma única etapa complexa no cálculo, se possível.
Considere cenários específicos onde a interdependência é crítica e calcule as possibilidades para esses cenários.
Embora o PFC ofereça uma abordagem sistemática para calcular o número de cenários de teste, tanto válidos quanto inválidos, a aplicação em cenários com dependências complexas pode exigir análises adicionais. Para esses casos, o PFC ainda serve como uma base para estruturar o pensamento e a análise, mas pode ser necessário complementá-lo com outras técnicas de análise combinatória.
Dito tudo isso, vamos analisar de perto como podemos utilizar o PFC para chegar a um número relevante de casos de testes para cenários válidos e inválidos quando temos ramificações com operadores lógicos.
Trabalhando com Operadores Lógicos
Suponha que temos uma classe Validador
que contém um método validarDados
que recebe dois parâmetros: idade
e pontuacao
. Este método valida se a idade
está dentro de um intervalo específico (por exemplo, 18 a 65 anos) e se a pontuacao
é maior que um determinado valor (por exemplo, 50).
public class Validador {
public boolean validarDados(int idade, int pontuacao) {
if (idade >= 18 && idade <= 65 && pontuacao > 50) {
return true; // Validação bem-sucedida
}
return false; // Validação falhou
}
}
Aplicando o PFC: Abordagem de Cálculo para Cenários Válidos
Quando consideramos cenários válidos onde ambas as condições ligadas pelo &&
devem ser verdadeiras:
Para a condição da idade (
idade >= 18 && idade <= 65
): Identificamos 2 cenários representativos que queremos testar que satisfaçam essa condição (por exemplo, 20 anos como um caso típico dentro do intervalo e 65 anos como um caso de limite).Para a condição da pontuação (
pontuacao > 50
): Aqui identificamos outros 2 cenários representativos para a pontuação que queremos testar que satisfaçam essa condição (por exemplo, 55 como um caso típico acima do limite e 51 como um caso de limite próximo).Combinação das Condições: Como estamos lidando com o operador
&&
, apenas os cenários em que ambas as condições são verdadeiras simultaneamente são relevantes. Se tomarmos 2 cenários paraidade
que satisfazem a primeira condição e 2 cenários parapontuacao
que satisfazem a segunda condição, e cada um desses cenários para uma condição pode ser combinado com cada cenário da outra condição, teríamos potencialmente 2 * 2 = 4 combinações.
No entanto, como estamos considerando apenas os cenários válidos (onde ambas as condições são verdadeiras), precisamos garantir que as combinações selecionadas de idade e pontuação sejam logicamente consistentes e atendam a ambas as condições. Portanto, cada cenário de idade válida deve ser emparelhado com cada cenário de pontuação válida, mantendo o total de 4 cenários válidos.
Mas e os cenários inválidos onde o método deveria retornar false?
Vamos expandir os cenários para incluir não apenas quando essas condições são satisfeitas (verdadeiras), mas também quando não são (falsas).
Cenários para a Condição da Idade:
Verdadeira: Podemos ter 2 cenários representativos para testes válidos, como mencionado anteriormente (por exemplo, 20 e 65 anos).
Falsa: Podemos identificar mais 2 cenários para testar a condição falsa, como uma idade abaixo de 18 (por exemplo, 16 anos) e uma acima de 65 (por exemplo, 70 anos).
Isso nos dá um total de 4 cenários para a condição da idade.
Cenários para a Condição da Pontuação:
Verdadeira: Consideramos 2 cenários para quando a condição é verdadeira (por exemplo, pontuações de 55 e 51).
Falsa: Podemos adicionar mais 2 cenários para quando a pontuação não satisfaz a condição, como 50 (o limite exato, que é tecnicamente inválido) e um valor claramente abaixo, como 30.
Então isso nos dá um total de 4 cenários para a condição da pontuação.
Aplicando o PFC para Cálculo Total:
Com 4 cenários possíveis para cada condição (incluindo válidos e inválidos), e considerando que precisamos testar todas as combinações possíveis de verdadeiro/falso para ambas as condições:
Se aplicarmos o PFC de forma que cada cenário de idade (4 possíveis) possa ser combinado com cada cenário de pontuação (4 possíveis), teríamos um total de 4×4=16 combinações possíveis.
Cenários de Idade:
Idade válida dentro do intervalo (ex: 20 anos)
Idade no limite inferior (ex: 18 anos)
Idade no limite superior (ex: 65 anos)
Idade abaixo do limite inferior (ex: 17 anos - inválido)
Idade acima do limite superior (ex: 66 anos - inválido)
Cenários de Pontuação:
Pontuação acima do limite (ex: 55 pontos)
Pontuação no limite (ex: 51 pontos - considerando > 50 como válido)
Pontuação no limite exato (ex: 50 pontos - inválido)
Pontuação abaixo do limite (ex: 49 pontos - inválido)
Combinação dos Cenários:
Agora, combinaremos as condições de idade e pontuação para formar os cenários completos. Dado que temos 5 cenários de idade e 4 de pontuação, teoricamente poderíamos considerar 20 combinações (5 x 4), mas para atingir 16 cenários, vamos focar nas combinações mais representativas e relevantes:
Idade 20, pontuação 55 (válido)
Idade 20, pontuação 51 (válido)
Idade 20, pontuação 50 (inválido)
Idade 20, pontuação 49 (inválido)
Idade 18, pontuação 55 (válido)
Idade 18, pontuação 51 (válido)
Idade 18, pontuação 50 (inválido)
Idade 18, pontuação 49 (inválido)
Idade 65, pontuação 55 (válido)
Idade 65, pontuação 51 (válido)
Idade 65, pontuação 50 (inválido)
Idade 65, pontuação 49 (inválido)
Idade 17, pontuação 55 (inválido - falha na idade)
Idade 66, pontuação 55 (inválido - falha na idade)
Idade 17, pontuação 49 (inválido - falha na idade e pontuação)
Idade 66, pontuação 49 (inválido - falha na idade e pontuação)
Incluindo cenários inválidos, temos um total de 16 combinações possíveis ao aplicar o PFC. No entanto, é essencial filtrar essas combinações para garantir que os cenários de teste sejam logicamente consistentes e relevantes para os objetivos de teste, considerando a natureza das condições e as regras de negócio do sistema.
Observações:
Cenários Válidos: Os cenários considerados válidos são aqueles onde a idade está dentro do intervalo especificado e a pontuação está acima de 50.
Cenários Inválidos: Incluem situações onde a idade está fora do intervalo permitido e/ou a pontuação não atende ao critério mínimo. Mesmo que um desses aspectos seja válido, a falha em atender a qualquer uma das condições resulta em um cenário inválido devido ao uso do operador
&&
.
É sempre bom reforçar que com um grande número de cenários, especialmente em casos mais complexos do que este exemplo, pois sabemos que a complexidade depende do contexto de négocios, é importante considerar a aplicabilidade e relevância de cada cenário para garantir uma alocação eficaz dos esforços de teste.
O PFC se assemelha a um Code Coverage?
São duas abordagens que têm objetivos e metodologias distintos na engenharia de software e no teste de software. Entender essas diferenças pode nos ajudar a aprimorar nossa estratégia de testes e garantir uma qualidade de software mais robusta.
Cobertura de Código (Code Coverage)
Foco na Execução: A cobertura de código concentra-se em quantificar a porcentagem do código-fonte que é executada durante os testes. Ela mede, por exemplo, se uma linha de código foi executada, se todas as ramificações de uma instrução condicional foram testadas ou se todos os loops foram percorridos em suas diversas capacidades.
Automatização: Ferramentas de cobertura de código são geralmente automatizadas e integradas ao processo de desenvolvimento e teste, fornecendo relatórios em tempo real sobre quais partes do código foram efetivamente testadas.
Limitações: Embora a cobertura de código seja fundamental para entender o alcance dos testes, ela não garante por si só a qualidade ou a completude dos testes. Uma alta porcentagem de cobertura de código não necessariamente implica em testes abrangentes das lógicas de negócio ou no tratamento adequado de casos de borda.
Princípio Fundamental da Contagem (PFC)
Foco na Lógica de Teste: O PFC e seus casos especiais, como permutações e combinações, oferecem uma abordagem matemática para estruturar e planejar casos de teste. Eles nos ajudam a entender todas as possíveis combinações de entradas e cenários que um software pode encontrar, garantindo que consideremos uma gama completa de situações.
Análise Profunda: Aplicar o PFC exige uma análise cuidadosa e detalhada das funcionalidades e possíveis interações dentro do software, levando a um entendimento mais profundo dos requisitos e comportamentos esperados.
Complementaridade: Diferentemente da cobertura de código, que é uma métrica automatizada, o uso do PFC é uma atividade analítica que pode revelar cenários de teste não cobertos, mesmo em áreas do código que já foram "cobertas" segundo as ferramentas de cobertura.
Vantagens do PFC para Programadores e QAs
Cobertura Lógica de Testes: Ao usar o PFC para planejar testes, garantimos que os cenários considerem todas as variáveis e suas interações, indo além da simples execução de linhas de código.
Identificação de Casos de Teste Críticos: Outro ponto de ajuda que ganhamos é poder identificar casos de testes essenciais que podem não ser imediatamente óbvios, incluindo casos de borda e combinações únicas de condições.
Complementação da Cobertura de Código: Ao combinar a análise baseada no PFC com a métrica de cobertura de código, podemos alcançar uma abordagem de teste mais holística e abrangente, garantindo tanto a execução do código quanto a lógica de negócio estão sendo devidamente testadas.
Aprofunde-se o suficiente em qualquer coisa e você encontrará a matemática. - Dean Schlicter
O que Concluí Nesses Últimos 8 Meses de Estudo?
Nos últimos 8 meses de estudo focados no Princípio Fundamental da Contagem e seus casos especiais dentro dos testes de software, concluí que esses conceitos matemáticos fornecem uma base sistemática para abordar o design e a implementação de testes de software. Eles oferecem uma maneira estruturada de pensar sobre os diferentes cenários de teste, tanto válidos quanto inválidos, garantindo uma cobertura de teste mais abrangente e eficaz. Aqui estão alguns pontos-chave que destaco dessa jornada de aprendizado:
Estruturação e Organização de Testes
O PFC e suas extensões, como permutações, combinações e arranjos, ajudam a organizar o processo de teste, permitindo uma visão clara de todas as possibilidades que precisam ser consideradas. Isso leva a uma melhor organização dos casos de teste e a uma abordagem mais metódica para garantir que todas as combinações relevantes de entradas sejam testadas.
Identificação de Casos de Teste
A aplicação desses conceitos nos ajuda a identificar sistematicamente casos de teste importantes, incluindo cenários válidos para verificar a funcionalidade esperada e cenários inválidos para testar a robustez do software contra entradas inesperadas ou incorretas. Isso é crucial para desenvolver software que seja não apenas funcional, mas também resistente e seguro.
Melhoria na Cobertura de Teste
Ao garantir que consideramos todas as combinações possíveis de entradas e ações, podemos melhorar significativamente a cobertura dos testes. Isso reduz a probabilidade de bugs não detectados, aumentando a confiabilidade do software.
Eficiência no Planejamento de Testes
Embora o PFC possa indicar um grande número de cenários potenciais, também nos permite priorizar quais são os mais críticos ou prováveis, ajudando a alocar recursos de teste de forma mais eficiente e a focar nos aspectos mais importantes do sistema.
Compreensão Profunda do Sistema
A necessidade de considerar todas as possíveis combinações e sequências de ações para aplicar esses conceitos matemáticos leva a uma compreensão mais profunda da lógica e do funcionamento interno do sistema, o que é inestimável para qualquer engenheiro de software ou profissional de QA.
Posso acrescentar que a análise combinatória ensina os programadores a pensar de maneira estruturada e sistemática sobre os problemas, uma habilidade essencial na engenharia de software, onde a resolução de problemas complexos é uma parte rotineira do trabalho. Essa abordagem analítica ajuda a desmistificar problemas complexos, decompondo-os em partes gerenciáveis.
Se eu estivesse recomeçando meus estudos, seguiria o conselho de Platão e começaria pela matemática. - Galileu Galilei
O que não espero…
Essas observações são minhas, você pode discordar e respeito isso. Obviamente não espero que todos concordem e achem uma abordagem válida para seu dia a dia. Estou tentando propor algo que seja mais preciso possível para entender os cenários de testes possíveis dentro de um comportamento do sistema, que precisa ser colocado a prova. Talvez essa não seja a forma mais intuitiva de pensar em cenários de testes, mas a abordagem matemática traz mais precisão no número de casos de testes e nos faz pensar sistematicamente e logicamente em como devemos entender o comportamento da funcionalidade que precisamos testar. Em grandes aplicações corporativas é fácil deixar isso passar, mesmo quando temos ferramentas de code coverage para nos fornecer feedbacks de cobertura.
Pensar dessa maneira é inicialmente estranho, mas ao estudar por um tempo esse tema percebi que podemos utilizar muitos conceitos matemáticos ao nosso favor! Pensar em cenários de testes pode ser difícil principalmente quando somos novatos ou não estamos atentos a todos as regras presentes em um comportamento que uma funcionalidade abrange, cobrir todos os cenários também é desgastante em softwares, por isso venho buscando formas de melhorar e acelerar a maneira que identificamos cenários, casos de testes tanto inválidos e válidos.
Para mim, matemática, ciência da computação e artes estão insanamente relacionadas. São todas expressões criativas.
— Sebastian Thrun
____________________________________________________________________________
Para calcular todas as diferentes maneiras de escolher e pendurar 3 das 5 chaves no chaveiro, onde a ordem importa, usamos a fórmula de arranjos simples, que é dada por:
Onde:
A(n,k) é o número de arranjos possíveis,
n é o número total de itens para escolher (neste caso, 5 chaves),
k é o número de itens a serem escolhidos e arranjados (neste caso, 3 espaços no chaveiro),
n! denota o fatorial de n,
(n−k)! é o fatorial da diferença entre n e k.
O cálculo final dá 60 maneiras diferentes de arranjar 3 das 5 chaves no chaveiro.
____________________________________________________________________________
Para organizar os 5 livros favoritos em uma prateleira onde a ordem importa, usamos o conceito de permutações simples. A fórmula para calcular permutações simples é o fatorial do número de itens, representado por n!, onde n é o número total de itens para organizar.
No caso dos 5 livros:
Portanto, existem 120 maneiras diferentes de dispor seus 5 livros favoritos na prateleira, considerando que a troca da posição de qualquer dois livros resulta em uma nova disposição.
____________________________________________________________________________
Para calcular o número de agrupamentos possíveis ao escolher 3 flores de um conjunto de 5 flores diferentes, utilizei a fórmula de combinações da análise combinatória. A fórmula para combinações é dada por:
Onde:
C(n,k) é o número de combinações (agrupamentos) possíveis,
n é o número total de itens para escolher (neste caso, 5 flores),
k é o número de itens a serem escolhidos (neste caso, 3 flores),
n! denota o fatorial de n, que é o produto de todos os inteiros positivos até n,
k! é o fatorial de k,
(n−k)! é o fatorial da diferença entre n e k.