2009-10-22 22 views
8

que tengo varios unittests de este patrón:cobertura de código vs ExpectedException

[TestMethod()] 
[ExpectedException (typeof (ArgumentNullException))] 
public void DoStuffTest_Exception() 
{ 
    var foo = new Foo(); 
    Foo.DoStuff (null); 
} 

Resulta que la cobertura de código markes la línea de lanzamiento como medio de gestión, por lo que obtener 1 bloque de código al descubierto cada vez.

Después de pensar en este problema por un tiempo, la mejor solución que se me ocurrió fue agregar un try/catch. Dado que este es un patrón repetido, voy a crear un método de ayuda a lo largo de las líneas de

public static void ExpectException<_T> (Action action) where _T: Exception 
{ 
    try { action(); } 
    catch (_T) { return; } 
    Assert.Fail ("Expected " + _T); 
} 

Esto tendría el beneficio adicional agradable que podría agregar todas las pruebas de excepción a las pruebas no tirar.

¿Es este un diseño válido, o me perdí algo?

Editar: Ugs ... parece que el método anterior ExpectException me deja con 1 bloque descubierto también.

Respuesta

10

Lo que estás sugiriendo es válido. Aparte del problema de cobertura del código, yo diría que es mejor que usar el atributo ExpectedException, ya que muestra explícitamente qué línea de la prueba arrojará la excepción. El uso de ExpectedException significa que cualquier línea de código en la prueba puede arrojar el tipo de excepción esperada y la prueba aún pasará. Si el error proviene de otra llamada que no se esperaba que arrojara, puede ocultar el hecho de que la prueba debería estar fallando porque la línea que debería lanzar no lo está.

lo que sería una modificación útil a lo que han propuesto sería devolver la excepción capturada:

public static _T ExpectException<_T> (Action action) where _T: Exception 
{ 
    try { action(); } 
    catch (_T ex) { return ex; } 
    Assert.Fail ("Expected " + typeof(_T)); 
    return null; 
} 

Esto permitiría que el código de prueba para afirmar aún más la excepción si se desea (es decir, para comprobar una. mensaje particular fue utilizado).

NUnit (aunque no parezca que lo está utilizando como usted tiene un atributo TestMethod) tiene un sistema incorporado en la construcción similar a lo que se ha propuesto:

Assert.Throws<ArgumentNullException>(() => Foo.DoStuff(null)) 
+0

+1. Buen material. Esto me ha estado molestando por un tiempo, pero nunca llegó a arreglarlo. – magnus

0

Sí, esta es una tarifa bastante estándar: muchas de nuestras pruebas hacen lo mismo. Al mismo tiempo, debe preguntarse si no está otorgando un valor demasiado alto a la cobertura del código si esas medias ramas pesan tanto que vale la pena el esfuerzo.

+0

Actualmente con una horrenda cobertura del 35%, por lo que esto no agrega mucho. Es más un problema de diseño menor (que podría pasar para guardar unas pocas líneas de código de prueba). – mafu

3

@adrianbanks la ExpectException no lo hace trabajar como se esperaba si el parámetro de acción lanza otra excepción que la excepción esperada:

[TestMethod] 
public void my_test() 
{ 
    ExpectException<InvalidOperationException>(delegate() 
    { 
     throw new ArgumentException("hello"); 
    }); 
} 

Cuando ejecuto el TestMethod "my_test" Acabo de recibir un mensaje diciendo que el método de ensayo y levantó System.ArgumentException: hola. En este caso, debería decir "Expected InvalidOperationException". propongo una nueva versión para el método ExpectException:

public static void VerifierException<T>(Action action) where T : Exception 
{ 
    try 
    { 
     action(); 
    } 
    catch (Exception ex) 
    { 
     Assert.IsInstanceOfType(ex, typeof(T)); 
     return; 
    } 

    Assert.Fail("Aucune exception n'a été déclenchée alors qu'une exception du type " + typeof(T).FullName + " était attendue"); 
} 
2

Sé que esto es un viejo tema, pero me encontré con el mismo problema.

Eventualmente me pregunté a mí mismo: ¿por qué necesito saber la cobertura de las pruebas? ¡Yo no! - Vamos a descartarlos, por lo que la cobertura es más clara.

En mi proyecto de prueba He añadido un archivo CodeCoverage.runsettings y este es el contenido:

<?xml version="1.0" encoding="utf-8" ?> 
<RunSettings> 
    <DataCollectionRunSettings> 
    <DataCollectors> 
     <DataCollector friendlyName="Code Coverage" uri="datacollector://Microsoft/CodeCoverage/2.0" assemblyQualifiedName="Microsoft.VisualStudio.Coverage.DynamicCoverageDataCollector, Microsoft.VisualStudio.TraceCollector, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> 
     <Configuration> 
      <CodeCoverage> 
      <ModulePaths> 
       <Exclude> 
       <ModulePath>.*tests.dll</ModulePath> 
       <ModulePath>.*Tests.dll</ModulePath> 
       <!-- Add more ModulePath nodes here. --> 
       </Exclude> 
      </ModulePaths> 
      </CodeCoverage> 
     </Configuration> 
     </DataCollector> 
    </DataCollectors> 
    </DataCollectionRunSettings> 
</RunSettings> 

Después de seleccionar esta Configuración de pruebas de mi archivo de cobertura de código es 100%

De esta manera no es no es necesario 'piratear' el sistema de cobertura del código de la unidad de prueba, solo para lograr el 100% :-)

+0

¡Gracias por esta idea! No puedo probarlo actualmente, pero parece una muy buena solución, si solo te aseguras de que las pruebas sean correctas. – mafu

Cuestiones relacionadas