减少单元测试的责任和合作者
我有一个具有明确定义的职责的类 - 使用对象所需的信息“丰富”对象。该信息是从各种来源(服务)收集的。例如:
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 种不同的方法。
在这种情况下,如何减少协作者的数量,并使该代码可测试?
I have a class which has a well-defined responsibility - to "Enrich" an object with the information it needs. This information is gathered from a variety of sources (Services). Eg:
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;
}
The logic for "how" to enrich a request is contained here. The class could be extended for different types of trade for example.
We enrich the trade in one step because we don't want any partially-enriched objects floating around, which wouldn't make a lot of sense.
The class is really difficult to unit test, because it has so many collaborators (up to 12 other services it calls upon). I would need to mock up 12 services, each with 3 or 4 different methods.
How do I reduce the number of collaborators in this case, and make this code testable?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
根本问题是你有太多的合作者。如果难以测试,那么设计可能可以改进。
一种选择是创建一个外观服务来管理协作者的交互。在您的情况下,您可能有一个外观层次结构,因为只有一个外观只会将大量模拟的需求转移到另一个区域,这并不能解决问题。如果可能,尝试将更有可能一起使用的服务分组到外观中。或者,如果您总是跨服务一起调用相同的 X 方法,请将该功能放在某个地方的单个方法中。如果您有一个外观,而该外观又调用 3-4 个其他外观,则每个测试只需要 3-4 个模拟,这更易于管理。
最终结果将是您的“丰富器”将仅调用一个外观服务,因此测试将很容易。权衡是需要测试你的外墙,这将是可管理的。
The fundamental problem is that you have too many collaborators. If its difficult to test, then design can probably be improved.
One option is to create a facade service that manages the interaction of the collaborators. In your case, you might have a hierarchy of facades, since having only one will just move the need to have a lot of mocks into another area, which doesn't solve the problem. If possible, try to group services that are more likely to be used together into facades. Or alternatively, if you always call the same X methods across services together, put that functionality in a single method somewhere. If you have a single facade which in turn calls 3-4 other facades, each test will only need 3-4 mocks which is more manageable.
The end result of this will be that your 'enricher', will only call one facade service, so testing will be easy. The trade off being the need to test your facades, which will be manageable.
我认为编写可测试代码的最佳方法是练习 TDD。这可以帮助您编写可测试的代码,因为您首先需要编写测试,然后才能编写任何生产代码。我建议您阅读Bob叔叔的TDD三定律。但下面我将向您总结第一部分:
这本指南:编写可测试代码也非常有趣,可以为您提供很多知识编写可测试代码的技巧。
更新
重构
当您进行了测试时,您只需要进行重构,但请记住,在测试失败之前,您不允许编写任何生产代码。我认为你的(可能)班级有很多责任 当它有最多 12 个协作者时。
有效地使用旧代码
我想指出有效地使用旧代码
模拟框架
另外我想指出的是,您可以通过使用模拟框架来消除模拟对象的许多痛苦,例如 Mockito。我玩过这个,我最喜欢这个。
I think the best way to write testable code is by practicing TDD. This helps you write testable code, because you first need to write your test before you can write any production code. I recommend you to read Uncle Bob's three laws of TDD. But below I will give you a summarize of the first part:
This Guide: Writing Testable Code is also very interesting read and gives you a lot of tips to write testable code.
UPDATE
Refactoring
When you have test in place you just need to do refactoring, but keep in mind you are not allowed to write any production code, before you have a failing test. I think your (might)class has to much responsibilities when it has up to 12 collaborators.
Working Effectively with Legacy Code
I would like to point out Working Effectively with Legacy Code
Mocking frameworks
Also I would like to point that you can eliminate much of the pain mocking objects by using mocking frameworks like for example Mockito. I played with this one and I like this one the most.
无需回到复杂的设计模式:为什么不将“丰富”方法转移到相应的服务中?这样,您可以在
Enricher
类中保留处理丰富步骤的列表,但将实际的enrich
调用委托给具有实际丰富知识的服务。贸易。然后可以单独测试这些服务。应用于您的代码:
Without going back to complicated design patterns: Why don't you move your "enrich" methods into the respective services? This way, you could keep the list of which steps of enrichments are processed inside your
Enricher
class but delegate the actualenrich
calls into the services which have the knowledge to actually enrich the trade. These services can then be tested individually.Applied to your code:
您的根本问题是最近的语言漂移,通过这种漂移,单元测试已经意味着隔离测试。
单元测试的一个稍微传统的定义来自短语“测试单元是发布单元”。因此,如果像您的丰富器这样的东西除了它所包装的服务之外无法进行有效的讨论、指定或交付,那么同样的情况也适用于它的测试。
如果这为您提供了“以这种方式戳它,并且它按照应有的方式运行”形式的测试,那么单元测试警察不会来逮捕您......
当有理由时代理:异步、缓慢、外部、多重实施或不稳定的服务。否则,使用真实的东西进行测试会给你更好、更快的结果。
Your undelying problem is the recent linguistic drift by which unit testing has come to mean isolation testing.
A slightly more traditional definition of unit testing comes from the phrase 'the unit of testing is the unit of release'. So if a something like your enricher can't be usefully discussed, specified or delivered except in terms of the services it wraps, the the same applies to its tests too.
If that gives you tests of of the form 'poke it this way, and it behaves according to the way it should', then the unit test police won't come and arrest you...
Proxy when there is a reason to: asynchronous, slow, external, multiply-implemented, or unstable services. Otherwise, a test that uses the real thing will give you better results, faster.
对于开始 - 一个空的丰富
要调用它 - 调用 Y = ID.apply(X)。
然后对每一项丰富功能进行一一开发、测试并引入生产。
For start - an empty enrichment
To invoke it - call Y = ID.apply(X).
Then one by one develop, test and introduce into the production each of the enrichment functions.