0

在我最近的项目中,我和我的团队成员聚集在一起,检查了需求,并一起创建了我们都同意的接口(带有方法声明,但没有实现)。然后,我们开始编写单元测试,然后是实现。

现在,我的项目负责人说我们的方法是错误的。他是说我们应该先创建一个测试,然后再提出接口。

我们首先提出接口的原因之一是因为我们认为必须有一个接口声明才能创建模拟测试。

哪一个是正确的方法?

4

3 回答 3

2

超过 TDD

从 TDD 开始,根据故事或用例以及它们的需求进行思考。根据测试定义需求。假设您在一个销售袜子的网站上工作。故事可能是这样的:作为客户,我需要输入我要购买的商品数量,以便获得包裹折扣。因此,您创建了一个测试,上面写着“客户输入了 24 双袜子,确保订单中添加了 5% 的折扣”。但这确实是高水平。你看着代码说:“嘿,我什至还不支持数量。” 因此,您要考虑订单上的订单项,以及它们需要如何数量。你为你的 LineItem 对象写了一个测试,上面写着

LineItemTest::testAddQuantity()
{
    LineItem lineItem = new LineItem();
    Item item = new Item();
    lineItem.add(item);
    lineItem.addQuantity(7);
    ASSERT(lineItem.getQuantity() == 7);
}

然后你实现代码。此时,您会更加仔细地考虑数量,并针对负数量、超过最大值的数量等添加更多测试。然后您会发现订单项的价格受数量影响。您的商品已有价格,但没有按数量扩展的价格。所以你加一个是因为你需要一个。

LineItemTest::testExtendPrice()
{
    LineItem lineItem = new LineItem();
    Currency price = 1.00;
    Item item = new Item(price);
    lineItem.add(item);
    lineItem.addQuantity(7);
    ASSERT(lineItem.getExtendedPrice() == 7.00);
}

所以我们通过定义一个新的需求来改变 LineItem 的接口。

最重要的教训是,如果我们在设计 LineItem 接口时没有考虑 ExtendedPrice,我们就会陷入困境,因为我们的需求迫切需要一个。使用 TDD,您不必提前设计它,因为无论您在设计过程中怎么想,无论如何,当您到达这里时,您最终会需要它。

应急设计

这种方法称为紧急设计,这就是为什么 TDD 通常被称为设计方法,而不是测试方法的原因。你不会提前花一大堆时间去想所有的设计元素,所有的接口是什么,它们到底是做什么的,如果我们需要数量折扣怎么办,如果这个怎么办,如果那个怎么办. 这就是导致遗漏错误的大设计预先方法。另一个例子可能是,如果他们使用优惠券或其他方式,您忘记检查折扣的销售税。取而代之的是,您仅在需要做出这些决定时才推迟这些决定 - 当您正在处理需求时。

应急设计是实现结果的不同途径。它让你边走边思考,而不是提前思考,它让你对手头的事实做出准确的反应,而不是试图预先想象它们。它利用了软件在这个阶段非常灵活和多变的优势。

这时候你也允许所有的“假设”输入。如果我们输入一个负数怎么办?添加另一个测试!

节省时间

在敏捷世界中,这种方法还有另一个很大的优势:如果您的客户决定推迟某个功能以支持更重要的功能,那么您就不会浪费时间来设计它。客户对事物的优先级有所不同是很常见的。我经常听到这样的决定:“今天的折扣不重要,我不会在明年进行促销活动。现在,我真的需要你把营业税整合到位。”

单元测试实现了这种灵活性。使用单元测试,您可以更改任何内容并免费重新运行测试,确信您的更改不会破坏其他任何内容。

避免的问题

预先设计的大设计并不能保护您。相反,它会阻碍你在正确的时间做正确的事情。我见过一些团队被一个完全忘记了一些细节的大设计困住了,他们没有修复设计来添加细节,而是修补缺陷。“我的项目落后于计划,我们现在不能改变设计,我们只能做一个变通办法。” 这就是真正的意大利面条代码的来源,而这通常是导致错误决策的过程的错误。

外部依赖

通常,不管你在内部创造了什么,你仍在与外部世界打交道。服务、固定需求、遗留数据库模式,所有这些都是您必须在 TDD 中处理的现实问题。所以如果你有一个到另一个系统的外部接口,你如何处理它?正如您对任何其他要求一样:通过测试。

这主要是您的模拟将发挥作用的地方。您将创建一个执行运输的模拟服务,实现现有的运输接口。您将使用 TDD 编写代码以与该接口进行交互。您可能会发现围绕它编写一个瘦包装器是有利的,然后在您的代码中访问适配器 - 这样您就可以编写一个模拟包装器,而不是编写一个完整的模拟服务。

于 2013-04-26T18:07:24.813 回答
1

测试驱动开发从单元测试开始。

在您的单元测试中,您定义了树阶段:Arrange、Act 和 Assert。此时您的代码将无法编译,因为尚未定义类/接口。

但是这些步骤确实可以帮助您考虑您的界面。在您的情况下,您可以编写一组单元测试来勾勒出您需要的类和接口,而不是与您的同事在白板上讨论您需要的接口和方法。

通过以测试优先的方式编写单元测试,你强迫自己在考虑实现之前查看类的外部。这就是做TDD的方式。

在为您的项目定义所有接口时考虑模拟是很快的。在您的单元测试中,您测试一个类。一个具体的对象。在编写该对象时,您会遇到类需要其他对象的情况。那是您开始考虑依赖关系的那一刻,这导致使用接口和依赖注入。

于 2013-04-26T14:58:17.807 回答
0

应用 TDD 时,您首先从测试开始……您首先要写下描述业务逻辑的测试用例。随着这些测试,必要的接口/方法变得显而易见。我认为编写第一个接口或测试用例并不那么重要。更重要的是在这两个步骤之后编写接口的实现!

于 2013-04-26T14:38:27.263 回答