【问题标题】:TDD: How would you work on this class, test-first style?TDD:你会如何在这门课上工作,测试优先的风格?
【发布时间】:2009-01-16 18:57:28
【问题描述】:

我正在编写一个小应用程序来自学 ASP.NET MVC,它的一个功能是能够在亚马逊(或其他网站)搜索书籍并将它们添加到“书架”。

所以我创建了一个名为 IBookSearch 的接口(使用方法 DoSearch),以及一个看起来像这样的 AmazonSearch 实现

public class AmazonSearch : IBookSearch
{
   public IEnumerable<Book> DoSearch(string searchTerms)
   {  
      var amazonResults = GetAmazonResults(searchTerms);
      XNamespace ns = "http://webservices.amazon.com/AWSECommerceService/2005-10-05";
      var books= from item in amazonResults.Elements(ns + "Items").Elements(ns + "Item")
                 select new Book
                 {
                      ASIN = GetValue(ns, item, "ASIN"),
                      Title = GetValue(ns, item, "Title"),
                      Author = GetValue(ns, item, "Author"),
                      DetailURL = GetValue(ns, item, "DetailPageURL")
                 };
      return books.ToList();
  }

  private static XElement GetAmazonResults(string searchTerms)
  { 
      const string AWSKey = "MY AWS KEY";
      string encodedTerms = HttpUtility.UrlPathEncode(searchTerms);
      string url = string.Format("<AMAZONSEARCHURL>{0}{1}",AWSKey, encodedTerms);
      return XElement.Load(url);
  }

  private static string GetValue(XNamespace ns, XElement item, string elementName)
  {
     //Get values inside an XElement
  }

}

理想情况下,我希望完成这种 TDD 风格,首先编写一个测试和所有内容。但我得承认我很难理解它。

我可以创建一个实现 DoSearch() 的 FakeSearch 并返回一些临时书籍,但我认为目前这不会带来任何价值,不是吗?也许稍后当我有一些使用书籍列表的代码时。

我还可以先测试什么?我能想到的唯一测试是模拟对云的调用(在 GetAmazonResults 处),然后检查 DoSearch 是否可以正确执行 Linq2XML 选择并返回正确的列表。但在我看来,这种类型的测试只能在我准备好一些代码之后才能编写,这样我就知道要模拟 什么了。

关于你们如何做这种测试优先的风格有什么建议吗?

【问题讨论】:

    标签: c# tdd mocking test-first


    【解决方案1】:

    您的主要问题似乎是知道何时编写模拟代码。我明白你的意思:如果你还没有写代码,你怎么能模拟它?

    我认为答案是您希望通过非常非常简单的测试开始您的 TDD,就像 Kent Beck 在Test Driven Development 中所做的那样。首先编写一个调用 DoSearch 并断言您收到的内容不为空的测试,然后编写一些代码以使其通过。然后编写一个测试,断言您正在为已知搜索词检索正确数量的书籍,并编写代码以使其通过。最终,您将到达需要接收实际、有效的 Book 数据以通过测试的地步,此时,您将编写 DoSearch 的一部分,您可以考虑模拟它(或其中的一部分) )。

    【讨论】:

    • “然后编写一个测试,断言您正在为已知搜索词检索正确数量的书籍,并编写代码以使其通过”我无法想象这样的测试将如何看起来,我正在测试正确的代码,而不是测试我的测试。
    • 请记住,您正在逐步开发,并且您永远不会做超过通过测试所必需的工作。对于该测试,您将首先返回一个完全虚假的书籍列表,但稍后,另一个测试应该要求您将其重构为真正的查询。
    • 这就是 TDD 如何帮助您确保您从不(或至少,很少)编写不必要的代码。您总是编写通过测试所需的最低限度的内容,并认为您稍后会在保持所有测试通过的同时进行重构。很难掌握,但可以编写优雅的代码。
    • 所以首先我会创建一个像 DoSearch_DoesNotReturnNull 这样的测试,这个测试会失败,因为我还没有任何代码(或者一个抛出 NotImplementedException 的函数,所以它可以编译)。第二个测试,它的名称是什么,代码是什么样的,以及通过它的代码?
    • PS:可能我最后的评论听起来有点懒惰,我不希望有人真正写代码,只是解释它的样子......
    【解决方案2】:

    当您测试使用搜索的代码时,您需要编写一个模拟,而不是测试搜索本身。

    对于上面的课程,我可能会测试:

    • 正在搜索一本普通书籍并检查它是否已找到且有效。
    • 搜索随机固定字符串“kjfdskajfkldsajklfjafa”并确保未找到任何书籍

    但是.. 最重要的是,我永远不会模拟我正在测试的类,我会模拟它使用的类。

    长话短说:当按下“搜索”按钮时测试 UI 是否正常工作时,将使用 FakeSearch。我可以确保它被调用,并且 UI 正在正确处理返回的书籍。

    希望对您有所帮助。

    【讨论】:

    • 是的,我同意模拟,它必须是课程范围之外的东西。因此,在我上面的示例中,看起来 XElement.Load() 将是要模拟的调用,对吗?
    • 这是我还没有得到的:我想编写一个测试来检查搜索“Isaac Asimov Foundation”是否返回了一些书籍。但是我想不出我能写什么来让它通过实际上并没有去亚马逊得到一些结果,这意味着要一次性编写这么一大块代码。
    • 一次测试有很多代码:XElement.Load 是静态的。我认为这是一个设计缺陷,应该通过允许注入 XElementLoader(新)来修复,这样您可以确保您的代码正确调用底层系统。
    【解决方案3】:

    在这个类中,主要关注点似乎是它与亚马逊的网络服务正确集成。由于该 Web 服务不是您拥有的,因此您不应该嘲笑它,因为您对它的工作原理没有深入的了解。 "Only mock types you own""don't mock third-party libraries"

    这里有一些解决问题的方法:

    编写一个通过网络连接到真实网络服务的测试,也许搜索一些您可以信赖的非常受欢迎的书,这些书会在未来几年出现。这可以很好地确保您正确使用该服务,但它也会受到许多误报的影响 - 例如,有时网络可能会关闭或远程系统中的数据发生变化。因此,您还需要测试...

    针对静态数据文件编写测试,这些文件基于来自真实网络服务的数据。要获取测试数据,您可以手动向 Web 服务发出请求并将响应写入文件*。您将需要模拟网络连接(使用不联网的存根,或者通过在测试中启动嵌入式 Web 服务器并连接到它而不是真实的 URL)。通过这种方式,您可以轻松测试各种极端情况和错误条件,并且无论实际 Web 服务发生什么情况,数据都将始终可用并保持不变。需要注意的是,如果真实 Web 服务的 API 发生变化,这些测试不会注意到它,因此您还需要针对真实 Web 服务编写一些测试(如上所述)。

    * 例如,有一次我使用 cron 和一个小 shell 脚本每隔几分钟从 Web 服务下载一次数据,其中包含不断变化的计划信息。在几周内收集此类数据作为测试数据非常有用。从这些数据中,我手工制作了静态响应,其中包含我在真实数据中注意到的各种特殊情况。它还可以用于设置一个虚假的网络服务和一个“时间机器”来重放早期捕获的数据,这样我们的系统就可以在不访问真实网络服务的情况下使用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-30
      • 1970-01-01
      • 2010-10-07
      • 1970-01-01
      相关资源
      最近更新 更多