Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Shim types, uma das duas principais tecnologias utilizadas pelo Microsoft Fakes Framework, são fundamentais para isolar os componentes do aplicativo durante o teste. Eles funcionam interceptando e desviando chamadas para métodos específicos, que você pode direcionar para o código personalizado em seu teste. Esse recurso permite que você gerencie o resultado desses métodos para garantir que os resultados sejam consistentes e previsíveis durante cada chamada, independentemente das condições externas. Esse nível de controle simplifica o processo de teste e ajuda a obter resultados mais confiáveis e precisos.
Empregue shims quando precisar criar um limite entre seu código e assemblies que não fazem parte da sua solução. Quando o objetivo é isolar os componentes da sua solução uns dos outros, o uso de stubs é recomendado. Para obter uma descrição detalhada sobre stubs, consulte Usar stubs para isolar partes do aplicativo umas das outras para teste de unidade.
Este artigo fornece um guia passo a passo para usar tipos de shim para desviar chamadas para métodos específicos em seu código de teste de unidade.
Entender as limitações de tipo com shims
Há algumas limitações ao trabalhar com shims. Eles não podem ser usados em todos os tipos de determinadas bibliotecas na classe base .NET, especificamente:
- mscorlib e System no .NET Framework
- System.Runtime no .NET Core ou .NET 5+
Ao projetar sua estratégia de teste, planeje essa restrição para garantir um teste de unidade bem-sucedido e eficaz.
Criar um shim
Suponha que seu componente contenha chamadas para o System.IO.File.ReadAllLines método:
// Code under test:
this.Records = System.IO.File.ReadAllLines(path);
Para preparar o componente para teste de unidade, conclua as etapas nos procedimentos a seguir.
Criar uma biblioteca de classes
Crie uma nova solução e um projeto inicial para a biblioteca de classes.
Na janela Visual Studio Start (File>Start Window), crie um projeto
Class Libraryselecionando o modelo para C# ou Visual Basic.
Configure o novo projeto:
- Defina a biblioteca de classes Project name como
HexFileReader. - Defina o nome da solução como
ShimsTutorial. - Defina a estrutura Target como .NET Framework 4.8.
Selecione Criar.
- Defina a biblioteca de classes Project name como
Depois que o projeto for aberto, localize o arquivo padrão
Class1.csem Gerenciador de Soluções e exclua o arquivo.Adicione um arquivo nomeado
HexFile.cse insira a seguinte definição de classe:
Adicionar um projeto de teste de unidade
Adicione outro projeto à sua solução para os testes de unidade.
Em Gerenciador de Soluções, clique com o botão direito do mouse na solução
ShimsTutoriale selecione Add>New Project.Na Janela Inicial, crie um
Unit Test Projectprojeto selecionando o modelo.
Configure o novo projeto:
- Defina o teste de unidade Project name como
TestProject. - Defina a estrutura Target como .NET Framework 4.8.
Selecione Criar.
- Defina o teste de unidade Project name como
Adicionar Assembly de Fakes
Adicione uma referência ao projeto HexFileReader.
Em Gerenciador de Soluções, expanda o nó
TestProject, clique com o botão direito do mouse no nó References e selecione Add Reference.
No painel esquerdo da janela do Gerenciador de Referência , selecione a seção Projetos .
No painel do meio, marque a caixa de seleção referente ao projeto
HexFileReader, e selecione OK.
Adicione a biblioteca Fakes.
Em Gerenciador de Soluções, localize o nó que contém o assembly:
Para um projeto mais antigo do .NET Framework (estilo não SDK), expanda o nó do projeto de teste de unidade e, em seguida, expanda o nó References.
Para um projeto no estilo SDK destinado ao .NET Framework, .NET Core ou .NET 5+, expanda o nó Dependencies para localizar o assembly a ser simulado em Assemblies, Projects ou Packages.
No Visual Basic, selecione Mostrar Todos os Arquivos na barra de ferramentas do Gerenciador de Soluções para ver o nó Referências.
Selecione o
Systemassembly que contém a definição deSystem.IO.File.ReadAllLines.Clique com o botão direito do mouse no nó
Systeme selecione Adicionar assembly de simulação.
O processo de compilação gera avisos e erros para tipos que não têm suporte para uso com shims.
Quando a compilação for concluída, o Gerenciador de Soluções é atualizado para exibir um nó Fakes no projeto de teste de unidade.
Selecione o
Fakes\mscorlib.fakesarquivo e substitua o XML para excluir os tipos sem suporte:<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/" Diagnostic="true"> <Assembly Name="mscorlib" Version="4.0.0.0"/> <StubGeneration> <Clear/> </StubGeneration> <ShimGeneration> <Clear/> <Add FullName="System.IO.File"/> <Remove FullName="System.IO.FileStreamAsyncResult"/> <Remove FullName="System.IO.FileSystemEnumerableFactory"/> <Remove FullName="System.IO.FileInfoResultHandler"/> <Remove FullName="System.IO.FileSystemInfoResultHandler"/> <Remove FullName="System.IO.FileStream+FileStreamReadWriteTask"/> <Remove FullName="System.IO.FileSystemEnumerableIterator"/> </ShimGeneration> </Fakes>
Criar um teste de unidade
Adicione um teste de unidade para seu projeto.
Atualize o arquivo padrão
TestProject\UnitTest1.csfornecido pelo modelo de projeto.Localize a seguinte seção de código no arquivo e substitua-a pelo snippet de código fornecido:
[TestMethod] public void TestMethod1() { }Snippet de substituição:
[TestMethod] public void TestFileReadAllLine() { using (ShimsContext.Create()) { // Arrange System.IO.Fakes.ShimFile.ReadAllLinesString = (s) => new string[] { "Hello", "World", "Shims" }; // Act var target = new HexFile("this_file_doesnt_exist.txt"); Assert.AreEqual(3, target.Records.Length); } }Para ver todos os assemblies do Fakes do teste de unidade, selecione Show All Files na barra de menus do Gerenciador de Soluções:
Abra o Gerenciador de Testes e execute o teste.
É fundamental descartar adequadamente cada contexto de shim. Como regra geral, chame o método ShimsContext.Create dentro de uma instrução using para garantir a limpeza correta dos shims registrados. Por exemplo, você pode registrar um shim para um método de teste que substitui o DateTime.Now método por um delegado que sempre retorna 1º de janeiro de 2000. Se você esquecer de limpar o shim registrado no método de teste, o restante da execução do teste sempre retornará 1º de janeiro de 2000 como valor de DateTime.Now. Esse resultado pode ser surpreendente e confuso.
Revisar convenções de nomenclatura para classes shim
Os nomes de classe Shim usam o Fakes.Shim prefixo seguido pelo nome de tipo original. Os nomes de parâmetro são acrescentados ao nome do método. (Você não precisa adicionar nenhuma referência de assembly a System.Fakes.)
System.IO.File.ReadAllLines(path);
System.IO.Fakes.ShimFile.ReadAllLinesString = (path) => new string[] { "Hello", "World", "Shims" };
Entender como funcionam os shims
Os shims funcionam introduzindo desvios de execução no código do aplicativo que está sendo testado. Sempre que há uma chamada para o método original, o sistema Fakes intervém para redirecionar a chamada, fazendo com que seu código shim personalizado seja executado em vez do método original.
É importante observar que esses desvios são criados e removidos dinamicamente no runtime. Os desvios devem sempre ser criados dentro do ciclo de vida de um ShimsContext. Quando o ShimsContext é descartado, todos os shims ativos criados dentro dele também são removidos. Para gerenciar essa estrutura de forma eficiente, recomenda-se encapsular a criação de desvios dentro de uma instrução using.
Explorar shims para diferentes tipos de métodos
Os shims dão suporte a vários tipos de métodos.
Métodos estáticos
Quando você aplica shims a métodos estáticos, as propriedades que armazenam os shims ficam contidas em um tipo de shim. Essas propriedades possuem apenas um setter, que é usado para anexar um delegado ao método de destino.
Por exemplo, para uma classe chamada MyClass com um método MyMethodestático:
//Code under test
public static class MyClass {
public static int MyMethod() {
...
}
}
Você pode anexar um shim a MyMethod de modo que ele retorne constantemente 5:
// Unit test code
ShimMyClass.MyMethod = () => 5;
Métodos de instância (para todas as instâncias)
Assim como os métodos estáticos, os métodos de instância também podem ser shimmed para todas as instâncias. As propriedades que agrupam esses shims são colocadas em um tipo aninhado chamado AllInstances para evitar confusão.
Para a classe MyClass com um método de instância MyMethod:
// Code under test
public class MyClass {
public int MyMethod() {
...
}
}
Você pode anexar um shim a MyMethod para que ele sempre retorne 5, independentemente da instância:
// Unit test code
ShimMyClass.AllInstances.MyMethod = () => 5;
O snippet a seguir mostra a estrutura de tipo gerada de ShimMyClass:
// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
public static class AllInstances {
public static Func<MyClass, int>MyMethod {
set {
...
}
}
}
}
Nesse cenário, o Fakes passa a instância de runtime como o primeiro argumento do delegado.
Métodos de instância (uma única instância em tempo de execução)
Os métodos de instância também podem ser shimmed usando delegados diferentes, dependendo do receptor da chamada. Essa abordagem permite que o mesmo método de instância apresente comportamentos diferentes em cada instância do tipo. As propriedades que mantêm esses shims são métodos de instância do próprio tipo de shim. Cada tipo de shim instanciado está vinculado a uma instância bruta de um tipo shimmed.
Por exemplo, dada uma classe MyClass com um método MyMethodde instância:
// Code under test
public class MyClass {
public int MyMethod() {
...
}
}
Você pode criar dois tipos de shim para MyMethod, de modo que o primeiro retorne consistentemente 5 e o segundo retorne consistentemente 10:
// Unit test code
var myClass1 = new ShimMyClass()
{
MyMethod = () => 5
};
var myClass2 = new ShimMyClass { MyMethod = () => 10 };
O snippet a seguir mostra a estrutura de tipo gerada de ShimMyClass:
// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
public Func<int> MyMethod {
set {
...
}
}
public MyClass Instance {
get {
...
}
}
}
A instância de tipo shimmed real pode ser acessada por meio da Instance propriedade:
// Unit test code
var shim = new ShimMyClass();
var instance = shim.Instance;
O tipo de shim também inclui uma conversão implícita para o tipo shimmed, que permite que você use o tipo de shim diretamente:
// Unit test code
var shim = new ShimMyClass();
MyClass instance = shim; // Implicit cast retrieves the runtime instance
Explorar construtores shim
Construtores também não são exceção à aplicação de shims. Eles podem ser adaptados para associar tipos de shim a objetos que venham a ser criados no futuro. Por exemplo, cada construtor é representado como um método estático nomeado Constructor dentro do tipo shim.
Considere uma classe MyClass com um construtor que aceita um inteiro:
public class MyClass {
public MyClass(int value) {
this.Value = value;
}
...
}
Um tipo de shim para esse construtor pode ser configurado de modo que, independentemente do valor passado para o construtor, cada instância futura retorna -5 quando o Value getter é invocado:
// Unit test code
ShimMyClass.ConstructorInt32 = (@this, value) => {
var shim = new ShimMyClass(@this) {
ValueGet = () => -5
};
};
Cada tipo de shim expõe dois tipos de construtores:
- Quando precisar de uma nova instância, use o construtor padrão.
- Quando você tiver um shim de construtor, use o construtor que usa uma instância shimmed como um argumento.
// Unit test code
public ShimMyClass() { }
public ShimMyClass(MyClass instance) : base(instance) { }
O código a seguir ilustra o tipo gerado para :ShimMyClass
// Fakes generated code
public class ShimMyClass : ShimBase<MyClass>
{
public static Action<MyClass, int> ConstructorInt32 {
set {
...
}
}
public ShimMyClass() { }
public ShimMyClass(MyClass instance) : base(instance) { }
...
}
Acessar membros da classe base
Você pode acessar as propriedades de shim dos membros da classe base ao criar um shim para o tipo base. Em seguida, passe a instância derivada para o construtor da classe base shim.
Por exemplo, considere uma classe MyBase com um método MyMethod de instância e um subtipo MyChild:
public abstract class MyBase {
public int MyMethod() {
...
}
}
public class MyChild : MyBase {
}
Um shim de MyBase pode ser configurado ao iniciar um novo shim de ShimMyBase:
// unit test code
var child = new ShimMyChild();
new ShimMyBase(child) { MyMethod = () => 5 };
É importante observar que, quando você passa o shim filho como parâmetro para o construtor do shim base, o tipo do shim filho é convertido implicitamente na instância derivada.
A estrutura do tipo gerado para ShimMyChild e ShimMyBase é semelhante ao seguinte código:
// Fakes generated code
public class ShimMyChild : ShimBase<MyChild> {
public ShimMyChild() { }
public ShimMyChild(Child child)
: base(child) { }
}
public class ShimMyBase : ShimBase<MyBase> {
public ShimMyBase(Base target) { }
public Func<int> MyMethod
{ set { ... } }
}
Construtores estáticos
Os tipos de shim expõem um método estático StaticConstructor para fazer o shim do construtor estático de um tipo. Como os construtores estáticos são executados apenas uma vez, você precisa garantir que o shim esteja configurado antes que qualquer membro do tipo seja acessado.
Finalizadores
Não há suporte para finalizadores em Fakes.
Métodos privados
O gerador de código Fakes cria propriedades shim para métodos privados cuja assinatura contém apenas tipos visíveis, isto é, em que os tipos dos parâmetros e o tipo de retorno são visíveis.
Interfaces de associação
Quando um tipo shimmed implementa uma interface, o gerador de código emite um método que permite associar todos os membros dessa interface ao mesmo tempo.
Por exemplo, dada uma classe MyClass que implementa IEnumerable<int>:
public class MyClass : IEnumerable<int> {
public IEnumerator<int> GetEnumerator() {
...
}
...
}
Você pode adaptar as implementações de IEnumerable<int> em MyClass chamando o método Bind:
// Unit test code
var shimMyClass = new ShimMyClass();
shimMyClass.Bind(new List<int> { 1, 2, 3 });
A estrutura de tipo gerada de ShimMyClass é semelhante ao código a seguir:
// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
public ShimMyClass Bind(IEnumerable<int> target) {
...
}
}
Alterar o comportamento padrão
Cada tipo de shim gerado contém uma instância da interface IShimBehavior, acessível pela propriedade ShimBase<T>.InstanceBehavior. Esse comportamento é acionado sempre que um cliente chama um membro de instância para o qual não foi definido explicitamente um shim.
Por padrão, se nenhum comportamento específico for definido, o design usará a instância retornada pela propriedade estática ShimBehaviors.Current , que normalmente gera uma NotImplementedException exceção.
Você pode modificar esse comportamento a qualquer momento ajustando a propriedade InstanceBehavior de qualquer instância de shim.
Por exemplo, você pode alterar o comportamento para não fazer nada ou retornar o valor padrão do tipo de retorno: default(T)
// Unit test code
var shim = new ShimMyClass();
//Return default(T) or do nothing
shim.InstanceBehavior = ShimBehaviors.DefaultValue;
Você também pode alterar globalmente o comportamento de todas as instâncias shimmed, em que a InstanceBehavior propriedade não está definida explicitamente, definindo a propriedade estática ShimBehaviors.Current :
// Unit test code
// Change default shim for all shim instances where the behavior isn't set
ShimBehaviors.Current = ShimBehaviors.DefaultValue;
Identificar interações com dependências externas
Para ajudar a identificar quando seu código interage com sistemas externos ou dependências (referidos como environment), você pode utilizar shims para atribuir um comportamento específico a todos os membros de um tipo (incluindo métodos estáticos). Ao definir o ShimBehaviors.NotImplemented comportamento na propriedade estática Behavior do tipo shim, qualquer acesso a um membro desse tipo que não seja explicitamente shimmed gerará um NotImplementedException. Esse resultado pode servir como um sinal útil durante o teste, indicando que seu código está tentando acessar um sistema ou dependência externa.
Aqui está um exemplo de como configurar essa forma de shim no código de teste de unidade:
// Unit test code
// Assign the NotImplementedException behavior to ShimMyClass
ShimMyClass.Behavior = ShimBehaviors.NotImplemented;
Para conveniência, um método abreviado também é fornecido para obter o mesmo efeito:
// Shorthand to assign the NotImplementedException behavior to ShimMyClass
ShimMyClass.BehaveAsNotImplemented();
Invocar métodos originais a partir de métodos shim
Em alguns cenários, talvez seja necessário executar o método original durante a execução do método shim. Por exemplo, talvez você queira gravar texto no sistema de arquivos depois de validar o nome do arquivo passado para o método.
Uma abordagem para lidar com essa situação é encapsular uma chamada ao método original usando um delegado e o método ShimsContext.ExecuteWithoutShims():
// Unit test code
ShimFile.WriteAllTextStringString = (fileName, content) => {
ShimsContext.ExecuteWithoutShims(() => {
Console.WriteLine("enter");
File.WriteAllText(fileName, content);
Console.WriteLine("leave");
});
};
Como alternativa, você pode anular o shim, chamar o método original e restaurar o shim:
// Unit test code
ShimsDelegates.Action<string, string> shim = null;
shim = (fileName, content) => {
try {
Console.WriteLine("enter");
// Remove shim in order to call original method
ShimFile.WriteAllTextStringString = null;
File.WriteAllText(fileName, content);
}
finally
{
// Restore shim
ShimFile.WriteAllTextStringString = shim;
Console.WriteLine("leave");
}
};
// Initialize the shim
ShimFile.WriteAllTextStringString = shim;
Manipular simultaneidade com tipos de shim
Os tipos de shim operam em todos os threads no AppDomain e não possuem afinidade de thread. Essa característica é crucial para ter em mente se você planeja utilizar um executor de teste que dê suporte à simultaneidade. Vale a pena observar que testes que envolvem tipos de shim não podem ser executados simultaneamente, embora o runtime do Fakes não imponha essa restrição.
Usar shims com System.Environment
Para criar um shim para a classe System.Environment, você precisa modificar o arquivo mscorlib.fakes.
Localize o \<Assembly> elemento e adicione o seguinte conteúdo após a definição do elemento:
<ShimGeneration>
<Add FullName="System.Environment"/>
</ShimGeneration>
Depois de fazer alterações e recompilar a solução, os métodos e as propriedades da System.Environment classe agora estão disponíveis para shimming.
Aqui está um exemplo de como você pode atribuir um comportamento ao GetCommandLineArgsGet método:
System.Fakes.ShimEnvironment.GetCommandLineArgsGet = ...
Ao fazer essas modificações, você obtém a capacidade de controlar e testar como seu código interage com variáveis de ambiente do sistema, que é uma ferramenta essencial para testes de unidade abrangentes.