【问题标题】:ASP.net: Unit testing with dependency on web service proxy classASP.net:依赖于 Web 服务代理类的单元测试
【发布时间】:2009-10-01 19:59:15
【问题描述】:

假设我有一个类似以下的课程:

public class Test{
        private RemoteDoc.Documentation docService = new RemoteDoc.Documentation();
        public Test(){}
}

所以这使得单元测试变得困难,因为它依赖于代理类。您可以像这样通过构造函数传入对象:

public class Test{
        private RemoteDoc.Documentation docService;
        public Test(RemoteDoc.Documentation serv)
        {
               docService = serv;
        }
}

现在在我的单元测试中,我可以实例化 Test 类并将一个模拟对象传递给构造函数。但是,此解决方案并不理想,因为现在其他类必须了解 RemoteDoc.Documentation 代理类并对其进行显式引用。有什么好的办法解决这个问题?

编辑:更清楚地说,RemoteDoc.Documentation 是 Web 参考的代理类。想象一下,如果您使用的是 salesforce.com 的 api,而您真正拥有的只是 wsdl 和 disco 文件。

【问题讨论】:

    标签: c# .net asp.net web-services unit-testing


    【解决方案1】:

    实际上,您提出的涉及通过构造函数传递依赖项的解决方案是理想的。它是一种众所周知的依赖注入 (DI) 模式,称为 构造函数注入

    起初看起来像弱点的东西实际上是一种优势。虽然 Test 类的每个消费者(在您的示例中)现在确实必须提供代理的 some 实现(我在此假设代理是接口或抽象基类),但他们可以提供该抽象的任何实现,而不仅仅是您最初想到的。恭喜:you have just opened your class for extensibility

    这仍然留下了一个问题,即您实际上将决定哪些依赖项放在哪里的责任放在哪里?您应该在应用程序的根目录中称为 Composition Root 的地方执行此操作。这在this SO answer 中有更详细的解释。

    您可以使用 DI 容器自动连接您的依赖项。一些常见的 DI 容器是:

    【讨论】:

    • 我知道依赖注入,并在我的应用程序的其他部分使用 Windsor。但是,RemoteDoc.Documentation 是 Web 引用的代理类(例如,假设我正在使用 salesforce.com 的 Web 服务)。因此,它不存在于程序集中,不是接口,我无法修改它。这种情况下可以使用依赖注入吗?
    • @aspnetuser:如果 RemoteDoc.Documentation 是一个代理类,您将无法模拟它,从而使您的大部分问题变得毫无意义。但是,您可以提取该类的接口并针对该接口进行编程。
    • 如果您真的需要,您也可以包装代理类并在其上应用接口。无论哪种方式,某种形式的注入都是可行的方法,因为它可以为您提供最大的控制权。
    【解决方案2】:

    我喜欢 RichardOD 的方法。对单元测试有用的一种改进是使用模拟对象而不是访问真实的 Web 服务。这意味着您的测试将与任何外部服务分离,并且运行得更快。

    如果将代码更改为如下所示,您可以这样做:

    public class Test
    {        
         private RemoteDoc.IDocumentation docService;     
    
         // Constructor providing default for docService
         public Test()
         {
             docService = new RemoteDoc.Documentation();
         }   
    
         // Constructor for injection
         public Test(RemoteDoc.IDocumentation serv)       
         { 
              docService = serv;        
         }
    }
    

    然后您使用模拟框架创建一个模拟文档对象,例如:

    ... 并将其传递给 Test(RemoteDoc.IDocumentation serv) 构造函数。

    由于 RemoteDoc.Documentation 是一个具体的类,您可以使用分部类使其继承 RemoteDoc.IDocumentation:

    namespace RemoteDoc
    {
        public interface IDocumentation
        {
            // public functions you want to mock go here
            string GetDocumentation();
        }
    
        public partial class Documentation : IDocumentation {}
    }
    

    【讨论】:

    • 不幸的是,我无法修改文档类,因为它是一个网络参考(请参阅我的编辑)。如果我可以修改它,我早就做了这件事,这就是我不确定该怎么做的原因。
    • 从您的 wsdl 和 disco 类中,Visual Studio 将生成一个具体的代理类。自动生成的代码通常包含在文件 References.cs 中。然后在另一个文件中,您将放置第二个摘录中显示的代码。无需修改原代理类代码(在References.cs中)
    • 代码可以在 References.cs 以外的文件中的原因是因为 'public partial class Documentation : IDocumentation' 中的 'partial' 关键字。
    【解决方案3】:

    我赞同马克的做法。为了完整起见,另一个选项如下:

    public class Test
    {        
         private RemoteDoc.Documentation docService;     
    
         // Constructor providing default for docService
         public Test()
         {
             docService = new RemoteDoc.Documentation();
         }   
    
         // Constructor for injection
         public Test(RemoteDoc.Documentation serv)       
         { 
              docService = serv;        
         }
    }
    

    这意味着您有一个默认实现以及插入不同实现的选项。如果您不想使用容器,这很有用。

    我过去使用过这两种方法。在开发非平凡软件时,DI 容器方法通常是更好的选择。

    【讨论】:

      【解决方案4】:

      请注意,使用 WCF 更容易,因为服务协定已经是一个接口。你的模拟类只需要实现接口。

      【讨论】:

        【解决方案5】:

        这种方法怎么样?您将不得不编写更多代码来支持代理类中的功能。但它会给你单元测试的灵活性。

        public interface IDocumentation
        {
            // Add whatever functionality you need from RemoteDoc.Documentation
        }
        
        public class RemoteDocumnetation : IDocumentation
        {
            private RemoteDoc.Documentation docService = new RemoteDoc.Documentation();
        
            // Implements IDocumentation 
        }
        
        public class Test{
                private IDocumentation doc;
                public Test(IDocumentation serv)
                {
                       doc= serv;
                }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-09-26
          • 1970-01-01
          • 2012-12-23
          • 1970-01-01
          相关资源
          最近更新 更多