我正在尝试为一个庞大的项目编写单元测试,而在编码时却从未实现过可测试性。我已经开始模拟对象并编写测试,但是我意识到我必须重构很多代码才能模拟它。

这是我要创建测试的方法之一:

public List<DctmViewDefinition> GetDctmViewDefinitions()
{
    List<DctmViewDefinition> dctmViewDefinitions = new List<DctmViewDefinition>();
    DataPackage dataPackage = MyDfsUtil.GetObjectsWithContent();
    foreach (DataObject dataObject in dataPackage.DataObjects)
    {
        DctmViewDefinition view = GetDctmViewDefinitionFromXmlFile(dataObject);
        dctmViewDefinitions.Add(view);
    }
    return dctmViewDefinitions;
}


MyDfsUtil类处理Web服务调用,我想对其进行模拟。
MyDfsUtil分为14个部分类,每个部分类由300-500行代码组成。因此,有很多代码!

这是该类的摘录,可让您了解:

public partial class MyDfsUtil
{
    public string Locale { get; set; }
    public string DfsServiceUrl { get; set; }
    public string UserName { get; set; }

    public DataPackage GetObjectsWithContent()
    {
        //Some code here
    }


}

我正在使用Moq,因此我无法直接模拟此类(据我所知)。我必须创建一个接口,一个抽象类或使方法虚拟化。
因此,我一直试图找出的是:为了能够模拟MyDfsUtil,什么是最佳方法?

首先,我正在考虑创建一个接口,但是在整个代码中使用的变量(Locale,UserName等)又如何呢?

其次,我尝试使用所有变量创建一个抽象基类MyDfsUtilBase,并使基类中的方法返回NotImplementedException。像这样:

public abstract class MyDfsUtilBase
{
    public string Locale { get; set; }
    public string DfsServiceUrl { get; set; }
    public string UserName { get; set; }

    public void GetObjectsWithContent()
    {
        throw new NotImplementedException();
    }
}


然后,Resharper告诉我将“ new”关键字添加到MyDfsUtil类中的GetObjectsWithContent()实现中。或者,我可以在基类中将我的方法声明为virtual,然后在实现中使用“ override”关键字。但是,如果无论如何我必须声明我的方法是虚拟的,我可以在MyDfsUtil中做到这一点,那么我就不需要创建抽象的基类。
我一直在阅读有关虚拟方法的信息,似乎人们对于是否使用虚拟方法并不认同。在MyDfsUtil中使用虚拟方法将使我的重构分配更加容易,并使我能够模拟它们。是否有针对我这样的案例的最佳实践?

我正在尝试以最好,最简单的方式做到这一点。我没有单元测试或模拟的经验,我真的想在不引入太多复杂性的情况下进行测试。

最佳答案

首先,我正在考虑创建一个界面,但是
  整个代码中使用的变量(语言环境,用户名等)?


您可以在接口中包括属性。


  是否有针对我这样的案例的最佳实践?


我建议您使用Interface Segregation Principle并创建将由您的MyDfsUtil类实现的一堆小接口:

public interface IDfsService
{
    string Locale { get; set; }
    string DfsServiceUrl { get; set; }
    string UserName { get; set; }
}

public interface IDataPackageService : IDfsService
{
    DataPackage GetObjectsWithContent()
}

public interface IFooService : IDfsService
{
    Foo GetFoo();
    void DoSomethingWithFoo();
}


使MyDfsUtil实现这些小接口

public partial class MyDfsUtil : IDataPackageService, IFooService
{
    public string Locale { get; set; }
    public string DfsServiceUrl { get; set; }
    public string UserName { get; set; }

    public DataPackage GetObjectsWithContent()
    {
        //Some code here
    }

    // ...
}


然后,使其他类依赖于小型接口,而不是使用这种庞大的类。例如。您的班级只能依靠IDataPackageService

好处:


您现在不需要重构怪物类。从客户的角度来看,它似乎已经重构。稍后,您可以将基类拆分为多个小类,并进行其他重构。
您不需要与怪物类的所有成员打交道。如果要测试仅使用方法A,B和C的客户端,则可以通过引入小的简单接口来反转客户端和MyDfsUtil之间的依赖关系。易于模拟,易于理解。
这就像由内而外的开发-在为客户端编写测试之后,您将拥有一组客户端需要的接口(顺便说一句,您会惊讶-某些客户端甚至没有使用某些甚至许多MyDfsUtil方法的情况)。 MyDfsUtil的进一步重构将更加容易,因为您不会考虑如何将其功能分组为较小的类-这些已经由接口定义。

08-05 21:07