C# ASP.net:依赖于web服务代理类的单元测试

C# ASP.net:依赖于web服务代理类的单元测试,c#,.net,asp.net,web-services,unit-testing,C#,.net,Asp.net,Web Services,Unit Testing,假设我有一门课,如下所示: public class Test{ private RemoteDoc.Documentation docService = new RemoteDoc.Documentation(); public Test(){} } 因此,这使得单元测试变得困难,因为存在对代理类的依赖。您可以通过构造函数传入对象,如下所示: public class Test{ private RemoteDoc.Documentation

假设我有一门课,如下所示:

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;
        }
}
现在在我的单元测试中,我可以实例化测试类并将模拟对象传递到构造函数中。然而,这个解决方案并不理想,因为现在其他类必须了解RemoteDoc.Documentation代理类,并对其进行显式引用。解决这个问题的好办法是什么


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

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

一开始看起来像是缺点的东西,实际上却变成了优点。虽然测试类的每个使用者(在您的示例中)现在都必须提供代理的一些实现(我在此假设代理是一个接口或抽象基类),但他们可以提供该抽象的任何实现,而不仅仅是您最初想到的实现。祝贺你:

这仍然留下了一个问题,即您实际将决定哪些依赖关系的责任放在哪里?您应该在应用程序的根目录中一个名为合成根目录的位置执行此操作。这将在中详细解释

您可以使用DI容器自动连接部门。一些常见的DI容器包括:


I第二个马克的方法。为完整起见,另一种选择如下:

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容器方法通常是一种更好的方法。

我喜欢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 {}
}

请注意,使用WCF要容易得多,因为服务契约已经是一个接口。您的模拟类只需要实现接口。

这种方法怎么样?您必须编写更多代码来支持代理类中的功能。但它将为您提供单元测试的灵活性

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;
        }
}

我知道依赖注入,并在应用程序的其他部分使用windsor。然而,RemoteDoc.Documentation是web引用的代理类(例如,假设我正在使用salesforce.com的web服务)。因此,它不存在于程序集中,也不是接口,我无法修改它。在这种情况下,您可以使用依赖项注入吗?@aspnetuser:如果RemoteDoc.Documentation是代理类,您将无法模拟它,这使得您的大部分问题都没有意义。但是,您可以提取该类的接口,并针对该接口编写程序。如果确实需要,您还可以包装代理类并在其上应用该接口。无论哪种方式,某种形式的注入都是最好的选择,因为它提供了最多的控制。不幸的是,我无法修改Documentation类,因为它是一个web引用(请参见我的编辑)。如果我可以修改它,我已经做了这件事,这也是我不确定该做什么的原因。Visual Studio将从您的wsdl和disco类生成一个具体的代理类。自动生成的代码通常包含在References.cs文件中。然后在另一个文件中,您将放入第二个摘录中显示的代码。无需修改原始代理类代码(在References.cs中)。代码可以位于References.cs以外的文件中的原因是“public partial class Documentation:IDocumentation”中的“partial”关键字。