Compartilhar via


Geração de entrada usando execução simbólica dinâmica

O IntelliTest gera entradas para testes de unidade parametrizados analisando as condições de branch no programa. As entradas de teste são escolhidas com base em se podem disparar novos comportamentos de ramificação do programa. A análise é um processo incremental. Ele refina um predicado q: I -> {true, false} sobre os parâmetros de entrada de teste formais I. q representa o conjunto de comportamentos que o IntelliTest já observou. Inicialmente, q := false, já que nada foi observado ainda.

As etapas do loop são:

  1. O IntelliTest determina entradas i de modo que q(i)=false use um solucionador de restrição. Por meio da construção, a entrada i tomará um caminho de execução não visto antes. Inicialmente, isso significa que i pode ser qualquer entrada, porque nenhum caminho de execução ainda foi descoberto.

  2. O IntelliTest executa o teste com a entrada iescolhida e monitora a execução do teste e do programa em teste.

  3. Durante a execução, o programa assume um caminho específico que é determinado por todos os branches condicionais do programa. O conjunto de todas as condições que determinam a execução é chamado de condição de caminho, gravado como o predicado p: I -> {true, false} sobre os parâmetros de entrada formais. O IntelliTest calcula uma representação desse predicado.

  4. IntelliTest define q := (q or p). Em outras palavras, ele registra o fato de ter visto o caminho representado por p.

  5. Vá para a etapa 1.

O solucionador de restrições do IntelliTest pode lidar com valores de todos os tipos que podem aparecer em programas .NET:

O IntelliTest filtra entradas que violam suposições declaradas.

Além das entradas imediatas (argumentos para testes de unidade parametrizados), um teste pode desenhar outros valores de entrada da classe estática PexChoose . As opções também determinam o comportamento de simulações parametrizadas.

Solucionador de restrições

O IntelliTest usa um solucionador de restrições para determinar os valores de entrada relevantes de um teste e do programa em teste.

O IntelliTest usa o solucionador de restrição Z3 .

Cobertura de código dinâmico

Como efeito colateral do monitoramento de runtime, o IntelliTest coleta dados de cobertura de código dinâmico. Isso é chamado de dinâmico porque o IntelliTest sabe apenas sobre o código que foi executado, portanto, ele não pode fornecer valores absolutos para cobertura da mesma maneira que outra ferramenta de cobertura normalmente faz.

Por exemplo, quando o IntelliTest relata a cobertura dinâmica como blocos básicos de 5/10, isso significa que cinco blocos de dez foram cobertos, em que o número total de blocos em todos os métodos que foram atingidos até agora pela análise (em oposição a todos os métodos que existem no assembly em teste) é dez. Posteriormente na análise, à medida que métodos mais acessíveis são descobertos, o numerador (5 neste exemplo) e o denominador (10) podem aumentar.

Inteiros e floats

O solucionador de restrição do IntelliTest determina valores de entrada de teste de tipos primitivos, como byte, int, float e outros, a fim de disparar caminhos de execução diferentes para o teste e o programa em teste.

Objetos

O IntelliTest pode criar instâncias de classes .NET existentes ou você pode usar o IntelliTest para criar automaticamente objetos fictícios que implementam uma interface específica e se comportam de maneiras diferentes dependendo do uso.

Instanciar classes existentes

Qual é o problema?

O IntelliTest monitora as instruções executadas quando executa um teste e o programa em teste. Em particular, ele monitora todo o acesso aos campos. Em seguida, ele usa um solucionador de restrições para determinar novas entradas de teste, incluindo objetos e seus valores de campo, de modo que o teste e o programa em teste se comportem de outras maneiras interessantes.

Isso significa que o IntelliTest deve criar objetos de determinados tipos e definir seus valores de campo. Se a classe estiver visível e tiver um construtor padrão visível , o IntelliTest poderá criar uma instância da classe. Se todos os campos da classe estiverem visíveis, o IntelliTest poderá definir os campos automaticamente.

Se o tipo não estiver visível ou se os campos não estiverem visíveis, o IntelliTest precisará de ajuda para criar objetos e trazê-los para estados interessantes para obter a cobertura máxima de código. O IntelliTest pode usar a reflexão para criar e inicializar instâncias de maneiras arbitrárias, mas isso geralmente não é desejável porque pode trazer o objeto para um estado que nunca pode ocorrer durante a execução normal do programa. Em vez disso, o IntelliTest depende de dicas do usuário.

Visibilidade

O .NET tem um modelo de visibilidade elaborado: tipos, métodos, campos e outros membros podem ser privados, públicos, internos e muito mais.

Quando o IntelliTest gera testes, ele tentará executar apenas ações (como chamar construtores, métodos e campos de configuração) que são legais em relação às regras de visibilidade do .NET de dentro do contexto dos testes gerados.

As regras são as seguintes:

  • Visibilidade de membros internos

    • O IntelliTest pressupõe que os testes gerados terão acesso a membros internos que estavam visíveis para o PexClass delimitador. O .NET possui o InternalsVisibleToAttribute para possibilitar que os membros internos sejam visíveis em outros assemblies.
  • Visibilidade de membros privados e familiares (protegidos em C#) do PexClass

    • O IntelliTest sempre coloca os testes gerados diretamente no PexClass ou em uma subclasse. Portanto, o IntelliTest pressupõe que ele pode usar todos os membros visíveis da família (protegidos em C#).
    • Se os testes gerados forem colocados diretamente no PexClass (geralmente usando classes parciais), o IntelliTest pressupõe que ele também pode usar todos os membros privados do PexClass.
  • Visibilidade de membros públicos

    • O IntelliTest pressupõe que ele possa usar todos os membros exportados visíveis no contexto do PexClass.

Simulações parametrizadas

Como testar um método que tem um parâmetro de um tipo de interface? Ou de uma classe não lacrada? O IntelliTest não sabe quais implementações serão usadas posteriormente quando esse método for chamado. E talvez não haja uma implementação real disponível no momento do teste.

A resposta convencional é usar objetos fictícios com comportamento explícito.

Um objeto fictício implementa uma interface (ou estende uma classe não lacrada). Ele não representa uma implementação real, mas apenas um atalho que permite a execução de testes usando o objeto fictício. Seu comportamento é definido manualmente como parte de cada caso de teste em que ele é usado. Existem muitas ferramentas que facilitam a definição de objetos fictícios e seu comportamento esperado, mas esse comportamento ainda deve ser definido manualmente.

Em vez de valores embutidos em código em objetos fictícios, o IntelliTest pode gerar os valores. Assim como habilita o teste de unidade parametrizado, o IntelliTest também habilita simulações parametrizadas.

Simulações parametrizadas têm dois modos de execução diferentes:

  • escolhendo: ao explorar o código, simulações parametrizadas são uma fonte de entradas de teste adicionais, e o IntelliTest tentará escolher valores interessantes
  • replay: ao executar um teste gerado anteriormente, simulações parametrizadas se comportam como stubs com comportamento (em outras palavras, comportamento predefinido).

Use PexChoose para obter valores para simulações parametrizadas.

Estruturas

O raciocínio do IntelliTest sobre valores de struct é semelhante à maneira como lida com objetos.

Matrizes e cadeias de caracteres

O IntelliTest monitora as instruções executadas enquanto executa um teste e o programa em teste. Em particular, ele observa quando o programa depende do comprimento de uma cadeia de caracteres ou de uma matriz (e dos limites inferiores e comprimentos de uma matriz multidimensional). Ele também observa como o programa usa os diferentes elementos de uma cadeia de caracteres ou matriz. Em seguida, ele usa um solucionador de restrição para determinar quais comprimentos e valores de elemento podem fazer com que o teste e o programa em teste se comportem de maneiras interessantes.

O IntelliTest tenta minimizar o tamanho das matrizes e cadeias de caracteres necessárias para disparar comportamentos interessantes do programa.

Obter entradas adicionais

A classe estática PexChoose pode ser usada para obter entradas adicionais em um teste e pode ser usada para implementar simulações parametrizadas.

Tem comentários?

Poste suas ideias e solicitações de recursos na Comunidade de Desenvolvedores.