Este artigo foi traduzido por máquina.
Teste de geração
Testes de unidade automatizados para código herdado com o Pex
Nikhil Sachdeva
Na minha vida anterior foi consultor. Um dos meus clientes banco à esquerda, queria automatizar seu processo de origem de empréstimo. O banco já tinha um sistema no lugar incluído um aplicativo baseado no Windows, um proprietário back-end e um sistema de mainframe também foi o coração de sua solução. Meu trabalho era integrar sistemas existentes com um conjunto de aplicativos desenvolvidos para a divisão de contas.
O cliente tinha criado um serviço Web comunicadas com o mainframe. Primeiro parecia bastante simples. Tudo que precisei fazer foi gancho no serviço, obter as informações e passar para o novo aplicativo de contas. Mas nunca é simples.
Durante a implementação descobri que novos sistemas esperado um atributo originador de empréstimo, mas o método Web service GetLoanDetails não retornou informações. Acontece que o serviço foi criado há anos por um desenvolvedor que não está mais com a empresa. O banco tiver sido usando o serviço sem modificações porque ela tem muitas camadas e todos são receio de ruptura algo.
Que o serviço Web é código herdado.
Eventualmente, criamos um wrapper de serviço leve para que novos sistemas poderiam chamar para quaisquer novas informações e continuar usando o serviço antigo. Teria sido muito mais fácil de modificar o serviço existente se tivesse sido seguido um design consistente e testável.
Manter o código novas
Código herdado é algo que foi desenvolvido no passado e ainda é usado, mas é difícil manter e alterar. Razões para mantendo esses sistemas geralmente giram em torno o custo e o tempo envolvido na criação de um novo sistema semelhante — Embora às vezes há falta de conscientização sobre as implicações futuras dos esforços de codificação atuais.
O fato é que durante um período de tempo código começa rot. Isso pode ser devido a alterações de requisito, um design não-muito-bem-pensamento-through, antipadrões aplicados ou falta de testes apropriados. O resultado final é o código que é difícil manter e difícil alterar.
Há várias abordagens para impedir que seu código rotting, mas um dos mais eficaz pode ser escrever código que é testável e gerar testes de unidade suficientes para o código. Testes de unidade atuem como agentes que sondar o sistema não revelados caminhos, identificam erros importunos e fornecem indicadores se alterações introduzidas em um subsistema têm um efeito boa ou ruim em geral software continuamente. Testes de unidade dar confiança para o desenvolvedor que qualquer alteração de código não apresentará qualquer regressões.
Tendo disse que, criação e manutenção de uma unidade de bom teste conjunto pode ser um desafio em si. É possível acabar escrevendo mais código para o conjunto de teste que o código sob teste. Outro desafio é dependências dentro do código. Classes de solução, as dependências mais provavelmente localizar entre mais complexo. As simulações e stubs são uma maneira de remover essas dependências e teste o código em isolamento reconhecida, mas esses requerem adicional conhecimento e experiência do desenvolvedor criar testes de unidade eficaz.
Pex em ação
Pex ( research.microsoft.com/projects/pex/ ) é uma ferramenta desenvolvida pela Microsoft Research sistematicamente e automaticamente produzir o conjunto mínimo de entradas de teste necessário para executar um número finito de caminhos finito. O Pex produz, automaticamente, um pequeno pacote de teste com ampla cobertura de código e asserção.
Pex localiza valores de entrada saída interessantes dos métodos, que podem ser salvo como um conjunto de teste pequena com cobertura de código alta. Pex executa uma análise sistemática caçar para condições de limite, exceções e falhas de declaração que você pode depurar imediatamente. Pex também permite parametrizada (PUT) de testes de unidade, uma extensão de unidade de teste que reduz os custos de manutenção de teste e utiliza execução simbólica dinâmica para sondar através do código sob teste para criar um conjunto de teste que abrangem a maioria das ramificações de execução.
Um PUT é simplesmente um método que usa parâmetros, estados asserções e chama o código sob teste. Uma amostra PUT esta aparência:
void AddItem(List<int> list, int item) {
list.Add(item);
Assert.True(list[list.Count - 1] == item);
}
O conceito PUT é derivado de uma terminologia mais abrangente chamada orientado a dados de teste (DDT), que foi usado por um longo tempo em testes de unidade tradicional para tornar os testes reproduzível. Como testes de unidade tradicionais são fechadas por natureza, a única maneira de fornecer valores de entrada a eles é através de fontes externas, como um arquivo XML, planilhas ou um banco de dados. Enquanto a abordagem DDT funciona bem, há sobrecarga adicional envolvido na manutenção e alterando o arquivo de dados externos e as entradas são dependentes conhecimento do desenvolvedor sobre o sistema.
Pex não depende de fontes externas e fornece entradas para o método de teste em vez disso, passando os valores para PUT correspondente. Porque o PUT é um método open, podem ser organizado para aceitar qualquer número de entradas. Além disso, Pex não gera valores aleatório para o PUT. Ele se baseia em introspection do método em teste e gera valores significativos com base em fatores como condições de limite, valores aceitáveis do tipo e um restrição de estado de arte o solver chamado Z3 (research.microsoft.com/en-us/um/redmond/projects/z3/ ). Isso garante que todos os caminhos do método sob teste relevantes são abordados.
A beleza Pex é que ele gera testes de unidade tradicional da PUT. Esses testes de unidade podem ser executados diretamente em uma estrutura de teste de unidade como MSTest no Visual Studio sem modificações. Pex fornece extensões para gerar testes de unidade para estruturas como NUnit ou xUnit.NET. Você também pode criar sua própria extensão personalizada. Um Pex gerado unidade tradicional teste parecidas com esta:
[TestMethod]
[PexGeneratedBy(typeof(TestClass))]
void AddItem01() {
AddItem(new List<int>(), 0);
}
Execução simbólica dinâmica é resposta da Pex para teste exploratório. Usando esta técnica, Pex executa o código várias vezes para compreender o comportamento do programa. Ele monitora o fluxo de controle e dados e cria um sistema de restrição para entradas de teste.
Teste com Pex de unidade
A primeira etapa é criar um PUT para o código sob teste. O PUT pode ser gerado manualmente pelo desenvolvedor ou usando o suplemento do Visual Studio para Pex. Você pode adaptar o PUT por modificando parâmetros, adicionando Pex fábricas e stubs, integrando com simulações, adicionando asserções e assim por diante. Pex fábricas e stubs são abordados neste artigo.
Atualmente o suplemento do Visual Studio para Pex cria coloca somente em translation from VPE for Csharp, mas o código sob teste pode estar em qualquer linguagem .NET.
É a segunda etapa após a PUT foi configurado executar a exploração Pex. Isso é onde Pex faz sua mágica. Ele analisa PUT para identificar o que está sendo testado. Em seguida, inicia a inspeção de código sob teste passar por cada ramificação e Avaliando os possíveis valores de entrada. Pex repetidamente executa o código sob teste. Após cada execução, ele escolherá uma ramificação que não foi abordada anteriormente, cria um sistema de restrição (um predicado sobre entradas de teste) para acessar essa ramificação e, em seguida, usa um restrição o solver para determinar novas entradas de teste, se houver. O teste é executado novamente com novas entradas e esse processo se repete.
Em cada execução Pex pode descobrir o novo código e aprofundar mais profundo para a implementação. Dessa forma, Pex explora o comportamento do código.
Ao explorar o código, Pex produz um conjunto de teste de unidade que contém testes cobrem todas as ramificações Pex pode exercício. Esses testes são teste unit padrão que pode executar no Visual Studio Test Editor. Se você encontrar uma cobertura inferior para algumas seções, você pode pensar de revisar seu código refatoração e aplicando o mesmo ciclo novamente para alcançar maior cobertura de código e um conjunto de teste mais abrangente.
Pex pode ajudar a reduzir a quantidade de esforço e tempo envolvidos ao lidar com código herdado. Porque Pex automaticamente explora as diferente ramificações e os caminhos de código, você não precisa entender todos os detalhes de toda a base de código. Outro benefício é que o desenvolvedor funciona no nível PUT. Escrever um PUT geralmente é muito mais simples de escrever fechado testes de unidade porque você se concentrar em cenário problema em vez de todos os casos de teste possíveis para a funcionalidade.
Colocando Pex para trabalho
Let’s usar Pex em um pedaço de código herdado e ver como ele pode ajudar a tornar o código mais passível de manutenção e fácil de testar.
Um fabricante de basquete e baseballs, à esquerda, a Fabrikam fornece um portal online onde o usuário pode exibir produtos disponíveis e colocar ordens para esses produtos. Os detalhes do estoque estão em um armazenamento de dados personalizados acessado através de um componente de warehouse fornece conectividade para o armazenamento de dados e operações como HasInventory e remover. Um componente de pedido fornece um método Fill que processa um pedido com base no produto e quantidade passado pelo usuário.
Componentes Order e depósito são rigidamente acoplados uns aos outros. Esses componentes foram desenvolvidos anos atrás e nenhum funcionário atual tem uma compreensão total do sistema. Testes de unidade foram criados durante o desenvolvimento e, provavelmente como resultado, os componentes são muito instáveis. Do Figura 1 mostra o design atual.
Figura 1 da herdado Order completo sistema
O método Fill de classe Order tem esta aparência:
public class Order {
public bool Fill(Product product, int quantity) {
// Check if WareHouse has any inventory
Warehouse wareHouse = new Warehouse();
if (wareHouse.HasInventory(product, quantity)) {
// Subtract the quantity from the product in the warehouse
wareHouse.Remove(product, quantity);
return true;
}
return false;
}
}
Há alguns itens chaves que exigem atenção aqui. Primeiro, Order e warehouse são acoplamento. As classes dependem implementações torná-los menos difícil usar simulação ou estruturas de stub e extensível. Existem testes de unidade disponíveis para que qualquer alteração pode introduzir regressões resultando em um sistema instável.
O componente warehouse foi escrito há muito tempo e a equipe de desenvolvimento atual tem implicações de quaisquer alterações ou nenhum conhecimento de como alterá-lo. Para tornar mais complicados de assuntos, pedido não é possível trabalhar com outras implementações do warehouse sem modificações.
Tente Let’s refatorar o código e usar Pex para gerar testes de unidade. Eu será refatorar objetos warehouse e Order e criar testes de unidade para o método Fill de classe Order.
Obviamente, refatoração de código herdado é um desafio. Uma abordagem nesses casos pode ser pelo menos tornar o código testável para que os testes de unidade suficientes podem ser gerados. Eu será aplicando somente os padrões mínimos bare que tornam o código testável.
O primeiro problema é que o pedido usa uma implementação específica do warehouse. Isso torna difícil desassociar a implementação de depósito do pedido. Modificar o código um pouco para tornar mais flexível e testável Let’s.
Eu começar criando uma interface IWareHouse e modificar o objeto warehouse implementar esta interface. Qualquer novo depósito exigirá esta interface para ser implementada.
Porque Order tem uma dependência direta no warehouse, eles são rigidamente acoplados. Uso injeção de dependência para abrir a classe de extensão de seu comportamento. Usando essa abordagem, uma instância de IWareHouse será passada para ordem no tempo de execução. Do Figura 2 mostra o novo design.
Figura 2 revisado System com IWareHouse
A nova classe Order é mostrada no do Figura 3.
Figura 3 revisado classe Order
public class Order {
readonly IWareHouse orderWareHouse;
// Use constructor injection to provide a wareHouse object
public Order(IWareHouse wareHouse) {
this.orderWareHouse = wareHouse;
}
public bool Fill(Product product, int quantity) {
// Check if WareHouse has any inventory
if (this.orderWareHouse.HasInventory(product, quantity)) {
// Update the quantity for the product
this.orderWareHouse.Remove(product, quantity);
return true;
}
return false;
}
}
Criando parametrizado Unit Tests
Let’s agora usar Pex para gerar testes para o código refatorado. Pex fornece um suplemento Visual Studio torna gerando coloca fácil. Clique com o botão direito do mouse no projeto, classe ou método para os quais a coloca precisa ser gerado e clique em Pex | criar Parameterized Unit Test Stubs. Comecei selecionando o método Fill de classe Order.
Pex permite que você selecione um projeto de teste de unidade existente ou criar um novo. Ele também oferece opções para filtrar testes com base no nome de tipo ou método (consulte do Figura 4).
Figura 4 do Configurar um novo projeto de Pex
Pex gera o seguinte PUT para o método Fill.
[PexClass(typeof(Order))]
[TestClass]
public partial class OrderTest {
[PexMethod]
public bool Fill([PexAssumeUnderTest] Order target,
Product product, int quantity) {
// Create product factory for Product
bool result = target.Fill(product, quantity);
return result;
}
}
O OrderTest não é apenas um TestClass normal; tenha sido anotado com um atributo PexClass, indicando que foi criado por Pex. Não há atualmente nenhuma TestMethod como você esperaria um teste de unidade padrão do Visual Studio. Em vez disso, você tem um PexMethod. Este método é um teste de unidade parametrizadas. Posteriormente, quando você deixar Pex explorar código sob teste, ele criará outra classe parcial que contém os testes de unidade padrão anotados com atributos TestMethod. Esses testes gerados será acessíveis através do Visual Studio Test Editor.
Observe que PUT para o método Fill assume três parâmetros.
[PexAssumeUnderTest] Ordem de destino
Esta é a classe em próprio teste. O atributo PexAssumeUnderTest informa Pex que devem passar somente valores não-nulo do tipo exato especificado.
Produto produto
Esta é a classe base do produto. Pex tentará criar instâncias da classe produto automaticamente. Para obter um controle mais granular, você pode fornecer Pex com métodos de fábrica. Pex usará esses fábricas para criar instâncias de classes complexas.
quantidade int
Pex irá fornecer valores para a quantidade com base no método em teste. Ele tente injetar valores que sejam significativos para o teste em vez de valores de lixo.
Fábricas de Pex
Conforme mencionado, Pex usa um restrição o solver para determinar novas entradas de teste para parâmetros. As entradas podem ser tipos .NET padrão ou entidades comerciais personalizados. Durante a exploração, Pex realmente cria instâncias desses tipos de modo que o programa de teste pode comportar-se de diferentes maneiras interessantes. Se a classe está visível e tem um construtor padrão visível, Pex pode criar uma instância da classe. Se todos os campos estiverem visíveis, ele pode gerar também valores para eles. No entanto, se os campos são encapsulados com propriedades ou não expostos para o mundo exterior, Pex requer ajuda para criar o objeto para atingir uma melhor cobertura de código.
Pex fornece dois ganchos para criar e vincular objetos necessários para exploração Pex. O usuário pode fornecer fábricas para objetos complexos, para que Pex pode explorar a estados de objeto diferente. Isso é conseguido por meio do método de fábrica Pex. Tipos que podem ser criados através de tais fábricas são chamados tipos explorable. Estamos usando essa abordagem neste artigo.
A abordagem é definir invariáveis dos campos particulares de objetos para que Pex pode fabrica objeto diferentes estados diretamente.
Voltem para o cenário de exemplo, se você executar uma exploração Pex para os testes gerados parametrizadas, a janela resultados de explorações Pex mostrará uma mensagem “ 2 objeto Creations ”. Não é um erro. Durante a exploração, Pex encontrou uma classe complexa (ordem neste caso) e criado uma fábrica padrão para essa classe. Esta fábrica foi necessário por Pex para compreender melhor o comportamento de programa.
A fábrica padrão criada pelo Pex é uma implementação da classe necessária baunilha. Você pode adaptar essa fábrica fornecer sua própria implementação personalizada. Clique em aceitar/editar Factory para injetar o código no seu projeto (consulte do Figura 5). Como alternativa, você pode criar uma classe estática com um método estático anotado com o atributo PexFactoryMethod. Enquanto Explorando, Pex irá pesquisar o projeto de teste para quaisquer classes estáticas com métodos com esse atributo e usá-los adequadamente.
Figura 5 Criando Factory padrão
OrderFactory tem esta aparência:
public static partial class OrderFactory {
[PexFactoryMethod(typeof(Order))]
public static Order Create(IWareHouse wareHouseIWareHouse) {
Order order = new Order(wareHouseIWareHouse);
return order;
}
}
Se você escrever métodos de fábrica em outros assemblies, você pode dizer Pex usá-los de maneira declarativa usando o nível de assembly PexExplorableFromFactoriesFromType ou atributos de PexExplorableFromFactoriesFromAssembly, por exemplo.
[assembly: PexExplorableFromFactoriesFromType(
typeof(MyTypeInAnotherAssemblyContainingFactories))]
Se Pex cria testes muito poucos ou falhar criar testes interessantes por apenas lançando uma NullReferenceException no objeto deve criar, isso é uma boa indicação de que Pex podem exigir uma fábrica personalizada. Caso contrário, Pex vem com um conjunto de heurística para criar o objeto fábricas que funcionam em muitos casos.
Framework de stubs de Pex
No desenvolvimento de software, a noção de um stub de teste se refere a uma implementação fictícia que pode substituir um componente possivelmente complexo para facilitar o teste. Embora a idéia de stubs é simples, a maioria das estruturas existentes podem ajudar a criar e manter fictícios implementações são realmente bem complexas. A equipe de Pex desenvolveu uma nova estrutura leve, que eles simplesmente chamam stubs. Stubs gera tipos de stub para .NET interfaces e classes não sealed.
Na estrutura, um stub do tipo T fornece uma implementação padrão de cada membro abstrato de T e um mecanismo para especificar uma implementação personalizada de cada membro dinamicamente. (Opcionalmente, stubs também podem ser gerados para não-abstrata membros virtuais.) Os tipos de stub são gerados como código translation from VPE for Csharp. O framework depende exclusivamente delegados para especificar o comportamento de stub membros dinamicamente. Stubs suporte o .NET Framework 2.0 e superior e integra com o Visual Studio 2008 e superior.
Em meu cenário de exemplo a ordem do tipo tem uma dependência no objeto warehouse. Lembre-se eu refatorar o código para implementar a injeção de dependência para que o acesso do warehouse pode ser fornecido de fora para o tipo de ordem. Que é útil ao criar os stubs.
Criar um stub é bastante simples. Tudo o que você precisa é um arquivo .stubx. Se você criou o projeto de teste através de Pex, você já deve tê-lo. Se não, esse arquivo pode ser criado dentro do Visual Studio. Clique com o botão direito do mouse no projeto de teste e selecione Add New Item. Um modelo de stubs está disponível (consulte do Figura 6).
Figura 6 Criando um novo stub
O arquivo aparece como um arquivo XML padrão no Visual Studio. No elemento assembly, especifique o nome do assembly para as quais os stubs precisam ser criado e salve o arquivo .stubx:
<Stubs xmlns="https://schemas.microsoft.com/stubs/2008/">
<Assembly Name="FabrikamSports" />
</Stubs>
Pex criar automaticamente os métodos stub necessário para todos os tipos no assembly.
Os métodos stub gerado têm campos correspondentes do delegado que fornecem ganchos para implementações resto. Por padrão, Pex fornecerá uma implementação para o delegado. Você também pode fornecer uma expressão lambda para anexar o comportamento para o delegado ou use o tipo de PexChoose para deixar Pex gerar automaticamente valores para o método.
Por exemplo, para fornecer opções para o método HasInventory, pode tenho algo assim:
var wareHouse = new SIWareHouse() {
HasInventoryProductInt32 = (p, q) => {
Assert.IsNotNull(p);
Assert.IsTrue(q > 0);
return products.GetItem(p) >= q;
}
};
Na verdade, usar PexChoose já está o comportamento padrão para stubs quando usando Pex, como o projeto de teste criado pelo Pex contém o atributo de nível de assembly following:
[assembly: PexChooseAsStubFallbackBehavior]
O tipo de SIWareHouse é gerado pela estrutura Stubs Pex. Ele implementa a interface IWareHouse. Let’s dê uma olhada mais detalhadamente o código gerado pelo Pex para stub SIWareHouse. O código fonte para um arquivo .stubx é criado em uma classe parcial com o nome < StubsxFilename >. designer.cs conforme mostrado no do Figura 7.
Figura 7 Gerados Pex IWareHouse stub
/// <summary>Stub of method System.Boolean
/// FabrikamSports.IWareHouse.HasInventory(
/// FabrikamSports.Product product, System.Int32 quantity)
/// </summary>
[System.Diagnostics.DebuggerHidden]
bool FabrikamSports.IWareHouse.HasInventory(
FabrikamSports.Product product, int quantity) {
StubDelegates.Func<FabrikamSports.Product, int, bool> sh
= this.HasInventory;
if (sh != (StubDelegates.Func<FabrikamSports.Product,
int, bool>)null)
return sh.Invoke(product, quantity);
else {
var stub = base.FallbackBehavior;
return stub.Result<FabrikamSports.Stubs.SIWareHouse,
bool>(this);
}
}
/// <summary>Stub of method System.Boolean
/// FabrikamSports.IWareHouse.HasInventory(
/// FabrikamSports.Product product, System.Int32 quantity)
/// </summary>
public StubDelegates.Func<FabrikamSports.Product, int, bool> HasInventory;
Stubs criado um campo delegate pública para o método HasInventory e invocado na implementação HasInventory. Se nenhuma implementação estiver disponível, Pex chama o método FallBackBehaviour.Result, que usaria PexChoose se a [assembly: PexChooseAsStubFallbackBehavior] está presente e lança um StubNotImplementedException contrário.
Para usar a implementação de IWareHouse resto, eu irá ajustar um pouco o teste de unidade Parameterized. Já modifiquei a classe Order ser capaz de tirar uma implementação IWareHouse no seu construtor. Eu agora criar uma instância de SIWareHouse e, em seguida, passar que para a classe Order, portanto, ele usa implementações personalizadas dos métodos IWareHouse. PUT revisado é mostrado aqui:
[PexMethod]
public bool Fill(Product product, int quantity) {
// Customize the default implementation of SIWareHouse
var wareHouse = new SIWareHouse() {
HasInventoryProductInt32 = (p, q) =>
PexChoose.FromCall(this).ChooseValue<bool>(
"return value")
};
var target = new Order(wareHouse);
// act
bool result = target.Fill(product, quantity);
return result;
}
Stubs realmente automaticamente fornece implementações padrão para os métodos stub, portanto, você poderia ter executado simplesmente PUT sem modificações bem.
Modelos parametrizados
Para um controle mais granular da implementação do resto, Pex suporta um conceito chamado um modelo parametrizado. Essa é uma maneira para gravar os stubs que não têm um determinado comportamento fixo. A abstração Pex fornece através esse conceito é que o desenvolvedor não precisa se preocupar com variações da implementação. Pex irá explorar diferentes valores de retorno do método com base em como elas são usadas pelo código sob teste. Parametrizadas modelos são um recurso poderoso que lhe permite tomar controle completo sobre como os stubs devem ser processados enquanto ao mesmo tempo permitindo Pex avaliar variantes valores para parâmetros de entrada.
Um modelo parametrizado para IWareHouse pode parecer como o código do Figura 8.
Figura 8 Parameterized modelo da IWareHouse
public sealed class PWareHouse : IWareHouse {
PexChosenIndexedValue<Product, int> products;
public PWareHouse() {
this.products =
new PexChosenIndexedValue<Product, int>(
this, "Products", quantity => quantity >= 0);
}
public bool HasInventory(Product product, int quantity) {
int availableQuantity = this.products.GetItem(product);
return quantity - availableQuantity > 0;
}
public void Remove(Product product, int quantity) {
int availableQuantity =
this.products.GetItem(product);
this.products.SetItem(product,
availableQuantity - quantity);
}
}
Essencialmente, eu criou minha própria implementação resto para IWareHouse, mas observe que eu não fornecem valores de quantidade e o. Em vez disso, eu permitem Pex gerar esses valores. PexChosenIndexedValue fornece automaticamente os valores para objetos, permitindo que apenas uma implementação resto com valores de parâmetro variante.
Para manter a simplicidade eu permitirá fornecer implementação HasInventory do tipo IWareHouse Pex. Vou adicionar código à classe OrderFactory criado anteriormente. Sempre que uma instância de Order é criada pelo Pex ele usará uma instância de warehouse resto.
Moles
Até agora me tiver concentrei em dois princípios — refatorar seu código para tornar testável e, em seguida, usar Pex para gerar testes de unidade. Essa abordagem permite que você limpar seu código, eventualmente, levando à mais passível de manutenção de software. No entanto, refatoração de código herdado pode ser um grande desafio em si. Pode haver várias restrições organizacionais ou técnicas que podem impedir que um desenvolvedor de refatoração de código fonte atual. Como você lidar com isso?
Em cenários onde é rígido refatorar código herdado, a abordagem deve ser pelo menos criar testes de unidade suficientes para lógica comercial para que você pode verificar a robustez de cada módulo do sistema. Estruturas de simulação como TypeMock (learn.typemock.com) foram ao redor por um tempo. Eles permitem criar testes de unidade sem realmente modificar a base de código. Essa abordagem comprova muito vantajosa especificamente para bases de código herdado grande.
Pex vem com um recurso chamado moles que permite que você alcançar metas mesmas. Ele permite que você gerar testes de unidade Pex para código herdado sem realmente refatoração seu código fonte. Moles realmente se destinam ao testar contrário untestable partes do sistema, como métodos estáticos e classes lacradas.
Moles trabalhar de maneira similar para stubs: Pex gera código mole tipos que expõem propriedades delegado para cada método. Você pode anexar um delegado e anexar um mole. Nesse ponto, todos os seus delegados personalizados obter cabeados até magicamente por profiler Pex.
Pex cria automaticamente moles para todas as interfaces estáticas, seladas e públicas conforme especificado no arquivo .stubx. Uma aparência Pex Mole muito semelhante a um stubs digite (consulte o baixar um exemplo de código).
Usar moles é bastante simples. Você pode fornecer implementações para métodos Mole e usá-los em seu PUT. Observe que, porque Mole injeta implementações resto durante o runtime, você não tem alterar seu codebase em todos os ser capaz de gerar testes de unidade usando moles.
Usar Let’s moles o método Fill herdado:
public class Order {
public bool Fill(Product product, int quantity) {
// Check if warehouse has any inventory
Warehouse wareHouse = new Warehouse();
if (wareHouse.HasInventory(product, quantity)) {
// Subtract the quantity from the product
// in the warehouse
wareHouse.Remove(product, quantity);
return true;
}
return false;
}
}
Criar um PUT para o método Fill que aproveita os tipos de Mole (consulte do Figura 9).
Figura 9 usando tipos de Mole em Fill
[PexMethod]
public bool Fill([PexAssumeUnderTest]Order target,
Product product, int quantity) {
var products = new PexChosenIndexedValue<Product, int>(
this, "products");
// Attach a mole of WareHouse type
var wareHouse = new MWarehouse {
HasInventoryProductInt32 = (p, q) => {
Assert.IsNotNull(p);
return products.GetItem(p) >= q;
}
};
// Run the fill method for the lifetime of the mole
// so it uses MWareHouse
bool result = target.Fill(product, quantity);
return result;
}
MWareHouse é um tipo de Mole foi criado automaticamente pelo Pex ao gerar stubs e moles via arquivo .stubx. Eu fornecer uma implementação personalizada para o delegado HasInventory do tipo MWareHouse e chamar o método Fill. Observe que nem eu fornecer uma implementação do objeto warehouse para construtores de tipo de ordem. Pex irá anexar instância MWareHouse para o tipo de pedido em tempo de execução. Para a vida útil a PUT qualquer código escrito dentro do bloco aproveitar a implementação do tipo MWareHouse sempre que uma implementação de warehouse será necessária no código herdado.
Quando Pex gera testes de unidade tradicional usam moles, ele anexa o atributo [HostType(“Pex”)]-los, para que será executado com o profiler Pex, que permitirá moles para se tornar ativo.
Juntando as peças
Falei sobre os vários recursos de Pex e como usá-los. Agora é hora para realmente executar o PUT e observar os resultados. Para executar uma exploração para o método Fill do pedido, clique com o botão direito do mouse o PUT e selecione Executar explorações Pex. Opcionalmente, você pode executar a exploração em uma classe ou o projeto inteiro.
Enquanto Pex exploração é executado, uma classe parcial é criada junto com o arquivo de classe PUT. Essa classe parcial contém todos os testes de unidade padrão que irá gerar Pex para a PUT. Para o método Fill Pex gera testes de unidade padrão usando várias entradas de teste. Os testes são mostrados no do Figura 10.
Figura 10 gerados Pex Tests
[TestMethod]
[PexGeneratedBy(typeof(OrderTest))]
public void Fill15()
{
Warehouse warehouse;
Order order;
Product product;
bool b;
warehouse = new Warehouse();
order = OrderFactory.Create((IWareHouse)warehouse);
product = new Product("Base ball", (string)null);
b = this.Fill(order, product, 0);
Assert.AreEqual<bool>(true, b);
}
[TestMethod]
[PexGeneratedBy(typeof(OrderTest))]
public void Fill16()
{
Warehouse warehouse;
Order order;
Product product;
bool b;
warehouse = new Warehouse();
order = OrderFactory.Create((IWareHouse)warehouse);
product = new Product("Basket Ball", (string)null);
b = this.Fill(order, product, 0);
Assert.AreEqual<bool>(true, b);
}
[TestMethod]
[PexGeneratedBy(typeof(OrderTest))]
public void Fill17()
{
Warehouse warehouse;
Order order;
Product product;
bool b;
warehouse = new Warehouse();
order = OrderFactory.Create((IWareHouse)warehouse);
product = new Product((string)null, (string)null);
b = this.Fill(order, product, 1);
Assert.AreEqual<bool>(false, b);
}
O ponto chave para observar aqui é variações para o tipo de produto. Embora eu não forneceu qualquer fábrica para ele, Pex foi capaz de criar variações diferentes para o tipo.
Observe também que o teste gerado contém uma declaração. Quando um PUT retorna valores, Pex incorporará os valores que foram retornados em tempo de geração de teste o código de teste gerados como declarações. Como resultado, o teste gerado freqüentemente é capaz de detectar alterações significativas no futuro, mesmo se eles não violar outras asserção em código de programa ou causar exceções no nível do mecanismo de execução.
A janela de resultados de exploração Pex (consulte do Figura 11) fornece detalhes dos testes de unidade gerados pelo Pex. Também fornece informações sobre eventos que ocorreram durante a exploração e fábricas que Pex criado. Observe na Figura 11 de dois testes falhou. Pex mostra uma NullReferenceException contra ambos. Isso pode ser um problema comum que você perca colocando verificações de validação em caminhos de código que eventualmente podem levar a exceções quando executando na produção.
Figura 11 do resultados de explorações de Pex
Pex não apenas gera testes, mas também analisa o código para melhoria. Ele fornece um conjunto de sugestões pode tornar o código ainda mais estável. Essas sugestões não são apenas descritiva mensagem, mas o código real para a área do problema. Com um clique de um botão, Pex injeta o código no arquivo de origem real. Selecione o teste falhou na janela resultado da exploração de Pex. No canto inferior direito, um botão aparece precondição intitulado Add. Este botão adiciona o código em seu arquivo de origem.
Esses testes gerados são testes de unidade MSTest normais e podem ser executados a partir do Visual Studio Test Editor também. Se você abrir o editor, todos os testes gerado Pex estarão disponíveis como testes de unidade padrão. Pex pode gerar testes semelhantes para outra estruturas como NUnit e xUnit de testes de unidade.
Pex também possui suporte interno para gerar relatórios de cobertura. Esses relatórios fornecem detalhes abrangentes em torno de cobertura dinâmica para o código sob teste. Você pode ativar relatórios de Pex opções no menu Ferramentas do Visual Studio e, em seguida, abri-las clicando em exibições | relatório na barra de menu Pex após uma exploração.
Tornando seus testes futuros pronto
Até agora você viu como Pex foi capaz de gerar cobertura de código para código herdado com pequenas refatoração do código-fonte. A beleza Pex é que ele libera o desenvolvedor de escrever testes de unidade e gera-los automaticamente, assim, reduzindo o esforço de teste geral.
Um dos pontos principais sofrimento durante testes de unidade é manter o conjunto de teste. Como o andamento em um projeto, você normalmente fazer muitas modificações ao código existente. Porque os testes de unidade são dependentes de código fonte, qualquer alteração de código impacta os testes de unidade correspondente. Eles podem quebrar ou reduzir a cobertura de código. Durante um período de tempo manter o conjunto de teste ao vivo se torna um desafio.
Pex é útil nessa situação. Porque Pex é baseado em uma abordagem exploratório, ele pode procurar qualquer novas alterações na base de código e criar novos casos de teste com base neles.
O principal objetivo do teste de regressão é detectar se modificações ou acréscimos ao código existente afetaram o codebase negativamente, por meio da introdução de bugs funcionais ou criação de novas condições de erro.
Pex pode gerar automaticamente um conjunto de teste de regressão. Quando este conjunto de teste de regressão é executado no futuro, irá detectar alterações que causam asserção no código de programa falha, que causar exceções no nível do mecanismo de execução (NullReferenceException) ou que causam declarações significativas incorporados em testes gerados para falhar. Cada vez que uma exploração Pex é executar nova, testes de unidade são gerados para o código de observação. Qualquer alteração no comportamento pegou por Pex e a unidade correspondente testes são gerados para ele.
Alteração é inevitável
Durante um período de tempo, a equipe de desenvolvedores na Fabrikam percebeu que fazia sentido para ter um atributo ProductId adicionado à classe Product para que se a empresa adiciona novos produtos para seu catálogo eles podem ser identificados exclusivamente.
Também a classe Order não salvava os pedidos para um armazenamento de dados para que um novo método particular SaveOrders foi adicionado para a classe Order. Esse método será chamado pelo método Fill quando o produto tem alguns estoque.
A classe de método Fill modificada tem esta aparência:
public bool Fill(Product product, int quantity) {
if (product == null) {
throw new ArgumentException();
}
if (this.orderWareHouse.HasInventory(product, quantity)) {
this.SaveOrder(product.ProductId, quantity);
this.orderWareHouse.Remove(product, quantity);
return true;
}
return false;
}
Porque a assinatura do método Fill não foi alterada, não preciso revisar a PUT. É simplesmente executar explorações Pex novamente. Pex executa a exploração, mas desta vez ela gera entradas usando a nova definição de produto, utilizando o ProductId bem. Ele gera o conjunto de teste nova a, levando em conta as alterações feitas para o método Fill. Cobertura código vem seja 100 por cento — assegurando que todos os caminhos de código novos e existentes tenham sido avaliados.
Testes de unidade adicionais são gerados pelo Pex para testar as variações do campo adicionado ProductId e as alterações feitas para o método Fill (consulte do Figura 12). Aqui PexChooseStubBehavior define o comportamento de fallback para os stubs; em vez de gerar apenas um StubNotImplementedException, o método resto chamará PexChoose para fornecer os valores de retorno possíveis. Cobertura código executando os testes no Visual Studio, vem a ser 100 por cento novamente horários.
Figura 12 do adicionais gerados Pex Unit Tests
[TestMethod]
[PexGeneratedBy(typeof(OrderTest))]
public void Fill12()
{
using (PexChooseStubBehavior.NewTest())
{
SIWareHouse sIWareHouse;
Order order;
Product product;
bool b;
sIWareHouse = new SIWareHouse();
order = OrderFactory.Create((IWareHouse)sIWareHouse);
product = new Product((string)null, (string)null);
b = this.Fill(order, product, 0);
Assert.AreEqual<bool>(false, b);
}
}
[TestMethod]
[PexGeneratedBy(typeof(OrderTest))]
public void Fill13()
{
using (PexChooseStubBehavior.NewTest())
{
SIWareHouse sIWareHouse;
Order order;
Product product;
bool b;
sIWareHouse = new SIWareHouse();
order = OrderFactory.Create((IWareHouse)sIWareHouse);
product = new Product((string)null, (string)null);
IPexChoiceRecorder choices = PexChoose.NewTest();
choices.NextSegment(3)
.OnCall(0,
"SIWareHouse.global::FabrikamSports.IWareHouse.HasInventory(Product, Int32)")
.Returns((object)true);
b = this.Fill(order, product, 0);
Assert.AreEqual<bool>(true, b);
}
}
Agradecimentos
Gostaria de agradecer Peli de Halleux e Nikolai Tillman encorajando me escrever este artigo, gratidão especial para Peli para seu suporte incansável, valiosos comentários e revisão exaustiva.
Nikhil Sachdeva é engenheiro de desenvolvimento de software da equipe do OCTO-SE na Microsoft. Você pode contatar em blogs.msdn.com/erudition de . Você também pode postar suas consultas ao redor Pex em social.msdn.microsoft.com/Forums/en/pex/threads de.