【问题标题】:Mocking a simple class with Microsoft Moles使用 Microsoft Moles 模拟一个简单的类
【发布时间】:2012-06-12 13:51:54
【问题描述】:

我对单元测试、TDD 和 mocking 很陌生,但是我理解一般概念。我的问题是如何模拟一个类,以便我可以调用实例化的方法,而不会在我的单元测试和实现类中重复代码?鉴于以下情况:

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

我希望能够在我的单元测试中调用 MarylandProductSimpleProduct 的计算成本方法。通常从数据库中获取价格和税金,但我已经这样做了,以便对这些值进行存根处理,以避免与数据库或服务或提供这些值的其他任何东西耦合。归根结底是我想编写一个单元测试来测试calculateCost() 的功能,而不必在单元测试中存根该方法,因为我知道在两年内MarylandProduct 中的逻辑会改变。

因此,例如,一旦我运行了这个测试,我应该能够进入并更改MarylandProduct.calculateCost() 的代码,以添加“奢侈税”,即在超过 750 的任何价格上加 50。如果我这样做,我知道我的单元测试将失败,因为预期值为 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);
    }

我想避免在单元测试和类的实现中放置重复的代码,因为如果类中的代码发生更改,那么单元测试仍然会通过,因为它没有被更改。我知道预计会在单元测试中做出这种改变,但是当你有大量的单元测试时,这种设计是否可以接受?在您的单元测试和实现中存在重复代码?

【问题讨论】:

  • 如果被测方法的行为发生变化,您的测试也必须如此。如果 calculatecost 的输出发生变化,那么很自然地期望你的断言会失败,因为它不再准确,因此你将不得不更新你的测试。你不能把你的蛋糕也吃掉
  • 我看不出有什么理由不使用普通的存根或模拟而不是使用 Moles?
  • @jflood.net 是的,但是我想避免重复代码(单元测试中的 1 个,实现中的 1 个)只是因为我在 BO 中更新了它,单元测试仍然会通过,因为代码没有在单元测试中没有改变,它仍然会通过。这是我想要避免的。
  • @Tim Mahy,你能举个例子让我接受吗?我也有针对 rhinoMock 和 Moq 的单元测试,我将更新问题
  • 所以你是说测试甚至没有执行业务对象逻辑?只是它的副本?首先,你的 bizzy 对象不应该依赖于 db。查找存储库模式。您的域应该是持久的无知。这样您就不需要存根获取价格和税收。

标签: c# unit-testing moq rhino-mocks moles


【解决方案1】:

好吧,您似乎误解了 Mock 的目的。模拟用于隔离被测系统 (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.
    }
}

这个对象已经被隔离了,没有依赖,所以不需要mock。

要对其进行测试,您只需直接使用它:

[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/

【讨论】:

  • 我相信您回答了我的问题的性质,听起来好像我的做法是错误的,我将接受阅读单元测试艺术的建议并确保我理解正确。再次感谢您的意见。
猜你喜欢
  • 1970-01-01
  • 2023-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多