¿Pones pruebas unitarias en el mismo proyecto u otro proyecto?

137

¿Coloca pruebas unitarias en el mismo proyecto por conveniencia o las coloca en un ensamblaje separado?

Si los coloca en un ensamblaje separado como lo hacemos nosotros, terminamos con una serie de proyectos adicionales en la solución. Es ideal para pruebas unitarias durante la codificación, pero ¿cómo libera la aplicación sin todos estos ensamblajes adicionales?

leora
fuente

Respuestas:

100

En mi opinión, las pruebas unitarias deben colocarse en un ensamblaje separado del código de producción. Aquí hay algunas desventajas de colocar pruebas unitarias en el mismo ensamblaje o ensambles que el código de producción:

  1. Las pruebas unitarias se envían con el código de producción. Lo único que se envía con el código del producto es el código de producción.
  2. Los ensamblajes se hincharán innecesariamente por pruebas unitarias.
  3. Las pruebas unitarias pueden afectar los procesos de compilación como la compilación automática o continua.

Realmente no conozco a ningún profesional. Tener un proyecto adicional (o 10) no es una estafa.

Editar: más información sobre compilación y envío

Además, recomendaría que cualquier proceso de construcción automatizado coloque la producción y las pruebas unitarias en diferentes ubicaciones. Idealmente, el proceso de compilación de prueba unitaria solo se ejecuta si se compila el código de producción y copia los archivos del producto en el directorio de pruebas unitarias. Hacerlo de esta manera da como resultado que los bits reales se separen para el envío, etc. Además, es bastante trivial ejecutar pruebas unitarias automatizadas en este punto en todas las pruebas en un directorio particular.

Para resumir, aquí está la idea general de una compilación diaria y prueba y envío de bits y otros archivos:

  1. La compilación de producción se ejecuta y coloca los archivos de producción en un directorio específico de "producción".
    1. Construir proyectos de producción solamente.
    2. Copie bits compilados y otros archivos en un directorio de "producción".
    3. Copie bits y otros archivos en un directorio de candidatos de lanzamiento, también conocido como un directorio de lanzamiento de Navidad sería "Release20081225".
  2. Si la compilación de producción tiene éxito, se ejecuta la compilación de prueba unitaria.
    1. Copie el código de producción al directorio "pruebas".
    2. Construya pruebas unitarias en el directorio "pruebas".
    3. Ejecute pruebas unitarias.
  3. Enviar notificaciones de compilación y resultados de pruebas unitarias a los desarrolladores.
  4. Cuando se acepta un candidato de lanzamiento (como Release20081225), envíe estos bits.
Jason Jackson
fuente
15
OMI, los inconvenientes que enumeras no siempre son aplicables. Un profesional para el mismo proyecto es la agrupación más fácil de pruebas de clase + probadas: estas pequeñas conveniencias son muy útiles al escribir pruebas. La preferencia personal gana aquí, y algunas veces sus puntos son relevantes, pero no todo el tiempo.
orip
77
Primero debe preguntar si necesita eliminar las pruebas cuando realiza el envío. En caso afirmativo, necesita un proyecto separado. Si no, use los otros pros y contras para decidir. Las personas que asumen que no pueden implementar las pruebas siempre llegarán a la conclusión de "proyecto separado" de forma predeterminada.
orip
77
No veo ninguna ventaja en el envío de código que no sea de producción, como las pruebas unitarias, y hay muchas desventajas. Las pruebas de unidades de envío significan que se trata de más bits que deben distribuirse. Las pruebas unitarias también tienen un conjunto separado de dependencias. Ahora está enviando NUnit, Rhino o Moq, etc. Eso es aún más hinchado. Colocar pruebas unitarias en un proyecto separado requiere solo una pequeña cantidad de esfuerzo, y eso es un costo único. Me siento muy cómodo con la conclusión de que las pruebas unitarias no deben enviarse.
Jason Jackson
44
Como alternativa al enfoque de "proyecto separado" para eliminar pruebas unitarias en el código de producción, considere usar un símbolo personalizado y directivas de compilación como #if. Este símbolo personalizado se puede alternar utilizando los parámetros de la línea de comandos del compilador en sus scripts de software de CI. Ver msdn.microsoft.com/en-us/library/4y6tbswk.aspx .
Rich C
23
.NET está detrás de la curva de tener pruebas en un proyecto completamente separado, y apostaría a que esto cambiará pronto. No hay razón para que el proceso de compilación no pueda ignorar el código de prueba en la compilación de lanzamiento. He usado varios generadores de aplicaciones web que hacen esto. Creo que es absolutamente mejor incluir archivos de códigos de pruebas unitarias junto con los archivos de códigos que describen, salpicados a lo largo de la misma jerarquía de archivos. Google recomienda esto, llamado la organización de archivos 'fractal'. ¿Por qué las pruebas son diferentes de los comentarios en línea y la documentación del archivo Léame? El compilador con mucho gusto ignora lo último.
Brandon Arnold
112

Proyecto separado, pero en la misma solución. (He trabajado en productos con soluciones separadas para el código de prueba y producción, es horrible. Siempre estás cambiando entre los dos)

Los motivos de los proyectos separados son los establecidos por otros. Tenga en cuenta que si está utilizando pruebas basadas en datos, podría terminar con una cantidad bastante considerable de hinchazón si incluye las pruebas en el ensamblaje de producción.

Si necesita acceso a los miembros internos del código de producción, use InternalsVisibleTo .

Jon Skeet
fuente
+1: Acabo de encontrar pruebas unitarias en el mismo proyecto que el código principal y es difícil encontrar las pruebas entre el código real, aunque se haya seguido una convención de nomenclatura.
Fenton
32
Para un lector menos experimentado, esto significa que agrega [assembly:InternalsVisibleTo("UnitTestProjectName")]a su AssemblyInfo.csarchivo de proyecto .
Margus
Muy útil además Margus. Gracias a Jon por mencionar InternalsVisibleTo en este contexto.
Bjørn Otto Vasbotten
1
@jropella: si está firmando el conjunto de productos, solo puede hacer que las partes internas sean visibles para los conjuntos firmados de todos modos. Y, por supuesto, si está ejecutando cualquier código con plena confianza, tienen acceso a través de la reflexión ...
Jon Skeet
2
@jropella: Reflection no se preocupa por InternalsVisibleTo de todos modos ... pero no habrías visto un ensamblaje firmado confiando en un ensamblado sin firmar usando InternalsVisibleTo, porque eso se evita en tiempo de compilación.
Jon Skeet
70

No entiendo la frecuente objeción a implementar pruebas con código de producción. Dirigí un equipo en un pequeño microcap (creció de 14 a 130 personas). Teníamos media docena de aplicaciones Java y nos pareció extremadamente valioso implementar pruebas en el campo para ejecutarlas en un determinadomáquina que exhibía un comportamiento inusual. Se producen problemas aleatorios en el campo y ser capaz de lanzar unos pocos miles de pruebas unitarias al misterio con un costo cero fue invaluable y a menudo se diagnosticaron problemas en minutos ... incluyendo problemas de instalación, problemas de RAM escamosos, problemas específicos de la máquina, problemas de red escamosos, etc, etc. Creo que es increíblemente valioso poner pruebas en el campo. Además, surgen problemas aleatorios en momentos aleatorios y es bueno tener las pruebas unitarias allí esperando a ser ejecutadas en cualquier momento. El espacio en el disco duro es barato. Al igual que tratamos de mantener juntos los datos y las funciones (diseño OO), creo que hay algo fundamentalmente valioso en mantener juntos el código y las pruebas (función + pruebas que validan las funciones).

Me gustaría poner mis pruebas en el mismo proyecto en C # / .NET / Visual Studio 2008, pero todavía no he investigado esto lo suficiente como para lograrlo.

¡Una gran ventaja de mantener a Foo.cs en el mismo proyecto que FooTest.cs es que a los desarrolladores se les recuerda constantemente cuando una clase se pierde una prueba de hermanos! Esto fomenta mejores prácticas de codificación basadas en pruebas ... los agujeros son más evidentes.


fuente
17
Puedo ver cómo sería valioso con las pruebas de integración, ¿pero para las pruebas unitarias? Si los escribe correctamente, no deberían tener dependencias en la máquina.
Joel McBeth
+1 estoy de acuerdo. Como estoy haciendo principalmente .NET, las pruebas de envío con código de producción también tienen el agradable efecto secundario de reducir el paso de integración continua de compilación y prueba: 1) Solo la mitad de los proyectos para construir, 2) todos los ensambles de prueba en el lugar de la aplicación principal entonces es más fácil parametrizar su corredor de prueba. Cuando se usa Maven, este argumento no se cumple, por supuesto. Finalmente, mis clientes son personas más técnicas y realmente les gusta poder ejecutar las pruebas ellos mismos, ya que esto documenta el estado actual de acuerdo con las especificaciones.
mkoertgen
Creo que tiene mucho sentido hacer pruebas de integración en estilo TDD / BDD, es decir, escribir código de prueba que se pueda automatizar fácilmente con corredores de prueba estándar como NUnit, xUnit, etc. Por supuesto, no debe mezclar la unidad con las pruebas de integración. Solo asegúrese de saber qué conjunto de pruebas ejecutar de forma rápida y frecuente para la verificación de compilación (BVT) y cuáles para las pruebas de integración.
mkoertgen
1
La razón por la que las personas se oponen es porque eso no es a lo que están acostumbrados. Si su primera prueba de unidad de experiencia fue que estaban dentro del código de producción, de hecho, con una sintaxis específica en el lenguaje de programación que indicaba qué prueba está asociada con un método de producción, jurarían que ese era un aspecto invaluable del flujo de trabajo de desarrollo y es una locura ponerlos en un proyecto separado. La mayoría de los desarrolladores son así. Seguidores
zumalifeguard
19

Ponga las pruebas unitarias en el mismo proyecto que el código para lograr una mejor encapsulación.

Puede probar fácilmente los métodos internos, lo que significa que no hará públicos los métodos que deberían haber sido internos.

También es realmente bueno tener las pruebas unitarias cerca del código que está escribiendo. Cuando escribe un método, puede encontrar fácilmente las pruebas unitarias correspondientes porque está en el mismo proyecto. Cuando construye un ensamblaje que incluye unitTests, cualquier error en unitTest le dará un error de compilación, por lo que debe mantener su unittest actualizado, solo para compilar. Tener unittest en un proyecto separado puede hacer que algunos desarrolladores olviden construir el unittest-project y se pierdan las pruebas rotas por un tiempo.

Y puede eliminar las pruebas unitarias del código de producción, utilizando etiquetas de compilación (IF #Debug).

Las pruebas de integración automática (hechas en i NUnit) deben estar en un proyecto separado ya que no pertenecen a ningún proyecto en particular.

jenspo
fuente
1
Pero eso significa que no puede ejecutar pruebas en la Releasecompilación y pocos "heisenbugs" pueden caer.
FELIZ
3
Con los ensamblajes de amigos ( msdn.microsoft.com/en-us/library/0tke9fxk.aspx ) el uso le InternalsVisibleTopermite probar métodos internos de un proyecto de prueba separado
ParoX
3
@hlpPy: puede configurar símbolos de compilación condicional arbitrarios y puede tener más de 2 configuraciones de compilación. P.ej; podrías tener Debug, Approvaly Release; y compilar aprobación con todas las optimizaciones de lanzamiento. Además, en general, las pruebas unitarias no son excelentes para detectar heisenbugs en primer lugar (en mi experiencia). Tiende a necesitar pruebas de regresión de integración específicas para esos, y puede colocarlos uno al lado del otro.
Eamon Nerbonne
Un proyecto separado da el mismo error de tiempo de compilación. Si necesita profundizar tanto en el código, entonces suena más como si necesitara más abstracciones que piratear pruebas unitarias en su código. Confiar condicionales en su código para cambiar de sucursal tampoco es bueno. Suena más a que las pruebas de Unidad e Integraciones se están confundiendo.
Nick Turner
12

Después de pasar un tiempo en proyectos TypeScript, donde las pruebas a menudo se colocan en un archivo junto con el código que están probando, crecí prefiriendo este enfoque en lugar de mantenerlos separados:

  • Es más rápido navegar al archivo de prueba.
  • Es más fácil recordar cambiar el nombre de las pruebas cuando cambia el nombre de la clase que se está probando.
  • Es más fácil recordar mover las pruebas cuando mueve la clase que se está probando.
  • Es inmediatamente obvio si a una clase le faltan pruebas.
  • No necesita administrar dos estructuras de archivos duplicados, una para pruebas y otra para código.

Entonces, cuando comencé un nuevo proyecto .NET Core recientemente, quería ver si era posible imitar esta estructura en un proyecto de C # sin enviar las pruebas o los ensambles de prueba con la versión final.

Poner las siguientes líneas en el archivo del proyecto parece estar funcionando bien hasta ahora:

  <ItemGroup Condition="'$(Configuration)' == 'Release'">
    <Compile Remove="**\*.Tests.cs" />
  </ItemGroup>
  <ItemGroup Condition="'$(Configuration)' != 'Release'">
    <PackageReference Include="nunit" Version="3.11.0" />
    <PackageReference Include="NUnit3TestAdapter" Version="3.12.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
  </ItemGroup>

Lo anterior asegura que en la Releaseconfiguración todos los archivos nombrados *.Tests.csse excluyan de la compilación, y también que se eliminen las referencias del paquete de pruebas de unidad requeridas.

Si aún desea poder probar las clases en su configuración de lanzamiento, puede crear una nueva configuración derivada de Releasealgo así llamado ReleaseContainingTests.


Actualización: después de usar esta técnica durante un tiempo, también he descubierto que es útil personalizar sus iconos en VS Code para que las pruebas (y otras cosas) se destaquen un poco más en el panel del explorador:

Captura de pantalla del código VS

Para hacer esto, use la extensión Tema de icono de material y agregue algo como lo siguiente a sus preferencias de código VS JSON:

"material-icon-theme.files.associations": {
  "*.Tests.cs": "test-jsx",
  "*.Mocks.cs": "merlin",
  "*.Interface.cs": "yaml",
}
James Thurley
fuente
Exactamente lo que acabamos de empezar a hacer
Matthew Steeples
¿Tienes esto funcionando para las bibliotecas de clases estándar .net también?
Zenuka
10

Mis pruebas unitarias siempre van en un proyecto separado. De hecho, para cada proyecto que tengo en mi solución, hay un proyecto de prueba separado que lo acompaña. El código de prueba no es un código de aplicación y no debe mezclarse con él. Una ventaja de mantenerlos en proyectos separados, al menos usando TestDriven.Net, es que puedo hacer clic derecho en un proyecto de prueba y ejecutar todas las pruebas en ese proyecto, probando una biblioteca completa de código de aplicación con un solo clic.

tvanfosson
fuente
1
Entonces, ¿cómo pruebas unitarias las clases internas? ¿O solo pruebas las clases públicas?
user19371
77
<ensamblaje: InternalsVisibleTo = "TestProject" /> si es necesario, aunque normalmente solo pruebo las interfaces públicas.
tvanfosson
@tvanfosson, ¿qué haces con las especificaciones (Specflow)? ¿Tendrías un proyecto separado o los incluirías en el proyecto de prueba de unidad? +1.
w0051977
@ w0051977 Nunca he usado Specflow, así que no sé
tvanfosson
@tvanfosson, ¿qué pasa con las pruebas de integración? ¿Los pondrías en un proyecto separado para las pruebas unitarias?
w0051977
10

Si se usa el marco NUnit , hay una razón adicional para poner las pruebas en el mismo proyecto. Considere el siguiente ejemplo del código de producción mezclado con pruebas unitarias:

public static class Ext
{
     [TestCase(1.1, Result = 1)]
     [TestCase(0.9, Result = 1)]
     public static int ToRoundedInt(this double d)
     {
         return (int) Math.Round(d);
     }
}

Las pruebas unitarias aquí sirven como documentación y especificación del código que se está probando. No sé cómo lograr este efecto de autodocumentación, con las pruebas ubicadas en un proyecto separado. El usuario de la función tendría que buscar las pruebas para ver esos casos de prueba, lo cual es poco probable.

Actualización : Sé que tal uso del TestCaseatributo no fue lo que los desarrolladores de NUnit pretendieron, pero ¿por qué no?

Andrej Adamenko
fuente
2
Me gusta esta idea; teniendo algunos ejemplos de entradas y salidas esperadas allí mismo con el código.
Preston McCormick
3

Fluctúo entre el mismo proyecto y diferentes proyectos.

Si está lanzando una biblioteca, lanzar el código de prueba con el código de producción es un problema, de lo contrario, creo que generalmente no lo es (aunque hay una fuerte barrera psicológica antes de intentarlo).

Al poner las pruebas en el mismo proyecto, me resulta más fácil cambiar entre las pruebas y el código que prueban, y más fácil refactorizarlas / moverlas.

orip
fuente
3

Los puse en proyectos separados. El nombre del ensamblado refleja el de los espacios de nombres, como regla general para nosotros. Entonces, si hay un proyecto llamado Company.Product.Feature.sln, tiene una salida (nombre de ensamblado) de Company.Product.Feature.dll. El proyecto de prueba es Company.Product.Feature.Tests.sln, produciendo Company.Product.Feature.Tests.dll.

Es mejor mantenerlos en una única solución y controlar la salida a través del Administrador de configuración. Tenemos una configuración con nombre para cada una de las ramas principales (Desarrollo, Integración, Producción) en lugar de usar la depuración y versión predeterminadas. Una vez que haya configurado sus configuraciones, puede incluirlas o excluirlas haciendo clic en la casilla de verificación "Compilar" en el Administrador de configuración. (Para obtener el Administrador de configuración, haga clic con el botón derecho en la solución y vaya al Administrador de configuración). Tenga en cuenta que a veces el CM en Visual Studio tiene errores. Un par de veces, tuve que ir al proyecto y / o archivos de solución para limpiar los objetivos que creó.

Además, si está utilizando Team Build (y estoy seguro de que otras herramientas de compilación .NET son las mismas), puede asociar la compilación con una configuración con nombre. Esto significa que si no construye sus pruebas unitarias para su construcción "Producción", por ejemplo, el proyecto de construcción también puede conocer esta configuración y no construirlas ya que se marcaron como tales.

Además, solíamos hacer XCopy de la máquina de compilación. El script simplemente omitiría la copia de cualquier cosa llamada * .Tests.Dll de ser implementada. Fue simple, pero funcionó.

Joseph Ferris
fuente
1

Yo diría que los mantenga separados.

Además de las otras razones mencionadas, tener código y pruebas juntas sesga los números de cobertura de prueba. Cuando informa sobre cobertura de prueba unitaria: la cobertura informada es mayor porque las pruebas están cubiertas cuando ejecuta pruebas unitarias. Cuando informa sobre la cobertura de la prueba de integración, la cobertura informada es menor porque las pruebas de integración no ejecutarían pruebas unitarias.

Sebastian K
fuente
¿Eso no depende de la tecnología utilizada para cubrir las pruebas? Quiero decir, OpenCover considera líneas de código cubiertas si son ejecutadas por una prueba, incluidas las líneas de la prueba en sí, de modo que si las pruebas tienen excepciones inesperadas, también se marcan como descubiertas.
Isaac Llopis
0

Estoy realmente inspirado por el marco de prueba de unidad de la biblioteca Flood NN de Robert Lopez. Utiliza un proyecto diferente para cada clase de unidad probada, y tiene una solución que contiene todos estos proyectos, así como un proyecto principal que compila y ejecuta todas las pruebas.

Lo bueno es también el diseño del proyecto. Los archivos de origen están en una carpeta, pero la carpeta para el proyecto VS está debajo. Esto le permite crear diferentes subcarpetas para diferentes compiladores. Todos los proyectos VS se envían con el código, por lo que es muy fácil para cualquiera ejecutar cualquiera o todas las pruebas unitarias.


fuente
0

Sé que esta es una pregunta muy antigua, pero me gustaría agregar mi experiencia allí. Recientemente cambié el hábito de las pruebas unitarias de proyectos separados al mismo.

¿Por qué?

Primero, tiendo a mantener la estructura de la carpeta principal del proyecto igual con el proyecto de prueba. Entonces, si tengo un archivo debajo, Providers > DataProvider > SqlDataProvider.csentonces estoy creando la misma estructura en mis proyectos de prueba unitaria comoProviders > DataProvider > SqlDataProvider.Tests.cs

Pero después de que el proyecto se hace cada vez más grande, una vez que mueve los archivos de una carpeta a otra, o de un proyecto a otro, se está volviendo un trabajo muy engorroso sincronizarlos con los proyectos de prueba unitaria.

En segundo lugar, no siempre es muy fácil navegar desde la clase que se va a probar hasta la clase de prueba unitaria. Esto es aún más difícil para JavaScript y Python.

Recientemente, comencé a practicar que, cada archivo que creé (por ejemplo SqlDataProvider.cs) estoy creando otro archivo con sufijo de prueba, comoSqlDataProvider.Tests.cs

Al principio parece que hinchará archivos y referencias de bibliotecas, pero a largo plazo, eliminará el síndrome del archivo móvil a primera vista, y también se asegurará de que cada archivo que sean candidatos para ser probados tenga un archivo par con .Tests sufijo Le permite saltar fácilmente al archivo de prueba (porque está uno al lado del otro) en lugar de mirar a través de un proyecto separado.

Incluso puede escribir reglas comerciales para escanear a través del proyecto e identificar la clase que no tiene el archivo .Tests e informarlas al propietario. También puede decirle a su corredor de prueba fácilmente que apunte a las .Testsclases.

Especialmente para Js y Python, no necesitará importar sus referencias desde una ruta diferente, simplemente puede usar la misma ruta del archivo de destino que se está probando.

Estoy usando esta práctica durante un tiempo, y creo que es una compensación muy razonable entre el tamaño del proyecto y la capacidad de mantenimiento y la curva de aprendizaje para los recién llegados al proyecto.

Teoman shipahi
fuente
Estoy en desacuerdo. Es mucho más organizado agrupar elementos que probar clases individuales. Si está probando a sus proveedores; Tendría un IProvider, ISqlProvider y un MockSqlConnection. En una carpeta de Proveedor, tendría la Prueba de Unidad para cada proveedor. Puede profundizar en las pruebas unitarias, pero luego simplemente escribe pruebas y no codifica, e incluso puede eliminar el profesional y comenzar a escribir código para probar.
Nick Turner
0

Como otros han respondido: ponga pruebas en proyectos separados

Una cosa que no se ha mencionado es el hecho de que en realidad no se puede ejecutar nunit3-console.execontra nada más que .dllarchivos.

Si planea ejecutar sus pruebas a través de TeamCity, esto planteará un problema.

Digamos que tiene un proyecto de aplicación de consola. Cuando compila, devuelve un ejecutable .exea la bincarpeta.

Desde allí nunit3-console.exeno podrá ejecutar ninguna prueba definida en esa aplicación de consola.

En otras palabras, una aplicación de consola devuelve un exearchivo y una biblioteca de clase devuelve dllarchivos.

Hoy acabo de morder esto y apesta :(

Cañón Kolob
fuente
0

Recientemente he estado implementando en Docker. No veo el valor de tener un proyecto separado cuando Dockerfile puede copiar fácilmente el directorio / src y abandonar el directorio / test. Pero soy nuevo en dotnet y podría faltar algo.

MikeF
fuente
-2

Proyectos separados, aunque debate conmigo mismo si deberían compartir el mismo svn. Por el momento, les estoy dando repositorios svn separados, uno llamado

"MyProject" - para el proyecto en sí

y uno llamado

"MyProjectTests": para las pruebas asociadas con MyProject.

Esto es bastante limpio y tiene la ventaja de que el compromiso con el proyecto y el compromiso con las pruebas están bastante separados. También significa que puede entregar el svn del proyecto si es necesario, sin tener que liberar sus pruebas. También significa que puede tener directorios de sucursales / troncales / etiquetas para sus pruebas y para su proyecto.

Pero estoy cada vez más inclinado a tener algo como lo siguiente, en un único repositorio svn, para cada proyecto.

Mi proyecto
| \ Tronco
El | | \ Code
El | \ Pruebas
| \ Etiquetas
El | | \ 0.1
El | El | | \ Code
El | El | \ Pruebas
El | \ 0.2
El | | \ Code
El | \ Pruebas
\ Ramas
  \ MyFork
   | \ Code
    \Prueba

Me interesaría saber qué piensan otras personas sobre esta solución.

sampablokuper
fuente
55
No debería necesitar confirmaciones separadas para la prueba y el código de la aplicación. Deberían ir de la mano y los commits involucrarían a ambos, especialmente si se usa el diseño de estilo * DD.
eddiegroves 03 de