在我的大多数项目中,我通常创建一个静态类,其中包含我在项目中使用的所有函数。 但是当谈到单元测试时,我觉得我的设计有一个很大的缺陷,因为我不能使用 Moq 来 Mock 这个静态类
我可以提供一个简单的示例,其中包含我正在处理的一个项目,该项目复制了很多文件,因此我做了一个小助手来避免代码中出现冗余
public static class Helpers
{
public static void CopyFile(string sourcePath, string destPath)
{
Log.Verbose("Start copy from {SourcePath} to {DestPath}", sourcePath, destPath);
if (!Directory.Exists(Path.GetDirectoryName(destPath)))
{
Directory.CreateDirectory(Path.GetDirectoryName(destPath));
}
File.Copy(sourcePath, destPath, true);
Log.Verbose("End of copy from {SourcePath} to {DestPath}", sourcePath, destPath);
}
}
所以基本上任何调用此函数的代码都是不可测试的,因为我无法在也依赖于 System.IO 的静态类中模拟静态函数
我尝试使用 System.IO.Abstraction 和依赖注入来解决这个问题,然后我有以下代码:
public interface IHelpers
{
public void CopyFile(string sourcePath, string destPath);
}
public class Helpers(IFileSystem fileSystem) : IHelpers
{
private readonly IFileSystem FileSystem = fileSystem;
public void CopyFile(string sourcePath, string destPath)
{
Log.Verbose("Start copy from {SourcePath} to {DestPath}", sourcePath, destPath);
if (!FileSystem.Directory.Exists(Path.GetDirectoryName(destPath)))
{
FileSystem.Directory.CreateDirectory(Path.GetDirectoryName(destPath));
}
FileSystem.File.Copy(sourcePath, destPath, true);
Log.Verbose("End of copy from {SourcePath} to {DestPath}", sourcePath, destPath);
}
}
现在我只需要在需要调用 CopyFile 的地方注入 IHelper ,一切看起来都很好。 但我有一些罕见的情况,在类中注入这个助手感觉是错误的
public class Component
{
public string Name;
public string Path;
public void ExportOutput()
{
// ...
// much calculation then...
// multiple calls to Helpers.CopyFile(src,dest);
// ...
}
}
为了解决这个问题,我将 IHelper 类作为参数传递给 ExportOutput,但这看起来绝对令人恶心。
我发现这个问题的唯一解决方案是删除 ExportOutput 函数并将其放在我可以依赖依赖注入来使用 Helper 类的地方
这是对的还是我完全没有抓住要点?
您通常如何处理代码中的那些小辅助函数?
如果您想模拟文件系统操作,您将需要一个抽象文件系统的接口,并将其注入到使用文件系统的任何地方。
您的方法的替代方法是创建
CopyFile
作为文件系统的扩展,即
public static class Helpers
{
public static void CopyFile(this IFileSystem fileSystem, string sourcePath, string destPath)
{
...
}
}
此方法会将
CopyFile
实现视为所测试事物的一部分,根据具体情况,这可能是可取的,也可能不是可取的。一个可能的优点是,它允许被测试的方法提供自己的 CopyFile 实现,而无需更改依赖项或测试。
除非我有一些罕见的情况,在类中注入这个助手感觉是错误的
如果您的
ExportOutput
方法依赖于文件系统,并且您希望此依赖项可测试,则需要以某种方式注入此依赖项。我可能会在 Component
的构造函数中注入依赖项,或者将 ExportOutput
移动到它自己的类中。但在某些情况下,使用方法参数可能是合适的。
请注意,文件系统可能存在大量难以准确重现的怪癖,特别是考虑到 IO 操作可能失败的所有方式。因此,您可能希望至少进行一些使用实际文件系统的测试,即使这可能会慢得多。请记住事后清理所有剩余的文件。