Execute testes de unidade em série (em vez de em paralelo)

96

Estou tentando fazer o teste de unidade de um mecanismo de gerenciamento de host WCF que escrevi. O mecanismo basicamente cria instâncias ServiceHost dinamicamente com base na configuração. Isso nos permite reconfigurar dinamicamente quais serviços estão disponíveis sem ter que desativá-los e reiniciá-los sempre que um novo serviço for adicionado ou um antigo removido.

No entanto, tive dificuldade em testar a unidade deste mecanismo de gerenciamento de host devido à maneira como o ServiceHost funciona. Se um ServiceHost já tiver sido criado, aberto e ainda não fechado para um ponto de extremidade específico, outro ServiceHost para o mesmo ponto de extremidade não pode ser criado, resultando em uma exceção. Por causa do fato de que as plataformas de teste de unidade modernas paralelizam sua execução de teste, não tenho uma maneira eficaz de testar essa parte do código.

Usei o xUnit.NET, esperando que, devido à sua extensibilidade, pudesse encontrar uma maneira de forçá-lo a executar os testes em série. No entanto, não tive sorte. Espero que alguém aqui no SO tenha encontrado um problema semelhante e saiba como fazer com que os testes de unidade sejam executados em série.

NOTA: ServiceHost é uma classe WCF, escrita pela Microsoft. Não tenho capacidade de mudar seu comportamento. Hospedar cada ponto de extremidade de serviço apenas uma vez também é o comportamento adequado ... entretanto, não é particularmente adequado para testes de unidade.

jrista
fonte
Não seria esse comportamento específico do ServiceHost algo que você gostaria de abordar?
Robert Harvey,
ServiceHost é escrito pela Microsoft. Eu não tenho controle sobre isso. E, tecnicamente falando, é um comportamento válido ... você nunca deve ter mais de um ServiceHost por terminal.
Jrista de
1
Eu tive um problema semelhante ao tentar executar vários TestServerno docker. Então eu tive que serializar os testes de integração.
h-rai

Respostas:

114

Cada classe de teste é uma coleção de teste exclusiva e os testes sob ela serão executados em sequência, portanto, se você colocar todos os seus testes na mesma coleção, ela será executada sequencialmente.

No xUnit, você pode fazer as seguintes alterações para conseguir isso:

O seguinte será executado em paralelo:

namespace IntegrationTests
{
    public class Class1
    {
        [Fact]
        public void Test1()
        {
            Console.WriteLine("Test1 called");
        }

        [Fact]
        public void Test2()
        {
            Console.WriteLine("Test2 called");
        }
    }

    public class Class2
    {
        [Fact]
        public void Test3()
        {
            Console.WriteLine("Test3 called");
        }

        [Fact]
        public void Test4()
        {
            Console.WriteLine("Test4 called");
        }
    }
}

Para torná-lo sequencial, você só precisa colocar as duas classes de teste na mesma coleção:

namespace IntegrationTests
{
    [Collection("Sequential")]
    public class Class1
    {
        [Fact]
        public void Test1()
        {
            Console.WriteLine("Test1 called");
        }

        [Fact]
        public void Test2()
        {
            Console.WriteLine("Test2 called");
        }
    }

    [Collection("Sequential")]
    public class Class2
    {
        [Fact]
        public void Test3()
        {
            Console.WriteLine("Test3 called");
        }

        [Fact]
        public void Test4()
        {
            Console.WriteLine("Test4 called");
        }
    }
}

Para mais informações, você pode consultar este link

Abhinav Saxena
fonte
23
Resposta subestimada, eu acho. Parece funcionar e eu gosto da granularidade, pois tenho testes paralelizáveis ​​e não paralelizáveis ​​em um assembly.
Igand 02 de
1
Esta é a maneira correta de fazer isso, consulte a documentação do Xunit.
Håkon K. Olafsen de
2
Esta deve ser a resposta aceita porque normalmente alguns testes podem ser executados em paralelo (no meu caso, todos os testes de unidade), mas alguns falham aleatoriamente quando executados em paralelo (no meu caso, aqueles que usam cliente / servidor web in-memory), então um é capaz de otimizar a execução do teste, se assim o desejar.
Alexei
2
Isso não funcionou para mim em um projeto principal .net, onde realizo testes de integração com um banco de dados sqlite. Os testes ainda foram executados em paralelo. A resposta aceita funcionou.
user1796440
Muito obrigado por esta resposta! Necessário fazer isso porque tenho testes de aceitação em classes diferentes que herdam do mesmo TestBase e a simultaneidade não estava funcionando bem com EF Core.
Kyanite
104

Conforme declarado acima, todos os bons testes de unidade devem ser 100% isolados. Usar o estado compartilhado (por exemplo, dependendo de uma staticpropriedade que é modificada por cada teste) é considerado uma prática ruim.

Dito isso, sua pergunta sobre a execução de testes xUnit em sequência tem uma resposta! Eu encontrei exatamente o mesmo problema porque meu sistema usa um localizador de serviço estático (que é menos do que ideal).

Por padrão, o xUnit 2.x executa todos os testes em paralelo. Isso pode ser modificado por assembly, definindo CollectionBehaviorem seu AssemblyInfo.cs em seu projeto de teste.

Para separação por montagem, use:

using Xunit;
[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]

ou para nenhuma paralelização, use:

[assembly: CollectionBehavior(DisableTestParallelization = true)]

Este é provavelmente o que você deseja. Mais informações sobre paralelização e configuração podem ser encontradas na documentação do xUnit .

Rabisco
fonte
5
Para mim, havia recursos compartilhados entre os métodos de cada classe. Executar um teste de uma classe, depois um da outra, interromperia os testes de ambas. Consegui resolver usando [assembly: CollectionBehavior(CollectionBehavior.CollectionPerClass, DisableTestParallelization = true)]. Graças a você, @Squiggle, posso fazer todos os meus testes e ir tomar um café! :)
Alielson Piffer
2
A resposta do Abhinav Saxena é mais granular para o .NET Core.
Yennefer
65

Para projetos .NET Core, crie xunit.runner.jsoncom:

{
  "parallelizeAssembly": false,
  "parallelizeTestCollections": false
}

Além disso, você csprojdeve conter

<ItemGroup>
  <None Update="xunit.runner.json"> 
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
</ItemGroup>

Para projetos antigos .Net Core, o seu project.jsondeve conter

"buildOptions": {
  "copyToOutput": {
    "include": [ "xunit.runner.json" ]
  }
}
Dmitry Polomoshnov
fonte
2
Suponho que o equivalente de núcleo dotnet csproj mais recente seria <ItemGroup><None Include="xunit.runner.json" CopyToOutputDirectory="Always" /></ItemGroup>ou semelhante?
Squiggle
3
Isso funcionou para mim no csproj:<ItemGroup> <None Update="xunit.runner.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup>
skynyrd
A desativação da paralelização funciona com as teorias xUnit?
John Zabroski
Isso é a única coisa que funcionou para mim, tentei correr como dotnet test --no-build -c Release -- xunit.parallelizeTestCollections=falsemas não funcionou para mim.
Harvey
18

Para projetos .NET Core, você pode configurar o xUnit com um xunit.runner.jsonarquivo, conforme documentado em https://xunit.github.io/docs/configuring-with-json.html .

A configuração que você precisa alterar para interromper a execução do teste paralelo é parallelizeTestCollections, cujo padrão é true:

Defina como truese o assembly estiver disposto a executar testes dentro desse assembly em paralelo uns com os outros. ... Defina como falsepara desativar toda a paralelização neste conjunto de teste.

Tipo de esquema JSON: booleano
Valor padrão:true

Portanto, um mínimo xunit.runner.jsonpara esta finalidade parece

{
    "parallelizeTestCollections": false
}

Conforme observado nos documentos, lembre-se de incluir este arquivo em sua compilação:

  • Configurando Copiar para Diretório de Saída para Copiar se for mais recente nas Propriedades do arquivo no Visual Studio, ou
  • Adicionando

    <Content Include=".\xunit.runner.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>

    para o seu .csprojarquivo, ou

  • Adicionando

    "buildOptions": {
      "copyToOutput": {
        "include": [ "xunit.runner.json" ]
      }
    }

    para o seu project.jsonarquivo

dependendo do seu tipo de projeto.

Finalmente, além do acima, se você estiver usando o Visual Studio, certifique-se de não clicar acidentalmente no botão Executar testes em paralelo , o que fará com que os testes sejam executados em paralelo, mesmo se você tiver desativado a paralelização em xunit.runner.json. Os designers de IU da Microsoft habilmente tornaram esse botão sem rótulo, difícil de notar e a cerca de um centímetro de distância do botão "Executar tudo" no Test Explorer, apenas para maximizar a chance de você acertá-lo por engano e não ter ideia de por que seus testes estão falhando de repente:

Captura de tela com o botão circulado

Mark Amery
fonte
@JohnZabroski Não entendo sua edição sugerida . O que o ReSharper tem a ver com qualquer coisa? Acho que provavelmente já o tinha instalado quando escrevi a resposta acima, mas tudo aqui não é independente de você estar usando ou não? O que a página para a qual você cria um link na edição tem a ver com a especificação de um xunit.runner.jsonarquivo? E o que especificar um xunit.runner.jsontem a ver com fazer os testes serem executados em série?
Mark Amery
Estou tentando fazer meus testes rodarem em série e inicialmente pensei que o problema estava relacionado ao ReSharper (já que o ReSharper NÃO tem o botão "Executar testes em paralelo" como o Visual Studio Test Explorer tem). No entanto, parece que quando eu uso [Teoria], meus testes não são isolados. Isso é estranho, porque tudo que li sugere que uma classe é a menor unidade paralelizável.
John Zabroski
8

Esta é uma questão antiga, mas eu queria escrever uma solução para as pessoas que pesquisam recentemente como eu :)

Nota: Eu uso este método em testes de integração Dot Net Core WebUI com xunit versão 2.4.1.

Crie uma classe vazia denominada NonParallelCollectionDefinitionClass e, a seguir, atribua o atributo CollectionDefinition a essa classe conforme abaixo. (A parte importante é DisableParallelization = true setting.)

using Xunit;

namespace WebUI.IntegrationTests.Common
{
    [CollectionDefinition("Non-Parallel Collection", DisableParallelization = true)]
    public class NonParallelCollectionDefinitionClass
    {
    }
}

Depois, adicione o atributo Collection à classe que você não deseja que execute em paralelo, como abaixo. (A parte importante é o nome da coleção. Deve ser o mesmo nome usado em CollectionDefinition)

namespace WebUI.IntegrationTests.Controllers.Users
{
    [Collection("Non-Parallel Collection")]
    public class ChangePassword : IClassFixture<CustomWebApplicationFactory<Startup>>
    ...

Quando fazemos isso, primeiro são executados outros testes paralelos. Depois disso, os outros testes que possuem o atributo Collection ("Non-Parallel Collection") são executados.

Gündüz Emre ÖZER
fonte
6

você pode usar a lista de reprodução

clique com o botão direito no método de teste -> Adicionar à lista de reprodução -> Nova lista de reprodução

então você pode especificar a ordem de execução, o padrão é, conforme você os adiciona à lista de reprodução, mas você pode alterar o arquivo da lista de reprodução como quiser

insira a descrição da imagem aqui

HB MAAM
fonte
5

Não sei os detalhes, mas parece que você está tentando fazer um teste de integração em vez de um teste de unidade . Se você pudesse isolar a dependência de ServiceHost, isso provavelmente tornaria seus testes mais fáceis (e rápidos). Então (por exemplo) você pode testar o seguinte independentemente:

  • Aula de leitura de configuração
  • Fábrica de ServiceHost (possivelmente como um teste de integração)
  • Classe de motor que leva um IServiceHostFactorye umIConfiguration

Ferramentas que ajudariam a incluir estruturas de isolamento (simulação) e (opcionalmente) estruturas de contêiner IoC. Vejo:

TrueWill
fonte
Não estou tentando fazer um teste de integração. Eu realmente preciso fazer testes de unidade. Sou totalmente versado em termos e práticas de TDD / BDD (IoC, DI, Mocking, etc.), então o tipo de coisas corriqueiras como criar fábricas e usar interfaces não é o que eu preciso (já está feito, exceto no caso do próprio ServiceHost.) ServiceHost não é uma dependência que pode ser isolada, uma vez que não pode ser zombada corretamente (como muitos dos namespaces .NET System.) Eu realmente preciso de uma maneira de executar os testes de unidade em série.
Jrista
1
@jrista - nenhuma intenção foi negligenciar suas habilidades. Não sou um desenvolvedor WCF, mas seria possível para o mecanismo retornar um wrapper em torno do ServiceHost com uma interface no wrapper? Ou talvez uma fábrica personalizada para ServiceHosts?
TrueWill
O mecanismo de hospedagem não retorna nenhum ServiceHosts. Na verdade, não retorna nada, ele simplesmente gerencia a criação, abertura e fechamento de ServiceHosts internamente. Eu poderia agrupar todos os tipos fundamentais do WCF, mas isso é MUITO trabalho que não fui autorizado a fazer. Além disso, como se viu, o problema não é causado pela execução paralela e ainda ocorrerá durante a operação normal. Comecei outra pergunta aqui no SO sobre o problema e espero obter uma resposta.
jrista
@TrueWill: BTW, eu não estava preocupado com você menosprezando minhas habilidades ... Eu só não queria receber muitas respostas corriqueiras que cobrem todas as coisas comuns sobre testes de unidade. Eu precisava de uma resposta rápida para um problema muito específico. Desculpe se saí um pouco rude, não era minha intenção. Eu só tenho um tempo muito limitado para fazer essa coisa funcionar.
jrista
3

Talvez você possa usar o Teste de Unidade Avançado . Ele permite que você defina a sequência em que você executa o teste . Portanto, pode ser necessário criar um novo arquivo cs para hospedar esses testes.

Veja como você pode dobrar os métodos de teste para funcionarem na sequência desejada.

[Test]
[Sequence(16)]
[Requires("POConstructor")]
[Requires("WorkOrderConstructor")]
public void ClosePO()
{
  po.Close();

  // one charge slip should be added to both work orders

  Assertion.Assert(wo1.ChargeSlipCount==1,
    "First work order: ChargeSlipCount not 1.");
  Assertion.Assert(wo2.ChargeSlipCount==1,
    "Second work order: ChargeSlipCount not 1.");
  ...
}

Deixe-me saber se funciona.

Graviton
fonte
Ótimo artigo. Na verdade, eu o marquei no CP. Obrigado pelo link, mas como descobrimos, o problema parece ser muito mais profundo, já que os executores de teste não parecem executar testes em paralelo.
Jrista
2
Espere, primeiro você diz que não quer que o teste seja executado em paralelo, e então você diz que o problema é que os executores de teste não executam testes em paralelo ... então qual é qual?
Graviton
O link que você forneceu não funciona mais. E isso é algo que você pode fazer com o xunit?
Allen Wang
0

Eu adicionei o atributo [Collection ("Sequential")] em uma classe base:

    namespace IntegrationTests
    {
      [Collection("Sequential")]
      public class SequentialTest : IDisposable
      ...


      public class TestClass1 : SequentialTest
      {
      ...
      }

      public class TestClass2 : SequentialTest
      {
      ...
      }
    }
Matías Romero
fonte
0

Nenhuma das respostas sugeridas até agora funcionou para mim. Eu tenho um aplicativo dotnet core com XUnit 2.4.1. Alcancei o comportamento desejado com uma solução alternativa, colocando um bloqueio em cada teste de unidade. No meu caso, não me importei com a ordem de execução, apenas que os testes eram sequenciais.

public class TestClass
{
    [Fact]
    void Test1()
    {
        lock (this)
        {
            //Test Code
        }
    }

    [Fact]
    void Test2()
    {
        lock (this)
        {
            //Test Code
        }
    }
}
Matt Hayes
fonte