C# 调用静态方法的单元测试方法的模式或实践
最近,我一直在认真思考“模拟”从我试图测试的类调用的静态方法的最佳方法。以以下代码为例:C# 调用静态方法的单元测试方法的模式或实践,c#,unit-testing,function,static,mocking,C#,Unit Testing,Function,Static,Mocking,最近,我一直在认真思考“模拟”从我试图测试的类调用的静态方法的最佳方法。以以下代码为例: using (FileStream fStream = File.Create(@"C:\test.txt")) { string text = MyUtilities.GetFormattedText("hello world"); MyUtilities.WriteTextToFile(text, fStream); } 我知道这是一个相当糟糕的示例,但它有三个静态方法调用,它们都略有
using (FileStream fStream = File.Create(@"C:\test.txt"))
{
string text = MyUtilities.GetFormattedText("hello world");
MyUtilities.WriteTextToFile(text, fStream);
}
我知道这是一个相当糟糕的示例,但它有三个静态方法调用,它们都略有不同。File.Create函数访问文件系统,我不拥有该函数。MyUtilities.GetFormattedText是我拥有的一个函数,它完全是无状态的。最后,MyUtilities.WriteTextToFile是我拥有的一个函数,它访问文件系统
我最近一直在思考的是,如果这是遗留代码,我如何重构它以使其更易于单元测试。我听过一些关于不应该使用静态函数的论点,因为它们很难测试。我不同意这个观点,因为静态函数是有用的,我不认为仅仅因为正在使用的测试框架不能很好地处理它,就应该放弃一个有用的工具
经过大量的搜索和思考,我得出结论,基本上有4种模式或实践可以用来使调用静态函数的函数单元可测试。这些措施包括:
public class MyInstanceClass
{
private Action<string, FileStream> writeFunction = delegate { };
public MyInstanceClass(Action<string, FileStream> functionDependency)
{
writeFunction = functionDependency;
}
public void DoSomething2()
{
using (FileStream fStream = File.Create(@"C:\test.txt"))
{
string text = MyUtilities.GetFormattedText("hello world");
writeFunction(text, fStream);
}
}
}
public class MyInstanceClass
{
private Action<string, FileStream> writeFunction = delegate { };
public MyInstanceClass(Action<string, FileStream> functionDependency)
{
writeFunction = functionDependency;
}
public void DoSomething2()
{
using (FileStream fStream = File.Create(@"C:\test.txt"))
{
string text = MyUtilities.GetFormattedText("hello world");
writeFunction(text, fStream);
}
}
}
公共类MyInstanceClass
{
私有操作writeFunction=delegate{};
公共MyInstanceClass(操作函数依赖项)
{
writeFunction=functionDependency;
}
公共无效DoSomething2()
{
使用(FileStream fStream=File.Create(@“C:\test.txt”))
{
string text=MyUtilities.GetFormattedText(“hello world”);
writeFunction(文本、流媒体);
}
}
}
有时,为静态函数调用创建接口和包装器类可能会很麻烦,而且它可能会污染您的解决方案,因为许多小型类的唯一目的是调用静态函数。我完全支持编写易于测试的代码,但这种做法似乎是一种解决糟糕测试框架的方法
当我思考这些不同的解决方案时,我了解到上面提到的所有4种实践都可以应用于不同的情况。以下是我所想的应用上述实践的正确立场:
这些都是我的想法,但我真的很想得到一些反馈。在调用外部静态函数时,测试代码的最佳方法是什么?选择#1是最佳方法。不要模仿,只要使用静态方法就可以了。这是最简单的路线,完全符合您的需要。您的两种“注入”方案仍在调用静态方法,因此您无法通过所有额外的包装获得任何好处。只需为静态方法创建一个单元测试,并在方法内部随意调用它即可进行测试,而无需对其进行模拟。使用依赖项注入(选项2或4)这绝对是我攻击它的首选方法。它不仅使测试更容易,而且有助于分离关注点,防止类膨胀 我需要澄清的是,静态方法很难测试,这不是真的。静态方法在另一种方法中使用时会出现问题。这使得调用静态方法的方法很难测试,因为静态方法无法模拟。通常的例子是I/O。在您的例子中,您将文本写入文件(WriteTextToFile)。如果在这种方法中出现故障怎么办?由于该方法是静态的,无法模拟,因此您不能按需创建失败案例等案例。如果创建接口,则可以模拟对WriteTextToFile的调用,并使其模拟错误。是的,您将有更多的接口和类
public void WriteMyFile(IFileRepository aRepository){
try{
using (FileStream fStream = aRepository.Create(@"C:\test.txt")){
string text = MyUtilities.GetFormattedText("hello world");
aRepository.WriteTextToFile(text, fStream);
}
}
catch(Exception e){
//You can now mock Create or WriteTextToFile and have it throw an exception to test this code.
}
}
public int GetNewSalary(int aRaiseAmount){
//Do you really want the test of this method to fail because the database couldn't be queried?
int oldSalary = DBUtilities.GetSalary();
return oldSalary + aRaiseAmount;
}
public int GetNewSalary(IDBRepository aRepository,int aRaiseAmount){
//This call can now be mocked to always return something.
int oldSalary = aRepository.GetSalary();
return oldSalary + aRaiseAmount;
}