在我最近的项目中,我和我的团队成员收集,完成了需求,并创建了我们都同意的接口(包含方法声明,但没有实现)。然后,我们开始编写单元测试然后实现。
现在,我的项目负责人说我们的做法是错误的。他说我们应该首先创建一个测试,然后提出接口。
我们首先提出接口的原因之一是因为我们认为我们必须有一个接口声明才能创建模拟测试。
哪一个是正确的方法?
答案 0 :(得分: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,您无需提前设计,因为无论您在设计过程中的想法如何,当您到达这里时,您最终都会需要它。
紧急设计
这种方法称为Emergent Design,这就是为什么TDD通常被称为设计方法,而不是测试方法。你没有提前花大量的时间,思考所有的设计元素,所有的接口是什么,他们究竟做了什么,如果我们需要数量折扣怎么办,如果这个以及如果那样做怎么办? 。这就是那种导致遗漏错误的Big Design Up Front方法。另一个例子可能是,如果他们使用优惠券或其他什么,你会忘记检查折扣的销售税。相反,只有当你需要制作这些决定时,你才会将这些决定推迟 - 当你正在处理这项要求时。
Emergent Design是实现成果的不同途径。它让你随心所欲,而不是提前思考,它让你对你手头的事实作出反应,而不是试图预先设想它们。它充分利用了软件在这个阶段的灵活性和可变性。
此时,您还允许输入所有“if ifs”。如果输入负数,该怎么办?再添一个测试!
节省时间
在敏捷世界中,这种方法还有另一大优势:如果您的客户决定延迟功能以支持更重要的功能,那么您就不会浪费时间进行设计。客户以不同的方式对事物进行优先排序是很常见的。我经常听到这样的决定:“折扣今天并不重要,直到明年我都不会进行促销活动。现在,我真的需要你将销售税整合到位。”
单元测试可实现这种灵活性。通过单元测试,您可以更改任何内容并免费重新运行测试,确信您的更改不会发生任何其他情况。
避免出现问题
Big Design Up Front并不能保护您。相反,它可以阻止你在正确的时间做正确的事情。我看到团队陷入了一个完全忘记一些细节的大设计,而不是修复设计以添加细节,他们修补了缺陷。 “我的项目落后于计划,我们现在无法改变设计,我们只是做一个解决方法。”这就是真正的意大利面条代码来自的地方,而且往往是导致错误决策的过程的错误。
外部依赖
通常,尽管你在内部创造了什么,但你仍然在与外界打交道。服务,固定需求,遗留数据库模式,所有这些都是您必须在TDD中处理的现实问题。因此,如果您有另一个系统的外部接口,您如何处理它?正如您所做的任何其他要求:进行测试。
这主要是你的模拟将发挥作用的地方。您将创建一个运送的模拟服务,实现现有的Shipping接口。您将使用TDD编写代码以与该接口进行交互。您可能会发现在它周围编写一个瘦包装器然后在代码中访问适配器是有利的 - 这可以让您编写一个模拟包装器而不是编写一个完整的模拟服务。
答案 1 :(得分:1)
测试驱动开发以单元测试开始。
在单元测试中,您可以定义树阶段:Arrange,Act和Assert。此时您的代码将无法编译,因为类/接口尚未定义。
但是这一步确实可以帮助您考虑您的界面。在您的情况下,您可以编写一组单元测试来描述您需要的类和接口,而不是在白板(或其他东西)上与同事讨论所需的接口和方法。
通过以测试优先的方式编写单元测试,您在考虑实现之前强迫自己查看类的外部。这是做TDD的方法。
在为项目定义所有接口时考虑模拟很快就会出现。在单元测试中,您将测试单个类。具体的对象。在编写该对象时,您会遇到类需要其他对象的情况。那是你开始考虑导致使用接口和依赖注入的依赖关系的那一刻。
答案 2 :(得分:0)
在应用TDD时,首先从测试开始......首先要写下描述业务逻辑的测试用例。沿着这些测试,必要的接口/方法变得明显。我认为编写第一个接口或测试用例并不重要。更重要的是在这两个步骤之后编写接口的实现!