代码之家  ›  专栏  ›  技术社区  ›  anonymous

TDD:存根、模拟或以上任何一项

  •  4
  • anonymous  · 技术社区  · 17 年前

    我试图通过将TDD应用于我的一个简单项目来学习它。以下是一些细节(以及前面的问题):

    TDD: Help with writing Testable Class

    具体来说,我有一个PurchaseOrderCollection类,它有一个PurchaseOrders的私有列表(在构造函数中传递),PurchaseOrders有一个布尔属性IsValid。PurchaseOrderCollection有一个属性HasErrors,如果列表中的任何PurchaseOrders的IsValid为false,该属性将返回true。这就是我想测试的逻辑。

    [TestMethod]
    public void Purchase_Order_Collection_Has_Errors_Is_True_If_Any_Purchase_Order_Has_Is_Valid_False()
    {
        List<PurchaseOrder> orders = new List<PurchaseOrder>();
    
        orders.Add(new PurchaseOrder(--some values to generate IsValid false--));
        orders.Add(new PurchaseOrder(--some values to generate IsValid true--));
    
        PurchaseOrderCollection collection = new PurchaseOrderCollection(orders);
    
        Assert.IsTrue(collection.HasErrors);
    }
    

    这与我之前的问题类似,因为这个测试过于耦合,我必须知道是什么使得PurchaseOrder为有效假或为真的逻辑才能通过测试,而实际上这个测试不应该在意。问题不同(imo),因为类本身不是问题所在。

    本质上,我希望能够声明一个PurchaseOrder,它的IsValid为false或true,而不需要知道PurchaseOrder是什么。

    根据我有限的TDD知识,这是您使用存根或模拟来实现的。我的主要问题是,这是正确的吗?或者我应该使用不同的方法来解决这个问题?还是我完全有缺陷,只是写了这个测试,并认为它是错误的?

    我最初的想法是使用某种模拟框架,创建一个总是返回true或false的PurchaseOrder。根据我读到的,我需要声明IsValid-virtual。因此,我的第二个想法是将其更改为添加IPurchaseOrder作为PurchaseOrder的接口,并创建一个始终返回false或true的伪PurchaseOrder。这两个想法都有效吗?

    谢谢

    7 回复  |  直到 8 年前
        1
  •  6
  •   Dale Ragan    17 年前

    无论是创建存根还是模拟,您都走在了正确的轨道上。我更喜欢使用模拟框架。

    使用模拟框架的工作原理是,您希望模拟PurchaseOrder类,从而抽象出它的实现。然后,设置调用IsValid的期望值,并在调用时返回此值。

    Moq ,如果您使用的是C#3.0和.NET Framework 3.5:

    [TestMethod]
    public void Purchase_Order_Collection_Has_Errors_Is_True_If_Any_Purchase_Order_Has_Is_Valid_False()
    {    
        var mockFirstPurchaseOrder = new Mock<IPurchaseOrder>();
        var mockSecondPurchaseOrder = new Mock<IPurchaseOrder>();
    
        mockFirstPurchaseOrder.Expect(p => p.IsValid).Returns(false).AtMostOnce();
        mockSecondPurchaseOrder.Expect(p => p.IsValid).Returns(true).AtMostOnce();
    
        List<IPurchaseOrder> purchaseOrders = new List<IPurchaseOrder>();
        purchaseOrders.Add(mockFirstPurchaseOrder.Object);
        purchaseOrders.Add(mockSecondPurchaseOrder.Object);
    
        PurchaseOrderCollection collection = new PurchaseOrderCollection(orders);
    
        Assert.IsTrue(collection.HasErrors);
    }
    

    编辑:
    在这里,我使用了一个接口来创建PurchaseOrder的模拟,但您没有。您可以将IsValid标记为virtual并模拟PurchaseOrder类。我的经验法则是什么时候首先使用虚拟机。仅仅是创建一个接口,这样我就可以模拟一个对象而不需要任何架构上的原因,这对我来说是一种代码味道。

        2
  •  2
  •   Jacob Mattison    17 年前

    必须知道是什么构成了一个 PurchaseOrder对有效false或true 通过测试,当真的通过了这个测试 我不应该在乎。。。

    事实上,我的观点正好相反——如果您的测试知道有效性在采购订单中被建模为布尔值,则意味着您的测试对PurchaseOrder的实现了解得太多(假设它实际上是对PurchaseOrderCollection的测试)。我使用真实世界的知识(即有效或无效的实际值)来创建适当的测试对象没有问题。最终,这才是您真正要测试的(如果我给我的收藏提供了一个带有荒谬值的采购订单,它是否会正确地告诉我有错误)。

    当测试表明您的生产代码可以设计得更好时,这非常好。不过,更改生产代码并不太好 只是 使测试成为可能。

    好像我写的还不够多,这里有另一个建议——这就是我在现实生活中解决这个问题的方法。

    创建具有接口的PurchaseOrderValidityChecker。设置isValid布尔值时使用该选项。现在创建有效性检查器的测试版本,允许您指定要给出的答案。(请注意,此解决方案可能还需要一个PurchaseOrderFactory或等效工具来创建PurchaseOrders,以便在创建每个采购订单时,都可以为其提供对PurchaseOrderValidityChecker的引用。)

        3
  •  1
  •   Community Mohan Dere    8 年前

    我最近问了一个问题 similar question

    最重要的是,您可以从测试PurchaseOrderCollection的行为中获得更多,而不仅仅是测试PurchaseOrderCollection的状态。在您的测试验证PurchaseOrderCollection可以进入不同的状态后,更重要的测试是行为测试。通过您认为合适的任何方式(在所需状态下模拟或更新具体类),将采购订单集合同时置于有效和无效状态,并测试PurchaseOrderCollection的每个状态的逻辑是否正确执行,而不仅仅是PurchaseOrderCollection是否处于有效/无效状态。

    PurchaseOrderCollection将始终依赖于另一个类,因为它是一个专门的集合。知道IPurchaseOrder具有IsValid属性与知道具体的PurchaseOrder具有IsValid属性没有任何区别。我会坚持使用最简单的方法,例如一个具体的采购订单,除非你有足够的理由相信你的系统中会有多种类型的采购订单。此时,PurchaseOrder接口将更有意义。

        4
  •  1
  •   Steven A. Lowe    17 年前

    我可能遗漏了一些上下文,但在我看来你 必须 你不是在测试什么 (IsValid属性除外,它是平凡的)。

    使用存根-同样的事情

    使用TDD时,如果不是强制性的,也可以进行白盒测试

        5
  •  1
  •   Steve Freeman    16 年前

    PurchaseOrder ,这就是你努力的方向。这取决于有多复杂 是如果它是一个具有明显行为的简单实体,那么仅仅创建实例可能是有意义的。如果它更复杂,那么按照您的描述提取接口是有意义的。

    提出的下一个问题是该接口中有什么。集合中的对象需要执行什么角色?也许你只需要知道它们是否有效,在这种情况下你可以提取 IValidatable 并缩小代码中的依赖关系。我不知道在这种情况下什么是正确的,但我经常发现我可以使用接口将我推向更集中的代码。

        6
  •  0
  •   BFree    17 年前

    我不是单元测试方面的专家,但这里是我过去所做的。如果您有一个PurchaseOrder类可以是有效/无效的,那么我相信您也有针对这些类的单元测试,以查看它们是否确实有效。为什么不调用这些方法来生成有效和无效的PurchaseOrder对象,然后将其添加到集合中?

        7
  •  0
  •   Jeffrey Fredrick    17 年前

    这两个想法都有效吗?

    您还可以创建一个对象母亲,它可以返回有效和无效的PurchaseOrders。