Partilhar 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 ramificação no programa. As entradas de teste são escolhidas com base em se elas podem desencadear 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 Iformais de entrada de teste. q representa o conjunto de comportamentos que o IntelliTest já observou. Inicialmente, q := false uma vez que ainda nada foi observado.

As etapas do loop são:

  1. O IntelliTest determina as entradas i de modo que q(i)=false utilizando um solucionador de restrições. Por construção, a entrada i tomará um caminho de execução nunca 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 monitoriza a execução do teste e do programa em teste.

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

  4. IntelliTest configura q := (q or p). Em outras palavras, 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 extrair outros valores de entrada da classe estática PexChoice . As escolhas também determinam o comportamento de simulações parametrizadas.

Solucionador de restrições

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

O IntelliTest utiliza o solucionador de restrições Z3 .

Cobertura de código dinâmico

Como efeito colateral do monitoramento do tempo de execução, 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 forma que outras ferramentas de cobertura geralmente fazem.

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

Números inteiros e flutuadores

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

Objects

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 monitoriza as instruções executadas quando executa um teste e o programa em teste. Em particular, monitoriza todos os acessos 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 é visível e tem um construtor padrão visível , IntelliTest pode criar uma instância da classe. Se todos os campos da classe estiverem visíveis, o IntelliTest pode definir os campos automaticamente.

Se o tipo não estiver visível, ou os campos não estiverem visíveis, o IntelliTest precisa de ajuda para criar objetos e colocá-los em estados interessantes, a fim de alcançar a cobertura máxima do 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 levar o objeto a 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 sejam legais em relação às regras de visibilidade do .NET de dentro do contexto dos testes gerados.

As regras são as seguintes:

  • Visibilidade dos membros internos

    • O IntelliTest assume que os testes gerados terão acesso a membros internos que estavam visíveis para o PexClass envolvente. O .NET tem o InternalsVisibleToAttribute para estender a visibilidade dos membros internos para outros conjuntos.
  • Visibilidade de membros privados e familiares (protegidos em C#) do PexClass

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

    • IntelliTest assume que ele pode usar todos os membros exportados visíveis no contexto da 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 selada? O IntelliTest não sabe quais implementações serão usadas posteriormente quando esse método for chamado. E talvez não haja sequer uma implementação real disponível no momento do teste.

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

Um objeto fictício implementa uma interface (ou estende uma classe não selada). Ele não representa uma implementação real, mas apenas um atalho que permite a execução de testes usando o objeto mock. Seu comportamento é definido manualmente como parte de cada caso de teste onde é 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 codificados em objetos fictícios, o IntelliTest pode gerar os valores. Assim como permite testes de unidade parametrizados, o IntelliTest também permite simulações parametrizadas.

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

  • escolha: 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 struct é semelhante à maneira como ele lida com objetos.

Matrizes e cadeias de caracteres

O IntelliTest monitoriza as instruções executadas à medida que executa um teste e o programa em teste. Em particular, ele observa quando o programa depende do comprimento de uma cadeia de caracteres ou uma matriz (e os 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ções para determinar quais comprimentos e valores de elementos 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 acionar comportamentos de programa interessantes.

Obter dados adicionais

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

Tem comentários?

Publique suas ideias e solicitações de recursos na Comunidade de desenvolvedores.