我正在尝试为一个庞大的项目编写单元测试,而在编码时却从未实现过可测试性。我已经开始模拟对象并编写测试,但是我意识到我必须重构很多代码才能模拟它。
这是我要创建测试的方法之一:
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
的进一步重构将更加容易,因为您不会考虑如何将其功能分组为较小的类-这些已经由接口定义。