Дразнить простой класс с Microsoft Moles

Я новичок в модульном тестировании и TDD, а также в издевательствах в целом, однако общую идею я понимаю. Мой вопрос состоит в том, как я могу издеваться над классом, чтобы я мог вызывать созданный экземпляр метода, не имея дублированного кода в моем модульном тесте и в моем классе реализации. Учитывая следующее:

//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;
        }
    }
}

Я начал писать модульный тест, который имеет следующее:

[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);
}

Я хочу иметь возможность вызвать метод расчета стоимости для любого MarylandProduct или SimpleProduct в моем модульном тесте. Обычно получение цены и налога происходит из базы данных, но вместо этого я сделал так, чтобы эти значения были заглушены, чтобы избежать какой-либо связи с базой данных или услугой или чем-то еще, что обеспечивает эти значения. То, что сводится к тому, что я хочу написать модульный тест, который будет проверять функциональность calculateCost() без необходимости заглушить этот метод в модульном тесте, потому что я знаю, что через 2 года логика в MarylandProduct изменится.

Так, например, когда у меня будет запущен этот тест, я смогу войти и изменить код для MarylandProduct.calculateCost() добавить "налог на роскошь", добавив 50 к любой цене, скажем, 750. Если я это сделаю, я знаю, что мой модульный тест не пройден, потому что ожидаемое значение составляет 1052, и теперь MarylandProduct возвращает что-то другое, чем то, что ожидается.

Я просто поступаю об этом неправильно? Я просто скучаю по духу TDD? Спасибо за любую помощь.

РЕДАКТИРОВАТЬ: (добавив в другие насмешливые рамки, которые я пробовал)

[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);
    }

Я хочу не помещать повторяющийся код в модульный тест и в реализацию класса, потому что, если код в классе изменится, модульный тест все равно пройдет, потому что он не был изменен. Я знаю, что это должно измениться в модульном тесте, однако, если у вас есть ТОННО модульных тестов, допустимо ли иметь такой дизайн? Наличие дублированного кода в вашем модульном тесте и в вашей реализации?

1 ответ

Решение

Итак, то, что вы, кажется, неправильно понимаете, является целью насмешки. Макет используется для изоляции тестируемой системы (SUT). Поэтому вы имитируете зависимости SUT, чтобы вы могли тестировать SUT изолированно (например, удалить зависимость от БД или какой-либо службы). Вы не издеваетесь над объектом, который тестируете, иначе, что вы тестируете?

Если я правильно понимаю ваш вопрос, в вашей доменной модели есть реализация IProduct (вопрос о том, почему вам нужны разные имплики продукта, - другой вопрос).

Вы хотите протестировать метод CalculateCost этой реализации. Я не вижу причин, по которым вам нужно использовать макеты для этого. На первый взгляд, у Product не должно быть никаких зависимостей, поэтому здесь нечего высмеивать.

например

Допустим, у вас есть этот продукт в вашем домене:

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.
    }
}

Этот объект уже изолирован, у него нет зависимости, поэтому нет необходимости издеваться.

Чтобы проверить это, вы просто использовали бы его напрямую:

[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 );
}

Рекомендуемое чтение: http://www.manning.com/osherove/

Другие вопросы по тегам