Ejecutar pruebas unitarias en serie (en lugar de en paralelo)

99

Estoy intentando realizar una prueba unitaria de un motor de administración de host WCF que he escrito. Básicamente, el motor crea instancias de ServiceHost sobre la marcha en función de la configuración. Esto nos permite reconfigurar dinámicamente qué servicios están disponibles sin tener que desactivarlos todos y reiniciarlos cada vez que se agrega un nuevo servicio o se elimina uno antiguo.

Sin embargo, me he encontrado con una dificultad en la prueba unitaria de este motor de administración de host debido a la forma en que funciona ServiceHost. Si ya se ha creado, abierto y no cerrado un ServiceHost para un punto final en particular, no se puede crear otro ServiceHost para el mismo punto final, lo que genera una excepción. Debido al hecho de que las plataformas modernas de pruebas unitarias paralelizan su ejecución de prueba, no tengo una forma efectiva de probar este código.

He usado xUnit.NET, esperando que debido a su extensibilidad, pueda encontrar una manera de forzarlo a ejecutar las pruebas en serie. Sin embargo, no he tenido suerte. Espero que alguien aquí en SO haya encontrado un problema similar y sepa cómo hacer que las pruebas unitarias se ejecuten en serie.

NOTA: ServiceHost es una clase WCF, escrita por Microsoft. No tengo la capacidad de cambiar su comportamiento. Alojar cada punto final de servicio solo una vez es también el comportamiento adecuado ... sin embargo, no es particularmente propicio para las pruebas unitarias.

jrista
fuente
¿No sería este comportamiento particular de ServiceHost algo que quizás desee abordar?
Robert Harvey
ServiceHost está escrito por Microsoft. Yo no tengo control sobre ello. Y técnicamente hablando, es un comportamiento válido ... nunca debe tener más de un ServiceHost por punto final.
jrista
1
Tuve un problema similar al intentar ejecutar varios TestServeren la ventana acoplable. Entonces tuve que serializar las pruebas de integración.
h-rai

Respuestas:

117

Cada clase de prueba es una colección de prueba única y las pruebas debajo de ella se ejecutarán en secuencia, por lo que si coloca todas sus pruebas en la misma colección, se ejecutará secuencialmente.

En xUnit puede realizar los siguientes cambios para lograr esto:

Lo siguiente se ejecutará en 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 que sea secuencial, solo necesita poner ambas clases de prueba en la misma colección:

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 obtener más información, puede consultar este enlace.

Abhinav Saxena
fuente
24
Respuesta subestimada, creo. Parece funcionar y me gusta la granularidad, ya que tengo pruebas paralelizables y no paralelizables en un ensamblado.
Igand
1
Esta es la forma correcta de hacer esto, refiérase a la documentación de Xunit.
Håkon K. Olafsen
2
Esta debería ser la respuesta aceptada porque, por lo general, algunas pruebas se pueden ejecutar en paralelo (en mi caso, todas las pruebas unitarias), pero algunas fallan aleatoriamente cuando se ejecutan en paralelo (en mi caso, las que usan un cliente / servidor web en memoria), por lo que una es capaz de optimizar la ejecución de pruebas si se desea.
Alexei
2
Esto no funcionó para mí en un proyecto principal .net donde realizo pruebas de integración con una base de datos sqlite. Las pruebas aún se ejecutaron en paralelo. Sin embargo, la respuesta aceptada funcionó.
user1796440
¡Muchas gracias por esta respuesta! Necesitaba hacer esto porque tengo pruebas de aceptación en diferentes clases que heredan del mismo TestBase y la concurrencia no funcionaba bien con EF Core.
Cianita
104

Como se indicó anteriormente, todas las buenas pruebas unitarias deben estar 100% aisladas. El uso de un estado compartido (por ejemplo, dependiendo de una staticpropiedad que se modifica en cada prueba) se considera una mala práctica.

Habiendo dicho eso, su pregunta sobre la ejecución de pruebas xUnit en secuencia tiene una respuesta. Encontré exactamente el mismo problema porque mi sistema usa un localizador de servicio estático (que es menos que ideal).

De forma predeterminada, xUnit 2.x ejecuta todas las pruebas en paralelo. Esto se puede modificar por ensamblaje definiendo elCollectionBehavior en su AssemblyInfo.cs en su proyecto de prueba.

Para uso de separación por ensamblaje:

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

o para ninguna paralelización en absoluto uso:

[assembly: CollectionBehavior(DisableTestParallelization = true)]

Este último es probablemente el que desea. Puede encontrar más información sobre la paralelización y la configuración en la documentación de xUnit .

Garabato
fuente
5
Para mí, había recursos compartidos entre métodos dentro de cada clase. Ejecutar una prueba de una clase, luego una de otra, rompería las pruebas de ambas. Pude resolver usando [assembly: CollectionBehavior(CollectionBehavior.CollectionPerClass, DisableTestParallelization = true)]. Gracias a ti, @Squiggle, puedo hacer todas mis pruebas e ir a tomar un café. :)
Alielson Piffer
2
La respuesta de Abhinav Saxena es más granular para .NET Core.
Yennefer
67

Para proyectos .NET Core, cree xunit.runner.jsoncon:

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

Además, csprojdebe contener

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

Para proyectos antiguos .Net Core, project.jsondebe contener

"buildOptions": {
  "copyToOutput": {
    "include": [ "xunit.runner.json" ]
  }
}
Dmitry Polomoshnov
fuente
2
¿Supongo que el último equivalente de csproj dotnet core sería <ItemGroup><None Include="xunit.runner.json" CopyToOutputDirectory="Always" /></ItemGroup>o similar?
Squiggle
3
Esto funcionó para mí en csproj:<ItemGroup> <None Update="xunit.runner.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup>
skynyrd
¿La desactivación de la paralelización funciona con xUnit Theories?
John Zabroski
Esto es lo único que funcionó para mí, intenté correr como dotnet test --no-build -c Release -- xunit.parallelizeTestCollections=falsepero no funcionó para mí.
Harvey
18

Para proyectos .NET Core, puede configurar xUnit con un xunit.runner.jsonarchivo, como se documenta en https://xunit.github.io/docs/configuring-with-json.html .

La configuración que debe cambiar para detener la ejecución de la prueba en paralelo es parallelizeTestCollections, que por defecto es true:

Establezca esto en truesi el ensamblaje está dispuesto a ejecutar pruebas dentro de este ensamblaje en paralelo entre sí. ... Configure esto para falsedeshabilitar toda la paralelización dentro de este ensamblaje de prueba.

Tipo de esquema JSON: booleano
Valor predeterminado:true

Entonces, un mínimo xunit.runner.jsonpara este propósito parece

{
    "parallelizeTestCollections": false
}

Como se indica en los documentos, recuerde incluir este archivo en su compilación, ya sea por:

  • Configurar Copiar al directorio de salida para copiar si es más reciente en las Propiedades del archivo en Visual Studio, o
  • Añadiendo

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

    a su .csprojarchivo, o

  • Añadiendo

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

    a su project.jsonarchivo

dependiendo de su tipo de proyecto.

Finalmente, además de lo anterior, si está utilizando Visual Studio, asegúrese de no haber hecho clic accidentalmente en el botón Ejecutar pruebas en paralelo , lo que hará que las pruebas se ejecuten en paralelo incluso si ha desactivado la paralelización en xunit.runner.json. Los diseñadores de UI de Microsoft han hecho que este botón no esté etiquetado, sea difícil de notar y esté a un centímetro del botón "Ejecutar todo" en el Explorador de pruebas, solo para maximizar la posibilidad de que lo presione por error y no tenga idea de por qué sus pruebas. están fallando repentinamente:

Captura de pantalla con el botón encerrado en un círculo

Mark Amery
fuente
@JohnZabroski No entiendo la edición que sugieres . ¿Qué tiene que ver ReSharper con todo? Creo que probablemente lo tenía instalado cuando escribí la respuesta anterior, pero ¿no es todo aquí independiente de si lo está usando o no? ¿Qué tiene que ver la página a la que enlaza en la edición con la especificación de un xunit.runner.jsonarchivo? ¿Y qué tiene que ver especificar una xunit.runner.jsoncon hacer que las pruebas se ejecuten en serie?
Mark Amery
Estoy tratando de que mis pruebas se ejecuten en serie, e inicialmente pensé que el problema estaba relacionado con ReSharper (ya que ReSharper NO tiene el botón "Ejecutar pruebas en paralelo" como el Explorador de pruebas de Visual Studio). Sin embargo, parece que cuando uso [Teoría], mis pruebas no son aisladas. Esto es extraño, porque todo lo que leo sugiere que una clase es la unidad paralelizable más pequeña.
John Zabroski
9

Esta es una vieja pregunta, pero quería escribir una solución para las personas que buscan recientemente como yo :)

Nota: utilizo este método en las pruebas de integración de Dot Net Core WebUI con xunit versión 2.4.1.

Cree una clase vacía llamada NonParallelCollectionDefinitionClass y luego asigne el atributo CollectionDefinition a esta clase como se muestra a continuación. (La parte importante es DisableParallelization = true setting.)

using Xunit;

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

Luego, agregue el atributo Collection a la clase que no desea que se ejecute en paralelo como se muestra a continuación. (La parte importante es el nombre de la colección. Debe coincidir con el nombre utilizado en CollectionDefinition)

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

Cuando hacemos esto, en primer lugar se ejecutan otras pruebas paralelas. Después de eso, se ejecutan las otras pruebas que tienen el atributo Colección ("Colección No Paralela").

Gündüz Emre ÖZER
fuente
6

puedes usar lista de reproducción

haga clic derecho en el método de prueba -> Agregar a la lista de reproducción -> Nueva lista de reproducción

luego puede especificar el orden de ejecución, el valor predeterminado es, ya que los agrega a la lista de reproducción, pero puede cambiar el archivo de la lista de reproducción como desee

ingrese la descripción de la imagen aquí

HB MAAM
fuente
5

No conozco los detalles, pero parece que está intentando hacer pruebas de integración en lugar de pruebas unitarias . Si pudiera aislar la dependencia de ServiceHost, eso probablemente haría sus pruebas más fáciles (y más rápidas). Entonces (por ejemplo) puede probar lo siguiente de forma independiente:

  • Clase de lectura de configuración
  • ServiceHost factory (posiblemente como prueba de integración)
  • Clase de motor que toma una IServiceHostFactoryy unaIConfiguration

Herramientas que ayudarían a incluir marcos de aislamiento (burla) y (opcionalmente) marcos de contenedores de IoC. Ver:

TrueWill
fuente
No estoy tratando de hacer pruebas de integración. De hecho, necesito hacer pruebas unitarias. Estoy completamente versado en términos y prácticas TDD / BDD (IoC, DI, Mocking, etc.), por lo que el tipo de cosas corrientes, como crear fábricas y usar interfaces, no es lo que necesito (ya está hecho, excepto en el caso del propio ServiceHost.) ServiceHost no es una dependencia que pueda aislarse, ya que no se puede simular adecuadamente (como gran parte de los espacios de nombres del sistema .NET). Realmente necesito una forma de ejecutar las pruebas unitarias en serie.
jrista
1
@jrista: no se pretendía menospreciar tus habilidades. No soy un desarrollador de WCF, pero ¿sería posible que el motor devuelva un contenedor alrededor de ServiceHost con una interfaz en el contenedor? ¿O quizás una fábrica personalizada para ServiceHosts?
TrueWill
El motor de alojamiento no devuelve ningún ServiceHosts. En realidad, no devuelve nada, simplemente administra la creación, apertura y cierre de ServiceHosts internamente. Podría envolver todos los tipos fundamentales de WCF, pero eso es MUCHO trabajo para el que realmente no he sido autorizado. Además, resultó que el problema no es causado por la ejecución paralela y seguirá ocurriendo durante el funcionamiento normal. Comencé otra pregunta aquí en SO sobre el problema, y ​​espero obtener una respuesta.
jrista
@TrueWill: Por cierto, no me preocupaba que menospreciaras mis habilidades en absoluto ... Simplemente no quería obtener muchas respuestas corrientes que cubran todas las cosas comunes sobre las pruebas unitarias. Necesitaba una respuesta rápida a un problema muy específico. Lo siento si salí un poco brusco, no era mi intención. Tengo un tiempo bastante limitado para que esto funcione.
jrista
3

Tal vez puedas usar las pruebas unitarias avanzadas . Le permite definir la secuencia en la que ejecuta la prueba . Por lo tanto, es posible que deba crear un nuevo archivo cs para alojar esas pruebas.

A continuación, le mostramos cómo puede modificar los métodos de prueba para que funcionen en la secuencia que desee.

[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.");
  ...
}

Hágame saber si funciona.

Graviton
fuente
Excelente artículo. De hecho, lo tenía marcado en CP. Gracias por el enlace, pero resultó que el problema parece ser mucho más profundo, ya que los corredores de pruebas no parecen ejecutar pruebas en paralelo.
jrista
2
Espera, primero dices que no quieres que la prueba se ejecute en paralelo, y luego dices que el problema es que los corredores de prueba no ejecutan pruebas en paralelo ... entonces, ¿cuál es cuál?
Graviton
El enlace que proporcionaste ya no funciona. ¿Y esto es algo que puedes hacer con xunit?
Allen Wang
0

Agregué el atributo [Colección ("Secuencial")] en una clase base:

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


      public class TestClass1 : SequentialTest
      {
      ...
      }

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

Ninguna de las respuestas sugeridas hasta ahora funcionó para mí. Tengo una aplicación de dotnet core con XUnit 2.4.1. Logré el comportamiento deseado con una solución al poner un candado en cada prueba unitaria. En mi caso, no me importaba el orden de ejecución, solo que las pruebas fueran secuenciales.

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

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