【问题标题】:How do you mock a Sealed class?你如何模拟一个密封类?
【发布时间】:2008-08-09 00:14:22
【问题描述】:

Mocking sealed classes 可能会很痛苦。我目前更喜欢Adapter pattern 来处理这个问题,但有些事情总是让人感觉很奇怪。

那么,模拟密封类的最佳方式是什么?

Java 答案非常受欢迎。事实上,我预计 Java 社区处理这个问题的时间会更长,并且可以提供很多东西。

但这里有一些 .NET 意见:

【问题讨论】:

    标签: unit-testing language-agnostic tdd mocking


    【解决方案1】:

    对于 .NET,您可以使用 TypeMock 之类的东西,它使用分析 API 并允许您挂钩对几乎任何东西的调用。

    【讨论】:

    • +1。使用正确的工具。不要让工具决定你应该如何做事。 API 是为人服务的,而不是为工具服务的——这样设计它们。在有意义的地方使用 DI 和接口以及完全解耦,而不仅仅是因为您需要它来测试工具。
    • Pavel - 问题在于 .NET 中的“正确”方式通常会引导您走上“使用 TypeMock 进行测试”的道路。走一条只有一个测试工具可用的路径似乎也不是很好。
    • 有些人可能不喜欢每月支付 80 美元购买测试框架。
    • 我认为 Moles 允许您使用密封类 - 如果您订阅了 MSDN,它是免费的。
    • AFAIK 这些可以模拟密封类、静态等的框架的主要问题之一是性能。就我个人而言,我更喜欢创建一个接口并在我的生产代码中使用它,在生产期间注入一个继承代理对象,在测试期间注入一个模拟对象。
    【解决方案2】:

    我的一般经验法则是,我需要模拟的对象也应该有一个通用接口。我认为这在设计上是正确的,并且使测试更容易(如果你做 TDD,通常是你得到的)。有关这方面的更多信息,请参阅 Google 测试博客latest post(参见第 9 点)。

    另外,在过去的 4 年里,我主要从事 Java 方面的工作,我可以说,我创建最终(密封)类的次数一方面是可以数得来的。这里的另一条规则是我应该始终有充分的理由来密封一个课程,而不是默认密封它。

    【讨论】:

    • 我认为你应该有充分的理由来封课。让一个类保持开放意味着您需要考虑继承者将如何使用它,这会打开关于类中所有代码(虚拟性、受保护属性或私有成员变量等)的决策闸门。它是 很难 正确地设计一个继承类。你不应该仅仅为了扩展而扩展一个类;推导应该意味着特定于正在建模的问题。否则,请改用构图。
    • @abyx:不幸的是,一个类是否被密封并不总是开发人员的选择。以 ASP.NET 中的 System.Web.HttpServerUtility 为例...
    • @BryanWatts 我强烈反对这一点。密封一个类只是剥夺了编码人员继承该类的机会。他们可能会发现以您没有预见到的方式扩展它很有用。您不必担心他们会以某种方式搞砸事情;这(可能)不是你的责任。我实际上会取消 sealed 关键字。
    • @Jez:如果我在编写课程时没有考虑扩展,有什么理由相信它?
    • 强烈同意@Jez。很高兴看到官方指南持有相同的观点:“不要在没有充分理由的情况下密封类。因为你无法想到可扩展性场景而密封类不是一个很好的理由。” (docs.microsoft.com/en-us/dotnet/standard/design-guidelines/…) 为了保护方法不被子类滥用,只需将它们设为私有即可。还不够吗?
    【解决方案3】:

    我相信来自 Microsoft Research 的 Moles 允许您这样做。来自 Moles 页面:

    Moles 可用于绕过任何 .NET 方法,包括非虚拟/静态 密封类型中的方法。

    更新:在即将发布的 VS 11 版本中有一个名为“Fakes”的新框架,旨在取代 Moles:

    Fakes Framework in Visual Studio 11 是下一代 Moles & Stubs,最终将取代它。然而,Fakes 与 Moles 不同,因此从 Moles 迁移到 Fakes 将需要对您的代码进行一些修改。稍后将提供此迁移的指南。

    要求:Visual Studio 11 Ultimate、.NET 4.5

    【讨论】:

      【解决方案4】:

      TypeMock 的问题在于它为糟糕的设计找借口。现在,我知道它隐藏的往往是其他人的糟糕的设计,但允许它进入你的开发过程很容易导致你允许自己的糟糕设计。

      我认为,如果您要使用模拟框架,您应该使用传统的框架(如 Moq)并围绕不可模拟的东西创建一个隔离层,然后模拟隔离层。

      【讨论】:

      • 不要仅仅因为测试工具的缺陷而需要它们,就在眼前的所有事物上拍打接口,这不是糟糕的设计。事实上,恰恰相反——它是关于设计的理智设计,而不是符合你的工具。我发誓,有时我认为经常被教条实践的 TDD 真的应该被称为“工具驱动设计”。另见weblogs.asp.net/rosherove/archive/2008/01/17/…
      • Pavel - 除了 TypeMock 之外,您还有其他工具可以提供这种类型的测试吗?测试用合理设计构建的类(例如,使用静态方法、代码中的新调用、将接口限制在需要多个实现的地方等)通常几乎是不可能测试的。
      • 我同意布拉德的观点。创建模拟对象意味着您正在测试该类型的公共行为。也就是说,您明确地说,“我需要此对象的公共 API 以某种方式运行。”这种行为是独立的,不管如何实现所述 API。这意味着您有一个已经定义的(尽管是隐式的)抽象,它需要以某种方式表现。在静态类型系统中,这必须通过创建抽象类型来明确。
      【解决方案5】:

      我几乎总是避免在我的代码深处依赖外部类。相反,我更愿意使用适配器/网桥与他们交谈。这样一来,我正在处理我的语义,翻译的痛苦被隔离在一个类中。

      从长远来看,它还可以更轻松地切换我的依赖项。

      【讨论】:

        【解决方案6】:

        我最近遇到了这个问题,在阅读/搜索网页后,似乎没有简单的方法,除了使用上面提到的另一个工具。 或者像我一样粗暴地处理事情:

        • 创建密封类的实例而不调用构造函数。
        • System.Runtime.Serialization.FormatterServices.GetUninitializedObject(instanceType);

        • 通过反射将值分配给您的属性/字段

        • YourObject.GetType().GetProperty("PropertyName").SetValue(dto, newValue, null);
        • YourObject.GetType().GetField("FieldName").SetValue(dto, newValue);

        【讨论】:

        • 这是唯一有意义的解决方案,可以在不添加任何额外库(如 Fakes)的情况下模拟 Microsoft 内部内容 - 谢谢!
        【解决方案7】:

        我通常采用创建接口和适配器/代理类的方式来促进密封类型的模拟。但是,我也尝试过跳过创建接口并使用虚拟方法使代理类型不密封。当代理真的是一个自然的基类,它封装了密封类的一部分和用户部分时,这很有效。

        在处理需要这种调整的代码时,我厌倦了执行相同的操作来创建接口和代理类型,因此我实现了一个库来自动执行任务。

        该代码比您参考的文章中给出的示例要复杂一些,因为它生成一个程序集(而不是源代码),允许在任何类型上执行代码生成,并且不需要太多配置.

        更多信息请参考this page

        【讨论】:

          【解决方案8】:

          模拟一个密封类是完全合理的,因为许多框架类都是密封的。

          在我的例子中,我试图模拟 .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;
              }
          

          【讨论】:

          • 我找到了解决方案并在此发布:dotnetmonkey.net/post/Hard-to-Moq.aspx
          • 太糟糕了,页面已经死了
          • 感谢您的解决方案!该页面仍然死了
          • 可以在wayback机器上找到页面;但是,不要浪费你的时间。该帖子已被作者更新,说它实际上并没有工作。
          【解决方案9】:

          虽然它目前仅在测试版中可用,但我认为值得牢记新的Fakes frameworkshim 功能(Visual Studio 11 测试版的一部分)。

          Shim 类型提供了一种机制,可以将任何 .NET 方法绕道到用户定义的委托。 Shim 类型由 Fakes 生成器生成代码,它们使用委托(我们称为 shim 类型)来指定新方法的实现。在底层,shim 类型使用在运行时注入到方法 MSIL 主体中的回调。

          我个人正在考虑使用它来模拟密封框架类(如 DrawingContext)上的方法。

          【讨论】:

            【解决方案10】:

            有没有办法从接口实现密封类...并模拟接口?

            我觉得一开始就有密封类是错误的,但这就是我 :)

            【讨论】:

            • 我认为密封类很棒......问题在于测试。由于测试 .NET 项目的限制,我们不得不更改整个设计,这很糟糕。在许多情况下,D.I.只是一种解决静态类/方法难以测试这一事实的方法。
            猜你喜欢
            • 1970-01-01
            • 2010-10-30
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-01-13
            • 2012-02-27
            相关资源
            最近更新 更多