2012-01-10 15 views
10

Este es mi primer intento de hacer pruebas unitarias, así que tenga paciencia conmigo.
I'm still trying to unit test a library that converts lists of POCOs to ADO.Recordsets.¿Cómo puedo evitar múltiples afirmaciones en esta prueba unitaria?

En este momento, intento escribir una prueba que crea un List<Poco>, la convierte en un Recordset (usando el método que quiero probar) y luego comprueba si contienen la misma información (como, Poco.Foo == RS.Foo, y pronto...).

Esta es la POCO:

public class TestPoco 
{ 
    public string StringValue { get; set; } 
    public int Int32Value { get; set; } 
    public bool BoolValue { get; set; } 
} 

... y esta es la prueba hasta el momento (estoy usando xUnit.net):

[Fact] 
public void TheTest() 
{ 
    var input = new List<TestPoco>(); 
    input.Add(new TestPoco { BoolValue = true, Int32Value = 1, StringValue = "foo" }); 

    var actual = input.ToRecordset(); 

    Assert.Equal(actual.BoolValue, true); 
    Assert.Equal(actual.Int32Value, 1); 
    Assert.Equal(actual.StringValue, "foo"); 
} 

Lo que no gusta de estos son los tres asertos al final, uno por propiedad del POCO.
He leído muchas veces que las afirmaciones múltiples en una prueba son malas (y entiendo los motivos, y estoy de acuerdo).

El problema es, ¿cómo puedo deshacerme de ellos?

tengo excelente libro de Roy "The Art of Unit Testing" Osherove justo en frente de mí, y él tiene un ejemplo que cubre exactamente este (para los que tienen el libro: el capítulo 7.2.6, página 202/203):

En su ejemplo, el método bajo prueba devuelve un objeto AnalyzedOutput con varias propiedades, y quiere afirmar todas las propiedades para verificar si cada una contiene el valor esperado.

La solución en este caso:
crear otro AnalyzedOutput ejemplo, llenarla con los valores esperados y afirmar si es igual a la devuelta por el método bajo prueba (y anulan Equals() para ser capaz de hacer esto).

Pero creo que no puedo hacer esto en mi caso, porque el método que deseo probar arroja un ADODB.Recordset.

Y con el fin de crear otro Recordset con los valores esperados, yo primero tendrá que crearlo desde cero:

// this probably doesn't actually compile, the actual conversion method 
// doesn't exist yet and this is just to show the idea 

var expected = new ADODB.RecordsetClass(); 
expected.Fields.Append("BoolValue", ADODB.DataTypeEnum.adBoolean); 
expected.Fields.Append("Int32Value", ADODB.DataTypeEnum.adInteger); 
expected.Fields.Append("StringValue", ADODB.DataTypeEnum.adVarWChar); 

expected.AddNew(); 
expected.BoolValue = true; 
expected.Int32Value = 1; 
expected.StringValue = "foo"; 
expected.Update(); 

No me gusta este tampoco, porque esto es básicamente una duplicación de algunas del código en el método de conversión real (el método bajo prueba), que es otra cosa para evitar en las pruebas.

Entonces ... ¿qué puedo hacer ahora?
¿Este nivel de duplicación sigue siendo aceptable en esta situación especial o existe una forma mejor de probarlo?

+8

es por eso que es el "arte" de las pruebas unitarias y no la "ciencia" ... – Berryl

+0

Múltiples afirmaciones en un caso de prueba no son malas. La idea de que cada caso de prueba debe tener solo una afirmación [es tonto] (http://stackoverflow.com/a/20300843/545127). – Raedwald

Respuesta

8

Yo diría que en el espíritu de la cosa, esto está bien. La razón por la que las afirmaciones múltiples son "malas", si no recuerdo mal, es que implica que estás probando varias cosas en una prueba. En este caso, de hecho lo estás haciendo porque estás probando cada campo, presumiblemente para asegurarte de que esto funcione para varios tipos diferentes. Como eso es todo lo que haría una prueba de igualdad de objetos de todos modos, creo que estás en el claro.

Si realmente quería ser militante de ello, escribir un ensayo por propiedad (j/k!)

6

Las múltiples afirmaciones por prueba unitaria están perfectamente bien en mi libro, siempre que las múltiples aserciones confirmen la misma condición de prueba. En su caso, están probando que la conversión fue exitosa, por lo que la aprobación de la prueba depende de que todas esas afirmaciones sean verdaderas. Como resultado, ¡está perfectamente bien!

Clasificaría "una afirmación por prueba" como una guía, no una regla difícil. Cuando no lo tenga en cuenta, considere por qué lo está ignorando.

Dicho esto, una forma de evitarlo es crear una única clase de prueba que, en la configuración de la clase, ejecute su proceso de prueba. Entonces cada prueba es solo una afirmación en una sola propiedad. Por ejemplo:

public class ClassWithProperities 
{ 
    public string Foo { get; set; } 
    public int Bar { get; set; } 
} 

public static class Converter 
{ 
    public static ClassWithProperities Convert(string foo, int bar) 
    { 
     return new ClassWithProperities {Foo=foo, Bar=bar}; 
    } 
} 
[TestClass] 
public class PropertyTestsWhenFooIsTestAndBarIsOne 
{ 
    private static ClassWithProperities classWithProperties; 

    [ClassInitialize] 
    public static void ClassInit(TestContext testContext) 
    { 
     //Arrange 
     string foo = "test"; 
     int bar = 1; 
     //Act 
     classWithProperties = Converter.Convert(foo, bar); 
     //Assert 
    } 

    [TestMethod] 
    public void AssertFooIsTest() 
    { 
     Assert.AreEqual("test", classWithProperties.Foo); 
    } 

    [TestMethod] 
    public void AssertBarIsOne() 
    { 
     Assert.AreEqual(1, classWithProperties.Bar); 
    } 
} 

[TestClass] 
public class PropertyTestsWhenFooIsXyzAndBarIsTwoThousand 
{ 
    private static ClassWithProperities classWithProperties; 

    [ClassInitialize] 
    public static void ClassInit(TestContext testContext) 
    { 
     //Arrange 
     string foo = "Xyz"; 
     int bar = 2000; 
     //Act 
     classWithProperties = Converter.Convert(foo, bar); 
     //Assert 
    } 

    [TestMethod] 
    public void AssertFooIsXyz() 
    { 
     Assert.AreEqual("Xyz", classWithProperties.Foo); 
    } 

    [TestMethod] 
    public void AssertBarIsTwoThousand() 
    { 
     Assert.AreEqual(2000, classWithProperties.Bar); 
    } 
} 
+0

¡Realmente me gusta su enfoque! – TuomasK

2

Los 3 afirma son válidos. Si ha utilizado un marco más como mspec, que se vería así:

public class When_converting_a_TestPoco_to_Recordset 
{ 
    protected static List<TestPoco> inputs; 
    protected static Recordset actual; 

    Establish context =() => inputs = new List<TestPoco> { new TestPoco { /* set values */ } }; 

    Because of =() => actual = input.ToRecordset(); 

    It should_have_copied_the_bool_value =() => actual.BoolValue.ShouldBeTrue(); 
    It should_have_copied_the_int_value =() => actual.Int32Value.ShouldBe (1); 
    It should_have_copied_the_String_value =() => actual.StringValue.ShouldBe ("foo"); 
} 

generalmente uso mspec como punto de referencia para ver si mis pruebas tienen sentido. Tus pruebas se leen muy bien con mspec, y eso me da algunas confusiones calientes semiautomatizadas de que estoy probando las cosas correctas.

De hecho, has hecho un mejor trabajo con múltiples afirmaciones. Odio ver las pruebas que se parecen:

Assert.That (actual.BoolValue == true && actual.Int32Value == 1 && actual.StringValue == "foo"); 

Porque cuando eso falla, el mensaje de error "se espera cierto que consiguió Falso" es completamente inútil. Las afirmaciones múltiples y el uso del marco de pruebas unitarias tanto como sea posible te ayudarán mucho.

3

Estoy de acuerdo con todos los demás comentarios de que está bien hacerlo, si está lógicamente probando una cosa.

Sin embargo, existe una diferencia entre tener muchas aserciones en una prueba de unidad individual que tener una prueba de unidad separada para cada propiedad. Lo llamo 'Blocking Assertions' (Posiblemente un mejor nombre por ahí). Si tiene muchas afirmaciones en una prueba, solo sabrá sobre una falla en la primera propiedad que falló la afirmación. Si tiene, por ejemplo, 10 propiedades y 5 de ellas arrojaron resultados incorrectos, deberá corregir la primera, volver a ejecutar la prueba y observar que otra falló, luego corregirla, etc.

Dependiendo de cómo se mire en esto esto podría ser bastante frustrante. Por otro lado, tener 5 pruebas de unidades simples que fallan de repente también podría ser desalentador, pero podría darle una idea más clara de lo que ha causado que fallen todas de una vez y posiblemente lo dirija más rápidamente a una solución conocida (tal vez).

Yo diría que si necesita probar varias propiedades, mantenga el número bajo (posiblemente por debajo de 5) para evitar que el problema de aserción de bloqueo se salga de control. Si hay un montón de propiedades para probar, quizás sea una señal de que su modelo representa demasiado o quizás pueda ver las propiedades de agrupación en múltiples pruebas.

Cuestiones relacionadas