Idioma
Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Os tipos de shim, uma das duas principais tecnologias utilizadas pelo Microsoft Fakes Framework, são fundamentais para isolar os componentes do seu aplicativo durante o teste. Eles funcionam intercetando e desviando chamadas para métodos específicos, que você pode direcionar para o código personalizado dentro do teste. Esta funcionalidade permite-lhe gerir o resultado destes métodos para garantir que os resultados são consistentes e previsíveis durante cada chamada, independentemente das condições externas. Esse nível de controle agiliza o processo de teste e ajuda a obter resultados mais confiáveis e precisos.
Utilize shims quando precisar de estabelecer uma separação entre o seu código e os assemblies que não fazem parte da sua solução. Quando o objetivo é isolar os componentes da sua solução uns dos outros, recomenda-se o uso de stubs . Para uma descrição detalhada sobre stubs, veja Use stubs para isolar partes da sua aplicação umas das outras para testes unitários.
Este artigo fornece um guia passo a passo sobre como utilizar tipos shim para redirecionar chamadas para métodos específicos no código dos seus testes unitários.
Compreenda as limitações de tipo com calços
Existem algumas limitações quando se trabalha com shims. Não podem ser usados em todos os tipos de certas bibliotecas da classe base .NET, especificamente:
- mscorlib e System no Quadro .NET
- System.Runtime no .NET Core ou .NET 5+
Ao desenhar a sua estratégia de testes, planeie esta restrição para garantir testes unitários bem-sucedidos e eficazes.
Criar um calço
Suponha que o seu componente contém chamadas para o System.IO.File.ReadAllLines método:
// Code under test:
this.Records = System.IO.File.ReadAllLines(path);
Para preparar o componente para testes unitários, complete os passos dos procedimentos seguintes.
Criar uma biblioteca de classes
Crie uma nova solução e um projeto inicial para a biblioteca da turma.
Na janela Visual Studio Iniciar (File>Janela Iniciar), crie um projeto
Class Libraryselecionando o modelo para C# ou Visual Basic.
Configure o novo projeto:
- Defina a biblioteca de classes Nome do projeto como
HexFileReader. - Defina o nome da Solução para
ShimsTutorial. - Defina o framework Target para .NET Framework 4.8.
Selecione Criar.
- Defina a biblioteca de classes Nome do projeto como
Depois de o projeto abrir, localiza o ficheiro predefinido
Class1.csem Explorador de Soluções e apaga o ficheiro.Adicione um ficheiro nomeado
HexFile.cse introduza a seguinte definição de classe:
Adicionar um projeto de teste unitário
Adiciona outro projeto à tua solução para os testes unitários.
Em Explorador de Soluções, clique com o botão direito na solução
ShimsTutoriale selecione Add>Novo Project.Na Janela Inicial, crie um
Unit Test Projectprojeto ao selecionar o modelo.
Configure o novo projeto:
- Defina o teste unitário nome do projeto para
TestProject. - Defina o framework Target para .NET Framework 4.8.
Selecione Criar.
- Defina o teste unitário nome do projeto para
Adicionar Fakes Assembly
Adicione uma referência ao projeto HexFileReader.
Em Explorador de Soluções, expanda o nó
TestProject, clique com o botão direito no nó Referências e selecione Adicionar Referência.
No painel esquerdo da janela do Gestor de Referências , selecione a secção Projetos .
No painel do meio, seleciona a caixa de seleção do
HexFileReaderprojeto e seleciona OK.
Adiciona a Assembleia dos Fakes.
Em Explorador de Soluções, localize o nó contendo para a assembleia:
Para um projeto .NET Framework mais antigo (estilo não SDK), expanda o nó do projeto de teste unitário e, em seguida, expanda o nó References.
Para um projeto do tipo SDK destinado ao .NET Framework, .NET Core ou .NET 5+, expanda o nó Dependencies para localizar o assembly a simular em Assemblies, Projects ou Packages.
Para Visual Basic, selecione Mostrar Todos os Ficheiros na barra de ferramentas Explorador de Soluções para ver o nó References.
Selecione o
Systemconjunto que contém a definição deSystem.IO.File.ReadAllLines.Clique com o botão direito no nó
Systeme selecione Adicionar Assemblagem Falsa.
O processo de compilação gera avisos e erros para tipos não suportados para uso com calços.
Quando a compilação termina, o Explorador de Soluções é atualizado para mostrar um nó Fakes para o projeto de testes unitários.
Selecione o
Fakes\mscorlib.fakesficheiro e substitua o XML para excluir os tipos não suportados:<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 unitário para o seu projeto.
Atualize o ficheiro predefinido
TestProject\UnitTest1.csfornecido pelo modelo do projeto.Localize a seguinte secção de código no ficheiro e substitua-a pelo excerto fornecido:
[TestMethod] public void TestMethod1() { }Fragmento 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 de Fakes para o teste unitário, selecione Mostrar Todos os Ficheiros na barra de menus do Explorador de Soluções:
Abre o Explorador de Testes e executa o teste.
É fundamental descartar adequadamente cada contexto de calço. Regra geral, chame o método ShimsContext.Create numa instrução using para garantir a correta limpeza dos shims registados. Por exemplo, pode registar um shim para um método de teste que substitui o DateTime.Now método por um delegado que devolve sempre a 1 de janeiro de 2000. Se se esquecer de limpar o shim registado no método de teste, durante o resto da execução dos testes será sempre devolvido 1 de janeiro de 2000 como o valor DateTime.Now. Este resultado pode ser surpreendente e confuso.
Revise as convenções de nomenclatura das classes shim
Os nomes das classes Shim usam o Fakes.Shim prefixo seguido do nome original do tipo. Os nomes dos parâmetros são acrescentados ao nome do método. (Não precisa de adicionar nenhuma referência de montagem a System.Fakes.)
System.IO.File.ReadAllLines(path);
System.IO.Fakes.ShimFile.ReadAllLinesString = (path) => new string[] { "Hello", "World", "Shims" };
Compreenda como funcionam os shims
Os shims operam introduzindo desvios na base de código da aplicação que está a ser testada. Sempre que há uma chamada para o método original, o sistema Fakes intervém para redirecionar a chamada, fazendo com que o seu código de shim personalizado seja executado em vez do método original.
É importante notar que esses desvios são criados e removidos dinamicamente em tempo de execução. Os desvios devem sempre ser criados dentro do ciclo de vida de um ShimsContext. Quando o ShimsContext é eliminado, todos os shims ativos criados nesse contexto também são removidos. Para gerir este design de forma eficiente, a recomendação é encapsular a criação de desvios numa using declaração.
Explore calços para diferentes tipos de métodos
Os interceptores suportam vários tipos de métodos.
Métodos estáticos
Quando aplica shims a métodos estáticos, as propriedades que contêm os shims estão contidas num tipo 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() {
...
}
}
Pode anexar um shim a MyMethod de forma a que 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 evitados para todas as instâncias. As propriedades que contêm estes shims são colocadas num tipo aninhado com o nome 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() {
...
}
}
Pode anexar um shim a MyMethod para que devolva sempre 5, independentemente da instância:
// Unit test code
ShimMyClass.AllInstances.MyMethod = () => 5;
O seguinte excerto mostra a estrutura de tipos 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, Fakes passa a instância de tempo de execução como o primeiro argumento do delegado.
Métodos de instância (instância de execução única)
Os métodos de instância também podem ser sincronizados usando diferentes delegados, dependendo do destinatário da chamada. Esta abordagem permite que o mesmo método de instância apresente comportamentos diferentes para cada instância do tipo. As propriedades que contêm esses calços são métodos de instância do próprio tipo de calço. Cada tipo de calço instanciado está ligado 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() {
...
}
}
Pode criar dois tipos de simulação para MyMethod, de modo que o primeiro devolva sempre 5 e o segundo devolva sempre 10:
// Unit test code
var myClass1 = new ShimMyClass()
{
MyMethod = () => 5
};
var myClass2 = new ShimMyClass { MyMethod = () => 10 };
O seguinte excerto mostra a estrutura de tipos gerada de ShimMyClass:
// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
public Func<int> MyMethod {
set {
...
}
}
public MyClass Instance {
get {
...
}
}
}
A instância real do tipo shimmed pode ser acedida através da Instance propriedade:
// Unit test code
var shim = new ShimMyClass();
var instance = shim.Instance;
O tipo shim inclui também uma conversão implícita para o tipo shimed, que permite usar o tipo shim diretamente:
// Unit test code
var shim = new ShimMyClass();
MyClass instance = shim; // Implicit cast retrieves the runtime instance
Explorar construtores de calço
Os construtores não são exceção ao shimming. Podem ser ajustadas para fixar tipos de calço em objetos criados no futuro. Por exemplo, cada construtor é representado como um método estático denominado 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 calço para este construtor pode ser configurado de modo que, independentemente do valor passado ao construtor, cada instância futura devolve -5 quando o Value getter é invocado:
// Unit test code
ShimMyClass.ConstructorInt32 = (@this, value) => {
var shim = new ShimMyClass(@this) {
ValueGet = () => -5
};
};
Cada tipo de shim disponibiliza dois tipos de construtores:
- Quando precisares de uma nova instância, usa o construtor padrão.
- Quando tens um shim construtor, usa o construtor que aceita uma instância shimmed como argumento.
// Unit test code
public ShimMyClass() { }
public ShimMyClass(MyClass instance) : base(instance) { }
O código seguinte ilustra o tipo gerado para o ShimMyClass:
// Fakes generated code
public class ShimMyClass : ShimBase<MyClass>
{
public static Action<MyClass, int> ConstructorInt32 {
set {
...
}
}
public ShimMyClass() { }
public ShimMyClass(MyClass instance) : base(instance) { }
...
}
Aceder aos membros da classe base
Pode aceder às propriedades de shim dos membros da classe base criando um shim para a classe base. Depois, insere a instância filha no construtor da classe shim de base.
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 {
}
Pode-se configurar um calço de MyBase ao iniciar um novo calço de ShimMyBase.
// unit test code
var child = new ShimMyChild();
new ShimMyBase(child) { MyMethod = () => 5 };
É importante notar que, quando o shim derivado é passado como parâmetro para o construtor do shim base, o tipo do shim derivado é implicitamente convertido 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
Shim types expõe um método StaticConstructor estático para shim o construtor estático de um tipo. Como os construtores estáticos são executados apenas uma vez, é necessário garantir que o shim está configurado antes de qualquer membro do tipo ser acedido.
Finalizadores
Não há suporte para finalizadores em Fakes.
Métodos privados
O gerador de código Fakes cria propriedades de shim para métodos privados que só têm tipos visíveis na assinatura, ou seja, tipos de parâmetro e tipo de retorno visível.
Interfaces de ligação
Quando um tipo shimmed implementa uma interface, o gerador de código emite um método que permite vincular todos os membros dessa interface de uma só vez.
Por exemplo, dada uma classe MyClass que implementa IEnumerable<int>:
public class MyClass : IEnumerable<int> {
public IEnumerator<int> GetEnumerator() {
...
}
...
}
Pode adaptar as implementações de IEnumerable<int> em MyClass ao chamar 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 seguinte código:
// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
public ShimMyClass Bind(IEnumerable<int> target) {
...
}
}
Alterar o comportamento padrão
Cada tipo de shim gerado inclui uma instância da interface IShimBehavior, acessível através da propriedade ShimBase<T>.InstanceBehavior. Este comportamento é acionado sempre que um cliente chama um membro de instância para o qual não foi definido explicitamente um shim.
Por predefinição, se não estiver definido nenhum comportamento específico, a conceção utiliza a instância devolvida pela propriedade estática ShimBehaviors.Current, que normalmente gera a exceção NotImplementedException.
Você pode modificar esse comportamento a qualquer momento, ajustando a propriedade InstanceBehavior para qualquer instância de shim.
Por exemplo, pode alterar o comportamento para não fazer nada ou devolver 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;
Também podes alterar globalmente o comportamento para todas as instâncias shimed, onde a InstanceBehavior propriedade não está explicitamente definida, 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 o seu código está a interagir com sistemas externos ou dependências (designados por environment), pode utilizar shims para atribuir um comportamento específico a todos os membros de um tipo (incluindo métodos estáticos). Ao definir o comportamento ShimBehaviors.NotImplemented na propriedade estática Behavior do tipo shim, qualquer acesso a um membro desse tipo ao qual não tenha sido aplicado explicitamente um shim gera um NotImplementedException. Este resultado pode servir como um sinal útil durante os testes, indicando que o seu código está a tentar aceder a um sistema ou dependência externo.
Eis um exemplo de como configurar este tipo de shim no seu código de testes unitários:
// Unit test code
// Assign the NotImplementedException behavior to ShimMyClass
ShimMyClass.Behavior = ShimBehaviors.NotImplemented;
Por conveniência, um método abreviado também é disponibilizado para alcançar o mesmo efeito.
// Shorthand to assign the NotImplementedException behavior to ShimMyClass
ShimMyClass.BehaveAsNotImplemented();
Invocar métodos originais dentro dos métodos shim
Em alguns cenários, pode ser necessário executar o método original ao executar o método shim. Por exemplo, você pode querer gravar texto no sistema de arquivos depois de validar o nome do arquivo passado para o método.
Uma abordagem para lidar com esta situação é encapsular uma chamada ao método original ao usar 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");
});
};
Em alternativa, pode anular o shim, chamar o método original e, em seguida, 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;
Gerir a concorrência com tipos shim
Os tipos shim operam em todas as threads no AppDomain e não têm afinidade de thread. Esta característica é crucial e importa ter presente se planeia utilizar um executor de testes que suporte execução concorrente. Convém notar que os testes que envolvem tipos Shim não podem ser executados em simultâneo, embora o runtime do Fakes não imponha esta restrição.
Use calços com System.Environment
Para criar um shim para a classe System.Environment, é necessário modificar o ficheiro 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 efetuar alterações e reconstruir a solução, os métodos e as propriedades na classe System.Environment passam a estar disponíveis para simulação.
Aqui está um exemplo de como você pode atribuir um comportamento ao GetCommandLineArgsGet método:
System.Fakes.ShimEnvironment.GetCommandLineArgsGet = ...
Ao fazer estas modificações, ganha a capacidade de controlar e testar como o seu código interage com as variáveis do ambiente do sistema, o que é uma ferramenta essencial para testes unitários abrangentes.