我正在尝试为一个巨大的项目编写单元测试,在这个项目中,编码时从未尝试过可测试性。我开始嘲笑对象并编写测试,但我意识到我必须重构很多代码才能模拟它。
这是我想要为其创建测试的方法之一:
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-class处理webservice-calls,我想模仿它。 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告诉我要添加新的&#39;我的GetObjectsWithContent()的关键字 - MyDfsUtil-class中的实现。或者我可以将基类中的方法声明为虚拟,然后在实现上使用&#39;覆盖&#39; -keyword。但是如果我必须将我的方法声明为虚拟,我可以在MyDfsUtil中执行此操作,然后我不需要创建抽象基类。 我一直在阅读虚拟方法,似乎人们不同意是否使用它们。在MyDfsUtil中使用虚拟方法将使我的重构分配更容易,并且它使我能够模拟它们。对我这样的案件有什么最好的做法吗?
我试图以最好,最简单的方式做到这一点。我没有经验单元测试或嘲笑,我真的想在不引入太多复杂性的情况下这样做。
答案 0 :(得分:3)
首先,我正在考虑创建一个界面,但是呢 整个代码使用的变量(Locale,UserName等)?
您可以在界面中包含属性。
对于像我这样的案件有没有最好的做法?
我建议您使用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
。
优点:
MyDfsUtil
之间的依赖关系。易于模拟,易于理解。MyDfsUtil
方法未被使用任何客户)。 MyDfsUtil
的进一步重构将更容易,因为您不会考虑如何将其功能分组为较小的类 - 这些类已经由接口定义。答案 1 :(得分:1)
我正好在三年或更久以前。
我对你的建议是单独离开MyDfsUtil
,不要碰它
(我认为它是静态方法的静态类?)
而是创建一个界面和匹配的类(比如说ISaneMyDfsUtil
&amp; SaneMyDfsUtil
)
从您提供的一个方法开始GetDctmViewDefinitions
添加到新类并使用MyDfsUtil
方法接口GetObjectsWithContent
。这个&#34;新&#34;新类的方法只是直接委托给现有的 - 和不可测试的MyDfsUtil
类。您将此类的实例注入到测试类中。
这样做有多种原因。
使MyDfsUtil
可模拟可能并不理想。
MyDfsUtil
上的不同接口和类来实现。及时 - 当你有时间时 - 功能可以来自MyDfsUtil
并进入它实际所属的新类。MyDfsUtil
中的方法可能对您的用例返回太多。例如说您正在测试的方法需要MyDfsUtil
的客户ID列表。您调用MyDfsUtil.QueryCustomers(myOrderId);
,返回客户列表。您执行并且仅使用客户的Id属性的代码。模拟该调用时,您必须创建客户对象,设置ID,并传回客户列表。在SaneMyDfsUtil
中,您可以使用QueryCustomerIds
方法仅返回客户ID。它使得测试中的代码更加明确,并使测试更简单。我在这里有一些遗留软件,它使用了一个静态Dal对象,有数百种(如果不是数千种)方法。我写了一些自动为它生成 Sane_Object 类和接口的代码。由于努力引入接缝进行测试,它并不是很强大,但我及时了解到它远非理想,并遵循我在这里布置的模式可以节省时间和努力,并帮助我以更轻松的方式将单元测试推向团队。
我现在可以回答my own question并说,不,这不是一个好主意。
在你做了太多其他事情之前,最后一句话是The Art of Unit Testing(诚实地购买并从头到尾阅读) 然后将Working Effectively with Legacy Code放在你的桌子上,浸入其中并将其作为参考,以便当事情变得艰难时。
任何问题只是大喊