用TDD思考OO - 从哪里开始?

时间:2009-06-25 11:12:50

标签: c# oop tdd

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

这是我的用例/故事:

  

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

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

我希望域代码成为焦点,我希望它能够在测试中使用。

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

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

有人可以帮助我吗?

3 个答案:

答案 0 :(得分:4)

在这里 - 我将通过几个测试开始你:

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<Criteria>方法。

这应该为您提供ClientSearcher类,它使用一个接口来命中数据库。 MockDBRealDB类都将实现该接口。

答案 1 :(得分:1)

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

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

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

您提到了困难的依赖关系,我强烈建议Michael Feathers 有效地使用遗留代码。这本书充满了conservative dependency-breaking techniques,可用于测试代码。

答案 2 :(得分:1)

我不熟悉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没有副作用,但返回一些内容,当然你会在这里测试返回值,也许是否有任何改变。 并重复直到第一次测试成功。

这样每个方面都经过了适当的测试,你甚至可以使用一些可以重复使用的方法。

希望这有帮助!