xUnit.net: Configuração global + desmontagem?

98

Esta pergunta é sobre o framework de teste de unidade xUnit.net .

Preciso executar algum código antes que qualquer teste seja executado e também algum código depois que todos os testes forem feitos. Achei que deveria haver algum tipo de atributo ou interface de marcador para indicar a inicialização global e o código de terminação, mas não consegui localizá-los.

Como alternativa, se eu invocar xUnit programaticamente, também posso conseguir o que desejo com o seguinte código:

static void Main()
{
    try
    {
        MyGlobalSetup();
        RunAllTests();  // What goes into this method?
    }
    finally
    {
        MyGlobalTeardown();
    }
}

Alguém pode me fornecer uma dica sobre como declarativamente ou programaticamente executar algum código de configuração / desmontagem global?

Codismo
fonte
1
Acho que aqui está a resposta: stackoverflow.com/questions/12379949/…
the_joric

Respostas:

118

Até onde eu sei, o xUnit não tem um ponto de extensão global de inicialização / desmontagem. No entanto, é fácil criar um. Basta criar uma classe de teste base que implemente IDisposablee faça sua inicialização no construtor e sua desmontagem no IDisposable.Disposemétodo. Isso ficaria assim:

public abstract class TestsBase : IDisposable
{
    protected TestsBase()
    {
        // Do "global" initialization here; Called before every test method.
    }

    public void Dispose()
    {
        // Do "global" teardown here; Called after every test method.
    }
}

public class DummyTests : TestsBase
{
    // Add test methods
}

No entanto, a configuração da classe base e o código de desmontagem serão executados para cada chamada. Isso pode não ser o que você deseja, pois não é muito eficiente. Uma versão mais otimizada usaria a IClassFixture<T>interface para garantir que a funcionalidade global de inicialização / desmontagem seja chamada apenas uma vez. Para esta versão, você não estende uma classe base de sua classe de teste, mas implementa a IClassFixture<T>interface onde Tse refere à sua classe de fixture:

using Xunit;

public class TestsFixture : IDisposable
{
    public TestsFixture ()
    {
        // Do "global" initialization here; Only called once.
    }

    public void Dispose()
    {
        // Do "global" teardown here; Only called once.
    }
}

public class DummyTests : IClassFixture<TestsFixture>
{
    public DummyTests(TestsFixture data)
    {
    }
}

Isso fará com que o construtor TestsFixtureseja executado apenas uma vez para cada classe em teste. Portanto, depende do que você deseja escolher exatamente entre os dois métodos.

Erik Schierboom
fonte
4
Parece que IUseFixture não existe mais, tendo sido substituído por IClassFixture.
GaTechThomas
9
Embora isso funcione, eu acho que CollectionFixture na resposta de Geir Sagberg é a melhor opção para esse cenário, pois foi projetada especificamente para esse propósito. Você também não precisa herdar suas classes de teste, basta marcá-las com o [Collection("<name>")]atributo
MichelZ
8
Existe alguma maneira de fazer a configuração e desmontagem assíncrona?
Andrii,
Parece que a MS também implementou a solução IClassFixture. docs.microsoft.com/en-us/aspnet/core/test/…
lbrahim
3
O XUnit oferece três opções de inicialização: por método de teste, por classe de teste e abrangendo várias classes de teste. A documentação está aqui: xunit.net/docs/shared-context
GHN
48

Eu estava procurando a mesma resposta e, neste momento, a documentação do xUnit é muito útil no que diz respeito a como implementar Fixtures de classe e Fixtures de coleção que fornecem aos desenvolvedores uma ampla gama de funcionalidade de configuração / desmontagem no nível de classe ou grupo de classes. Isso está de acordo com a resposta de Geir Sagberg e fornece uma boa implementação do esqueleto para ilustrar como deve ser.

https://xunit.github.io/docs/shared-context.html

Fixtures da coleção Quando usar: quando você deseja criar um único contexto de teste e compartilhá-lo entre os testes em várias classes de teste, e limpá-lo depois que todos os testes nas classes de teste terminarem.

Às vezes, você desejará compartilhar um objeto de fixação entre várias classes de teste. O exemplo de banco de dados usado para fixtures de classe é um ótimo exemplo: você pode querer inicializar um banco de dados com um conjunto de dados de teste e, em seguida, deixar esses dados de teste no local para uso por várias classes de teste. Você pode usar o recurso de fixação de coleção de xUnit.net para compartilhar uma única instância de objeto entre os testes em várias classes de teste.

Para usar acessórios de coleta, você precisa seguir as seguintes etapas:

Crie a classe de fixture e coloque o código de inicialização no construtor da classe de fixture. Se a classe fixture precisar realizar uma limpeza, implemente IDisposable na classe fixture e coloque o código de limpeza no método Dispose (). Crie a classe de definição de coleção, decorando-a com o atributo [CollectionDefinition], dando a ela um nome exclusivo que identificará a coleção de teste. Adicione ICollectionFixture <> à classe de definição da coleção. Adicione o atributo [Collection] a todas as classes de teste que farão parte da coleção, usando o nome exclusivo que você forneceu ao atributo [CollectionDefinition] da classe de definição da coleção de teste. Se as classes de teste precisam acessar a instância do fixture, adicione-o como um argumento do construtor, e ele será fornecido automaticamente. Aqui está um exemplo simples:

public class DatabaseFixture : IDisposable
{
    public DatabaseFixture()
    {
        Db = new SqlConnection("MyConnectionString");

        // ... initialize data in the test database ...
    }

    public void Dispose()
    {
        // ... clean up test data from the database ...
    }

    public SqlConnection Db { get; private set; }
}

[CollectionDefinition("Database collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{
    // This class has no code, and is never created. Its purpose is simply
    // to be the place to apply [CollectionDefinition] and all the
    // ICollectionFixture<> interfaces.
}

[Collection("Database collection")]
public class DatabaseTestClass1
{
    DatabaseFixture fixture;

    public DatabaseTestClass1(DatabaseFixture fixture)
    {
        this.fixture = fixture;
    }
}

[Collection("Database collection")]
public class DatabaseTestClass2
{
    // ...
}

xUnit.net trata os acessórios de coleção da mesma maneira que os acessórios de classe, exceto que o tempo de vida de um objeto de acessório de coleção é mais longo: ele é criado antes que qualquer teste seja executado em qualquer uma das classes de teste na coleção e não será limpo até que todas as classes de teste na coleção tenham concluído a execução.

As coleções de teste também podem ser decoradas com IClassFixture <>. xUnit.net trata isso como se cada classe de teste individual na coleção de teste fosse decorada com o acessório da classe.

As coleções de teste também influenciam a maneira como o xUnit.net executa os testes ao executá-los em paralelo. Para obter mais informações, consulte Executando testes em paralelo.

Nota importante: Os acessórios devem estar na mesma montagem que o teste que os utiliza.

Larry Smith
fonte
1
"As coleções de teste também podem ser decoradas com IClassFixture <>. XUnit.net trata isso como se cada classe de teste individual na coleção de teste fosse decorada com o acessório da classe." Alguma chance de eu conseguir um exemplo disso? Eu não entendo muito bem.
rtf
@TannerFaulkner A fixação da classe era uma maneira de ter uma configuração e desmontagem de nível CLASSE, como você obtém com um Projeto de Teste de Unidade tradicional .net quando você tem um método Test Initialize: [TestInitialize] public void Initialize () {
Larry Smith,
O único problema que tenho com isso é que você precisa decorar suas classes de teste com o Collectionatributo para que a configuração "global" ocorra. Isso significa que, se você tem algo que deseja configurar antes de -any- test ser executado, você precisa decorar -all- test classes com este atributo. Isso é muito frágil na minha opinião, pois esquecer de decorar uma única classe de teste pode levar a erros que são difíceis de rastrear. Seria bom se o xUnit criasse uma forma de configuração e desmontagem verdadeiramente global.
Zodman
13

Existe uma solução fácil e fácil. Use o plugin Fody.ModuleInit

https://github.com/Fody/ModuleInit

É um pacote nuget e quando você o instala, ele adiciona um novo arquivo chamado ModuleInitializer.cs ao projeto. Há um método estático aqui que é inserido na montagem após a construção e é executado assim que a montagem é carregada e antes de qualquer coisa ser executada.

Eu uso isso para desbloquear a licença do software para uma biblioteca que comprei. Eu estava sempre me esquecendo de desbloquear a licença em cada teste e até mesmo esquecendo de derivar o teste de uma classe base que iria desbloqueá-lo. As faíscas brilhantes que escreveram esta biblioteca, em vez de dizer que ela estava com licença bloqueada, introduziram erros numéricos sutis que fazem com que os testes falhem ou passem quando não deveriam. Você nunca saberia se desbloqueou corretamente a biblioteca ou não. Então agora meu módulo de inicialização parece

/// <summary>
/// Used by the ModuleInit. All code inside the Initialize method is ran as soon as the assembly is loaded.
/// </summary>
public static class ModuleInitializer
{
    /// <summary>
    /// Initializes the module.
    /// </summary>
    public static void Initialize()
    {
            SomeLibrary.LicenceUtility.Unlock("XXXX-XXXX-XXXX-XXXX-XXXX");
    }
}

e todos os testes colocados nesta montagem terão a licença desbloqueada corretamente para eles.

bradgonesurfing
fonte
2
Ideia sólida; infelizmente, ainda não parece funcionar com os testes de unidade DNX.
Jeff Dunlop,
12

Para compartilhar o código SetUp / TearDown entre várias classes, você pode usar o CollectionFixture do xUnit .

Citar:

Para usar acessórios de coleta, você precisa seguir as seguintes etapas:

  • Crie a classe de fixture e coloque o código de inicialização no construtor da classe de fixture.
  • Se a classe fixture precisar realizar uma limpeza, implemente IDisposable na classe fixture e coloque o código de limpeza no método Dispose ().
  • Crie a classe de definição de coleção, decorando-a com o atributo [CollectionDefinition], dando a ela um nome exclusivo que identificará a coleção de teste.
  • Adicione ICollectionFixture <> à classe de definição da coleção.
  • Adicione o atributo [Collection] a todas as classes de teste que farão parte da coleção, usando o nome exclusivo que você forneceu ao atributo [CollectionDefinition] da classe de definição da coleção de teste.
  • Se as classes de teste precisam acessar a instância do fixture, adicione-o como um argumento do construtor, e ele será fornecido automaticamente.
Geir Sagberg
fonte