C# 如何有效地对这个复杂的查询进行单元测试?
我处理一个大型应用程序。代码库主要分为不同的任务,每个任务通过DI接收其依赖项(通常是存储库),例如这个简化的理论类:C# 如何有效地对这个复杂的查询进行单元测试?,c#,unit-testing,nhibernate,castle-windsor,moq,C#,Unit Testing,Nhibernate,Castle Windsor,Moq,我处理一个大型应用程序。代码库主要分为不同的任务,每个任务通过DI接收其依赖项(通常是存储库),例如这个简化的理论类: public class EmailTasks { public EmailTasks( IUserRepository userRepository ) { UserRepository = userRepository; } private readonly IUserRepository UserRepository;
public class EmailTasks
{
public EmailTasks( IUserRepository userRepository )
{
UserRepository = userRepository;
}
private readonly IUserRepository UserRepository;
public void SendNoticeEmail( DateTime minDate, DateTime maxDate, etc... )
{
var users = GetUsersWithNotices( minDate, maxDate, etc... );
// send email to each user
}
private IEnumerable<User> GetUsersWithNotices( DateTime minDate, DateTime maxDate, etc... )
{
return UserRepository.FindAll( u => u.Active && !u.Whatever
&& u.JoinDate > minDate && u.JoinDate < maxDate
&& u.Notices.Any( n => n.Active && !n.Something
&& Whatever
&& etc... ) );
}
}
显然,这并不能测试用户是否符合标准。我针对模拟结果编写的任何测试UserRepository.FindAll
都只会验证我模拟的内容
那么,我怎样才能有效地对这个复杂的查询进行单元测试呢
编辑
我试图从查询中分离业务逻辑:
public class EmailTasks
{
public EmailTasks( IUserRepository userRepository )
{
UserRepository = userRepository;
}
private readonly IUserRepository UserRepository;
public void SendNoticeEmail( DateTime minDate, DateTime maxDate, etc... )
{
var users = GetUsersWithNotices( minDate, maxDate, etc... );
// send email to each user
}
private IEnumerable<User> GetUsersWithNotices( DateTime minDate, DateTime maxDate, etc... )
{
return UserRepository.FindAll( u => UserIsValidForNotice( u, minDate, maxDate, etc... ) );
}
private bool UserIsValidForNotice( User user, DateTime minDate, DateTime maxDate, etc... )
{
return user.Active && !user.Whatever
&& u.JoinDate > minDate && u.JoinDate < maxDate
&& u.Notices.Any( n => n.Active && !n.Something
&& Whatever
&& etc... ) );
}
}
公共类电子邮件任务
{
公共电子邮件任务(IUserRepository userRepository)
{
UserRepository=UserRepository;
}
专用只读IUserRepository UserRepository;
公共作废SendNoticeEmail(DateTime minDate、DateTime maxDate等)
{
var users=GetUsersWithNotices(minDate、maxDate等);
//向每个用户发送电子邮件
}
private IEnumerable GetUsersWithNotices(DateTime minDate、DateTime maxDate等)
{
返回UserRepository.FindAll(u=>UserIsValidForNotice(u、minDate、maxDate等);
}
私有bool UserIsValidForNotice(用户用户、DateTime minDate、DateTime maxDate等)
{
返回user.Active&&!user.where
&&u.JoinDate>minDate&&u.JoinDaten.Active&&!n.Something)
&&随便
&&),;
}
}
这会导致NHibernate引发异常:
System.ServiceModel.FaultException`1 was unhandled
HResult=-2146233087
Message=Boolean UserIsValidForNotice(User, System.DateTime, System.DateTime)
Source=Castle.Facilities.WcfIntegration
StackTrace:
at System.ServiceModel.Channels.ServiceChannel.ThrowIfFaultUnderstood(Message reply, MessageFault fault, String action, MessageVersion version, FaultConverter faultConverter)
at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
at Castle.Facilities.WcfIntegration.Proxy.WcfRemotingInterceptor.InvokeRealProxy(RealProxy realProxy, WcfInvocation wcfInvocation)
at Castle.Facilities.WcfIntegration.Proxy.WcfRemotingInterceptor.<>c__DisplayClass1.<PerformInvocation>b__0(WcfInvocation wcfInvocation)
at Castle.Facilities.WcfIntegration.Proxy.WcfRemotingInterceptor.ApplyChannelPipeline(Int32 policyIndex, WcfInvocation wcfInvocation, Action`1 action)
at Castle.Facilities.WcfIntegration.Proxy.WcfRemotingInterceptor.<>c__DisplayClass4.<ApplyChannelPipeline>b__3()
at Castle.Facilities.WcfIntegration.WcfInvocation.Proceed()
at Castle.Facilities.WcfIntegration.RefreshChannelPolicy.Apply(WcfInvocation invocation)
at Castle.Facilities.WcfIntegration.Proxy.WcfRemotingInterceptor.ApplyChannelPipeline(Int32 policyIndex, WcfInvocation wcfInvocation, Action`1 action)
at Castle.Facilities.WcfIntegration.Proxy.WcfRemotingInterceptor.PerformInvocation(IInvocation invocation, Action`1 action)
at Castle.Facilities.WcfIntegration.Proxy.WcfRemotingInterceptor.PerformInvocation(IInvocation invocation)
at Castle.Facilities.WcfIntegration.Async.WcfRemotingAsyncInterceptor.PerformInvocation(IInvocation invocation)
at Castle.Facilities.WcfIntegration.Proxy.WcfRemotingInterceptor.Intercept(IInvocation invocation)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.IEmailServiceProxy.SendNoticeEmail()
at [Excised]
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
未处理System.ServiceModel.FaultException`1
HResult=-2146233087
Message=Boolean UserIsValidForNotice(User,System.DateTime,System.DateTime)
Source=Castle.Facilities.wcfinintegration
堆栈跟踪:
在System.ServiceModel.Channels.ServiceChannel.ThrowiffaultUnderstanding处(消息回复、消息故障、字符串操作、消息版本、故障转换器故障转换器)
位于System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime操作,ProxyRpc&rpc)
在System.ServiceModel.Channels.ServiceChannel.Call(字符串操作、布尔单向、ProxyOperationRuntime操作、对象[]输入、对象[]输出、时间跨度超时)
位于System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage方法调用,ProxyOperationRuntime操作)
位于System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage消息)
位于Castle.Facilities.wcfinintegration.Proxy.WcfRemotingInterceptor.invokeralproxy(RealProxy RealProxy,wcfinocation wcfinocation)
在Castle.Facilities.wcfinintegration.Proxy.WcfRemotingInterceptor.c_udisplayClass1.b_u0(wcfinocation-wcfinocation)
位于Castle.Facilities.wcfinintegration.Proxy.WcfRemotingInterceptor.ApplyChannelPipeline(Int32 policyIndex,wcfinocation,wcfinocation,Action`1 Action)
在Castle.Facilities.wcfinintegration.Proxy.WcfRemotingInterceptor.c_udisplayClass4.b_u3()中
在Castle.Facilities.wcfinintegration.wcfinocation.procedure()中
在Castle.Facilities.wcfinintegration.RefreshChannelPolicy.Apply(wcfinCallation调用)
位于Castle.Facilities.wcfinintegration.Proxy.WcfRemotingInterceptor.ApplyChannelPipeline(Int32 policyIndex,wcfinocation,wcfinocation,Action`1 Action)
位于Castle.Facilities.wcfinintegration.Proxy.WcfRemotingInterceptor.PerformInvocation(IInvocation调用,操作'1操作)
在Castle.Facilities.wcfinintegration.Proxy.WcfRemotingInterceptor.PerformInvocation(IInvocation调用)中
位于Castle.Facilities.wcfinintegration.Async.WcfRemotingAsyncInterceptor.PerformInvocation(IInvocation调用)
在Castle.Facilities.wcfinintegration.Proxy.WcfRemotingInterceptor.Intercept(IInvocation调用)
在Castle.DynamicProxy.AbstractInvocation.procedure()中
在Castle.proxy.IEmailServiceProxy.SendNoticeEmail()上
在[切除]
位于System.AppDomain.\u nExecuteAssembly(RuntimeAssembly程序集,字符串[]args)
位于System.AppDomain.ExecuteAssembly(字符串汇编文件、证据汇编安全性、字符串[]args)
在Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()上
位于System.Threading.ThreadHelper.ThreadStart\u上下文(对象状态)
位于System.Threading.ExecutionContext.RunInternal(ExecutionContext ExecutionContext、ContextCallback回调、对象状态、布尔值preserveSyncCtx)
在System.Threading.ExecutionContext.Run(ExecutionContext ExecutionContext,ContextCallback回调,对象状态,布尔保存SyncCTX)
在System.Threading.ExecutionContext.Run(ExecutionContext ExecutionContext,ContextCallback回调,对象状态)
位于System.Threading.ThreadHelper.ThreadStart()处
内部异常:
我想NHibernate转换为SQL太复杂了。[Edit]
我做了一些额外的研究,最后问了同样的问题,得到了一些很好的答案:
这可能是使用存根而不是Moq的好地方,如下所示:
public class StubRepo : IUserRepository
{
public IList<User> PersonList { get; set; }
public IList<User> FindAll(Func<User, bool> q)
{
return PersonList.Where(q).ToList();
}
}
公共类StubRepo:IUserRepository
{
公共IList个人列表{get;set;}
公共IList FindAll(职能q)
{
return PersonList.Where(q.ToList();
}
}
然后,您可以传入一个虚拟的人员列表,并验证返回的人员是否满足您的条件。因为GetUsersWithValidNotices是私有的,所以您可以使用Moq来验证您的电子邮件发送逻辑只被调用了一次。它看起来像这样:
[TestMethod]
public void TestMethod1()
{
//Arrange
var userList = new List<User>();
userList .Add(new User { Name="Mike", Active = false });
userList .Add(new User { Name="Mary", Active = true });
var stubRepo = new StubRepo{ PersonList = userList});
var emailSender = Mock<IEmailSender>();
var emailTask = new EmailTask(stubRepo);
emailTask.EmailSender = emailSender.Object;
//Action
emailTask.SendNoticeEmail(.....);
//Assert - Verify email only sent to the one active user
emailSender.Verify(x => x.SendEmail(It.IsAny<User>()), Times.Once())
}
[TestMethod]
公共void TestMethod1()
{
//安排
var userList=新列表();
添加(新用户{Name=“Mike”,Active=false});
添加(新用户{Name=“Mary”,Active=true});
var stubRepo=new stubRepo{PersonList=userList});
var emailSender=Mock();
var emailTask=新的emailTask(stubRepo);
emailTask.EmailSender=EmailSender.Object;
//行动
emailTask.SendN
[TestMethod]
public void TestMethod1()
{
//Arrange
var userList = new List<User>();
userList .Add(new User { Name="Mike", Active = false });
userList .Add(new User { Name="Mary", Active = true });
var stubRepo = new StubRepo{ PersonList = userList});
var emailSender = Mock<IEmailSender>();
var emailTask = new EmailTask(stubRepo);
emailTask.EmailSender = emailSender.Object;
//Action
emailTask.SendNoticeEmail(.....);
//Assert - Verify email only sent to the one active user
emailSender.Verify(x => x.SendEmail(It.IsAny<User>()), Times.Once())
}
private IEnumerable<User> GetUsersWithNotices( DateTime minDate, DateTime maxDate, etc... )
{
return UserRepository.FindAll( u => u.Active && !u.Whatever
&& u.JoinDate > minDate && u.JoinDate < maxDate
&& u.Notices.Any( n => n.Active && !n.Something
&& Whatever
&& etc... ) );
}