【问题标题】:Thinking OO with TDD - Where to start?用 TDD 思考面向对象——从哪里开始?
【发布时间】:2009-06-25 11:12:50
【问题描述】:

我正在努力提高我的 TDD/OO 技能,每次我尝试使用 TDD 来影响设计时,我都会遇到从哪里开始的墙。

这是我的用例/故事:

确定需要审核的客户子集。开始为他们审核并发送几封信。

现在我的肌肉记忆已经打开了一个查询窗口,编写了查询,设计了一个 UI,然后我必须编写一些代码将这些位粘合在一起。

我希望域代码成为焦点,并且我希望它处于测试状态。

那么在这种情况下最简单的方法是什么?

我想我想要我的客户列表。我已经有一个客户端对象(CSLA 风格),尽管它有很多难以打破的依赖关系。我想我可以有一个 ClientReviewClients 对象并测试我是否获得了正确数量的评论。我需要考虑许多因素,因此看起来并不简单。无论如何,我如何嘲笑我在 20 位客户中有 10 条评论的事实?

有人可以帮我吗?

【问题讨论】:

    标签: c# oop tdd


    【解决方案1】:

    这里 - 我会先做几个测试:

    class IdentifyClientsDueForReview {
       public void CanStartSearch() {
          var s = new ClientSearcher();
       }
    
       public void CanSearchClients() {
          var s = new ClientSearcher();
          var r = s.Find(c => c.Id == 1);
          Assert.IsNotNull(r);
       }
    
       public void Finds10Clients() {
          var db = new MockDB();
          // Clients that need review
          for (int i = 0; i < 10; i++) {
             db.Add(new Client() { 
                NextReview = DateTime.Today.SubtractDays(i) 
             });
          }
          // Clients that don't need review
          for (int i = 0; i < 10; i++) {
             db.Add(new Client() { 
                NextReview = DateTime.Today.AddDays(i) 
             });
          }
    
          var s = new ClientSearcher(db);
          var r  = s.Find(c => c.NextReview <= DateTime.Today);
          Assert.AreEqual(10, r.Count);
       }
    }
    

    这是在考虑到 Linq To Sql 或类似的后端 ORM 构建的 - 否则,您可能会放弃 Find 方法并使用一些硬编码的 FindBy&lt;Criteria&gt; 方法。

    这应该给你ClientSearcher 类,它使用一个接口来访问数据库。 MockDB 和您的 RealDB 类都将实现该接口。

    【讨论】:

    • 那里有很多很棒的东西。搜索客户的类。我想我会把它放在一个像上帝一样的客户对象中。
    • CanStartSearch 里面没有 Assert 怎么办?
    • @John - 没什么。只需创建类即可获得第一个绿色条。
    【解决方案2】:

    我不熟悉 C#,所以我无法帮助您解决模拟问题,我想这取决于您的测试框架。

    虽然我确实做了很多 TDD,但通常我的方法是自上而下的方法。我首先想到的只是我想要编写的代码来做某事。假设在您的示例中,我有一个类 Client 并且希望能够执行以下操作:Client.initiate_reviews

    所以我编写了一个设置上下文的测试(几个客户,一些带有评论)。然后调用 Client.initiate_reviews,然后编写所有断言以确定它是否已完成其工作,即对于应审查的客户子集,现在是否正在进行审查并且是否已发送所有预期?

    根据一个方法有多少副作用,将其拆分为多个测试可能是明智的。

    然后我进入客户端类并定义方法并考虑我想在其中编写的代码。可能是这样的:

    clients = Client.find_all_due_for_review
    for_each client in clients {
        review = Review.start_new_for(client)
        Letter.send_for_review(review)
    }
    

    然后,我将为必须实现的在此方法中调用的方法编写测试。 find_all_due_for_review 没有副作用,但是会返回一些东西,所以当然你会在这里测试返回值,也许没有任何改变。 并重复,直到第一次测试成功。

    这样每个方面都得到了正确的测试,你甚至可以得到一些可以重复使用的方法。

    希望这会有所帮助!

    【讨论】:

    • 所以你的方法和 Mark Brackett 的不同之处在于你使用了一个 Client 对象。我会做什么,但这不违反单一责任原则吗?只是我们好奇那也是什么语言?
    • 嗯,我想这是真的,但我总是尽量保持模型简单,只包含域中的实体,所以在这种情况下是 Client 和 Review(可能还有字母)。然后适应它影响的类中的功能。考虑到这一点,我想启动评论方法应该在 Review 中,它从 Client 调用一个方法来收集到期的客户。但是在 Client 和 Review 中使用单独的类调用方法也可以谨慎地从关注点分离 p.o.v.特别是如果有更多相关的功能可以放在那里。
    • 关于语言:我是一名ruby程序员,但是我之前写的代码更多的是伪代码。在 ruby​​ 中,它更像是:Client.find_all_due_for_review.each do |client| Review.start_for(client) end 而且我可能会有发送信件作为开始审查引起的副作用,这取决于它背后的业务规则。
    【解决方案3】:

    单元测试必须很快。如果测试涉及数据库,那么它就是集成测试(也很有价值),而不是单元测试。

    至于需要审核的客户数量,我不会特别想知道我有 20 个需要审核的客户中有 10 个需要审核,但对于特定客户,我是否正确决定该客户是否需要审核根据我的业务规则?

    您可能会发现由两部分组成的系列“TDD/在 CSLA.Net 中使用 Mock 对象”很有帮助:

    您提到了困难的依赖关系,我强烈推荐 Michael Feathers 的 Working Effectively with Legacy Code。这本书充满了conservative dependency-breaking techniques,对于测试代码很有用。

    【讨论】:

      猜你喜欢
      • 2011-08-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多