我有一个具有明确责任的课程 - 用所需信息“丰富”一个对象。这些信息来自各种来源(服务)。例如:
public class Enricher
{
private CounterpartyService counterPartyService;
private BookingEntityService bookingEntityService;
private ExchangeRateService exchangeRateService;
private BrokerService brokerService;
... 6 more services
public EnrichedTradeRequest enrichTrade(TradeRequest request)
{
EnrichedTradeRequest enrichedRequest = new EnrichedRequest(request);
// Enrich with counterparty info
enrichedRequest = enrichCounterParty(enrichedRequest)
// Enrich with booking entity info
enrichedRequest = enrichBookingEntity(enrichedRequest)
// Enrich with exchange rate info
...
// Enrich with broker info
...
// ....etc
return enrichedRequest;
}
private EnrichedTradeRequest enrichCounterparty(EnrichedRequest enrichedRequest)
{
// Get info from CounterpartyService
// ...
return enrichedRequest;
}
此处包含“如何”丰富请求的逻辑。例如,该类可以扩展到不同类型的交易。
我们一步到位地丰富了贸易,因为我们不希望任何部分富集的物体漂浮在周围,这没有多大意义。
这个类很难进行单元测试,因为它有很多协作者(最多可以调用12个其他服务)。我需要模拟12个服务,每个服务有3或4种不同的方法。
在这种情况下,如何减少协作者的数量,并使此代码可测试?
答案 0 :(得分:2)
根本问题是你有太多的合作者。如果难以测试,那么设计可能会得到改善。
一种选择是创建一个管理协作者交互的外观服务。在你的情况下,你可能有一个外观的层次结构,因为只有一个将只需要将大量的模拟转移到另一个区域,这无法解决问题。如果可能,尝试将更有可能一起使用的服务分组到外墙中。或者,如果您总是在服务中调用相同的X方法,请将该功能放在某个方法中。如果你有一个门面,反过来调用3-4个其他外墙,每个测试只需要3-4个模拟,这是更易于管理。
最终的结果是你的'richher'只会调用一个门面服务,所以测试很容易。需要对外墙进行测试,这是可以控制的。
答案 1 :(得分:2)
我认为编写可测试代码的最佳方法是练习TDD。这有助于您编写可测试代码,因为您首先需要在编写任何生产代码之前编写测试。我建议你阅读Uncle Bob's three laws of TDD。但是下面我会给你一个第一部分的总结:
多年来,我开始描述测试驱动开发 三个简单规则的术语。他们是:你不被允许写 任何生产代码,除非它是一个失败的单元测试通过。您 不允许再写单元测试了 失败;和编译失败是失败。你不被允许 写下足以传递一个的生产代码 失败的单元测试。
您必须首先为您的功能编写单元测试 打算写。但是根据规则2,你不能写很多单位 测试。一旦单元测试代码无法编译,或者失败了 断言,你必须停止并编写生产代码。但是按照规则3你 只能编写使测试编译或生成的生产代码 通过,而不是更多。
如果你想到这一点,你就会意识到你根本无法写作 很多代码都没有编译和执行某些东西。 实际上,这确实是重点。我们所做的一切,无论是写作 测试,编写生产代码或重构,我们保留系统 始终执行。运行测试之间的时间是订单 秒或分钟。即使10分钟也太长了。
此Guide: Writing Testable Code也非常有趣,并为您提供了编写可测试代码的大量提示。
当您进行测试时,您只需要进行重构,但请记住,在测试失败之前,不允许编写任何生产代码。我认为你的(可能)class has to much responsibilities最多有12个合作者。
提取您正在改变现有行为的类。当你工作 关于现有功能,(即添加另一个条件)提取a 阶级拉着这个责任。这将开始采取 从传统课程中分离出来,您将能够测试每个课程 孤立的块(使用依赖注入)。
我想指出Working Effectively with Legacy Code
传统代码更改算法
当你必须在遗留代码库中进行更改时,这是一个 您可以使用的算法。
1.确定变更点 2.找到测试点 3.打破依赖关系 4.写测试。
5.进行更改和重构。
此外,我想指出,您可以使用模拟框架(例如Mockito)来消除大量的模拟对象。我玩过这个,我最喜欢这个。
答案 2 :(得分:1)
不回到复杂的设计模式:为什么不将“丰富”方法转移到相应的服务中?这样,您可以保留在Enricher
类中处理哪些富集步骤的列表,但是将实际的enrich
调用委托给具有实际丰富交易知识的服务。然后可以单独测试这些服务。
应用于您的代码:
public class CounterpartyService {
private EnrichedTradeRequest enrichCounterparty(EnrichedRequest enrichedRequest)
{
// Enrich trade with counterparty details..
// ..
return enrichedRequest;
}
}
public class Enricher
{
private CounterpartyService counterPartyService;
// ... 6 more services
public EnrichedTradeRequest enrichTrade(TradeRequest request)
{
EnrichedTradeRequest enrichedRequest = new EnrichedRequest(request);
// Enrich with counterparty info
enrichedRequest = counterPartyService.enrichCounterParty(enrichedRequest);
// Enrich with other info
// ...
}
}
答案 3 :(得分:0)
你的不良问题是最近的语言漂移,单元测试已经意味着隔离测试。
稍微更传统的单元测试定义来自“测试单位是发布单位”这一短语。因此,如果您的 richher 之类的东西无法被有效地讨论,指定或传递,除了它所包含的服务之外,同样适用于它的测试。
如果这给你测试形式'以这种方式戳它,并且它按照应该的方式行事',那么单位测试警察不会来逮捕你......
代理何时有理由:异步,慢速,外部,多次实现或不稳定的服务。否则,使用真实物体的测试将更快地为您提供更好的结果。
答案 4 :(得分:0)
开始 - 空洞的充实
final Function<EnrichedTradeRequest,EnrichedTradeRequest> ID =
new Function<EnrichedTradeRequest,EnrichedTradeRequest>() {
public EnrichedTradeRequest apply(EnrichedTradeRequest arg) {
return arg;
}});
要调用它 - 调用Y = ID.apply(X)。
然后逐一开发,测试并将每种浓缩功能引入生产中。
import com.google.common.base.*;
private FuEnrichCounterParty = ...;
void setFuEnrichCounterParty(...)
private FuEnrichBookingEntity = ...
void setFuEnrichBookingEntity(...)
...
public EnrichedTradeRequest enrichTrade(TradeRequest request)
{
EnrichedTradeRequest enrichedRequest = new EnrichedRequest(request);
// Enrich with counterparty info
enrichedRequest = FuEnrichCounterParty.apply(enrichedRequest)
// Enrich with booking entity info
enrichedRequest = FuEnrichBookingEntity(enrichedRequest)
// Enrich with exchange rate info
...
return enrichedRequest;