xUnit.net: ¿Configuración global + desmontaje?

98

Esta pregunta trata sobre el marco de pruebas unitarias xUnit.net .

Necesito ejecutar algún código antes de que se ejecute cualquier prueba, y también algo de código después de que se hayan realizado todas las pruebas. Pensé que debería haber algún tipo de interfaz de atributo o marcador para indicar el código de inicialización y terminación global, pero no pude encontrarlos.

Alternativamente, si invoco xUnit mediante programación, también puedo lograr lo que quiero con el siguiente código:

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

¿Alguien puede darme una pista sobre cómo ejecutar de forma declarativa o programática algún código de instalación / desmontaje global?

Codismo
fuente
1
Supongo que aquí está la respuesta: stackoverflow.com/questions/12379949/…
the_joric

Respuestas:

118

Hasta donde yo sé, xUnit no tiene un punto de extensión de inicialización / desmontaje global. Sin embargo, es fácil crear uno. Simplemente cree una clase de prueba base que implemente IDisposabley realice su inicialización en el constructor y su desmontaje en el IDisposable.Disposemétodo. Esto se vería así:

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
}

Sin embargo, el código de instalación y desmontaje de la clase base se ejecutará para cada llamada. Puede que esto no sea lo que desea, ya que no es muy eficiente. Una versión más optimizada utilizaría la IClassFixture<T>interfaz para garantizar que la funcionalidad de inicialización / desmontaje global solo se llame una vez. Para esta versión, no extiende una clase base de su clase de prueba, sino implementa la IClassFixture<T>interfaz donde se Trefiere a su clase de dispositivo:

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)
    {
    }
}

Esto dará como resultado que el constructor TestsFixturesolo se ejecute una vez por cada clase bajo prueba. Por lo tanto, depende de lo que desee elegir exactamente entre los dos métodos.

Erik Schierboom
fuente
4
Parece que IUseFixture ya no existe, después de haber sido reemplazado por IClassFixture.
GaTechThomas
9
Si bien esto funciona, creo que CollectionFixture en la respuesta de Geir Sagberg encaja mejor en este escenario, ya que fue diseñado específicamente para este propósito. Tampoco tiene que heredar sus clases de prueba, simplemente márquelas con el [Collection("<name>")]atributo
MichelZ
8
¿Hay alguna forma de realizar la configuración y desmontaje asíncronos?
Andrii
Parece que MS también ha implementado la solución IClassFixture. docs.microsoft.com/en-us/aspnet/core/test/…
lbrahim
3
XUnit ofrece tres opciones de inicialización: por método de prueba, por clase de prueba y abarcando varias clases de prueba. La documentación está aquí: xunit.net/docs/shared-context
GHN
48

Estaba buscando la misma respuesta, y en este momento la documentación de xUnit es muy útil en cuanto a cómo implementar accesorios de clase y accesorios de colección que brindan a los desarrolladores una amplia gama de funciones de configuración / desmontaje a nivel de clase o grupo de clases. Esto está en línea con la respuesta de Geir Sagberg y ofrece una buena implementación esquemática para ilustrar cómo debería verse.

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

Accesorios de la colección Cuándo usarlo: cuando desea crear un único contexto de prueba y compartirlo entre las pruebas en varias clases de prueba, y limpiarlo después de que todas las pruebas en las clases de prueba hayan finalizado.

A veces querrá compartir un objeto fijo entre varias clases de prueba. El ejemplo de base de datos utilizado para los accesorios de clase es un gran ejemplo: es posible que desee inicializar una base de datos con un conjunto de datos de prueba y luego dejar esos datos de prueba en su lugar para que los usen múltiples clases de prueba. Puede utilizar la función de fijación de colección de xUnit.net para compartir una instancia de un solo objeto entre pruebas en varias clases de prueba.

Para usar los accesorios de colección, debe seguir los siguientes pasos:

Cree la clase de dispositivo y coloque el código de inicio en el constructor de la clase de dispositivo. Si la clase de dispositivo necesita realizar una limpieza, implemente IDisposable en la clase de dispositivo y coloque el código de limpieza en el método Dispose (). Cree la clase de definición de colección, decorándola con el atributo [CollectionDefinition], dándole un nombre único que identificará la colección de prueba. Agregue ICollectionFixture <> a la clase de definición de colección. Agregue el atributo [Colección] a todas las clases de prueba que serán parte de la colección, utilizando el nombre único que proporcionó al atributo [CollectionDefinition] de la clase de definición de colección de prueba. Si las clases de prueba necesitan acceso a la instancia del dispositivo, agréguelo como un argumento de constructor y se proporcionará automáticamente. He aquí un ejemplo sencillo:

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 los elementos de colección de la misma manera que los elementos de clase, excepto que la vida útil de un objeto de elemento de colección es más larga: se crea antes de que se ejecuten las pruebas en cualquiera de las clases de prueba de la colección y no se limpiará hasta que todas las clases de prueba de la colección hayan terminado de ejecutarse.

Las colecciones de prueba también se pueden decorar con IClassFixture <>. xUnit.net trata esto como si cada clase de prueba individual en la colección de prueba estuviera decorada con el accesorio de clase.

Las colecciones de pruebas también influyen en la forma en que xUnit.net ejecuta las pruebas cuando las ejecuta en paralelo. Para obtener más información, consulte Ejecución de pruebas en paralelo.

Nota importante: los accesorios deben estar en el mismo ensamblaje que la prueba que los usa.

Larry Smith
fuente
1
"Las colecciones de prueba también se pueden decorar con IClassFixture <>. XUnit.net trata esto como si cada clase de prueba individual en la colección de prueba estuviera decorada con el accesorio de clase". ¿Alguna posibilidad de que pueda obtener un ejemplo de eso? No lo entiendo del todo.
rtf
@TannerFaulkner El accesorio de clase fue una forma de tener una configuración y desmontaje de nivel CLASS, como se obtiene con un proyecto de prueba unitario .net tradicional cuando tiene un método de inicialización de prueba: [TestInitialize] public void Initialize () {
Larry Smith
El único problema que tengo con esto es que necesitas decorar tus clases de prueba con el Collectionatributo para que ocurra la configuración "global". Eso significa que, si tiene algo que desee configurar antes de ejecutar cualquier prueba, debe decorar todas las clases de prueba con este atributo. En mi opinión, esto es demasiado frágil, ya que olvidar decorar una sola clase de prueba puede provocar errores que son difíciles de rastrear. Sería bueno si xUnit creara una forma de configuración y desmontaje verdaderamente global.
Zodman
13

Hay una solución fácil y sencilla. Utilice el complemento Fody.ModuleInit

https://github.com/Fody/ModuleInit

Es un paquete nuget y cuando lo instalas agrega un nuevo archivo llamado ModuleInitializer.csal proyecto. Hay un método estático aquí que se integra en el ensamblaje después de la compilación y se ejecuta tan pronto como se carga el ensamblaje y antes de que se ejecute algo.

Lo uso para desbloquear la licencia de software de una biblioteca que he comprado. Siempre me olvidaba de desbloquear la licencia en cada prueba e incluso me olvidaba de derivar la prueba de una clase base que la desbloquearía. Las chispas brillantes que escribieron esta biblioteca, en lugar de decirle que estaba bloqueada por licencia, introdujeron sutiles errores numéricos que hacen que las pruebas fallen o pasen cuando no deberían. Nunca sabrás si has desbloqueado correctamente la biblioteca o no. Así que ahora mi módulo init 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");
    }
}

y todas las pruebas que se colocan en este ensamblaje tendrán la licencia desbloqueada correctamente para ellos.

bradgonesurf
fuente
2
Idea sólida; desafortunadamente, no parece funcionar todavía con las pruebas unitarias DNX.
Jeff Dunlop
12

Para compartir el código SetUp / TearDown entre varias clases, puede usar CollectionFixture de xUnit .

Citar:

Para usar los accesorios de colección, debe seguir los siguientes pasos:

  • Cree la clase de dispositivo y coloque el código de inicio en el constructor de la clase de dispositivo.
  • Si la clase de dispositivo necesita realizar una limpieza, implemente IDisposable en la clase de dispositivo y coloque el código de limpieza en el método Dispose ().
  • Cree la clase de definición de colección, decorándola con el atributo [CollectionDefinition], dándole un nombre único que identificará la colección de prueba.
  • Agregue ICollectionFixture <> a la clase de definición de colección.
  • Agregue el atributo [Colección] a todas las clases de prueba que serán parte de la colección, utilizando el nombre único que proporcionó al atributo [CollectionDefinition] de la clase de definición de colección de prueba.
  • Si las clases de prueba necesitan acceso a la instancia del dispositivo, agréguelo como un argumento de constructor y se proporcionará automáticamente.
Geir Sagberg
fuente