模拟密封类可能会很痛苦。我目前喜欢使用适配器模式来处理这个问题,但有些东西总是让人感觉很奇怪。
那么,模拟密封类的最佳方式是什么?
Java 答案非常受欢迎。事实上,我预计 Java 社区处理这个问题的时间会更长,并且可以提供很多东西。
但这里有一些 .NET 意见:
对于 .NET,您可以使用TypeMock之类的东西,它使用分析 API 并允许您挂钩对几乎任何东西的调用。
我的一般经验法则是,我需要模拟的对象也应该有一个通用接口。我认为这在设计上是正确的,并且使测试更容易(如果你做 TDD,通常是你得到的)。有关这方面的更多信息,请参阅 Google 测试博客的最新帖子(请参阅第 9 点)。
另外,在过去的 4 年里,我主要在 Java 领域工作,我可以说我一方面可以数出我创建最终(密封)类的次数。这里的另一条规则是我应该始终有充分的理由来密封一个课程,而不是默认密封它。
我相信来自 Microsoft Research 的Moles可以让您做到这一点。从痣页面:
Moles 可用于绕过任何 .NET 方法,包括密封类型中的非虚拟/静态方法。
更新:在即将发布的 VS 11 版本中有一个名为“Fakes”的新框架,旨在取代 Moles:
Visual Studio 11中的Fakes Framework是 Moles & Stubs 的下一代,最终将取代它。然而,Fakes 与 Moles 不同,因此从 Moles 转移到 Fakes 将需要对您的代码进行一些修改。稍后将提供此迁移的指南。
要求:Visual Studio 11 Ultimate,.NET 4.5
TypeMock 的问题在于它为糟糕的设计找借口。现在,我知道它隐藏的往往是别人的糟糕设计,但是允许它进入你的开发过程很容易导致你自己的糟糕设计被允许。
我认为如果你要使用一个模拟框架,你应该使用一个传统的框架(比如 Moq)并围绕不可模拟的东西创建一个隔离层,然后模拟隔离层。
我几乎总是避免在我的代码深处依赖外部类。相反,我更愿意使用适配器/网桥与他们交谈。这样,我正在处理我的语义,翻译的痛苦被隔离在一个类中。
从长远来看,它还可以更轻松地切换我的依赖项。
我最近遇到了这个问题,在阅读/搜索网页之后,似乎除了使用上面提到的另一个工具之外没有简单的方法。或者像我一样粗暴地处理事情:
System.Runtime.Serialization.FormatterServices.GetUninitializedObject(instanceType);
通过反射为您的属性/字段赋值
我通常采用创建接口和适配器/代理类的方法来促进密封类型的模拟。但是,我也尝试过跳过创建接口并使用虚拟方法使代理类型不密封。当代理真的是一个自然的基类,它封装了密封类的一部分和用户部分时,这很有效。
在处理需要这种调整的代码时,我厌倦了执行相同的操作来创建接口和代理类型,因此我实现了一个库来自动化任务。
该代码比您参考的文章中给出的示例要复杂一些,因为它生成程序集(而不是源代码),允许在任何类型上执行代码生成,并且不需要太多配置。
欲了解更多信息,请参阅此页面。
模拟密封类是完全合理的,因为许多框架类都是密封的。
就我而言,我正在尝试模拟 .Net 的 MessageQueue 类,以便我可以对优雅的异常处理逻辑进行 TDD。
如果有人对如何克服 Moq 关于“不可覆盖成员的无效设置”的错误有任何想法,请告诉我。
代码:
[TestMethod]
public void Test()
{
Queue<Message> messages = new Queue<Message>();
Action<Message> sendDelegate = msg => messages.Enqueue(msg);
Func<TimeSpan, MessageQueueTransaction, Message> receiveDelegate =
(v1, v2) =>
{
throw new Exception("Test Exception to simulate a failed queue read.");
};
MessageQueue mockQueue = QueueMonitorHelper.MockQueue(sendDelegate, receiveDelegate).Object;
}
public static Mock<MessageQueue> MockQueue
(Action<Message> sendDelegate, Func<TimeSpan, MessageQueueTransaction, Message> receiveDelegate)
{
Mock<MessageQueue> mockQueue = new Mock<MessageQueue>(MockBehavior.Strict);
Expression<Action<MessageQueue>> sendMock = (msmq) => msmq.Send(It.IsAny<Message>()); //message => messages.Enqueue(message);
mockQueue.Setup(sendMock).Callback<Message>(sendDelegate);
Expression<Func<MessageQueue, Message>> receiveMock = (msmq) => msmq.Receive(It.IsAny<TimeSpan>(), It.IsAny<MessageQueueTransaction>());
mockQueue.Setup(receiveMock).Returns<TimeSpan, MessageQueueTransaction>(receiveDelegate);
return mockQueue;
}
尽管它目前仅在 beta 版本中可用,但我认为值得牢记新Fakes 框架(Visual Studio 11 Beta 版本的一部分)的shim功能。
Shim 类型提供了一种将任何 .NET 方法绕道到用户定义的委托的机制。Shim 类型由 Fakes 生成器生成代码,它们使用委托(我们称为 shim 类型)来指定新方法的实现。在底层,shim 类型使用在运行时注入到方法 MSIL 主体中的回调。
就我个人而言,我正在考虑使用它来模拟密封框架类(如 DrawingContext)上的方法。
有没有办法从接口实现密封类......并模拟接口?
我觉得一开始就有密封的课程是错误的,但这就是我:)