在阅读了Uncle Bob博客和TSS id DEAD文章后,我想编写更多测试代码。
我的应用程序正在使用图层:ApplicationService - >型号 - >支持。
应用程序服务定义用例。这是一个程序代码(loadX,调用X.doSomething,saveX)。在某些情况下,appService类需要许多外部依赖项。
例如,我在“工作流程”结尾处使用了一个对象“D”。因此,在使用它时,我需要检查与此链接的对象“A”/“B”/“C”的一些规则。假设我们有3个相关的实体,每个实体都有自己的存储库,在非常特殊的用例中我需要更深入地检查另一个规则(如“AA”==另一个存储库)。
我的appService将有8到10个依赖项(5个存储库+一些业务服务实体间)。测试不是我的“强项”。在嘲笑太多服务时我缺乏勇气。如果每个服务有3个方法,但我的appService只需要一个,那么我需要知道哪个方法要模拟或模拟/存根所有方法(以及模拟/存根2/3无用的方法)。
我认为有很多问题。我的第一个是太多依赖项。我只是不想拆分依赖关系并将它们隐藏在地毯下。当然,域实体也可能存在问题:边界差,分离太多等等。
在阅读article (uncle bob)后,我在想:我做错了。也许appService需要表达/声明一个它需要什么的接口。但如何命名呢?怎么分裂呢?我不确定将相关服务放在一起是个好主意。写一个类似于立面的东西,测试会变得更容易(我现在知道我的课程与这些外部服务相结合,我确切知道哪些服务)。
感谢
更新1(2016年6月23日): 例如,这里为应用程序服务类DPAppService提供了依赖项列表:
DPRepository dpRepository;
RechercheFournisseurQueryService rechercheFournisseur;
RechercheFactureQueryService rechercheFacture;
DateService dateService;
SFRepository sfRepository;
CommandeRepository commandeRepository;
RepartitionBudgetRepository repartRepo;
GenerateurRepartitionsDPService genRepartDpService;
SaisieRepartitionsSurDpQueryService saisieRepartitionsSurDpQueryService;
ImputationBudgetaireService imputationBudgetaireService;
NomenclatureService nomenclatureService;
ComptaGeneraleService comptaGeneraleService;
OperationInfoService operationInfoService;
DemandePaiementCalculateurService dpCalculateurService;
CodeMarcheAnnualiseRepository codeMarcheAnnualiseRepository;
DemandePaiementLigneFactory dpLigneFactory;
EcritureRepository ecritureRepository;
BrouillardNonViseRepository brouillardNonViseRepository;
非常可怕,使用构造函数注入非常烦人。有一些存储库(我认为太多了,DP服务只需要存储库公开的方法的一个子集),一些服务(目前我的域名不能真正掌握的计算)。
我需要对域进行更多解释。 该类负责“DP”概念附带的所有用例(20种方法,其中一些方法只有另一种方法的不同)。这个概念在“工作流程”结束时(这里使用的术语很宽松):
想法:
任何利弊/想法?
非常感谢您的时间/答案。
答案 0 :(得分:1)
是的,这是一个熟悉的代码气味;或者反过来 - 你已经完成了工作测试,但你必须继续回到他们那里去寻找另一种方法来保持测试的编译....如果每个服务有3个方法,但我的appService只需要一个,那么我需要知道哪个方法可以模拟或模拟/存根所有方法(以及模拟/存根2/3无用的方法)。
读完一篇文章(叔叔鲍勃)后,我在想:我做错了。也许appService需要表达/声明一个它需要什么的接口。
是。应用服务声明了一些服务提供者接口的风格,这使它成为一种与实现无关的方式来描述它需要运行的上下文。
如果有帮助,这类似于在域模型中使用Repository或Domain Service接口。
但如何命名呢?
命名很糟糕:
怎么分裂呢?我不确定将相关服务放在一起是个好主意。
我开始考虑聚合设计。我的意思是这个;我们通常通过想象模型中实体的结构来开始考虑聚合。但结构是一个非常静态的东西;域模型中最重要的不是两个状态的状态是按结构连接的,而是那些状态位的变化是由业务规则连接的。
这在应用程序服务级别转化为什么?也就是说,如果应用程序服务支持多个用例,那么这些用例的共同点是什么?除了&#34之外应该有一些动机;很容易实现这种方式"。我自己也没有答案,但我认为这是正确问题的方向。
您如何测试您的appServices? (仅限单元或集成测试?)
两者,理想情况下。应用程序服务所在的模块具有测试,其中测试本身充当服务提供者。集成检查将许多模块连接在一起以提供服务,然后针对程序集运行一系列独立的测试。
您是否认为拆分注入的服务(" facade"喜欢或更多业务或其他什么)是一种好方法?
是的,但并非没有成本。外墙本身非常适合记录实际发生的事情。但每个外观至少需要一个实现,可能更多(测试存根等)。你要为某个地方付出代价;模块中更复杂的布线,寻找某些服务的实现实际存在的位置,尝试查找名称类/包/模块,以便您可以保持一切顺利等。
答案 1 :(得分:0)
我们过去曾经多次遇到过这个问题,我们有类似工作流的逻辑,因此我们有很多依赖。
我们解决这个问题的方法是使用管道/责任链模式等模式。因此,如果存在具有10+依赖性的工作流,我们将工作流的每个阶段划分为单独的域服务,例如检查银行详细信息的工作流程中将存在以下服务:
initial-validation
check-name
check-account
check-for-fraudulent-behaviour
event-publication
result-builder
save-result
每个域服务都将实现以下接口:
using System;
public interface IService
{
bool CanExecute(
IContext subject);
Context Execute(
IContext subject);
}
请注意,某些域服务可能会也可能不会执行,例如如果初始验证失败,则无需检查名称。但是在保存结果中,无论成功还是失败,它都将始终执行
然后,您尝试测试的工作流程,而不是注入10+依赖项,它只是注入域服务的集合(管道)。
这种模式非常出色,因为您仍然可以将代码链接在一起以创建工作流逻辑,但代码的可测试性,可扩展性等要大得多。
在测试方面,每个域服务实现都可以轻松地在单元级别进行测试,每个域服务实现可能都有自己的依赖关系,例如存储库/外部服务等。工作流本身也很容易测试,就像现在你一样需要模拟你想要的管道行为 - 这是相对简单的。
我们经常会喜欢管道构建器来帮助管道测试,以便重用管道构建代码。
最后,我们在每个域服务实现中使用的Context对象将所有域服务数据结构链接在一起。每个域服务的每个结果都在上下文中携带,请参阅下面的示例:
public interface IContext
{
InitialValidation.IResult InitialValidationResult { get; }
CheckName.IResult CheckNameResult { get; }
CheckAccount.IResult CheckAccountResult { get; }
CheckFraud.IResult CheckFraudResult { get; }
EventPublish.IResult EventPublishResult { get; }
ResultBuild.IResult ResultBuildResult { get; }
Save.IResult SaveResult { get; }
}
啰嗦的答案,但很难在几个段落中描述。简而言之,将工作流划分为有序域服务的集合。这简化了依赖结构,使代码更容易测试。