Este artigo foi traduzido por máquina.
Execução de teste
Automação de testes de interface do usuário em aplicativos WPF
James McCaffrey
Download do código disponível na Galeria de código do MSDN
Procure o código on-line
Conteúdo
O aplicativo WPF em teste
A automação de teste de interface do usuário
Palavras finais
Na coluna deste mês, mostro como escrever automação de teste da interface do usuário para aplicativos do Windows Presentation Foundation (WPF). Aplicativos WPF usam um novo subsistema de elementos gráficos e técnicas de automação de teste da interface do usuário mais tradicionais simplesmente não funcionam com aplicativos WPF. Felizmente, a biblioteca de automação de interface do usuário da Microsoft (MUIA) foi projetada com automação de interface do usuário de aplicativo WPF em mente. Você também pode usar a biblioteca de automação de interface do usuário do Microsoft para testar aplicativos Win32 clássicos e aplicativos.NET WinForm-com base em host computadores que executam sistemas operacionais que oferecem suporte o Microsoft .NET 3.0 Framework.
Comparados com antigas alternativas para a automação da interface do usuário, a biblioteca de automação de interface do usuário do Microsoft é mais poderoso e mais consistente e após uma curva de aprendizado inicial, você encontrará é muito mais fácil usar. Esta coluna presume que você tenha alguma familiaridade básica com os aplicativos WPF, habilidades de C# nível intermediárias, mas nenhuma experiência com a biblioteca MUIA.
Uma boa maneira para mostrar onde eu estou cabeças é com uma captura de tela. A imagem na Figura 1 mostra que eu estou testando um aplicativo WPF simples mas representativo. O aplicativo é chamado CryptoCalc, e ele calcula um hash criptográfico de uma cadeia de entrada usando um dos três técnicas de hash: MD5 hash, hash SHA1 ou criptografia DES.
Figura 1 automação de interface do usuário aplicativo WPF
O MD5 (Message Digest 5) hash técnica aceita uma matriz arbitrariamente tamanho de bytes e retorna uma impressão digital de 16 bytes que, em seguida, pode ser usada para diversos fins de identificação. O SHA1 (Secure Hash Algorithm 1) hash técnica é semelhante ao MD5, exceto que SHA1 usa um algoritmo diferente e retorna uma impressão digital de 20 bytes. O DES (digital Encryption Standard) é uma técnica de criptografia de chave simétrica que também pode ser usada para produzir uma matriz de bytes identificação. Criptografia DES-hash retorna uma matriz byte que tenha pelo menos o tamanho o número de bytes de entrada.
A automação de teste da interface do usuário mostrada na Figura 1 é executada por meio de um aplicativo de console que inicia o aplicativo em teste; usa a biblioteca de automação de interface do usuário do Microsoft para obter referências para os controles de aplicativo e usuário do aplicativo; e simula um usuário inserir "Hello!", a opção criptografar DES e clicar no controle de botão COMPUTE. A automação de teste, em seguida, verifica o estado resultante do aplicativo em teste, examinando o controle de caixa de texto resultado para um valor esperado e imprime uma passagem ou falha resultado. Capturei a screen shot na Figura 1 somente antes da automação de teste fechar o aplicativo em teste, simulando cliques do usuário na arquivo e, em seguida, itens de menu sair.
As seções desta coluna a seguir, será rapidamente descrevem o aplicativo de WPF CryptoCalc que estou testando, explique como iniciar o aplicativo em teste, como usar a biblioteca de automação de interface do usuário do Microsoft para obter referências para os controles do aplicativo e de usuário, como simular ações do usuário e como verificar o estado do aplicativo. Também descreverei como você pode estender e modificar o sistema de teste apresentado aqui para atender às suas próprias necessidades. Eu acho que você encontrará a capacidade de usar a biblioteca de automação de interface do usuário do Microsoft para testar aplicativos WPF uma adição bons ao seu conjunto ferramenta particulares.
O aplicativo WPF em teste
Vamos dar uma breve olhada no aplicativo WPF em teste para que você vai compreender o objetivo a automação de teste e entender as questões de design que afetam a interface do usuário automação de teste. O aplicativo CryptoCalc é um aplicativo de usuário simples, janela única que calcula um hash criptográfico de uma seqüência de caracteres. O aplicativo aceita uma seqüência fornecida pelo usuário de até 24 caracteres em um controle TextBox, converte a seqüência de caracteres de entrada em uma matriz de bytes, calcula um dos três tipos de criptografia-hashes dos bytes de entrada e exibe os bytes de hash resultantes em um segundo controle de TextBox.
Eu criado o aplicativo em teste usando Visual Studio 2008 com C# e chamado CryptoCalc o projeto. O modelo WPF gera uma definição de interface do usuário do aplicativo esqueleto como um arquivo XAML:
<Window x:Class="CryptoCalc.Window1"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid></Grid>
</Window>
Observe que a definição de controle de janela nível superior não contém um atributo name. Isso é significativo porque, como veremos logo, quando você escreve automação de teste, uma maneira fácil para obter uma referência a um controle usando a biblioteca MUIA é acessar a propriedade AutomationId, que é gerada pelo compilador do atributo de nome do controle. Controles sem um atributo de nome XAML não receberão uma propriedade AutomationId. Essa idéia é um exemplo específico, de baixo nível da importância de considerar os problemas de design de aplicativo para itens como segurança, extensibilidade e automação de teste.
Em seguida, adicionei um controle de rótulo e um controle TextBox, arrastando itens da Visual Studio Toolbox para a superfície de design:
<Label Height="28" HorizontalAlignment="Left"
Margin="10,33,0,0" Name="label1" VerticalAlignment="Top"
Width="120">Enter string:</Label>
<TextBox MaxLength="24" Height="23" Margin="10,57,51,0"
Name="textBox1" VerticalAlignment="Top" />
Observe que por padrão, esses controles recebem atributos de nome normais — label1 e textBox1, respectivamente. Em seguida, eu colocado três controles de botão de opção dentro de um controle GroupBox. Diferentemente o controle WinForms GroupBox antigo, um GroupBox WPF aceita somente um único item, para que eu quebradas meus três controles RadioButton para um contêiner StackPanel único (que pode conter vários itens):
<GroupBox Header="Crypto Type" Margin="10,91,118,127"
Name="groupBox1">
<StackPanel Height="52" Name="stackPanel1" Width="127">
<RadioButton Height="16" Name="radioButton1" Width="120">
MD5 Hash</RadioButton>
<RadioButton Height="16" Name="radioButton2" Width="120">
SHA1 Hash</RadioButton>
<RadioButton Height="16" Name="radioButton3" Width="120">
DES Encrypt</RadioButton>
</StackPanel>
</GroupBox>
Em seguida, eu colocado um controle de Button para disparar o cálculo de hash de criptografia e um controle TextBox para armazenar o resultado para o controle de janela principal:
<Button Height="23" Margin="10,0,0,90" Name="button1"
VerticalAlignment="Bottom" Click="button1_Click"
HorizontalAlignment="Left" Width="89">Compute</Button>
<TextBox Height="63" Margin="10,0,51,13" Name="textBox2"
VerticalAlignment="Bottom" TextWrapping="Wrap" />
Um dos recursos que eu realmente gosto sobre os aplicativos WPF é o novo paradigma de menu de controle, o que, ao contrário de itens de menu do WinForm, trata menus e submenus como controles de usuário comum. Em primeiro lugar, arrastei um controle de recipiente do menu principal para a parte superior do aplicativo CryptoCalc:
<Menu Height="22" Name="menu1"
VerticalAlignment="Top" IsMainMenu="True" >
</Menu>
O Visual Studio 2008 não oferece suporte um design de arrastar-e-soltar para controles do filho MenuItem, lo, eu adicionado Meus itens de menu para o arquivo de definição XAML manualmente, adicionando um item de menu arquivo de nível superior:
<MenuItem Header="_File">
<MenuItem Header="_New" Name="fileNew" />
<MenuItem Header="_Open" Name="fileOpen" />
<Separator />
<MenuItem Header="E_xit" Name="fileExit"
InputGestureText="Alt-F4" ToolTip="Exit CryptoCalc"
Click="OnFileExit" />
</MenuItem>
Design do WPF oferece suporte a sintaxe de chave do acelerador normal, como _File. O < Separador / > marca está limpo e fácil. O atributo de dica de ferramenta irá gerar código que exibe uma mensagem curta de ajuda quando o usuário mouses sobre o controle MenuItem associado. Quando compilado, o atributo clique OnFileExit gerará código C# que espera um manipulador de eventos com essa assinatura:
public void OnFileExit(object sender, RoutedEventArgs e) {
//...
}
Portanto, depois que eu adicionado o código de lógica de aplicativo comum para o aplicativo CryptoCalc, eu adicionado manualmente o manipulador de eventos e, em seguida, fornecidos funcionalidade colocando this.Close no corpo do método. Observe que os controles de item de menu novo e abrir não tem os eventos associados no momento. Fiz isso para destacar que interface do usuário automação de teste geralmente ocorre durante o desenvolvimento quando muitos recursos de aplicativo estão incompletos. O design de controle de ajuda do nível superior segue o mesmo padrão como o controle de arquivo:
<MenuItem Header="_Help">
<MenuItem Header="Help Topics" />
<Separator />
<MenuItem Header="About CryptoCalc" />
</MenuItem>
O recipiente de menu e definições de controle MenuItem gerará código C# que por sua vez produz a interface do usuário mostrada na captura de tela na Figura 2 . Com o design da interface do usuário feito, eu alternado sobre para o modo de exibição de código. Em seguida, adicionei dois instruções using ao usando instruções geradas pelo Visual Studio no arquivo Window1.xaml.cs para que eu poderia acessar classes de criptografia e os métodos sem qualificar totalmente seus nomes:
using System.Security.Cryptography;
using System.IO;
A Figura 2 menu do arquivo de aplicativo WPF
A funcionalidade principal do aplicativo CryptoCalc simples está contida no método Button1_Click e é listada na Figura 3 . Meu código primeiro captura o texto no controle textBox1 e converte texto em uma matriz de bytes. Observe que para manter o tamanho do meu código de exemplo curto, omita o normal verificação de erro que você pode executar em um ambiente de produção.
A Figura 3 StatCalc aplicativo código
private void button1_Click(object sender, RoutedEventArgs e)
{
string input = textBox1.Text;
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
if (radioButton1.IsChecked == true) {
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
byte[] hashedBytes = md5.ComputeHash(inputBytes);
textBox2.Text = BitConverter.ToString(hashedBytes);
}
else if (radioButton2.IsChecked == true) {
SHA1CryptoServiceProvider sha = new SHA1CryptoServiceProvider();
byte[] hashedBytes = sha.ComputeHash(inputBytes);
textBox2.Text = BitConverter.ToString(hashedBytes);
}
else if (radioButton3.IsChecked == true) {
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] blanks = System.Text.Encoding.UTF8.GetBytes(" "); // 8 spaces
des.Key = blanks;
des.IV = blanks;
des.Padding = PaddingMode.Zeros;
MemoryStream ms = new MemoryStream();
CryptoStream cs =
new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write);
cs.Write(inputBytes, 0, inputBytes.Length);
cs.Close();
byte[] encryptedBytes = ms.ToArray();
ms.Close();
textBox2.Text = BitConverter.ToString(encryptedBytes);
}
}
As ramificações de lógica de aplicativo dependendo de qual botão de opção controle esteja selecionado. Curiosamente, porque a propriedade IsChecked retorna tipo? booleano (Boolean anulável), eu tive que verifique explicitamente a propriedade de igualdade em true. O código para MD5 e SHA1 hash deve ser auto-explicativo.
O algoritmo de criptografia DES requer uma chave de 64 bits. Como eu estou usando DES para hash em vez de codificar e decodificar, usam uma chave fictícia gerada por 8 caracteres de espaço. Ao usar criptografia de chave simétrica para fins de hash, geralmente usar uma chave nula de {0 x 00, 0 x 00, 0 x 00, 0 x 00, 0 x 00, 0 x 00, 0 x 00, 0 x 00}, mas o objeto DESCryptoServiceProvider sinalizadores isso como uma chave fraca conhecida e lança uma exceção. Eu uso a matriz de bytes de espaço em branco-espaço mesmo para fornecer um valor para o vetor de inicialização chamada.
Criptografia DES funciona em blocos de 8 bytes; portanto, eu especificar PaddingMode.Zeros para que qualquer entrada será ser preenchida com zeros para trazer o tamanho da entrada de backup para um múltiplo par do 8 bytes. Observe que você não usaria PaddingMode.Zeros para codificação e decodificação como preenchimento com zeros não permite que você descriptografar (o algoritmo de descriptografia não pode determinar quais zeros a codificação de texto são enchimento e que são parte a formatação original).
A automação de teste de interface do usuário
Ao escrever automação de teste usando a biblioteca de automação de interface do usuário do Microsoft, você precisará saber como identificar os controles no aplicativo em teste. Uma boa maneira de fazer isso é usar a ferramenta UISpy. Para aqueles que não conhecem, UISpy é o equivalente a WPF a antiga ferramenta Spy ++ e permite examinar propriedades dos componentes da interface do usuário de um aplicativo WPF. A ferramenta UISpy é parte do SDK Microsoft Windows e está disponível como um download gratuito doMicrosoft.com / downloads.
A captura de tela na Figura 4 mostra o resultado de destino no controle Button no aplicativo CryptoCalc. De uma perspectiva de automação de teste, os campos importantes para identificar os controles do aplicativo são o ControlType (ControlType.Edit neste caso), AutomationId (button1) e valores de nome (computação). O campo importante para manipulação de controles do aplicativo é a lista de ControlPatterns (Invoke).
A Figura 4 examinando controles com UISpy
Manualmente teste mesmo esse pequeno aplicativo de CryptoCalc por meio de sua interface do usuário seria entediante, sujeito a erro, demorado e ineficiente. Você precisará inserir uma entrada, clique no controle de botão Compute, verificar visualmente a resposta e registrar manualmente o resultado de êxito/falha. Uma abordagem muito melhor é usar a biblioteca de automação de interface do usuário do Microsoft para escrever automação de teste que simula um usuário exercitar o aplicativo e, em seguida, determina se o aplicativo respondeu corretamente. Automatizando entediante casos de teste, você pode liberar tempo para mais interessantes e úteis manuais casos de teste no qual sua experiência e intuição desempenham um papel grande.
A estrutura geral do equipamento de teste que produziu o resultado mostrado na Figura 1 está listada na Figura 5 . Eu iniciado o Visual Studio 2008 e criou um novo programa de aplicativo de console. Usei C#, mas será possível converter facilmente meu código de automação de teste para o Visual Basic .NET se desejar. Em seguida, adicionei as referências de projeto para as bibliotecas UIAutomationClient.dll e UIAutomationTypes.dll. Essas bibliotecas são parte do .NET Framework 3.0, mas não estão visíveis por padrão para um projeto do Visual Studio. As bibliotecas são normalmente localizadas no diretório c:\Arquivos Files\Reference Assemblies\Microsoft\Framework\v3.0. Observe que a biblioteca UIAutomationClient.dll contém as classes principais necessárias para a automação de teste. A biblioteca UIAutomationTypes.dll contém várias definições de tipo usadas pelo MUIA automação de teste.
A Figura 5 da interface do usuário Test Automation code estrutura
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Automation;
namespace Harness {
class Program {
static void Main(string[] args) {
try {
Console.WriteLine("\nBegin WPF UIAutomation test run\n");
// launch CryptoCalc application
// get reference to main Window control
// get references to user controls
// manipulate application
// check resulting state and determine pass/fail
Console.WriteLine("\nEnd automation\n");
}
catch (Exception ex) {
Console.WriteLine("Fatal: " + ex.Message);
}
} // Main()
} // class
} // ns
Para sua conveniência, adicionei usando instruções que apontam para o namespace System.Diagnostics (para facilmente possa usar a classe Process) e para o namespace System.Threading (para facilmente possa usar o método Thread.Sleep()). Como de costume com qualquer automação de teste, quebra meu equipamento com um bloco de nível superior try-catch para manipular erros fatais. Meu código de automação de teste começa por iniciar o aplicativo em teste:
Console.WriteLine("Launching CryptoCalc application");
Process p = null;
p = Process.Start("..\\..\\..\\CryptoCalc\\bin\\Debug\\CryptoCalc.exe");
Agora, antes de eu tentar qualquer automação de teste, preciso verificar se o processo associado o aplicativo CryptoCalc em teste está registrado na máquina host. Embora eu pôde pausar meu equipamento de teste, simplesmente inserindo uma instrução Thread.Sleep, eu tenho há boa maneira de saber quanto para fazer uma pausa. Uma abordagem melhor é usar um loop com atraso inteligente:
int ct = 0;
do {
Console.WriteLine("Looking for CryptoCalc process. . . ");
++ct;
Thread.Sleep(100);
} while (p == null && ct < 50);
Aqui eu pausar por 100 milissegundos cada vez pelo loop atraso. O utilitário sai do loop do atraso se o objeto de processo se torna não-nulos, que significa que o processo foi encontrado, ou se o loop executou 50 vezes. a Figura 6 mostra como POSSO determinar se o loop de atraso tempo-limite ou o processo AUT foi encontrado.
A Figura 6 determinar o que aconteceu
if (p == null)
throw new Exception("Failed to find CryptoCalc process");
else
Console.WriteLine("Found CryptoCalc process");
// Next I fetch a reference to the host machine's Desktop as an
// AutomationElement object:
Console.WriteLine("\nGetting Desktop");
AutomationElement aeDesktop = null;
aeDesktop = AutomationElement.RootElement;
if (aeDesktop == null)
throw new Exception("Unable to get Desktop");
else
Console.WriteLine("Found Desktop\n");
Você pode pensar em todos os controles do WPF aplicativo em teste como filhos do controle de janela principal do aplicativo. A janela principal é um filho da área de trabalho total, portanto, preciso uma referência para a área de trabalho para obter uma referência para o aplicativo. Agora use o método FindFirst para anexar ao aplicativo em teste:
AutomationElement aeCryptoCalc = null;
int numWaits = 0;
do {
Console.WriteLine("Looking for CryptoCalc main window. . . ");
aeCryptoCalc = aeDesktop.FindFirst(TreeScope.Children,
new PropertyCondition(AutomationElement.NameProperty, "CryptoCalc"));
++numWaits;
Thread.Sleep(200);
} while (aeCryptoCalc == null && numWaits < 50);
EU use a técnica de loop com atraso inteligentes em vez da técnica de suspensão arbitrariamente longa. O método FindFirst é usado quando o controle que está procurando é um único controle. FindFirst aceita dois argumentos. A primeira é um valor de escopo. Os três tipos escopo mais comuns usados em automação de teste são TreeScope.Parent, TreeScope.Children e TreeScope.Descendants. Como o aplicativo CryptoCalc é um filho direto da área de trabalho, eu uso o escopo de TreeScope.Children.
O segundo argumento para FindFirst é um objeto que representa informações que identifica o controle que você está procurando. Aqui, eu especifico que eu estou procurando um controle que tem uma propriedade de nome com valor "CryptoCalc". Eu poderia também ter usado a propriedade AutomationId, que descreverei em breve. Agora verificar se eu realmente tem uma referência para o controle de janela principal do aplicativo:
if (aeCryptoCalc == null)
throw new Exception("Failed to find CryptoCalc main window");
else
Console.WriteLine("Found CryptoCalc main window");
Assim que tiver o aplicativo, POSSO usar sua referência para obter referências a todos os controles de usuário que a automação de teste será manipular ou examinar. EU comece apanhar o controle de botão:
Console.WriteLine("\nGetting all user controls");
AutomationElement aeButton = null;
aeButton = aeCryptoCalc.FindFirst(TreeScope.Children,
new PropertyCondition(AutomationElement.NameProperty, "Compute"));
if (aeButton == null)
throw new Exception("No compute button");
else
Console.WriteLine("Got Compute button");
Eu uso o mesmo padrão que usei para obter uma referência para a janela principal; aqui no controle Button é um filho direto do controle de aplicativo de janela principal. Como o controle botão é um controle estático, não preciso usar a técnica de loop com atraso antes de acessar a referência ao controle. No caso de controles dinâmicos, você deve usar a técnica de loop com atraso.
Em seguida, eu desejo obter referências para os dois controles TextBox. Embora os dois controles TextBox tenham nomes textBox1 e textBox2, os controles não recebem propriedades de nome, portanto, que não é possível usar o mesmo padrão de NameProperty que usei para o controle Button. No entanto, os controles TextBox receber uma propriedade AutomationId que eu poderia usar para obter uma referência para os controles, como:
aeTextBox1 = aeCryptoCalc.FindFirst(TreeScope.Children,
new PropertyCondition(AutomationElement.AutomationIdProperty, "textBox1"));
Em vez disso, decidi usar uma abordagem diferente, principalmente para fins de demonstração. Em vez de identificar um único controle usando propriedade do nome do controle, uso o método FindAll para buscar uma coleção de controles por tipo de controle. Acontece que controles TextBox são tipos de ControlType.Edit, portanto, o meu código captura todos os controles TextBox:
AutomationElementCollection aeAllTextBoxes = null;
aeAllTextBoxes = aeCryptoCalc.FindAll(TreeScope.Children,
new PropertyCondition(AutomationElement.ControlTypeProperty,
ControlType.Edit));
if (aeAllTextBoxes == null)
throw new Exception("No textboxes collection");
else
Console.WriteLine("Got textboxes collection");
Uma vez que eu tenho este conjunto pode acessar cada TextBox usando a indexação de matriz:
AutomationElement aeTextBox1 = null;
AutomationElement aeTextBox2 = null;
aeTextBox1 = aeAllTextBoxes[0];
aeTextBox2 = aeAllTextBoxes[1];
if (aeTextBox1 == null || aeTextBox2 == null)
throw new Exception("TextBox1 or TextBox2 not found");
else
Console.WriteLine("Got TextBox1 and TextBox2");
Em geral, obter referências de controle, usando a propriedade Name ou a propriedade AutomationId é uma abordagem melhor que usar ControlType, mas em alguns casos você não pode ter nenhuma opção. Em seguida, eu obtenha uma referência ao controle de botão de opção que deseja usar na meu cenário de teste:
AutomationElement aeRadioButton3 = null;
aeRadioButton3 = aeCryptoCalc.FindFirst(TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty,
"DES Encrypt"));
if (aeRadioButton3 == null)
throw new Exception("No RadioButton");
else
Console.WriteLine("Got RadioButton3");
Eu uso um padrão semelhante àquele que usado para obter uma referência para o controle de botão. No entanto, observe que eu especificar TreeScope.Descendants em vez de TreeScope.Children. Faço isso como o controle de botão de opção é que um filho do controle GroupBox e, portanto, não é um filho direto do controle de janela principal. Como alternativa, eu poderia primeiro obter uma referência ao controle GroupBox (como um filho do controle de janela principal) e usada essa referência para obter uma referência para o controle de botão de opção. Assim que tiver as referências a Minhas controles, É possível iniciar manipulando o aplicativo em teste. Começar, simulando a entrada do usuário para o controle TextBox1:
Console.WriteLine("\nSetting input to 'Hello1!'");
ValuePattern vpTextBox1 =
(ValuePattern)aeTextBox1.GetCurrentPattern(ValuePattern.Pattern);
vpTextBox1.SetValue("Hello!");
Usando um método de SetValue provavelmente não é fornecido como um não-programada, mas observe que eu não acessam SetVaue() diretamente através do objeto aeTextBox1. Em vez disso, eu usar um objeto de ValuePattern intermediário. O conceito de objetos de AutomationPattern como ValuePattern é provavelmente a barreira conceitual maior para engenheiros de novo para a biblioteca de automação de interface do usuário do Microsoft. Você pode pensar padrão de objetos como uma abstração para expor a funcionalidade do controle que é independente do tipo ou aparência do controle. Colocar outra maneira, você pode usar instâncias AutomationPattern específicas, como ValuePattern para habilitar a funcionalidade de controle específico.
Eu simplificar as coisas ainda mais, pensando que ControlType um controle expõe o tipo de controle é o controle e que padrão um controle expõe o que o controle pode fazer. Use uma abordagem semelhante para simular um usuário selecionando o controle RadioButton3:
Console.WriteLine("Selecting 'DES Encrypt' ");
SelectionItemPattern spSelectRadioButton3 =
(SelectionItemPattern)aeRadioButton3.GetCurrentPattern(
SelectionItemPattern.Pattern);
spSelectRadioButton3.Select();
Desta vez eu uso o SelectionItemPattern para habilitar uma seleção. Às vezes, o nome do método GetCurrentPattern confunde MUIA biblioteca iniciantes. Um teste de automação de ponto de vista, o método é configuração, não obtendo um AutomationPattern especificado. Mas, da perspectiva do cliente e servidor, o código de cliente de automação está buscando uma propriedade específica do aplicativo em código de servidor de teste.
O código que usei para simular um clique no controle de botão calcular deve ajudar a esclarecer:
Console.WriteLine("\nClicking on Compute button");
InvokePattern ipClickButton1 =
(InvokePattern)aeButton.GetCurrentPattern(
InvokePattern.Pattern);
ipClickButton1.Invoke();
Thread.Sleep(1500);
Aqui, em essência, eu uso o InvokePattern para habilitar um clique de botão e, em seguida, executar o clique usando o método Invoke. Observe que uma pausa 1,5 segundo para fornecer meu tempo de aplicativo para responder. Eu poderia também ir em um loop de atraso, verificar periodicamente se o campo de textBox2 resultado estiver vazio ou não. Neste momento no meu código de automação de teste, eu ter iniciado o aplicativo em teste, inseridos "Olá!" no controle TextBox entrado selecionado o controle de botão de opção de criptografia do DES e clicou no controle de botão de computador.
Agora examinar o controle TextBox2 para verificar se que possui um valor esperado correto:
Console.WriteLine("\nChecking TextBox2 for '91-1E-84-41-67-4B-FF-8F'");
TextPattern tpTextBox2 =
(TextPattern)aeTextBox2.GetCurrentPattern(TextPattern.Pattern);
string result = tpTextBox2.DocumentRange.GetText(-1);
Aqui, usarei TextPattern para preparar uma chamada para um método GetText. Observe que eu chamo GetText indiretamente através uma propriedade DocumentRange, que retorna um intervalo de texto que inclui o texto principal de um documento, nesse caso, uma caixa de texto simples. O argumento-1 para GetText é usado para que não há um limite máximo o tamanho da seqüência de caracteres retorno. Uma maneira alternativa de ler o conteúdo do controle TextBox2 é usar o método GetCurrentPropertyValue:
string result =
(string)aeTextBox2.GetCurrentPropertyValue(ValuePattern.ValueProperty);
Eu tenho codificado a entrada de caso de teste para o utilitário de teste. Uma abordagem mais flexível é lê a entrada de caso de teste e os valores esperados do armazenamento alguns dados externos. Agora, com o valor real do aplicativo em teste em mãos, verifico com um valor esperado para determinar o resultado de êxito/falha do cenário meu teste:
if (result == "91-1E-84-41-67-4B-FF-8F") {
Console.WriteLine("Found it");
Console.WriteLine("\nTest scenario: Pass");
}
else {
Console.WriteLine("Did not find it");
Console.WriteLine("\nTest scenario: *FAIL*");
}
Eu simplesmente exibir o resultado de cenário de teste para o shell de comando. Em um ambiente de produção, você geralmente desejará gravar os resultados do teste em armazenamento externo.
Com meu cenário de teste concluído, pode fechar o aplicativo em teste exercitar o controle Menu. Em primeiro lugar, obtenho o controle de arquivo MenuItem nível superior:
Console.WriteLine("\nClicking on File-Exit item in 5 seconds");
Thread.Sleep(5000);
AutomationElement aeFile = null;
aeFile = aeCryptoCalc.FindFirst(TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "File"));
if (aeFile == null)
throw new Exception("Could not find File menu");
else
Console.WriteLine("Got File menu");
Observe que eu uso o escopo de TreeScope.Descendants porque o controle MenuItem de arquivo é um subcontrole do controle menu recipiente. Agora simular um usuário clicando no item de arquivo:
Console.WriteLine("Clicking on 'File'");
ExpandCollapsePattern expClickFile =
(ExpandCollapsePattern)aeFile.GetCurrentPattern(ExpandCollapsePattern.Pattern);
expClickFile.Expand();
Os controles do MenuItem que possuem submenus não expõe um padrão de Invoke como você pode esperar; elas expõem um padrão de expandir. Com itens de submenu arquivo agora processados e visíveis, pode obter uma referência para o comando Sair:
AutomationElement aeFileExit = null;
aeFileExit = aeCryptoCalc.FindFirst(TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "Exit"));
if (aeFileExit == null)
throw new Exception("Could not find File-Exit");
else
Console.WriteLine("Got File-Exit");
E agora eu pode usar um InvokePattern no submenu Sair para fechar o aplicativo CryptoCalc em teste:
InvokePattern ipFileExit =
(InvokePattern)aeFileExit.GetCurrentPattern(InvokePattern.Pattern);
ipFileExit.Invoke();
Console.WriteLine("\nEnd automation\n");
Neste ponto minha automação de teste é feita, e eu posso registrar o resultado de caso de teste e iniciar outro cenário de teste.
Palavras finais
O código apresentado na coluna deste mês oferece uma boa base para a Introdução à criação de automação de teste personalizado para aplicativos WPF. A biblioteca MUIA é muito extensa e pode manipular a maioria dos cenários de testes simples.
O padrão para adaptar o exemplo simples que apresentei aqui para testar seu próprio aplicativo WPF é simples. Ao criar seu aplicativo WPF, tente garantir que todos os controles tenham um atributo de nome XAML para que um AutomationID é gerado. Use a ferramenta UISpy para determinar como identificar e manipular os controles de usuário. Determine o padrão de MUIA permitirá que você examinar o estado e o valor de um controle de usuário. Com essas informações em mãos, você pode manipular cenários de automação de teste de interface do usuário mais básicos.
Em todas as situações de testes, você deve avaliar cuidadosamente o esforço necessário para criar sua automação de teste contra o benefício que você obter a automação. Com base na minha experiência, automação de teste de interface do usuário do WPF é geralmente melhor usada para testes de regressão de cenários relativamente simples. Isso lhe concentre-se o manual testes em cenários complexos e encontrar bugs sutis, novos sem precisar se preocupar faltando um bug óbvio que foi introduzido acidentalmente quando desenvolvimento adicionada nova funcionalidade.
Como uma prática da regra geral, para o tipo de automação de testes leve descrito nesta coluna, eu descobriu que se a minha automação de teste levar menos de quatro horas para criar, em seguida, recebo um retorno razoável de valor do meu investimento de tempo. É claro que seu ambiente será diferente; o ponto é que você deve não automaticamente presuma que automação de teste da interface do usuário é sempre o melhor uso possível de seus recursos de testes. WPF ainda é uma tecnologia relativamente nova. Mas como a presença de aumentos de aplicativos do WPF, as técnicas de automação de teste da interface do usuário apresentadas nesta coluna são apta para se tornar cada vez mais úteis para você para criar software melhor.
Dr. James McCaffrey trabalha para a Volt Information Sciences Inc., onde gerencia o treinamento técnico para software engenheiros de trabalham no campus de Redmond, Washington da Microsoft. Ele trabalhou em vários produtos da Microsoft incluindo o Internet Explorer e o MSN Search. James é autor de .NET Test Automation Recipes (NET Apress, 2006). James pode ser contatado pelo jmccaffrey@volt.com ou v-jammc@microsoft.com.