Normas de codificación de prueba unitaria

22

Por lo general, cuando hablamos de estándares de codificación nos referimos al código del programa en sí, pero ¿qué pasa con las pruebas unitarias? ¿Existen ciertas pautas de estándares de codificación que son exclusivas de las pruebas unitarias? ¿Qué son?

EpsilonVector
fuente

Respuestas:

12

Fuera de mi cabeza, puedo pensar en tres diferencias en el estilo de codificación para el código de prueba.

Al nombrar los métodos de prueba, sigo el patrón de shouldDoSomethingWhenSomeConditionHolds.

Dentro de la prueba, se acostumbra seguir el siguiente patrón de espaciado:

@Test
shouldReturnAccountBalenceWhenGetBalenceIsCalled() {
    // Some lines 
    // of setup code
    // go here.

    // The action being tested happens after a blank line.

    // An assertion follows another blank line.
}

Algunos insisten en una sola afirmación por prueba, pero esto está lejos de ser universal.

El DRY (no se repita) es menos importante en el código de prueba que en el código de producción. Si bien algunos códigos repetidos deben colocarse en un método de configuración o en una clase testUtils, la lucha por la repetición cero en el código de prueba conducirá a pruebas estrechamente acopladas e inflexibles, lo que desalienta la refactorización.

Eric Wilson
fuente
Por supuesto, hay una variedad de patrones, por lo que también debe proporcionar una respuesta.
Eric Wilson el
10
Ese es el patrón Arrange, Act, Assert .
StuperUser
SECO sigue siendo importante. Si necesita hacer las mismas afirmaciones en varias pruebas, cree una función común y llámela en todas las pruebas.
MiFreidgeim SO-deja de ser malvado
@MichaelFreidgeim Tal vez solo estamos hablando de grados, pero tengo una tolerancia significativamente mayor para la repetición en el código de prueba. He tenido varias experiencias en la construcción de conjuntos de pruebas con muy poca repetición, y descubrí que las pruebas se volvieron difíciles de modificar y comprender cuando cambiaron los requisitos. Luego dejé de preocuparme tanto por DRY en las pruebas, y mis series de pruebas han sido más fáciles de usar. <shrug>
Eric Wilson
16

Roy Osherove recomienda el siguiente patrón para nombrar sus pruebas:

NameOfMethodUnderTest_StateUnderTest_ExpectedBehavior() 

Ver http://weblogs.asp.net/rosherove/archive/2005/04/03/TestNamingStandards.aspx

Robert Harvey
fuente
Estoy de acuerdo con Roy. Mejora la legibilidad, aunque ReSharper sigue diciéndome que debería eliminarlos paraNameOfMethodUnderTestStateUnderTestExpectedBehavior() ;)
Oscar Mederos
¿Cómo hacer que esto funcione cuando el método está sobrecargado, por lo que puede haber varios métodos con el mismo nombre?
Narendra Pathai
6

Lo principal es recordar que las pruebas unitarias son esencialmente mini especificaciones. Esto significa que el énfasis siempre debe estar en la legibilidad.

En primer lugar, esto significa que los nombres deben comunicar claramente lo que se está probando y lo que se afirma.

Sin embargo, en segundo lugar, lo que a veces se olvida, es que como especificaciones deberían estar haciendo exactamente eso: especificar el comportamiento. Es decir, las pruebas unitarias no deben contener lógica, o potencialmente caen en la trampa de repetir la funcionalidad del programa en lugar de probarlo.

A veces, las pruebas involucrarán objetos que son complejos de configurar, debe esforzarse por mantener esta lógica de configuración separada de sus pruebas utilizando algo como una madre de objeto o un generador de datos de prueba .

Terminaré con algunas recomendaciones de libros:

xUnit Test Patterns: Refactoring Test Code: Excelente libro, algunos dicen que es un poco seco, pero no lo creo. Entra en muchos detalles sobre muchas formas diferentes de organizar las pruebas y cómo mantenerlas mantenibles. Relevante si está usando algo como NUnit, etc.

El arte de las pruebas unitarias: con ejemplos en .Net : el mejor libro sobre el meollo de la escritura y el mantenimiento de las pruebas. A pesar de ser realmente nuevo, creo que las secciones de burla ya están un poco anticuadas, ya que la sintaxis AAA ahora es bastante estándar en lugar de simplemente otra forma de hacerlo.

Creciente software orientado a objetos, guiado por pruebas : ¡este libro es simplemente increíble! Con mucho, el mejor libro de pruebas unitarias y el único avanzado que coloca a las pruebas unitarias como un ciudadano de primera clase en el proceso de diseño. Estaba leyendo esto cuando era una versión beta pública y lo he estado recomendando desde entonces. Excelente ejemplo trabajado del mundo real utilizado en todo el libro. Sin embargo, recomendaría leer el libro de Roy primero.

FinnNk
fuente
En mi humilde opinión, está bien que las pruebas unitarias contengan lógica: es perfectamente razonable probar una versión altamente optimizada y eficiente de un algoritmo utilizando un algoritmo ingenuo que hace lo mismo para determinar el comportamiento correcto. Por ejemplo, imagine probar una tabla hash construyendo una matriz asociativa basada en búsqueda lineal.
dsimcha 31/10/10
2
Sí, pero eso pertenece fuera de la prueba en los creadores de datos de prueba (que deberían ser probados por unidad si la lógica dentro de ellos no es trivial). La excepción a esto sería bibliotecas de terceros que generalmente son "confiables" para ser correctas y podrían usarse sin pruebas.
FinnNk
3

No ponga lógica en sus pruebas unitarias. Por ejemplo, supongamos que está probando un método de agregar, podría tener algo como esto:

void MyTest_SaysHello()
{
   string name = "Bob";
   string expected = string.Format("Hello, {0}", name);
   IMyObjectType myObject = new MyObjectType();
   string actual = myObject.SayHello(name);
   Assert.AreEqual(expected, actual);
}

En este caso particular, es probable que esté repitiendo la misma lógica que lo que está en la prueba, por lo que esencialmente está probando "1 + 1 == 1 + 1", en lugar de "1 + 1 == 2", que es el prueba "real". Entonces, lo que realmente querría que se viera su código de prueba es:

void MyTest_SaysHello()
{
   string expected = "Hello, Bob";
   IMyObjectType myObject = new MyObjectType();
   string actual = myObject.SayHello("Bob");
   Assert.AreEqual(expected, actual);
}
Hugo
fuente
2
Pequeña corrección: creo que te refieres a 'cadena esperada = cadena.Formato ("Hola, Bob")' debería ser 'cadena esperada = "Hola, Bob"'.
Mike Rosenblum
@MikeRosenblum obviamente tienes razón, y alguien intentó corregirlo, pero dos revisores rechazaron esta edición
Konrad Morawski el
@ Konrad: eso es extraño. Este es un foro de programación, ¿verdad?
Mike Rosenblum
He editado la respuesta una vez más como lo sugiere Mike Rosenblum.
bdsl
0

Nombres largos y descriptivos de métodos. Recuerde, los métodos de prueba nunca se llaman desde el código (son llamados por el corredor de prueba de unidad que los descubre y los llama a través de la reflexión), por lo que está bien volverse loco y tener nombres de métodos de 50-80 caracteres de largo. La convención de nomenclatura específica (camello, guiones bajos, "debería", "debe", "cuándo", "dado", etc.) no es realmente importante siempre que el nombre responda tres preguntas:

  • ¿Qué está bajo prueba?
  • ¿Cuales son las condiciones?
  • ¿Cuál es el resultado esperado?

Los métodos de prueba deben ser cortos .

Los métodos de prueba deben tener una estructura lineal simple . No si o construcciones de bucle.

Los métodos de prueba deben seguir el patrón "organizar-actuar-afirmar" .

Cada prueba debe probar una cosa . Esto generalmente significa una afirmación por prueba. Una prueba como { Do A; Assert B; Assert C; }debería refactorizarse en dos: { Do A; Assert B; }y{ Do A; Assert C; }

Evite datos aleatorios o cosas como 'DateTime.Now'

Asegúrese de que todos los miembros del dispositivo de prueba vuelvan a su estado original al final de la prueba (p. Ej., Utilizando un desmontaje )

Incluso si elimina la duplicación sin piedad en su código de producción, la duplicación de código en los accesorios de prueba es una preocupación mucho menor.

azheglov
fuente
-1

Algo similar a lo que Farmboy ya ha mencionado, Formato de nombre de mi método

 <MethodName>Should<actionPerformed>When<Condition>

p.ej

 GetBalanceShouldReturnAccountBalance() {
Amit Wadhwa
fuente