Nunit: Función de instancia por caso de prueba

Creado en 24 nov. 2017  ·  112Comentarios  ·  Fuente: nunit/nunit

El modelo mental de todos parece ser que hay una instancia creada por caso de prueba cuando se paraleliza dentro de un dispositivo (https://github.com/nunit/nunit/issues/2105, https://github.com/nunit/nunit/issues / 2252, https://github.com/nunit/nunit/issues/2573) aunque este nunca ha sido el caso. Tiendo a resolver este problema usando siempre clases estáticas y definiendo una clase de accesorio anidado desechable que proporciona una experiencia más rica de alguna manera ( ejemplo ), pero eso podría no ser lo que queremos para todos. El pequeño inconveniente aquí está en la terminología, que la clase estática es lo que NUnit considera que es el accesorio, pero el accesorio real es la clase anidada.

No es una opción hacer que la instancia por caso de prueba sea el predeterminado porque eso rompe los dispositivos no paralelos que dependen de que una prueba pueda acceder al estado desde otra prueba. Las personas que usan el atributo [Order] son probablemente las únicas personas a las que afectaría, pero rompería totalmente cualquier prueba dependiente del orden que se me ocurra.

Me gusta la propuesta de Charlie:

  • Un atributo de nivel de ensamblaje que permite configurar la forma en que se crean los dispositivos de prueba para que sea una instancia única o una instancia por dispositivo.
  • La instancia única funcionaría como ahora
  • Instancia por dispositivo crearía un nuevo dispositivo para cada prueba
  • O BIEN realice actividades de configuración y desmontaje una sola vez para cada prueba (es decir, ya no "una vez") O haga que OneTimeSetUp y OneTimeTearDown se marquen como errores.

Posibles extensiones:

  • Haga este nivel de clase también, permitiendo que los dispositivos individuales tengan diferentes ciclos de vida.
  • Tenga una tercera configuración de nivel de ensamblaje que ejecute automáticamente instancias separadas para cualquier dispositivo que contenga una o más pruebas que se ejecuten en paralelo. (Difícil de saber siempre de antemano)

Sin embargo, diría que haga lo básico primero.

En este momento, se garantiza que será un error si hay un estado de instancia mutable y un dispositivo se ejecuta en paralelo consigo mismo, por lo que podríamos tener una opción Auto que cambie a instancia por caso de prueba si los casos de prueba del dispositivo están configurados para ejecutarse. en paralelo y evitar cambios importantes . Creo que Auto tiene el potencial de ser confuso, pero también podría ser el valor predeterminado más sensato para que todos no agreguen [assembly: InstancePerTestCase] como una cuestión de rutina.

Estoy a favor de las barandillas. OneTimeSetUp y OneTimeTearDown deben marcarse como errores si el dispositivo hace una instancia por caso de prueba. ¿Quizás a menos que sean estáticos?

done feature normal docs releasenotes

Comentario más útil

¿Alguna posibilidad de que podamos revisar esto? se siente como si nos estuviéramos acercando mucho

Todos 112 comentarios

Actualmente, __puede__ compartir el estado entre las pruebas paralelas siempre que (1) establezca el estado en el constructor o en la configuración única y (2) no lo cambie en ninguna prueba. Incluso puede arreglárselas en algunos casos si el estado se establece en una constante en la prueba o configuración, siempre que nunca lo establezca en ningún otro valor. Este último es asqueroso, aunque creo que tenemos algunas pruebas de NUnit que lo hacen.

Mi pensamiento es que no podemos cambiar el valor predeterminado en el corto plazo, si es que alguna vez lo hacemos. Tener un estado mutable no garantiza actualmente un error. Es solo un error si realmente lo muta y no lo sabemos a menos que hagamos un análisis de código, ¡lo cual no haremos!

Si no cambia ningún estado de instancia dentro de una prueba, configuración o desmontaje, no se romperá. Si lo hace, ya está roto siempre que sus pruebas puedan ejecutarse en paralelo con ellas mismas. No hay forma de que no estés roto que yo pueda pensar. Esa es la justificación de la opción Auto, si pensamos que la complejidad resultante para el usuario vale la pena el resultado de "valores predeterminados sensibles".

Tiene un punto, pero yo seguiría defendiendo que se trata de una posible extensión, que no es necesario que decidamos ahora. Hagamos esto de forma iterativa en lugar de intentar tomar todas las decisiones ahora.

Estoy con @CharliePoole, los valores predeterminados deben permanecer como están y no se pueden eliminar, de lo contrario, podría romper a las personas que desean ese comportamiento.

tal vez un atributo de nivel de dispositivo para activar una instancia por hilo en lugar de una instancia compartida para todos los hilos.

@BlythMeister ¡ No te preocupes! No estamos considerando ningún cambio que pueda causar que el código existente comience a romperse.

Esto es interesante. Creo que la instancia por caso de prueba es más fácil de entender que la instancia por hilo. Las formas en que las pruebas se asignan a los subprocesos no serían confiables, por lo que no puedo pensar en ningún beneficio que tendría sobre la instancia por caso de prueba.

sí, estoy usando el término hilo para referirme a la ejecución paralela ... lo cual es inexacto de hecho.
mis disculpas.

En términos de XP (y espero que todos sepan que esa es siempre mi perspectiva) tenemos tres historias, que se complementan entre sí ...

  1. El usuario puede especificar el comportamiento de instancia por caso de prueba globalmente para todos los accesorios en un ensamblaje.

  2. El usuario puede especificar el comportamiento de instancia por caso de prueba en un dispositivo.

  3. Los usuarios pueden especificar el comportamiento de instancia por caso de prueba para dispositivos que contienen pruebas que pueden ejecutarse en paralelo.

Me gustaría ver (1) completamente implementado en este tema. Creo que deberíamos sacar (2) de nuestras cabezas, o al menos de la parte que escribe el código, por el momento. Creo que (3) es una posibilidad, pero tiene algunos errores que ya hemos encontrado en otros temas. (¡Un dispositivo no sabe si los casos de prueba se ejecutarán en paralelo!) Hablemos de ese JustInTime, que sería después de que (1) y (2) se completen y fusionen. FWIW, creo que tenemos una tendencia a atarnos en nudos al tratar de pensar en varios movimientos por delante. Mencioné (2) y (3) para que no los bloqueemos de ninguna manera, lo que parece bastante fácil de evitar.

Creo que entonces estamos todos de acuerdo. Sin embargo, tengo algunas prioridades de NUnit antes de esto, así que lo dejaré para que otros comenten y posiblemente lo retomen.

La belleza de hacer esto con un atributo en lugar de una configuración de línea de comandos es que funcionará para cualquier corredor.

Estoy de acuerdo con @CharliePoole en que debería ser un atributo. Las pruebas deberán diseñarse teniendo en cuenta la instancia por prueba o la instancia por dispositivo, por lo que siempre deben ejecutarse de esa manera. También prefiero hacer solo la opción 1. Creo que si desea ese estilo de prueba, debe optar por participar en todo su conjunto de pruebas. Permitirle participar en niveles más bajos introduce una complejidad que no creo que tenga un buen retorno de la inversión.

¿Solo tienes curiosidad por saber si hay planes inminentes para implementar esta función?

@rprouse Esto todavía está marcado como que requiere diseño, pero parece que la discusión ya cubrió todo.

@CharliePoole estuvo de acuerdo en que esto puede pasar del diseño. @pfluegs no hemos empezado a trabajar en esto más allá de diseñarlo, por lo que aún no hay planes.

No estoy seguro de que alguien le preste tanta atención, pero la ausencia de discusiones o etiquetas de diseño implica que alguien podría ofrecerse como voluntario. 😄

Este mismo problema me ha mordido gravemente: +1 para solucionarlo.

En caso de que alguna vez queramos más opciones, querríamos:

`` c #
[ensamblaje: FixtureCreation (FixtureCreationOptions.PerTestCase)]

And:
```diff
namespace NUnit.Framework
{
+   public enum FixtureCreationOptions
+   {
+       Singleton, // = default
+       PerTestCase
+   }
}

Mi propio pensamiento sobre esto ha cambiado un poco, al menos en el contexto de un nuevo marco.

Ahora creo que debería haber dos atributos a nivel de clase, uno para cada comportamiento. Haría que TestFixture funcione con una instancia por prueba y Shared Fixture funcione como lo hace NUnit ahora. Obviamente, ese es un cambio radical en el contexto de NUnit, pero vale la pena considerar el enfoque general.

@CharliePoole [TestFixture(FixtureOptions.PerTestCase)] parece una buena sintaxis, pero también quiero un atributo de nivel de ensamblaje para poder participar en cada proyecto de prueba. ¿Cómo querríamos que se viera un atributo de nivel de ensamblaje?

Bien, por eso expresé la reserva WRT de lo que haría en un nuevo marco. En el caso de NUnit, sin embargo, no querría cambiar la semántica de TestFixture y no puedo pensar en un nombre realmente bueno para el comportamiento de instancia por caso. Si tuviera dos nombres, todo lo que tendría que hacer para obtener el comportamiento deseado en todos los dispositivos sería hacer una edición global de su archivo fuente.

No me gusta la configuración global a nivel de ensamblado porque terminan confundiendo a algunos desarrolladores, que pueden no estar al tanto de lo que está configurado en el archivo AssemblyInfo. Actualmente, permitimos algunos, así que supongo que no hay mucho daño en agregar otro. No tengo pensamientos sobre un nombre para el atributo de nivel de ensamblaje, excepto que debería comunicar que estamos configurando una opción (¿por defecto?) Para todos los accesorios en el ensamblaje.

También me desagradan los valores predeterminados a nivel de ensamblaje hasta cierto punto, por la misma razón que mencionas. En comparación con agregar un nuevo atributo a cada una de mis clases de accesorios actualmente sin atributos, el atributo de nivel de ensamblaje es la ruta que preferiría.

@ nunit / framework-team ¿Qué opinas? Para la introducción inicial de la función:

  • sí / no a la configuración a nivel de montaje?
  • ¿Sí / no al ajuste del nivel del dispositivo?
  • sí / no a la configuración a nivel de método?
  • sí / no a la configuración de nivel de caso de prueba?

Buen punto sobre las clases de aparatos sin atributos.

Yo diría que si, si, no, no

Estoy de acuerdo con Charlie, con la aclaración de que 'nivel de accesorio' significa 'nivel de clase'. (A diferencia de, por ejemplo, un método de fuente de casos de prueba. 😊)

¡Eso ayuda! Entonces, el problema con [TestFixture(FixtureCreationOptions.PerTestCase)] es que se permiten múltiples atributos TestFixture en la misma clase y luego podrían contradecirse entre sí. ¿Quizás sea mejor y más simple tener un nuevo atributo con nombre aplicable a ensamblajes y clases?

`` c #
[montaje: ShareFixtureInstances (falso)]

[ShareFixtureInstances (falso)]
clase pública SomeFixture
{
// ...
}

Framework API:

```diff
namespace NUnit.Framework
{
+   [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)]
+   public sealed class ShareFixtureInstancesAttribute : PropertyAttribute
+   {
+       public bool ShareFixtureInstances { get; }

+       public ShareFixtureInstancesAttribute(bool shareFixtureInstances);
+   }
}

Suena bien. ¿Cuál es el comportamiento de herencia de esto, si tengo el atributo en una clase base, con sus propias pruebas, y luego también heredo de esa clase? 😊

Presumiblemente, como es un atributo de 'clase', ¿debe estar en la clase real desde la que se ejecutan las pruebas en cuestión?

Además, no estoy totalmente interesado en el nombre, ya que un accesorio no siempre es una clase en términos de nunidades, no creo (por ejemplo, todas las pruebas en una fuente de caso de prueba, a menos que 'suite' y 'accesorio' tengan una semántica más fuerte significando de lo que creo, en términos de Nunit!)

¿Qué tal algo parecido a InstancePerTest ... pero mejor llamado ...

De acuerdo sobre el nivel de Ensamblaje y Clase, pero no más profundo. También prefiero algo parecido a la sugerencia de ClassInstancePerTest(bool) . También asumo que el atributo a nivel de clase anularía el nivel de ensamblado y esperaría que se heredara. Creo que la herencia es importante porque normalmente querría una instancia por prueba basada en los miembros de una clase combinados con paralelismo. Por lo tanto, querrá que se aplique automáticamente a las clases derivadas.

Una vez me sorprendió genuinamente que NUnit no crea una instancia por prueba cuando un tipo de dispositivo de prueba es construible. Siempre pensé en pruebas unitarias como, no sé, prueba unitaria : instalas los andamios, ejecutas la prueba y los andamios desaparecieron. Sin transferencia involuntaria de estado entre pruebas.

Habiendo aprendido eso. Yo, como @ jnm2 , instancia las pruebas manualmente. Pero cuando las pruebas, por ejemplo, crean y eliminan archivos, los correctores de casos se vuelven realmente engorrosos: deben ser ID desechables; Ya no hay [TearDown] mágicos para ti. :( Tampoco hay una forma coherente de distinguir los errores de prueba de los errores de andamiaje. Este es un caso de uso realmente doloroso.

Si puedo irrumpir en el diseño, estoy con

El nombre PerTestInstance , InstancePerTest o algo parecido suena mucho mejor que ShareFixtureInstances . Me disgusta levemente la palabra Class en el nombre del atributo, ya que cuando estoy probando el código F #, no creo que las instancias de tipo sean clases . ¡Pero seguro que puedo vivir con eso! :)

¡Vaya, no puedo esperar por esta función!

@ kkm000 Estoy de acuerdo con el uso de "clase". NUnit normalmente ha utilizado nombres que son distintos de la implementación. Incluso entonces, la gente hace suposiciones, pero es mejor ser constante para evitar tales cosas.

Ejemplos de lo que estoy hablando: Test, TestFixture, SetUpFixture, Worker (no thread), etc. En principio, una clase __ podría__ representar una sola prueba, no un accesorio ... No estoy diciendo que hagamos eso o incluso debería, pero __podemos__.

@ kkm000

¡Gracias por el comentario! No vas a irrumpir en absoluto. Tenemos el objetivo de diseño de ser independientes del idioma siempre que sea posible.
Además del enfoque en C #, otra cosa sobre la palabra class : ¿qué pasa si un día admitimos accesorios que no son clases?

[InstancePerTestCase] evitaría el problema, pero podría perder claridad intuitiva.

Hoy en día, los accesorios y las clases no divergen mucho, pero si lo hicieron, estoy bastante seguro de que este atributo se refiere a los accesorios en lugar de a las clases.

Las suites son un concepto organizativo genérico, mientras que los accesorios son cosas que mantienen la lógica de configuración / desmontaje y el estado de prueba y se pasan a los métodos de prueba (actualmente como el argumento this métodos de instancia). Los accesorios no tienen por qué ser parte de la jerarquía organizativa. Imaginemos dispositivos que se apartan del concepto de clases y del concepto de conjuntos de pruebas:

`` c #
public static class SomeTestSuite // No es un accesorio: sin lógica de configuración / desmontaje y sin estado de prueba
{
[Prueba] // Prueba única, no una suite parametrizada;
// el accesorio se inyecta a través de un parámetro en lugar de a través del implícito este parámetro
vacío estático público SomeTest (accesorio SomeFixture)
{
fixture.SomeService.SomeProperty = true;
fixture.SomeAction ();
fixture.AssertSomething ();
}
}

[Accesorio de prueba]
estructura pública SomeFixture: IDisposable
{
public SomeService SomeService {get; } // Estado

public SomeFixture() // This was almost legal syntax in C# 6, but I'm making a point 😁
{
    // Setup
}

public void Dispose()
{
    // Teardown
}

public void SomeAction()
{
    // Helper method
}

public void AssertSomething()
{
    // Helper method
}

}
''

En el ejemplo anterior, [FixtureInstancePerTestCase] o [SharedFixtureInstances(false)] no se centra en las clases y no se centra en la jerarquía (suites): se centra en los accesorios, y los accesorios son la lógica y el estado de configuración / desmontaje reutilizables. Específicamente, se centra en si se crea un nuevo SomeFixture para cada caso de prueba o si se pasa el mismo a cada prueba.

Dado que este atributo controlará si diferentes ITests compartirán instancias de ITest.Fixture, y lo que se está controlando es lo que decoremos con [TestFixture] (otro atributo con Fixture en el nombre), parece un Opción _consistent_ para usar la palabra Fixture.

(Estoy trayendo puntos que parecen importantes para discutir, pero no estoy muy interesado en cómo termina siendo el nombre).

@ jnm2 Tienes razón en que el concepto de suite y de luminaria son separables. NUnit, sin embargo, siguió el ejemplo de todos los marcos de prueba de xunit existentes en el momento en que se creó al combinar los dos. Se desvió de otros marcos al hacer que el accesorio fuera compartido.

Que yo sepa, xunit.net fue el primer (¿único?) Marco que hizo que el dispositivo fuera una entidad separada de la jerarquía de herencia de prueba.

Cuando creé pruebas parametrizadas, podría haber introducido TestCaseSource como un "accesorio" separado. No hice eso y creo que los cambios necesarios para rehacerlo se romperían ahora.

Creo que la razón por la que la gente ocasionalmente (antes del paralelismo) preguntaba sobre el comportamiento de instancia por caso es que es lo que hacen otros marcos. Por una razón similar, __no__ pidieron accesorios separables.

Entonces, resumiendo, esto es lo que pienso ...

  1. Necesitamos instancia por comportamiento de caso de prueba.

  2. No puede ser el predeterminado debido a la compatibilidad con versiones anteriores.

  3. Un accesorio separable es una buena idea, pero también es un tema aparte.

  4. Los accesorios separables parecen al menos tan complejos de implementar como los accesorios de instancia. Por supuesto, no tendría que lidiar con la compatibilidad con versiones anteriores en este caso, lo que lo hace más fácil.

Solo para confirmar los puntos 3 y 4, pretendía que mi muestra de código no fuera una sugerencia de nuevas características, sino un experimento mental destinado a ayudarnos a encontrar el nombre correcto para este atributo. El experimento mental me dice que estamos realmente enfocados en la idea de un accesorio, no tanto en una clase o una suite, aunque coincidan en este momento.

Cualquiera que sea la forma que adopten, los accesorios son definiciones reutilizables de configuración / desmontaje / estado. Creo que ese es el significado de la palabra. Las instancias de dispositivos (apariciones de configuración / desmontaje / estado) son lo que nuestro nuevo atributo decide compartir o no compartir entre casos de prueba. Creo que FixtureInstances en algún lugar del nombre aportaría la máxima claridad.

¿Esto les parece bien a todos? Si no es así, ¿qué otros puntos podemos considerar?

¡Ah! Lo siento, pensé que habías acertado en un punto muy bueno, que generalmente se pasa por alto, pero también temía que pudiéramos estar divagando. : leve_frowning_face:

Creo que el nombre completo (lo que escriba) debería incluir tanto el Aparato como la Instancia. La forma en que se hace depende de si se trata de un nombre de atributo o de una enumeración utilizada para construirlo. No estoy seguro de si se ha decidido por uno u otro.

Sí, buen punto.

Si hacemos una enumeración, se alarga la escritura. Esto es más importante para los atributos dirigidos a clases que para los dirigidos al ensamblaje. También debemos evitar agregar una enumeración como parámetro a TestFixtureAttribute porque se permiten múltiples TestFixtureAttributes en la misma clase y el parámetro de uno podría contradecir el parámetro del otro. Podemos manejar esto a través de un error en tiempo de prueba, pero generalmente es bueno hacer que los estados no válidos no sean representables.

Subamos algunas opciones y podemos modificar desde allí.
Buscamos algo que equilibre la intuición, la coherencia con el estilo de NUnit y la cantidad adecuada de flexibilidad. (En su mayor parte, estas cosas ya deberían superponerse. 😄)


Propuesta A

`` c #
[ensamblado: FixtureOptions (InstancePerTestCase = true)]

[FixtureOptions (InstancePerTestCase = false)]
clase SomeFixture
{
}

Pro: extending later if there's ever more options for fixture handling (doubtful?)
Pro: two names so there's less stuffed into a single name
Con: `[FixtureOptions]` would be legal but highly misunderstandable syntax. (Does it reset the assembly-wide setting to the default, `false`, or does it have no effect, e.g. `null`?)


### Proposal B

```c#
[assembly: FixtureOptions(FixtureCreationOptions.PerTestCase)]

[FixtureOptions(FixtureCreationOptions.Singleton)]
class SomeFixture
{
}

public enum FixtureCreationOptions
{
    Singleton, // = default
    PerTestCase,
    // Doubtful there will be other options?
    // Maybe Pool, where we reset and reuse between nonconcurrent tests?    
}

Ventaja: los estados inválidos no son representables
Ventaja: ampliar más adelante si hay más opciones para el manejo de dispositivos (¿dudoso?)
Con: redundancia al escribir tanto el nombre del atributo como el nombre de la enumeración

Propuesta C

`` c #
[ensamblado: FixtureInstancePerTestCase]

[FixtureInstancePerTestCase (falso)]
clase SomeFixture
{
}
''

Ventaja: los estados inválidos no son representables
Ventaja: difícil de entender (pero tengo un punto ciego aquí porque no soy un usuario nuevo)
Con: nombre largo
Quizás con: nombre muy específico que puede especificar tanto la opción como el valor. (Pero también tenemos [NonParallelizable] que me gusta).


(¡Se aceptan propuestas alternativas!)

Hay algo que decir acerca de tener dos atributos: uno para el ensamblaje y otro para la clase en sí.

Ventaja: más fácil de implementar dos atributos simples en lugar de un atributo con diferentes comportamientos en contexto. (Lección aprendida de ParallelizableAttribute)
Ventaja: probablemente sea más fácil de entender para los usuarios, ya que cada uno puede tener un nombre apropiado. Por ejemplo, el nivel de ensamblaje puede incluir la palabra "Predeterminado".
Con: dos atributos para recordar.

Enfoque utilizando cada una de sus alternativas:

`` C #
[ensamblado: DefaultFixtureOptions (InstancePerTestCase = true)]

[FixtureOptions (InstancePerTestCase = false)]
clase SomeFixture
{
}

[ensamblaje: DefaultFixtureOptions (FixtureCreationOptions.PerTestCase)]

[FixtureOptions (FixtureCreationOptions.Singleton)]
clase SomeFixture
{
}

[ensamblado: DefaultFixtureInstancePerTestCase]

[FixtureInstancePerTestCase (falso)]
clase SomeFixture
{
}
''

Otros comentarios:

  • Me gustan A y B mejor que C
  • No debería llamar al comportamiento de dispositivo compartido "Singleton", porque no es uno. : smile: si hereda, obtendrá varias instancias del dispositivo base.
  • Si utiliza atributos separados en el nivel de montaje y de dispositivo, puede eliminar el dispositivo del nombre en el nivel de dispositivo.
  • No hemos especificado en absoluto cómo encaja SetUpFixtures en esto. Son problemáticos y pueden necesitar un error en tiempo de ejecución si se usa el atributo en ellos. De hecho, una ventaja de un atributo completamente separado es que no tenemos que lidiar con esto. (También es una ventaja para una propiedad de TestFixture, BTW)
  • Necesitamos advertir a la gente que la instancia por caso de prueba no se aplica a las clases estáticas. Es obvio para todos nosotros, pero hay personas que no lo entenderán de inmediato.

Por cierto, a menudo me hubiera gustado haber elegido DefaultTestCaseTimeOut para los niveles de ensamblaje y accesorios y haber mantenido el tiempo de espera solo en los métodos.

Bien, gracias por mencionar esta posibilidad.

Algo que me molesta en este momento es que, en teoría, esto podría tener sentido, aunque no vamos en esta dirección:

c# [DefaultFixtureOptions(InstancePerTestCase = true)] class SomeFixture { [FixtureOptions(InstancePerTestCase = false)] public void SomeTest() { } }

Porque el nivel en el que esto realmente se desarrolla no es el nivel del dispositivo en sí, sino el nivel del caso de prueba.
Como ejemplo: un dispositivo derivado puede anular la configuración del dispositivo base, pero todo lo que realmente hace es afectar la configuración de cada caso de prueba producido por la clase derivada.

O:

`` c #
[ensamblado: DefaultFixtureOptions (InstancePerTestCase = true)]

// ¿Por qué no debería considerarse esto también como predeterminado?
[DefaultFixtureOptions (InstancePerTestCase = true)]
Clase pública abstracta BaseFixture
{
}

Clase pública sellada SomeFixture: BaseFixture
{
}
''

¿Cómo se nos ocurre una justificación de cuándo y cuándo no usar Default ?

Creo que implementar este atributo en cada caso de prueba introduciría una gran complejidad con solo un pequeño beneficio.

Para los dispositivos de prueba "normales", continuaremos creando instancias de la instancia como parte de la cadena de comandos asociada con el dispositivo de prueba. Por ejemplo, los dispositivos de instancia por prueba, no lo crearemos allí, pero tendremos que implementar un nuevo comando como parte de la cadena de prueba, que hará más o menos lo mismo que lo que se hace ahora en la cadena de dispositivos.

Si un aparato puede contener ambos tipos de prueba, entonces tendremos que ejecutarlo dos veces, una para cada modelo.

El beneficio parece pequeño, ya que el usuario puede lograr fácilmente lo mismo dividiendo las pruebas en dispositivos separados. Los dispositivos, después de todo, son un artefacto de NUnit y es razonable exigir a los usuarios que pongan pruebas que necesiten el mismo comportamiento de dispositivo en el mismo dispositivo.

Con respecto a cuándo usar "Predeterminado" ... me parece que __no__ lo usaría en ningún TestFixxture y lo usaría solo en el nivel de ensamblaje. Las clases base no son una excepción porque el atributo se encuentra realmente en la clase derivada en virtud de la herencia. Por lo tanto, establecer el comportamiento particular en la base no es un valor predeterminado. Más bien es el escenario de esa clase y cualquier otra que se derive de ella. Las especificaciones contradictorias deberían causar un error o ignorarse, como hacemos en todos los casos de atributos heredados actualmente. Es decir, colocar el atributo A en la clase base y B en la clase derivada no se distingue de colocar A y B directamente en la clase base. Lo diseñamos de esa manera porque sería sorprendente para el usuario hacer cualquier otra cosa.

Accesorios de configuración de WRT, por cierto, creo que debería generar un error de tiempo de ejecución para especificar cualquiera de las opciones de creación de instancias allí. Los SetUpFixtures no son TestFixtures.

Es decir, colocar el atributo A en la clase base y B en la clase derivada no se distingue de colocar A y B directamente en la clase base. Lo diseñamos de esa manera porque sería sorprendente para el usuario hacer cualquier otra cosa.

¡Oh, hubiera esperado que [Apartment] o [Parallelizable] reemplazaran la configuración de la clase base o del método base!

Todo esto tiene sentido para mí.

¿Podemos descartar querer una enumeración como FixtureCreationOptions ? PerTestCase , PerFixture , hipotético ReusedPerTestCase ?

Me inclino por la simplicidad de:

`` c #
[ensamblado: DefaultFixtureOptions (InstancePerTestCase = true)]

[FixtureOptions (InstancePerTestCase = false)]
clase SomeFixture
{
}

Hypothetical other creation options would then be added this way:
```c#
[FixtureOptions(InstancePerTestCase = false, ReuseInstances = true)]

@ nunit / framework-team ¿Cuáles son sus preferencias?

Con respecto a

[assembly: DefaultFixtureOptions(InstancePerTestCase = true)]

Tal vez solo yo, pero generalmente entiendo que los atributos indican que el objeto marcado posee un rasgo determinado, que de otra manera no estaría presente; los argumentos del atributo, si los hay, están refinando dicho rasgo. Tomemos, por ejemplo, Serializable: el objeto es serializable, pero si no está decorado, no lo es; InternalsVisibleTo: elementos internos visibles para algo específico y los argumentos refinan esto; de lo contrario, los internos no son visibles para nada, etc. Algunos atributos no siguen el patrón, pero suelen ser de bajo nivel, e, g, CompilationRepresentation, MethodImpl. Por lo general, vienen con los argumentos de constructor necesarios.

Ahora considera
c# [assembly: DefaultFixtureOptions]
Este es un C # bien formado, ya que la asignación a InstancePerTestCase es opcional. ¿Qué expresa semánticamente?

Para mí, [assembly: DefaultFixtureInstancePerTestCase] parece más natural (con la excepción de su nombre sesquipedaliano, pero eso es solo una opinión, no justificable salvo estéticamente). Un atributo, un rasgo. Quizás este no necesita ningún parámetro o propiedades configurables opcionalmente.

Además, como usuario, me sorprende que haya dos atributos con nombres diferentes, uno aplicable solo a nivel de ensamblaje ( DefaultSomething ) y otro solo a nivel de tipo (solo Something ) . Naturalmente, no entiendo cuál fue la "lección aprendida" que mencionó @CharliePoole con respecto al atributo Parallelizable , pero desde este lado de la isla, un par de atributos para saber dónde se haría (en el pensamiento superficial de este usuario) parece desconcertante.

@ kkm000 Estoy de acuerdo con su comentario sobre un atributo que expresa un rasgo único.

WRT dos atributos diferentes para el "mismo" rasgo en diferentes niveles, yo estaría de acuerdo si pensara que en realidad son lo mismo. Pero no creo que lo sean. Usaré Timeout como ejemplo.

Si se coloca Timeout en un método de prueba, significa "Este método de prueba expirará si tarda más de la cantidad de milisegundos especificada en ejecutarse". Sin embargo, cuando el tiempo de espera se coloca en un dispositivo o ensamblaje, tiene un significado diferente: "El tiempo de espera predeterminado para todos los casos de prueba contenidos en este elemento es el número especificado de milisegundos". Por supuesto, ese valor predeterminado puede anularse en niveles inferiores. Dos significados bastante diferentes, que por cierto tienen dos implementaciones separadas. El primero aplica el tiempo de espera a la prueba en la que aparece. El segundo almacena el valor predeterminado en el contexto de prueba donde se encontrará y se utilizará como predeterminado cuando se encuentren casos de prueba contenidos. ParallelizableAttribute funciona de manera similar.

En este caso particular, colocado en un dispositivo, el atributo significa "Instanciar este dispositivo una vez por caso de prueba". A nivel de ensamblaje, significa "Instanciar accesorios de prueba en este ensamblaje una vez por accesorio a menos que se especifique lo contrario".

En este caso particular, colocado en un dispositivo, el atributo significa "Instanciar este dispositivo una vez por caso de prueba". A nivel de ensamblaje, significa "Instanciar accesorios de prueba en este ensamblaje una vez por accesorio a menos que se especifique lo contrario".

Suponiendo que se haya equivocado en la última oración, fue _ "Instanciar accesorios de prueba en este ensamblaje una vez por ~ accesorio ~ caso de prueba a menos que se especifique lo contrario". _ ¿Pretendía? ¿O estoy malinterpretando seriamente el alcance y la intención?

Suponiendo que no soy, para mí, como usuario, la semántica no se ve diferente en absoluto: "instanciar una clase por prueba dentro del alcance marcado por el atributo ", tal vez aclarado con "a menos que su alcance interno esté marcado de manera diferente, si hay es de hecho un alcance tan interno ”, ya sea un accesorio o el conjunto. Solo mi opinión, pero dos atributos parecen un poco confusos. Tal vez sea solo mi forma de pensar, en realidad no lo sé.

dos implementaciones separadas

En el gedankenworld de unicornios y arcoíris, la implementación no afecta el lado que mira al usuario. Pero nosotros (tanto usted, el diseñador de NUnit, como yo, uno de sus usuarios) somos ingenieros, y la ingeniería se trata de compromisos. Si el uso de un atributo para diferentes ámbitos de hecho daría lugar a problemas internos y a una carga de mantenimiento, no me quejaría en absoluto. :)

Al final, para mí este es un problema menor, y no siento mucho por el mismo atributo y puedo vivir con los atributos organizados de cualquier manera.

@ kkm000 Sí, pretendía "una vez por caso de prueba".

Estoy de acuerdo en que todo tiene mucho sentido si lo considera una serie de ámbitos anidados. Sin embargo, la experiencia muestra que los usuarios (a) no siempre piensan de esa manera o (b) no entienden bien la definición de alcance.

Como ejemplo de lo último, considere las clases anidadas y las clases base. NUnit no trata a ninguno de ellos como "ámbitos" anidados con fines de prueba, pero pueden parecerle así a los usuarios.

Reconozco que el problema que mencioné con Timeout / DefaultTimeout es un poco más serio, ya que el tiempo de espera en un dispositivo podría significar fácilmente que el tiempo de espera se aplica a todo el dispositivo, lo que, por supuesto, no es así. De hecho, el tiempo de espera en el ensamblado podría aplicarse a todo el ensamblado, lo cual no es así.

En este caso, parece haber menos ambigüedad, porque estamos usando "por caso de prueba" en el nombre, por lo que podría ir fácilmente en cualquier dirección. De todos modos, no estoy trabajando en eso. :sonrisa:

Ahora que lo pienso, si Timeout se hubiera llamado TestCaseTimeout, ¡habría sido mejor!

Estoy feliz con un solo [FixtureInstancePerTestCase] . Parece la menor carga mental para los usuarios.

@ nunit / framework-team ¿Puede ayudarnos a establecer una dirección, echando un vistazo a https://github.com/nunit/nunit/issues/2574#issuecomment -426347735 hacia abajo?

[FixtureInstancePerTestCase] también es mi opción menos desagradable. 😄 La falta de extensibilidad es el único inconveniente.

No me gusta el problema [FixtureOptions] ; creo que es demasiado confuso. Personalmente, preferiría un solo atributo tanto para el ensamblaje como para la clase; aprecio que nos haya causado problemas antes con Timeout y Paralellizable; sin embargo, cuando este tiene "Fixture" en el nombre, me parece menos ambiguo.

Personalmente, cito me gustó la opción de enumeración ... ¡excepto que no puedo pensar en cómo nombrarla apropiadamente para que sea más concisa!

¿Hay alguna actualización sobre esto?

Se publicarán todas las actualizaciones de este problema.

¿Alguien puede resolver este problema, por favor?
Actualmente debemos envolver todo nuestro código de prueba así:
csharp [Test] public void TestExample(){ using(var tc = new MyTestContext()){ //code goes here } }
Con esta función, podemos inicializar el contexto de prueba en la configuración y eliminarlo al desmontarlo y seguir siendo seguro para subprocesos.

Hola, también nos encontramos con este problema en nuestra empresa. Estamos migrando desde un marco que admite esta función (Py.Test), por lo que esto es un poco complicado. Le agradeceríamos enormemente si pudiera mostrarnos el código fuente que controla esta instanciación para que podamos bifurcarlo y realizar los cambios necesarios.

@MChiciak
Si este problema no se solucionará pronto, estamos considerando la migración a xUnit, ya que en xUnit este es el comportamiento predeterminado: "xUnit.net crea una nueva instancia de la clase de prueba para cada prueba que se ejecuta" https: // xunit .github.io / docs / shared-context

@LirazShay , @MChiciak También encontré este problema, pero estoy de acuerdo con los puntos de @CharliePoole . Así que hice mis campos específicos de prueba que se inicializan en SetUp [ThreadStatic] y funciona bien.

@dfal
¡Gracias por la buena solución!

Me encantaría esto. Actualmente tengo un conjunto de pruebas existente con miles de pruebas ejecutándose en nunit, que no se pueden paralelizar fácilmente debido al estado compartido. Si se proporcionara esta opción, podríamos paralelizar nuestro conjunto de pruebas mucho más fácilmente.

Como experimento mental, imagina que simplemente cambiamos cómo funcionaba TestFixture. ¿Qué rompería esto?

  1. Cualquier prueba que utilice la ordenación y se base en los cambios de estado realizados por una prueba para influir en otras pruebas. Esta es una práctica muy mala en el caso de pruebas unitarias (supuestamente) independientes. Siempre hemos advertido a la gente que no haga esto y no me importaría romper ninguna prueba (con un mensaje, por supuesto) que se basara en tener una sola instancia de esta manera.

  2. Dependiendo de lo que se haga dentro de la configuración única, es posible que las instancias de dispositivos individuales no puedan ejecutarse en paralelo. ¡Esto es irónico, ya que la motivación para el cambio es permitir que los accesorios se ejecuten en paralelo! Este no es un cambio importante para las personas que necesitan la función, ya que ya no pueden ejecutarse en paralelo, pero es un cambio importante para aquellos que ya confían en ejecutar las pruebas en paralelo __ después__ de un `OneTimeSetUp 'no paralelizable.

  3. Cualquier OneTimeSetUp nivel de dispositivo ahora se ejecutará varias veces. Esto eliminaría cualquier ganancia de eficiencia de la configuración única y podría, en algunos casos, cambiar la semántica. En particular, si la inicialización afectó al estado global de alguna manera (por ejemplo, configurando una base de datos), podría dar lugar a errores. Este me parece una rotura inaceptable.

Encontré este experimento mental útil para pensar en cómo implementar la creación de instancias de accesorios por caso de prueba. Claramente, debe ser una nueva característica no predeterminada si se introduce en NUnit 3. Para NUnit 4 o cualquier otro marco nuevo (por ejemplo, TestCentric), el nuevo comportamiento podría ser fácilmente el predeterminado.

Como nota, simplemente reemplacé TestClass y TestMethod algunos MSTest con TestFixture y Test respectivamente, y descubrí que NUnit y MSTest tienen semánticas diferentes aquí. MSTest crea nuevas instancias para cada prueba donde, como estoy seguro de que todos los que leen esto saben, NUnit reutiliza las instancias de TestFixture.

Sería bueno si pudiera escribir TestFixture(lifeCycle = OneTestPerInstance)] o similar, para facilitar esta migración.

_edit: _ En realidad, habiendo portado las pruebas para usar un método Setup , en realidad resulta que el código inicializador / antes / desmontaje del código existente fue bastante torturado bajo MSTest, y ahora son simples constructores de inicializadores de propiedades, que es mucho mejor y más completo, así que me alegro de haber hecho la traducción. Aún así, por una cuestión de compatibilidad, sería bueno tenerlo.

@CharliePoole

Cualquier prueba que utilice la ordenación y se base en los cambios de estado realizados por una prueba para influir en otras pruebas.

Aah, sí, rompe, rompe estas pruebas, ¡¡¡por favor !!! 🤣 Ese es el peor abuso de un marco de pruebas unitarias que se me ocurre.

Cualquier nivel de accesorio OneTimeSetUp

Uh, siempre he pensado que esto se ejecuta una vez por proceso ... Siempre me pregunté por qué no es estático.

Pero sí, el rendimiento también es un punto muy válido.

Esta característica ha estado en discusión durante más de 2 años, no quiero sonar ofensiva, pero ¿hay planes concretos y realistas para implementarla en el futuro más cercano?

Me gustaría saber si mi equipo debería planificar la migración a otro marco o si esto se resolverá en los próximos meses, porque este problema es un éxito para nosotros.

Este es un cambio importante en la forma en que se ejecutan las pruebas y debería considerar el impacto total para no romper por completo a aquellos que dependen de la funcionalidad actual.

La mejor manera de "resolver" esto es hacer que el subproceso de la clase de prueba sea seguro. Al igual que cualquier buena aplicación de subprocesos múltiples, la seguridad de los subprocesos debe ser primordial.
Si está utilizando, por ejemplo, campos que configura en un método de configuración y luego se usa en muchos métodos de prueba, su clase no es segura para subprocesos. eso no es un defecto de NUnit. Ha escrito un singleton no seguro para subprocesos.

Al crear una subclase dentro de su TestFixture que contiene información contextual que crea como parte de cada prueba, se asegura de que su singleton TestFixture sea seguro para subprocesos ... así ... usted "resuelve" el problema.
Entonces, en lugar de pasar tiempo "migrando a otro marco", podría sugerirle que dedicó ese tiempo a hacer que su Singleton TestFixtures sea seguro para subprocesos ... probablemente encontrará que es un ejercicio mucho más rápido.

Por ejemplo, cuando me encontré por primera vez con el hecho de que un NUnit TestFixture es un singleton, convertí mis más de 100 dispositivos de prueba con más de 500 pruebas para que fueran seguros para subprocesos (y tuve que pasar de RhinoMocks a Moq ya que RhinoMocks tampoco es seguro para subprocesos) y esto tomó alrededor de 4 semanas (para ser honesto, Rhino -> ¡Moq tomó la mayor parte de ese tiempo!)

En principio, tiene 100% de razón, pero cuando tiene un conjunto de pruebas existente de ~ 7000 + pruebas unitarias, simplemente reescribirlas para que sean seguras para subprocesos es una gran inversión.

No estoy seguro de si es más una inversión que mudarse a otro marco (puede haber alguna herramienta para automatizar eso) pero no creo que esta solicitud de función deba descartarse en principio, en mi opinión

No estoy defendiendo que esta característica no se deba considerar.

Pero un cambio tan grande en la forma en que funciona nunit, en mi opinión, debería considerarse para la compatibilidad con versiones anteriores (que puede ser bastante difícil) o la nueva versión principal (tampoco una tarea pequeña)

Estoy sugiriendo que hacer que el código que un usuario de nunit escribe sea seguro para ejecutar en un entorno de múltiples subprocesos es una forma de acción más fácil. Incluso podrías escribir eso.

No soy un colaborador de este repositorio, por lo que no tengo ningún contexto sobre cómo se implementa. Esto puede ser increíblemente ingenuo debido a eso, pero ¿no sería posible implementar esto como un cambio opcional? ¿Para que esto no rompa nada?

Todo este tema está discutiendo varias formas en que se podría implementar un cambio opcional ... hay muchas formas

Este es un proyecto de código abierto integrado en su totalidad por voluntarios. Una vez que se acepta una función, como se ha hecho, espera a que alguien del equipo o de fuera la recoja y la haga. No hay un "jefe" para asignarle trabajo a nadie. De hecho, es bueno que nadie haya hecho eso si no tiene el tiempo para dedicarlo a hacerlo. De esa manera está disponible para quien sea capaz e interesado en hacerlo.

Los RP siempre son bienvenidos, pero probablemente esta no sea la primera contribución de alguien, ya que tiene el potencial de romper muchas otras cosas en NUnit. Mi opinión de todos modos.

Ya no estoy contribuyendo tan activamente como en el pasado, por lo que mis comentarios sobre cómo creo que debería hacerse se dieron como un consejo para quien lo recoja.

Estoy de acuerdo en que ha existido por un tiempo y me encantaría verlo hecho yo mismo.

@BlythMeister Creo que un cambio lo simplifica un poco, como probablemente sepa, pero es posible que otros no.

Claro, es un interruptor, pero no solo enciende una bombilla. Es más como cambiar su radio de AM a FM ... entran en juego circuitos completamente nuevos.

En este caso, lo que está involucrado es un ciclo de vida completamente nuevo en la ejecución de una prueba, por lo que se debe reemplazar una buena cantidad de código. No es poca cosa. El riesgo estará en asegurarse de que el nuevo "circuito" no afecte al antiguo de ninguna manera.

Lo siento, tienes razón, mi explicación fue vaga en ellos por qué un "cambio" no es simple.

@BlythMeister De hecho, hice exactamente lo que usted describe: creé un objeto desechable para rastrear el estado durante una prueba y luego desecharlo una vez finalizada la prueba, para conducir mi arnés de prueba Selenium. Un problema importante con ese enfoque es que el estado no persiste en el derribo. Como resultado, no es posible ejecutar acciones condicionalmente para pruebas fallidas (tomar capturas de pantalla, registros u otras partes del estado) porque el estado de prueba no se resolverá dentro del método de prueba y solo se puede evaluar durante el desmontaje. Entonces, si bien un objeto de estado es una forma poderosa de hacer que las pruebas sean seguras para subprocesos, solo funciona cuando no necesita hacer cosas con el resultado de la prueba durante el desmontaje.

Es útil recordar que hacer cosas con el resultado de la prueba no es para lo que diseñamos TearDown. Por supuesto, sé que mucha gente lo usa de esa manera y entiendo la motivación, pero hay muchos casos complicados que básicamente se reducen al hecho de que TearDown es parte de la prueba y no termina hasta que termina.

Con NUnit 3 esperábamos ver a más usuarios examinando resultados fuera del marco utilizando extensiones de motor, pero trabajar dentro de la prueba en sí parece ser más familiar y accesible.

@CharliePoole

  1. Cualquier OneTimeSetUp nivel de dispositivo ahora se ejecutará varias veces. Esto eliminaría cualquier ganancia de eficiencia de la configuración única y podría, en algunos casos, cambiar la semántica. En particular, si la inicialización afectó al estado global de alguna manera (por ejemplo, configurando una base de datos), podría dar lugar a errores. Este me parece una rotura inaceptable.

¿Por qué no podemos implementar la función Instancia por caso de prueba y aún ejecutar OneTimeSetUp solo una vez (mantener el hilo principal, recordar llamarlo o algo así)?

Porque rompería cualquier OneTimeSetUp existente que inicializara la instancia, lo cual es muy común.

Veo esto como un problema en cualquier solución basada en conmutadores. Es un problema menor si definimos una alternativa completamente nueva para probar el dispositivo, un enfoque que encuentro bastante tentador.

Porque rompería cualquier OneTimeSetUp existente que inicializara la instancia, lo cual es muy común.

¿Qué tal si solo se permiten los métodos estáticos OneTimeSetUp y OneTimeTearDown cuando la función "instancia por caso de prueba" está habilitada? Eso obligaría a los miembros inicializados dentro de esos métodos a ser estáticos, por lo que obviamente se compartirían entre instancias de dispositivos.

Si bien la motivación principal de este problema de Github es la paralelización, me gustaría incluir el _principio de la menor sorpresa_ y _eludir los errores causados ​​por las pruebas que se influyen entre sí_ como motivaciones también. (Disculpas si me perdí que alguien más ya haya mencionado esto).

En caso de que no haya leído lo suficiente en este hilo bastante antiguo, mis comentarios no discuten en contra de la función, sino en contra de una implementación particular que se sugirió. Prefiero reemplazar TestFixture con un tipo de accesorio completamente nuevo, que funciona de manera diferente.

Creo que el principio de mínima sorpresa funciona bien aquí. No es de extrañar que un nuevo dispositivo funcione de manera diferente.

Si, por otro lado, creamos una bandera que controla el dispositivo existente, puedo pensar en cinco o seis lugares diferentes en la implementación actual donde necesitaríamos bifurcar en esa bandera.

Eso no quiere decir que los dos tipos de dispositivos no compartan cierto código. Pero eso estaría fuera de la vista de los usuarios.

@fschmied Por cierto, si estuviéramos haciendo un nuevo marco (o tal vez incluso NUnit 4) entonces discutiría de manera diferente ... Creo que deberíamos haber hecho mucho más difícil que las pruebas interfieran entre sí en NUnit 2 y 3. Sin embargo , siempre que hagamos cambios en NUnit 3, creo que hay reglas de compatibilidad.

Prefiero reemplazar TestFixture con un tipo de accesorio completamente nuevo, que funciona de manera diferente.
[...]
Creo que el principio de mínima sorpresa funciona bien aquí. No es de extrañar que un nuevo dispositivo funcione de manera diferente.

Por supuesto que tienes razón. Lo que quise decir es que para el tipo original de dispositivo de prueba, el comportamiento seguirá siendo sorprendente para la mayoría de la gente. (Sucedió que sorprendió a un compañero de trabajo hace dos semanas, cuando encontró un caso de una prueba que influía en otra. Ha estado usando NUnit desde 2013, por lo que busqué este problema).

Pero, por supuesto, es un compromiso entre la compatibilidad con versiones anteriores, la complejidad de la implementación y el deseo de corregir un pecado original [1].

Volviendo al problema de OneTimeSetUp : Creo que sigue siendo importante tener un OneTimeSetUp por dispositivo de prueba, incluso si la clase de dispositivo de prueba se crea una instancia por prueba. Para evitar confusiones en caso de que OneTimeSetUp manipule el estado de la instancia, propondría hacer que OneTimeSetUp (y OneTimeTearDown ) sea estático con la función de instancia por prueba. (No importa si está implementado como un interruptor o como un nuevo tipo de dispositivo de prueba).


[1] James Newkirk escribió esto :

Creo que uno de los errores más grandes que cometimos cuando escribimos NUnit V2.0 fue no crear una nueva instancia de la clase de dispositivo de prueba para cada método de prueba contenido. Digo "nosotros" pero creo que este fue mi culpa. No entendí del todo el razonamiento en JUnit para crear una nueva instancia del dispositivo de prueba para cada método de prueba.

Sin embargo, también escribe esto:

[...] sería difícil cambiar la forma en que NUnit funciona ahora, demasiada gente se quejaría [...]

:)

Sí, Jim y yo hemos discutido sobre esto durante años. : smile: Una cosa en la que estamos de acuerdo es en la última oración. Jim se fue a hacer un nuevo marco antes de implementar su enfoque preferido. Básicamente, estoy haciendo lo mismo y SetUp / TearDown es una cosa que probablemente haga de manera diferente.

Me sigue sorprendiendo la cantidad de usuarios de NUnit bastante experimentados que no conocen el uso de la misma instancia para todos los casos de prueba. Puede ser que fuera más conocido cuando NUnit salió por primera vez porque era una diferencia obvia de JUnit. Ahora que todo está olvidado y nuevas personas vienen a NUnit con expectativas que NUnit no coincide. Algo sobre lo que pensar.

así que ... ¿esto está en juego? o todavía en la fase de diseño?

ahora mismo en .NET aterriza en su XUnit sin TestContext o NUnit, con pruebas paralelas repetitivas

La función se acepta y se le asigna una prioridad normal. Nadie está asignado a él y no figura en un hito. Eso significa que alguien tiene que decidir que quiere trabajar en ello. Eso podría ser un comprometido o un contribuyente.

Por lo general, no hacemos una fase de diseño (si queremos una, se aplica la etiqueta de diseño), pero aun así, quienquiera que haga esto, debería publicar un comentario que describa cómo planean implementarlo, particularmente cómo se verá usuarios. Si tuviera que comenzar codificándolo, podría terminar en discusiones de diseño de todos modos, por lo que probablemente sea mejor terminar con eso primero.

Entonces, ¿te gustaría trabajar en eso?

seguro, acabo de empezar a trabajar en
https://github.com/avilv/nunit/tree/instance-per-test

Soy nuevo en esta base de código, así que estoy tratando de ceñirme al estilo del código original tanto como puedo
hasta ahora introduje un atributo InstancePerTestCase y planeo que sea compatible a nivel de ensamblaje y clase

avísame si estoy lejos de la base aquí

Agregué comentarios sobre algunos detalles de implementación en su última confirmación.

En cuanto al diseño, creo que un atributo derivado de IApplyToContext tiene sentido. Creo que preferiría algo más general que solo InstancePerTestCase, tal vez

C# [FixtureLifeCycle(LifeCycle.InstancePerTestCase]

Eso le permitiría configurarlo a nivel de ensamblaje y restablecerlo en dispositivos individuales.

CharliePoole eres demasiado, muchas gracias por los comentarios y la ayuda

Creo que estoy entendiendo cómo se construyen las cosas / qué hace qué, etc. de forma lenta pero segura

aquí está mi propuesta para la enumeración LifeCycle (también he actualizado mi rama con su sugerencia)

    /// <summary>
    /// Specifies the lifecycle for a fixture.
    /// </summary>
    public enum LifeCycle
    {
        /// <summary>
        /// A single instance is created and for all test cases.
        /// </summary>
        SingleInstance,

        /// <summary>
        /// A new instance is created for each test case for fixtures that are marked with <see cref="ParallelizableAttribute"/>.
        /// </summary>
        InstancePerTestCaseForParallelFixtures,

        /// <summary>
        /// A new instance is created for each test case.
        /// </summary>
        InstancePerTestCase
    }

@ jnm2 ¿Qué opinas de este enfoque? En primer lugar, eres una especie de "patrocinador" de esta función. No estoy un poco inseguro acerca de InstancePerTestCaseForParallelFixtures , que podría ser demasiado para una implementación inicial. Como de costumbre, una vez implementado, nos quedamos con él.

Si aceptamos el vínculo con el paralelismo, debe basarse en el contexto, no en los atributos. Es decir, cualquier prueba se puede ejecutar en paralelo, por lo que es más como `InstancePerTestCaseWhenCasesAreParallel. Los accesorios paralelos no requieren particularmente esta característica. Y tal vez sea mejor dejar que el usuario decida.

También estoy de acuerdo en que no debería estar asociado de ninguna manera con el paralelismo. El paralelismo fue la razón para agregar esta característica, pero una vez que estemos de acuerdo en que esta característica es útil, en mi opinión, no debería reflejarse en el código.

Estoy de acuerdo, entiendo el caso de uso para usarlo solo en escenarios paralelos, ya que ahí es donde el modelo predeterminado actual de NUnit lo morderá, pero espero que este sea el ciclo de vida predeterminado en el futuro (¿nunit 4?) Ya que creo que realmente debería haberlo hecho. ha sido así desde el principio

quizás deberíamos dejarlo en SingleInsance / InstancePerTestCase

otra pregunta es si deberíamos manejar el patrón IDisposable aquí, que haría que SetUp / TearDown fuera redundante pero se sentiría mucho más natural al estilo XUnit.

Ya disponemos de cualquier dispositivo de prueba que implemente IDisposable. Solo necesita asegurarse de que continúe funcionando.

genial, servirá.

Probablemente comenzaré a agregar casos de prueba hoy, pero dado que es un mecanismo tan central, podría agregar bastantes casos que solo se aseguran de que funcione lo mismo cuando InstancePerTestCase está activado, corrígeme si me equivoco

DisposeFixtureCommand se encarga de llamar a Dispose.

En mi opinión, su principal problema es asegurarse de que se invoca todo el flujo de comandos para el dispositivo de prueba __para cada prueba__. Los cambios que veo hasta ahora crean correctamente una secuencia de comandos que parece hacer el trabajo, pero la pregunta clave es cómo asegurarse de que se invoque para cada caso de prueba. (Tenga en cuenta que la estructura de comando para los accesorios es distinta de la de los casos de prueba). Creo que el elemento de trabajo compuesto debe ser sensible al contexto e invocar el comando correctamente (como lo ha hecho), pero no estoy seguro de que tenga una oportunidad para hacerlo para __todos__ casos de prueba. (Solo estoy leyendo el código, por lo que podría estar equivocado).

@CharliePoole parece que tienes razón, hice un pequeño gráfico sobre lo que ejecuta exactamente un método de prueba:
image

Actualmente creo la instancia del objeto de prueba en el bucle RunChildren, pero eso no será suficiente para los comandos Retry / Repeat, sin mencionar si hay otros contenedores que me perdí / alguien necesita nuevos.
va a tener que estar mucho más cerca de TestMethodCommand

Actualicé los últimos cambios en
https://github.com/avilv/nunit/tree/instance-per-test-2

eliminado InstancePerTestCaseWhenCasesAreParallel
implementó FixtureLifeCycle agregando el accesorio Construct / Dispose dentro de MakeTestCommand en SimpleWorkItem , y saltándolo en CompositeWorkItem MakeOneTimeSetUpCommand

parece funcionar bien, también tiene más sentido con la forma en que se construye el marco
ahora solo necesito agregar un montón de pruebas unitarias para asegurarme de que todo funcione correctamente

¿Alguna posibilidad de que esto se revise antes de la próxima versión? todavía estoy listo para mejoras / cambios, por supuesto

¡Este enfoque me suena bien y me encantaría usarlo en la próxima versión si podemos hacerlo!

¿Alguna posibilidad de que podamos revisar esto? se siente como si nos estuviéramos acercando mucho

¿Tiene curiosidad por saber si ha habido algún movimiento en esta función? Parece que @avilv se ha acercado bastante. He estado anticipando una función como esta durante mucho tiempo y estoy emocionado de usarla.

¡Gracias a todos!

¿Está funcionando esta característica ahora?

@CharliePoole, ¿ podrías revisar la última versión de @avilv ? Parece estar completamente implementado. Creo que mucha gente está esperando esta función.

@janissimsons Lo siento, pero ya no estoy en este proyecto, por lo que mi revisión no lo haría avanzar. @ nunit-framework-team ¿No puede alguien revisar esto?

Aprecio todo el trabajo que se hizo para fusionar el # 3427. @Rprouse ¿Alguna idea de cuándo podemos esperar esto en un lanzamiento?

¿Cómo utilizamos la nueva función? ¿Alguien podría escribir una breve descripción sobre los nuevos atributos y cómo usarlos correctamente?

Estoy planeando lanzarlo en las próximas semanas. Quería esperar hasta. NET 5 es final y haga algunas pruebas finales con él antes de su lanzamiento. Dudo que haya algún problema, pero está tan cerca que pensé que sería mejor esperar.

@rprouse ¿ Alguna estimación de cuándo lanzará la nueva versión? Me gustaría empezar a utilizar esta función.

@janissimsons muy pronto. Estamos resolviendo algunos problemas con nuestras compilaciones .NET 5.0 y un par de problemas más, luego lo lanzaré. Puede rastrear el estado en este tablero de proyecto, https://github.com/nunit/nunit/projects/4

¡Esta es una maravillosa noticia!

¿Entiendo correctamente que esto hará posible lo siguiente en NUnit?

Combine todos estos:

  • Las pruebas están parametrizadas
  • Ejecute pruebas en paralelo.
  • Ser capaz de ejecutar variaciones parametrizadas de la misma prueba en paralelo.
  • Las pruebas pueden basarse en datos

Probé algunas cosas en el pasado aquí https://github.com/jawn/dotnet-parallel-parameterized-tests

¿La nueva versión seguirá siendo compatible con .NET Full Framework 4.7.2 o al menos con 4.8?

Atentamente,
DR

@jawn Creo que todo eso funcionará, sí. He probado todas esas cosas, pero NUnit tiene muchas características, por lo que puede haber casos extremos con algunas combinaciones de características. ¡Siéntase libre de sacar nuestro paquete NuGet nocturno y probarlo! Nos encantaría realizar más pruebas y comentarios antes de su lanzamiento.

@drauch sí, la versión 3.13 seguirá siendo compatible con .NET 3.5.

@avilv ¡¡
por cierto, ¿con qué herramienta creaste ese bonito diagrama de flujo?
Gracias

@rprouse ¿Podría también actualizar la documentación sobre cómo utilizar esta nueva función? ¡Gracias!

@janissimsons Estoy planeando actualizar los documentos para el lanzamiento. Sin embargo, la versión corta es agregar [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] a su clase de prueba, probablemente junto con [Parallelizable(ParallelScope.All)] y se creará una instancia de su clase de prueba para cada prueba que se ejecute dentro de la clase. Hacer eso le permite usar variables miembro en la clase en ejecuciones de prueba paralelas sin preocuparse de que otras pruebas cambien las variables.

@LirazShay El diagrama de la captura de pantalla se creó en Visual Studio Enterprise. Puede hacer clic con el botón derecho en una selección en el Explorador de soluciones y elegir "Mostrar en mapa de código", por ejemplo. https://docs.microsoft.com/en-us/visualstudio/modeling/map-dependencies-across-your-solutions ReSharper tiene una función similar.

@ jnm2 Muchas gracias !!!
Tengo VS Enterprise y no conocía esta función que puede ser muy útil.
Gracias por la propina

¿Estoy en lo cierto de que esto se ha publicado y es parte de la versión 3.13?

¿Estoy en lo cierto de que esto se ha publicado y es parte de la versión 3.13?

Creo que ha habido un par de problemas observados y corregidos, especialmente con respecto a la aplicación de atributos de nivel de ensamblaje en # 3720 y estamos esperando 3.13.1 ahora ...

Se ha lanzado NUnit 3.13.1.
¿Quedan más problemas?

No, por eso estaba cerrado @andrewlaser

¿Fue útil esta página
0 / 5 - 0 calificaciones