【问题标题】:Starting a TDD project from scratch从头开始一个 TDD 项目
【发布时间】:2011-06-16 16:29:49
【问题描述】:

我阅读了很多关于 TDD 和 SO 单元测试的问答,但我没有找到任何答案:我从哪里开始?

我和团队已经完成了几个项目,其中我们对代码采用了单元测试……但首先是代码,然后是单元测试。在开发过程的某个阶段,先编写测试然后编写代码变得很自然,这将我们带入了一种更加 TDD 的风格。

现在我们想进行下一步,并尝试从头开始使用 TDD 开始一个新项目。问题来了……从哪里开始?当我完全没有代码时,我会写的第一个测试是什么?

假设,只是为了考虑一个上下文,我必须开发一个互联网应用程序,以文档为中心,有一点工作流程和......其他东西。 但是让我们从头开始:首先,我想创建一个简单的页面,列出存储在数据库表中的所有文档(元数据)(很简单,嗯?)。 我要写的第一个测试是什么?假设我正在使用 Hibernate 访问数据库...我会测试虚拟方法 getAllDocuments() 吗?但是我应该使用模拟对象来代替 Hibernate 吗?那我在测试什么?

我在这里有点困惑...而且 getAlDocuments() 可能永远不会成为生产方法...所有文档集合都将被排序和过滤...这有意义吗? 任何建议将不胜感激

已编辑:

阅读您的答案(以及http://programmers.stackexchange.com 上的类似帖子)后,我对 TDD 有了更好的认识,但我仍然有一个问题。

我一直认为 TDD 是关于首先进行单元测试...从没想过端到端测试。 但是让我问一下:TDD说你必须写一个测试并看到一个编译错误;然后你创建类和方法,你得到一个测试失败;然后你实现该方法并通过测试。在测试失败之前,您无法编写代码;在所有测试通过之前,您不能编写另一个测试。我在吗?

如何将端到端测试作为我的第一个测试?我应该在所有层中编写所有代码,以使该测试通过。但是我会有一堆类和方法,所有这些类和方法都经过我的端到端测试(我不应该称之为集成测试吗?)。这意味着我不再需要单元测试,因为我已经有一个涵盖我的代码的测试。而且我不能编写已经通过的测试,这违反了 TDD 实践。

请帮助我理解这进一步的步骤

【问题讨论】:

标签: unit-testing tdd


【解决方案1】:

TDD 不是关于单元测试 - TDD 是关于通过测试来推动您的开发和架构 - 使用您需要的任何类型的自动化测试。你明白了吗?

您正在开始一个新项目,并且您可能拥有一组功能。对于要实现的功能,您应该有一些验收标准。这些标准可以定义您的顶级测试。让我们从端到端测试开始(这有时会很困难,因为它涉及尚不存在的 UI)或这些验收标准的集成测试。一旦您的测试失败,您将继续实现与大型测试相关的功能,但每个此功能将再次通过集成或单元测试驱动。如果所有顶级测试都通过,则该功能完成。

如果您跳过大型测试(端到端、集成),您将开发一组经过良好测试的单元,这些单元在集成在一起时将无法工作,或者由于单元测试定义的本地范围,您的架构将不会很好.集成和端到端测试为您提供全局范围。

这在Growing Object-Oriented Software Guided by Tests 一书中用大型示例 (Java) 进行了描述。

【讨论】:

  • 请看我更新的问题,帮助我更深入地理解这些概念
  • 我检查了你的更新。我确认我描述的版本不同并且没有遵循这种方法(但 Kent Beck 描述的前 TDD 也没有包含这些规则)。我的描述基于我参考的书,我发现它更有用。无论如何,您仍然可以调整您的流程并仅使用一个不完整的单元测试来遵循规则,但在进行单元测试时允许不完整的集成测试。
【解决方案2】:

我通常从上到下开始。在您的情况下,我将从编写新页面的控制器逻辑开始。控制器是指 UI 下方的代码层,模拟下面的所有内容。然后编写服务层(如果有的话),模拟数据层。最后,还使用底层类的模拟测试数据层(在您的情况下可能是 ISession)。最后,我将为每个数据层方法编写一个集成测试并构建页面 (html)。

【讨论】:

    【解决方案3】:

    由于您尝试基于测试来推动开发,因此开始的方法是从您的第一个功能开始。例如,假设您具有上传文档的功能。您的第一个测试可能是:

    public class DocumentManagementTest {
      @Test public void allowsDocumentUploads() {
        DocumentManagement dm = new DocumentManagement();
        Reader mockReader = new MockDocumentReader();
    
        Document result = dm.createDocument("Document name", mockReader);
    
        assertEquals("Document name", result.getName());
        assertEquals(0, result.getTags().size());
        assertTrue(mockReader.fileWasRead);
      }
    }
    

    我肯定会从一开始就模拟数据库,数据库设置和拆卸既昂贵又脆弱。请记住,尽管要进行非常小的步骤,但我上面展示的测试可能会经过几次迭代而演变。推动更多设计的后续测试可能是:

    @Test public void allowsDocumentRenames() { ... }
    @Test public void allowsAddingTagsToExistingDocuments() { ... }
    @Test public void showsErrorWhenAddingDocumentThatAlreadyExists() { ... }
    

    一旦您构建了诸如 createDocument 之类的功能,您就可以围绕它创建一个控制器。

    public void doPost(HttpServletRequest req, HttpServletResponse resp) {
      String name = req.getParameter("doc_name");
      Document d = docMgmt.createDocument(name, req.getInputStream());
      // Hand the newly created document to the view engine.
    }
    

    我不会太担心为控制器编写测试,因为从复杂性的角度来看它的风险相当低(如果控制器获取太多代码,那么它可能是控制器代码属于另一个类的气味,可能您的 DocumentManagement 类)。

    通过一次构建一个功能并遵循 SOLID 原则,您将慢慢发展出一个具有良好测试覆盖率和非常好的 OO 属性的系统。

    干杯!

    布兰登

    【讨论】:

      【解决方案4】:

      从简单开始,稍后您将逐步添加功能。从快速设计开始:哪些类、哪些职责、哪些关系。您可以使用CRC cards。不要在该设计上花费太多时间,因为您以后可以通过重构来改进它。选择最简单的类开始实现系统的简单功能。例如,您可以先创建一个空页面。

      从一门课开始?它的对象应该做什么?您如何验证这是否正确完成?这是第一次测试。

      您也可以在没有数据库的情况下开始,并将您的文档存储在一个平面文件中。稍后您将重构为数据库。然后你可以从 getAllDocuments() 函数开始。

      【讨论】:

        猜你喜欢
        • 2015-08-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-12-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多