Simulando una clase simple con Microsoft Moles

Soy nuevo en pruebas unitarias y TDD y burlas en general, sin embargo, entiendo la idea general. Mi pregunta es ¿cómo me burlo de una clase para poder llamar al método instanciado sin tener un código duplicado en mi prueba de unidad y en mi clase de implementación? Dado lo siguiente:

//Simple Interface
public interface IProduct {
    double price { get; set; }
    double tax { get; set; }
    double calculateCost();
}

//Simple Implementation of IProduct
public class SimpleProduct : IProduct {
    private double _price;
    private double _tax;

    public double price {
        get { return _price; }
        set { _price = value; }
    }

    public double tax {
        get { return _tax; }
        set { _tax = value; }
    }

    public double calculateCost() {
        return _price + (_price * _tax);
    }
}
//Complex implementation of IProduct
public class MarylandProduct : IProduct {
    private double _price;
    private double _tax;

    public double price {
        get { return _price; }
        set { _price = value; }
    }

    public double tax {
        get { return _tax; }
        set { _tax = value; }
    }

    public double calculateCost() {
        if (_price <= 100) return _price + (_price * _tax);
        else {
            double returnValue = 100 + (100 * _tax); //use tax rate for first 100
            returnValue += (_price - 100) + ((_price - 100) * 0.05); //use a flat rate of 0.05 for everything over 100
            return returnValue;
        }
    }
}

He comenzado a escribir una prueba unitaria que tiene lo siguiente:

[TestMethod]
[HostType("Moles")]
public void molesCalculateCostforMarylandProduct() {
    //Assign
    MMarylandProduct marylandProduct = new MMarylandProduct();
    marylandProduct.priceGet = () => 1000;
    marylandProduct.taxGet = () => 0.07;

    const double EXPECTED = 1052;

    //Act
    double actual = marylandProduct.Instance.calculateCost();

    //Assert
    Assert.AreEqual(EXPECTED, actual);
}

Quiero poder llamar al método de cálculo de costos para un MarylandProduct o con una SimpleProduct en mi prueba unitaria. Por lo general, obtener el precio y el impuesto proviene de la base de datos, pero en lugar de eso, lo he hecho para que estos valores se bloqueen para evitar cualquier acoplamiento con una base de datos o servicio o cualquier otra cosa que proporcione estos valores. Todo se reduce a que quiero escribir una prueba unitaria que probará la funcionalidad de calculateCost() sin tener que stub ese método en la prueba unitaria porque sé que en 2 años la lógica en el MarylandProduct cambiará.

Entonces, por ejemplo, una vez que tenga esta prueba en ejecución, debería poder ingresar y cambiar el código para MarylandProduct.calculateCost() para agregar un "impuesto de lujo" de agregar 50 a cualquier precio por encima de 750. Si hago esto, sé que mi prueba unitaria fallará porque el valor esperado es 1052 y ahora el MarylandProduct está devolviendo algo diferente de lo esperado.

¿Estoy haciendo esto de la manera incorrecta? ¿Me estoy perdiendo el espíritu de TDD? Gracias por cualquier ayuda.

EDITAR: (agregando otros marcos de burla que he probado)

[TestMethod]
    public void rhinoMockCalculateCostForMarylandProduct() {
        //assign
        IProduct marylandProduct = MockRepository.GenerateMock<IProduct>();
        marylandProduct.Stub(price => price.price).Return(1000);
        marylandProduct.Stub(tax => tax.tax).Return(0.07);

        const double EXPECTED = 1052;

        //act
        double actual = marylandProduct.calculateCost();

        //assert
        Assert.AreEqual(EXPECTED, actual);
    }

    [TestMethod]
    public void moqCalculateCostForMarylandProduct() {
        //assign
        var marylandProduct = new Mock<IProduct>();
        marylandProduct.Setup(price => price.price).Returns(1000);
        marylandProduct.Setup(tax => tax.tax).Returns(0.07);

        const double EXPECTED = 1052;

        //act
        double actual = ((MarylandProduct)marylandProduct.Object).calculateCost();

        //assert
        Assert.AreEqual(EXPECTED, actual);
    }

Quiero evitar poner código duplicado en la prueba unitaria y en la implementación de la clase porque si el código en la clase cambia, la prueba unitaria aún pasará porque no se ha cambiado. Sé que se espera que haga ese cambio en la prueba unitaria; sin embargo, cuando tiene una TONELADA de pruebas unitarias, ¿es aceptable tener este tipo de diseño? ¿Tiene código duplicado en su prueba de unidad y en su implementación?

preguntado el 12 de junio de 12 a las 14:06

Si el comportamiento del método bajo prueba cambia, también debe hacerlo su prueba. si el resultado de calcular el costo cambia, entonces es natural esperar que su afirmación falle, ya que ya no es precisa y, por lo tanto, tendrá que actualizar su prueba. No puedes tener tu pastel y comértelo también.

No veo ninguna razón por la que no usar stubbing o burlas normales en lugar de usar lunares. -

@jflood.net sí, sin embargo, quiero evitar el código duplicado (1 en la prueba unitaria y 1 en la implementación) solo porque lo actualicé en el BO, la prueba unitaria aún pasará porque el código no ha cambiado en la prueba unitaria, y todavía pasará. Esto es lo que quiero evitar. -

@Tim Mahy, ¿podría darme un ejemplo para que pueda aceptarlo? También tengo pruebas unitarias para rhinoMock y Moq con las que actualizaré la pregunta:

Entonces, ¿está diciendo que la prueba ni siquiera está ejecutando la lógica del objeto comercial? ¿Sólo un duplicado de él? En primer lugar, tu objeto bizzy no debería tener dependencias en db. Busque el patrón del repositorio. Su dominio debe ser ignorante persistente. Entonces no necesitará obtener el talón para obtener el precio y obtener impuestos. -

1 Respuestas

Bien, lo que parece estar malinterpretando es el propósito de un simulacro. Se utiliza un simulacro para aislar el sistema bajo prueba (SUT). Por lo tanto, se burla de las dependencias del SUT, de modo que puede probar el SUT de forma aislada (por ejemplo, eliminar la dependencia en la base de datos o algún servicio). No te burlas del objeto que estás probando, de lo contrario, ¿qué estás probando?

Si entiendo su pregunta correctamente, su modelo de dominio tiene una implementación de IProduct (por qué necesita diferentes impls de un Producto es otra pregunta).

Desea probar el método CalculateCost de esa implementación. No veo ninguna razón por la que necesites usar simulacros para esto. Desde la superficie, Product no debería tener ninguna dependencia, por lo tanto, no hay nada de lo que burlarse.

p.ej

Supongamos que tiene este producto en su dominio:

public class MyProduct : IProduct {
    private double _price;
    private double _tax;

    public double Price {
        get { return _price; }
        set { _price = value; }
    }

    public double Tax {
        get { return _tax; }
        set { _tax = value; }
    }

    public double CalculateCost() {
        return //some complex logic here.
    }
}

Este objeto ya está aislado, no tiene dependencia, por lo que no hay necesidad de burlarse.

Para probarlo, simplemente lo usaría directamente:

[TestMethod]
public void CalculateCost() {
    //arrange
    var myProduct = new MyProduct();
    myProduct.Price = 1000;
    myProduct.Tax= 0.07;

    //act
    double actualCost = myProduct.CalculateCost();

    //assert
    double expectedCost = 1052;

    Assert.AreEqual(expectedCost, actualCost );
}

Lectura recomendada: http://www.manning.com/osherove/

Respondido el 13 de junio de 12 a las 01:06

Creo que respondiste la naturaleza de mi pregunta y parece que estoy haciendo esto de manera incorrecta, voy a seguir el consejo de leer sobre el arte de las pruebas unitarias y asegurarme de que lo entiendo correctamente. Gracias de nuevo por la entrada. - szeliga

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.