【发布时间】:2014-02-27 20:35:13
【问题描述】:
我在处理一个大型应用程序。代码库大多分为各种任务,每个任务通过 DI 接收其依赖项(通常是存储库),例如这个简化的理论类:
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... ) );
}
}
我的任务是弄清楚如何对GetUsersWithNotices 进行单元测试。测试需要验证方法只返回符合条件的用户。
我不知道从哪里开始。使用 Moq,我可以验证是否调用了 FindAll 方法:
[TestClass]
public class EmailTasksTest
{
private Mock<IUserRepository> userRepositoryMock;
[TestInitialize]
public void MyTestInitialize()
{
userRepositoryMock = new Mock<IUserRepository>();
}
[TestMethod]
public void SendNoticeEmailTest()
{
var minDate = DateTime.Today.AddDays( -30 );
var maxDate = DateTime.Today.AddDays( 30 );
var user1 = new Mock<User>();
var user2 = new Mock<User>();
userRepositoryMock.Setup( r => r.FindAll( It.IsAny<Expression<Func<User, bool>>>() ) )
.Returns( new List<User>
{
user1Mock.Object,
user2Mock.Object
}.AsQueryable() )
.Verifiable();
var tasks = new EmailTasks( userRepositoryMock.Object );
task.SendNoticeEmail( minDate, maxDate, etc... );
userRepositoryMock.Verify();
}
}
显然,这并不能测试用户是否符合条件。我针对模拟 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... ) );
}
}
这会导致 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:
我想 NHibernate 转换成 SQL 太复杂了。
【问题讨论】:
-
就个人而言(因为可能有很多解决方案),我会在您的存储库和域逻辑之间创建一个新层。这一层将封装所有这些查询(仅这些查询就很容易测试)。或者甚至可以直接在您的存储库中添加该查询逻辑。在 EmailTask 中,您将能够抽象此查询逻辑(通过模拟新层)以具有易于测试的操作。因为目前,您的班级并不是“只做一件事”,而这很难测试。
-
提取查询的任何部分的问题是它会导致 NHibernate 抛出异常。我将编辑我的问题以反映我刚刚尝试过的内容。
标签: c# unit-testing nhibernate castle-windsor moq