我们对大部分业务逻辑进行单元测试,但仍然坚持如何最好地测试我们的一些大型服务任务和导入/导出例程.例如,考虑将工资单数据从一个系统导出到第三方系统.要以公司需要的格式导出数据,我们需要达到~40个表,这会产生一个创建测试数据和模拟依赖关系的噩梦.
例如,请考虑以下(~3500行导出代码的子集):
public void ExportPaychecks()
{
var pays = _pays.GetPaysForCurrentDate();
foreach (PayObject pay in pays)
{
WriteHeaderRow(pay);
if (pay.IsFirstCheck)
{
WriteDetailRowType1(pay);
}
}
}
private void WriteHeaderRow(PayObject pay)
{
//do lots more stuff
}
private void WriteDetailRowType1(PayObject pay)
{
//do lots more stuff
}
Run Code Online (Sandbox Code Playgroud)
我们在这个特定的导出类中只有一个公共方法 - ExportPaychecks().这真的是唯一一个对调用这个类的人有意义的行为......其他一切都是私有的(约80个私有函数).我们可以将它们公开用于测试,但是我们需要模拟它们来单独测试每个(即你不能在没有模拟WriteHeaderRow函数的情况下在真空中测试ExportPaychecks.这也是一个巨大的痛苦.
由于这是单个导出,对于单个供应商而言,将逻辑移入域中是没有意义的.逻辑在此特定类之外没有域重要性.作为测试,我们构建了接近100%代码覆盖率的单元测试......但是这需要将大量的测试数据输入到存根/模拟对象中,加上超过7000行代码,因为存根/模拟我们的许多依赖项.
作为HRIS软件的制造商,我们拥有数百种进出口产品.其他公司真的对这种类型的东西进行单元测试吗?如果是这样,是否有任何捷径可以减少痛苦?我很想说"没有单元测试导入/导出例程",只是稍后实现集成测试.
更新 - 感谢所有答案.我喜欢看到的一件事就是一个例子,因为我还没有看到有人可以将像大文件导出这样的东西变成一个易于测试的代码块,而不会把代码弄得一团糟.
我们正在使用Moq对我们的服务类进行单元测试,但是他们仍然坚持如何测试服务方法调用同一类的另一个服务方法的情况.我尝试将被调用的方法设置为虚拟,但仍然无法弄清楚在Moq中要做什么.例如:
public class RenewalService : IRenewalService
{
//we've already tested this
public virtual DateTime? GetNextRenewalDate(Guid clientId)
{
DateTime? nextRenewalDate = null;
//...<snip> a ton of already tested stuff...
return nextRenewalDate;
}
//but want to test this without needing to mock all
//the methods called in the GetNextRenewalDate method
public bool IsLastRenewalOfYear(Renewal renewal)
{
DateTime? nextRenewalDate = GetNextRenewalDate(renewal.Client.Id);
if (nextRenewalDate == null)
throw new Exceptions.DataIntegrityException("No scheduled renewal date, cannot determine if last renewal of year");
if (nextRenewalDate.Value.Year != renewal.RenewDate.Year)
return true; …Run Code Online (Sandbox Code Playgroud) 我们正在开发一个网站来取代旧的绿屏数据输入应用程序.问题是我们的用户习惯于通过屏幕飞行(即盲目打字......他们从不需要看屏幕或手指).他们对移动到网络非常紧张,我希望通过向他们展示一些处理数据输入的网站示例来缓解这种紧张情绪.
有什么建议?
更新
为了澄清"飞越屏幕"的评论,这里是典型的用法:
我们正在构建一个ASP.NET项目,并将所有业务逻辑封装在服务类中.有些是在域对象中,但通常那些是相当贫血的(由于我们正在使用的ORM,这不会改变).为了更好地启用单元测试,我们为每个服务定义接口并使用DI.例如,这里有几个接口:
IEmployeeService
IDepartmentService
IOrderService
...
Run Code Online (Sandbox Code Playgroud)
这些服务中的所有方法基本上都是任务组,并且这些类不包含私有成员变量(除了对依赖服务的引用).在我们担心单元测试之前,我们只是将所有这些类声明为静态并让它们直接相互调用.现在,如果服务依赖于其他服务,我们将设置这样的类:
public EmployeeService : IEmployeeService
{
private readonly IOrderService _orderSvc;
private readonly IDepartmentService _deptSvc;
private readonly IEmployeeRepository _empRep;
public EmployeeService(IOrderService orderSvc
, IDepartmentService deptSvc
, IEmployeeRepository empRep)
{
_orderSvc = orderSvc;
_deptSvc = deptSvc;
_empRep = empRep;
}
//methods down here
}
Run Code Online (Sandbox Code Playgroud)
这通常不是一个问题,但我想知道为什么不建立一个我们传递的工厂类呢?
即
public ServiceFactory
{
virtual IEmployeeService GetEmployeeService();
virtual IDepartmentService GetDepartmentService();
virtual IOrderService GetOrderService();
}
Run Code Online (Sandbox Code Playgroud)
然后而不是调用:
_orderSvc.CalcOrderTotal(orderId)
Run Code Online (Sandbox Code Playgroud)
我们打电话
_svcFactory.GetOrderService.CalcOrderTotal(orderid)
Run Code Online (Sandbox Code Playgroud)
这种方法的缺点是什么?它仍然是可测试的,它仍然允许我们使用DI(并通过工厂内外的DI处理外部依赖关系,如数据库上下文和电子邮件发件人),它消除了大量的DI设置并更多地整合了依赖关系.
谢谢你的想法!
我们的所有报告都是从从我们的域对象转换而来的对象图创建的.为了实现这一点,我们为每个报告都有一个Translator类,并且一直使用依赖注入来传递依赖关系.
这很好用,会产生这样结构很好的类:
public class CheckTranslator : ICheckTranslator
{
public CheckTranslator (IEmployeeService empSvc
, IPaycheckService paySvc)
{
_empSvc = empSvc;
_paySvc = paySvc;
}
public Check CreateCheck()
{
//do the translation...
}
}
Run Code Online (Sandbox Code Playgroud)
但是,在某些情况下,映射具有许多不同的分组选项.结果,c-tor将变成类依赖性和参数的混合.
public class CheckTranslator : ICheckTranslator
{
public CheckTranslator (IEmployeeService empSvc
, IPaycheckService paySvc
, bool doTranslateStubData
, bool doAttachLogo)
{
_empSvc = empSvc;
_paySvc = paySvc;
_doTranslateStubData = doTranslateStubData;
_doAttachLogo = doAttachLogo;
}
public Check CreateCheck()
{
//do the translation...
}
}
Run Code Online (Sandbox Code Playgroud)
现在,我们仍然可以测试它,但它不再适用于IoC容器,至少以干净的方式.另外,如果每次检查的设置不同,我们就不能再调用CreateCheck两次.
虽然我认识到这是一个问题,但我不一定能看到正确的解决方案.为每个班级创建一个工厂似乎有点奇怪...或者这是最好的方法吗?
使用域对象时,您通常如何对调用对象中另一个方法的方法进行单元测试?例如:
public class Invoice
{
public IList<InvoiceLine> InvoiceLines;
public string Company;
public bool IsDiscounted;
public DateTime InvoiceDate;
//...
public GetTotalAmt();
public GetExtendedTotalAmt();
public decimal GetTotalAmt()
{
decimal total;
foreach (InvoiceLine il in InvoiceLines)
{
total += il.Qty * il.Price;
}
return total;
}
public decimal GetExtendedTotalAmt()
{
decimal discount;
if (IsDiscounted)
discount = .05M;
return GetTotalAmt() * discount;
}
}
Run Code Online (Sandbox Code Playgroud)
单元测试GetTotalAmt()很简单,但是使用GetExtendedTotalAmt()我必须使用存根/模拟InvoiceLine对象来使它工作,当我真正想做的是测试如果IsDiscounted标志为真则应用折扣.
别人怎么处理这个?我不认为拆分域对象是有意义的,因为这些方法都被认为是核心发票功能的一部分(并且拆分它可能会导致开发人员更频繁地调用错误的方法).
谢谢!