Este artigo foi traduzido por máquina.

Execução de teste

Teste de injeção de falha com o TestApi

James McCaffrey

Baixe o código de exemplo

James McCaffreyO teste de injeção de falha é o processo de deliberadamente inserindo um erro em um aplicativo em teste e, em seguida, executar o aplicativo para determinar se o aplicativo lida com o erro adequadamente. O teste de injeção de falha pode levar vários formulários diferentes. Na coluna deste mês, explicarei como você pode introduzir falhas em aplicativos .net em tempo de execução usando um componente da biblioteca TestApi.

A melhor maneira de ver o que eu estou direcionamento nesta coluna é dar uma olhada na captura de tela em do Figura 1. A captura de tela mostra que eu estou realizando testes em um aplicativo .net WinForm fictício chamado TwoCardPokerGame.exe de injeção de falha. Um programa c# chamado FaultHarness.exe está sendo executado no shell de comando. Ele altera o comportamento normal do aplicativo em teste para que o aplicativo lança um período de exceção a terceira que um usuário clica no botão avaliar. Nessa situação, o aplicativo de Pôquer 2 do cartão não manipula a exceção de aplicativo normalmente e o resultado é a caixa de mensagem gerados pelo sistema.

Figura 1 de de injeção de falha de teste em ação

Let’s vejamos mais detalhadamente neste cenário para considerar alguns dos detalhes envolvidos. Quando FaultHarness.exe é iniciado a partir do shell de comando, nos bastidores, o equipamento de prepara o código de criação de perfil que interceptará a execução de código normal de TwoCard ­ PokerGame.exe. Isso é chamado na sessão de injeção de falha.

A sessão de injeção de falha usa uma DLL para começar a observar de chamadas ao método button2_Click do aplicativo, o que é o manipulador de eventos do botão avaliar. A sessão de injeção de falha tiver sido configurada para que a primeira de duas vezes em que um usuário clica no botão avaliar, o aplicativo se comporta como codificado, mas na terceira clique a sessão de falha faz com que o aplicativo para lançar uma exceção do tipo System.ApplicationException.

A sessão de falhas registra as atividades da sessão e faz um conjunto de arquivos para o computador host de teste. Observe na Figura 1 de que os dois primeiros aplicativos avaliar de acordo clique pares trabalho corretamente, mas o terceiro clique gerou uma exceção.

Nas seções a seguem, vai rapidamente descrevem o fictício dois placa de jogo de póquer aplicativo sob teste, apresentar e explicar em detalhes o código do programa FaultHarness.exe do Figura 1 mostra e fornece algumas dicas sobre quando o uso do teste de injeção de falha é adequado e quando as técnicas alternativas são mais adequadas. Embora o próprio programa FaultHarness.exe é bastante simples e grande parte do trabalho difícil é executada em segundo plano, as DLLs TestApi, Compreendendo e modificando o código que apresento aqui atender aos seus próprios testes de cenários requer uma compreensão sólida do ambiente de programação .net. Dito isso, mesmo se você for um principiante em .net, você deve ser capaz de acompanhar minhas explicações sem muita dificuldade. Tenho certeza que você encontrará a discussão sobre a inserção de falha de uma adição interessante e útil, possivelmente, para o conjunto de ferramentas.

O aplicativo em teste

Meu aplicativo fictício em teste é um aplicativo simples mas representativo do c# WinForm que simula um jogo de cartas hipotética chamado pôquer de cartão de dois. O aplicativo consiste em dois componentes principais: TwoCardPokerGame.exe fornece a interface do usuário e TwoCardPokerLib.dll oferece a funcionalidade subjacente.

Para criar a DLL do jogo iniciou o Visual Studio 2008 e selecionado o modelo de biblioteca de classes do c# do arquivo | caixa de diálogo New Project. Chamei a biblioteca TwoCardPokerLib. A estrutura geral da biblioteca é apresentada no do Figura 2. O código para TwoCardPokerLib é muito longo para apresentar em sua totalidade neste artigo. O código-fonte completo para a biblioteca TwoCardPokerLib e equipamento de injeção de falha de FaultHarness está disponível no download do código que acompanha este artigo.

A Figura 2 A biblioteca TwoCardPokerLib

using System;
namespace TwoCardPokerLib {
  // -------------------------------------------------
  public class Card {
    private string rank;
    private string suit;
    public Card() {
      this.rank = "A"; // A, 2, 3, . . ,9, T, J, Q, K
      this.suit = "c"; // c, d, h, s
    }
    public Card(string c) { . . . }
    public Card(int c) { . . . }
    public override string ToString(){ . . . }
    public string Rank { . . . }
    public string Suit { . . . }
    public static bool Beats(Card c1, Card c2) { . . . }
    public static bool Ties(Card c1, Card c2) { . . . }
  } // class Card

  // -------------------------------------------------
  public class Deck {
    private Card[] cards;
    private int top;
    private Random random = null;

    public Deck() {
      this.cards = new Card[52];
      for (int i = 0; i < 52; ++i)
        this.cards[i] = new Card(i);
      this.top = 0;
      random = new Random(0);
    }

    public void Shuffle(){ . . . }
    public int Count(){ . . . } 
    public override string ToString(){ . . . }
    public Card[] Deal(int n) { . . . }
    
  } // Deck

  // -------------------------------------------------
  public class Hand {
    private Card card1; // high card
    private Card card2; // low card
    public Hand(){ . . . }
    public Hand(Card c1, Card c2) { . . . }
    public Hand(string s1, string s2) { . . . }
    public override string ToString(){ . . . }
    private bool IsPair() { . . . }
    private bool IsFlush() { . . . }
    private bool IsStraight() { . . . }
    private bool IsStraightFlush(){ . . . }
    private bool Beats(Hand h) { . . . }
    private bool Ties(Hand h) { . . . }
    public int Compare(Hand h) { . . . }
    public enum HandType { . . . }
    
 } // class Hand

} // ns TwoCardPokerLib

O código de interface do usuário do aplicativo

Depois que eu tivesse o código subjacente de biblioteca de TwoCardPokerLib concluído, eu criei um componente de interface do usuário fictício. Iniciei um novo projeto no Visual Studio 2008 usando o modelo de aplicativo WinForm em c# e o chamei o meu aplicativo TwoCardPokerGame.

Utilizando o designer do Visual Studio, eu arrastado um controle Label para a coleção de caixa de ferramentas para a superfície de design de aplicativo e modificado a propriedade Text do controle de “ textBox1 ” para “ cartão dois pôquer ”. Em seguida, adicionei dois controles de rótulo de mais (“ sua mão ” e “ mão popular do computador ”), dois controles TextBox, dois controles de botão (“ Deal ” e “ Evaluate ”) e um controle ListBox. Não alterar os nomes de controle padrão de qualquer um dos oito controles — textBox1, textBox2, button1 e isso no.

Uma vez que meu design foi in-loco, eu clicou duas vezes no controle button1 para que o Visual Studio, gerar um esqueleto de manipulador de eventos do botão e carregar o arquivo Form1. cs no editor de código. Nesse ponto eu right-clicked no projeto TwoCardPokerGame na janela Solution Explorer, selecionado a opção Add Reference no menu de contexto e apontava para o arquivo TwoCardPokerLib.dll. Em Form1. cs, adicionei um usando instrução, para que eu precisaria qualificar totalmente os nomes de classes na biblioteca.

Em seguida, adicionei quatro objetos estáticos da classe-escopo para meu aplicativo:

namespace TwoCardPokerGame {
  public partial class Form1 : Form {
    static Deck deck;
    static Hand h1;
    static Hand h2;
    static int dealNumber; 
...

Objeto h1 é a mão do usuário e h2 a mão para o computador. Em seguida, adicionei alguns códigos de inicialização para o construtor do formulário:

public Form1() {
  InitializeComponent();
  deck = new Deck();
  deck.Shuffle();
  dealNumber = 0;
}

O construtor de bandeja cria um baralho de 52 cartas, em ordem a partir o ás de paus e o rei de espadas, o método em ordem aleatória
a ordem as cartas do baralho de aleatoriamente.

Em seguida, adicionei a lógica de código para o método Button1_Click, conforme mostrado no do Figura 3. Para cada uma das duas mãos, eu chamo o método de Deck.Deal para remover duas cartas do baralho objeto. Em seguida, eu passar essas duas cartas para o construtor de mão e exibir o valor da mão em um controle TextBox. Observe que o método Button1_Click trata qualquer exceção exibindo uma mensagem no controle ListBox.

De lidando os cartões, a Figura 3

private void button1_Click(
  object sender, EventArgs e) { 

  try  {
    ++dealNumber;
    listBox1.Items.Add("Deal # " + dealNumber);
    Card[] firstPairOfCards = deck.Deal(2);
    h1 = new Hand(firstPairOfCards[0], firstPairOfCards[1]);
    textBox1.Text = h1.ToString();

    Card[] secondPairOfCards = deck.Deal(2);
    h2 = new Hand(secondPairOfCards[0], secondPairOfCards[1]);
    textBox2.Text = h2.ToString();
    listBox1.Items.Add(textBox1.Text + " : " + textBox2.Text);
  }
  catch (Exception ex) {
    listBox1.Items.Add(ex.Message);
  }
}

Duas vezes em seguida, na janela de designer do Visual Studio que eu com controle do button2 geração automática de manipulador de eventos do controle
esqueleto. Adicionei alguns códigos simples para comparar os dois objetos de mão e exibir uma mensagem no controle ListBox. Observe que o método button2_Click não manipula as exceções diretamente:

private void button2_Click(
  object sender, EventArgs e) {
  int compResult = h1.Compare(h2);
  if (compResult == -1)
    listBox1.Items.Add(" You lose");
  else if (compResult == +1)
    listBox1.Items.Add(" You win");
  else if (compResult == 0)
    listBox1.Items.Add(" You tie");

  listBox1.Items.Add("-------------------------");
}

O equipamento de injeção de falha

Antes de criar a estrutura de injeção de falha de do Figura 1 mostrada, baixei a chave de DLLs em minha máquina de host de teste. Essas DLLs fazem parte de uma coleção de bibliotecas do .net chamada TestApi e podem ser encontradas em testapi.codeplex.com de .

A biblioteca TestApi é uma coleção de utilitários de software de teste-relacionados. Incluída na biblioteca TestApi é um conjunto de APIs de injeção de falha de código gerenciado. (Leia mais sobre eles em blogs.msdn.com/b/ivo_manolov/archive/2009/11/25/9928447.aspx de .) Baixei a inserção de falha de APIs de versão, o que, no meu caso, era versão 0,4 e descompactou o download do mais recente. Explicarei o que está no download e onde colocar os binários de injeção de falha em breve.

Versão 0,4 oferece suporte à inserção de falha de teste para aplicativos criados usando o .NET Framework 3. 5. A biblioteca de TestApi está em desenvolvimento ativo, portanto, você deve verificar o site da CodePlex atualizações para as técnicas apresentadas neste artigo. Além disso, convém verificar se há atualizações e dicas sobre o blog de Bill Liu, o desenvolvedor principal da biblioteca de injeção de falha TestApi em blogs.msdn.com/b/billliu/ de .

Para criar a estrutura de injeção de falha iniciou um novo projeto no Visual Studio 2008 e selecionado o modelo de aplicativo de console em c#. Chamei o aplicativo FaultHarness e adicionei alguns códigos mínimo para o modelo de programa (consulte do Figura 4).

Figura 4 do FaultHarness

using System;
namespace FaultHarness {
  class Program {
    static void Main(string[] args) {
      try {
        Console.WriteLine("\nBegin TestApi Fault Injection environmnent session\n");

        // create fault session, launch application

        Console.WriteLine("\nEnd TestApi Fault Injection environment session");
      }
      catch (Exception ex) {
        Console.WriteLine("Fatal: " + ex.Message);
      }
    }
  } // class Program
} // ns

Eu pressionar a tecla de <F5> para criar e executar o esqueleto de equipamento que criou uma pasta de \bin\Debug na pasta raiz FaultHarness.

O download de TestApi tem dois componentes principais. A primeira é TestApiCore.dll, que foi localizado na pasta de binários do download descompactado. Copiei dessa DLL para o diretório raiz do aplicativo FaultHarness. Em seguida, eu right-clicked no projeto FaultHarness na janela Solution Explorer, selecionados Add Reference e apontada TestApiCore.dll-lo. Em seguida, adicionei um usando a instrução para Microsoft.Test.FaultInjection na parte superior do meu código de estrutura de falhas para que o meu código de estrutura poderia acessar diretamente a funcionalidade de TestApiCore.dll. Também adicionei usando uma instrução para System. Diagnostics porque, como você verá em breve, quero acessar as classes Process e ProcessStartInfo a partir desse namespace.

O segundo componente de chave no download de injeção de falha é uma pasta chamada FaultInjectionEngine. Isso mantém versões de 32 bits e 64 bits do FaultInjectionEngine.dll. Copiei a pasta inteira do erro de ­ InjectionEngine para a pasta que contém minha FaultHarness executável no meu caso C:\FaultInjection\FaultHarness\bin\Debug\. Versão do sistema de injeção de falha estava usando 0,4 requer a pasta FaultInjectionEngine estar no mesmo local que o executável do equipamento. Além disso, o sistema exige que o aplicativo em teste binários estar localizado na mesma pasta do executável equipamento, para que eu copiados arquivos TwoCardPokerGame.exe e TwoCard ­ PokerLib.dll C:\FaultInjection\FaultHarness\bin\Debug\.

Para resumir, ao usar o sistema de injeção de falha de TestApi, uma boa abordagem é gerar uma estrutura de esqueleto e executá-lo para que um diretório de \bin\Debug equipamento é criado, e em seguida, coloque o arquivo TestApiCore.dll no diretório raiz do conjunto de colocar a pasta FaultInjectionEngine em \bin\Debug e coloque o aplicativo em teste binários (. exe e. dll) em \bin\Debug também.

Usando o sistema de injeção de falha de TestApi requer que você especifique o aplicativo em teste, o método do aplicativo em teste que irá disparar uma falha, a condição que irá disparar uma falha e o tipo de falha que será disparado:

string appUnderTest = "TwoCardPokerGame.exe";
string method = 
  "TwoCardPokerGame.Form1.button2_Click(object, System.EventArgs)";
ICondition condition =
  BuiltInConditions.TriggerEveryOnNthCall(3);
IFault fault =
  BuiltInFaults.ThrowExceptionFault(
    new ApplicationException(
    "Application exception thrown by Fault Harness!"));
FaultRule rule = new FaultRule(method, condition, fault);

Observe que, como o sistema requer que o aplicativo em teste para estar na mesma pasta do executável do equipamento, o nome do aplicativo em teste executável não é necessário o caminho para o local que.

Especifica o nome do método que irá disparar a falhas injetadas é uma fonte comum de problemas para iniciantes de injeção de falha de TestApi. O nome do método deve ser totalmente qualificado no space.Class.Method(args) de ­ de nome do formulário. Minha técnica preferida é usar a ferramenta ildasm.exe para examinar o aplicativo em teste para me ajudar a determinar a assinatura do método de disparo. Partir do Visual Studio especial de ferramentas de comando shell eu ildasm.exe de iniciar, aponte para o aplicativo em teste e clique duas vezes no método de destino. A Figura 5 mostra um exemplo de uso ildasm.exe para examinar a assinatura do método button2_Click.

A Figura 5 de usando ILDASM para examinar as assinaturas de método

Ao especificar a assinatura do método de disparador, você não usar o tipo de retorno do método, e você não usar nomes de parâmetro. Ao obter a assinatura do método correto às vezes requer um pouco de tentativa e erro. Por exemplo, na minha primeira tentativa de direcionar button2_Click, usei:

TwoCardPokerGame.Form1.button2_Click(object,EventArgs)

Eu tive que corrigi-lo para:

TwoCardPokerGame.Form1.button2_Click(object,System.EventArgs)

O download de TestApi contém uma pasta de documentação que contém um documento de conceitos que fornece uma boa orientação sobre como construir corretamente os diferentes tipos de assinaturas de método, incluindo construtores, propriedades e métodos genéricos e sobrecarga de operadores. Aqui eu direcionar um método que está localizado no aplicativo em teste, mas eu poderia também direcionou um método no subjacente dois ­ CardPokerLib.dll, tais como:

string method = "TwoCardPokerLib.Deck.Deal(int)"

Depois de especificar o método de disparador, a próxima etapa é especificar a condição sob a qual a falha será injetada para o aplicativo em teste. No meu exemplo que eu utilizei TriggerEveryOnNthCall(3), que como você viu injeta uma falha de toda a terceira vez que o disparador é chamado de método. O sistema de injeção de falha de TestApi tem um conjunto claro de condições do disparador incluindo TriggerIfCalledBy(method), TriggerOnEveryCall e outros.

Depois de especificar a condição de disparador, a próxima etapa é especificar o tipo de falha que será injetado no sistema em teste. Eu usei o BuiltInFaults.ThrowExceptionFault. Além das falhas de exceção, o sistema de injeção de falha de TestApi tem falhas de tipo de retorno interna que permitem que você inserir valores incorretos de retorno no seu aplicativo em teste em tempo de execução. Por exemplo, isso fará com que o método de disparador para retornar um valor (supostamente incorreto) -1:

IFault f = BuiltInFaults.ReturnValueFault(-1)

Depois que o método de disparador de falhas, condição e tipo de falha foi especificado, a próxima etapa é criar um novo FaultRule e passar essa regra para um novo FaultSession:

FaultRule rule = new FaultRule(method, condition, fault);
Console.WriteLine(
  "Application under test = " + appUnderTest);
Console.WriteLine(
  "Method to trigger injected runtime fault = " + method);
Console.WriteLine(
  "Condition which will trigger fault = On 3rd call");
Console.WriteLine(
  "Fault which will be triggered = ApplicationException");
FaultSession session = new FaultSession(rule);

Com todas as preliminares in-loco, a última parte de escrever o código de estrutura de falha é iniciar o aplicativo em teste no ambiente de sessão de falhas por meio de programação:

ProcessStartInfo psi = 
  session.GetProcessStartInfo(appUnderTest);
Console.WriteLine(
  "\nProgrammatically launching application under test");
Process p = Process.Start(psi);
p.WaitForExit();
p.Close();

Quando você executar o equipamento de falha, ele iniciará o aplicativo em teste em sua sessão de falhas com FaultInjection ­ Engine.dll assistindo para situações em que o método de disparador é chamado quando a condição de disparador for verdadeira. Os testes são executados manualmente aqui, mas você também pode executar a automação de teste em uma sessão de falhas.

Enquanto estiver em execução na sessão de falhas, informações sobre a sessão são registradas no diretório atual — ou seja, o diretório que contém o executável de equipamento de falhas e o aplicativo em teste executável. Você pode examinar esses arquivos de log para ajudar a resolver quaisquer problemas que podem ocorrer enquanto você estiver desenvolvendo seu equipamento de injeção de falha.

Explicação

O exemplo e explicações que apresentei aqui devem obter você ativos e funcionando com a criação de um equipamento de injeção de falha para seu aplicativo em teste. Assim como acontece com qualquer atividade que seja parte do processo de desenvolvimento de software, você poderá ter recursos limitados e você deve analisar os custos e benefícios de executar o teste de injeção de falha. No caso de alguns aplicativos, o esforço necessário para criar a injeção de falha de teste não talvez valha a pena, mas há muitos cenários de testes em que o teste de injeção de falha é extremamente importante. Imagine que o software que controla um dispositivo médico ou um sistema de vôo. Em situações como essas, aplicativos absolutamente devem ser robusto e é capaz de lidar corretamente com todos os tipos de falhas inesperadas.

Há uma certa ironia envolvidas com o teste de injeção de falha. A idéia é que, se você pode prever situações quando uma exceção pode ocorrer, você pode em teoria geralmente por meio de programação se proteger contra essa exceção e testar o comportamento correto desse comportamento guarding. No entanto, mesmo em tais situações, o teste de injeção de falha é útil para a geração de difícil criar exceções. Além disso, é possível inserir falhas são muito difícil de prever como o System. OutOfMemoryException.

O teste de injeção de falha está relacionado ao e às vezes confuso com testes de mutação. Em testes de mutação deliberadamente insere erros de sistema em teste, mas, em seguida, executar um conjunto de testes existentes no sistema com problemas para determinar se o conjunto de testes captura os novos erros criados. Testes de mutação são uma maneira para medir a eficiência do conjunto de teste e, por fim, aumentar a cobertura do caso de teste. Como você viu neste artigo, o principal objetivo do teste de injeção de falha é determinar se o sistema em teste corretamente trata os erros.

Dr.Da James McCaffrey trabalha para a Volt Information Sciences Inc., onde gerencia o treinamento técnico para engenheiros de software trabalham em Redmond da Microsoft, Wash., campus. Para fazer trabalhou em vários produtos da Microsoft, incluindo o Internet Explorer e o MSN Busca. O Dr. McCaffrey é autor de “ .NET Test Automation Recipes ” (Apress, 2006) e pode ser contatado pelo jammc@microsoft.comde .

Graças aos seguintes especialistas técnicos para revisão deste artigo: Bill Liu e do Paul Newson