Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/275.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 从文件夹中删除文件的单元测试方法_C#_Unit Testing_Moq - Fatal编程技术网

C# 从文件夹中删除文件的单元测试方法

C# 从文件夹中删除文件的单元测试方法,c#,unit-testing,moq,C#,Unit Testing,Moq,我们有一个以文件夹名称和天数为参数的方法 public void Delete(string folder, int days) { var files = Directory.GetFiles(folder); foreach (var file in files) { var fi = new FileInfo(file); var fiCreationTime = fi.Cre

我们有一个以文件夹名称和天数为参数的方法

    public void Delete(string folder, int days)
    {
        var files = Directory.GetFiles(folder);

        foreach (var file in files)
        {
            var fi = new FileInfo(file);
            var fiCreationTime = fi.CreationTime;
            var deleteOlderThan= DateTime.Now.AddDays(-days);

            if (fiCreationTime >= deleteOlderThan) continue;
                fi.Delete();
        }
    }

在c#

中对此类方法进行单元测试的最佳方法是什么?100%的解决方案是,必须注入大量此类方法,因为它要么有副作用,要么是非决定性的:

1) Directory.GetFiles
2) new FileInfo(file)
3) fi.CreationTime
4) DateTime.Now.AddDays
5) fi.Delete

例如,在生产环境中注入datetimeservice返回datetime,在测试环境中注入datetimeservice总是返回某个固定日期。并使用模拟框架检查是否有时调用delete方法,有时不调用它。

实际上,您无法对您的方法进行单元测试,因为它依赖于外部API(文件系统、日期时间)

因此,您应该做的是分离逻辑并与外部源集成,它可能如下所示:

public class MyFileInfo
{
    public string FileName { get; set; }

    public DateTime CreationTime { get; set; }
}

public interface IDateTimeProvider
{
    DateTime GetCurrentTime();
}

public interface IMyFileSystemService
{
    IEnumerable<MyFileInfo> GetFileInfos(string folder);

    void DeleteFile(MyFileInfo myFileInfo);
}

public class MyService
{
    private readonly IMyFileSystemService _myFileSystemService;
    private readonly IDateTimeProvider _dateTimeProvider;

    public MyService(IMyFileSystemService myFileSystemService, IDateTimeProvider dateTimeProvider)
    {
        _myFileSystemService = myFileSystemService;
        _dateTimeProvider = dateTimeProvider;
    }

    public void Delete(string folder, int days)
    {
        var files = _myFileSystemService.GetFileInfos(folder);

        foreach (var file in files)
        {
            var deleteOlderThan = _dateTimeProvider.GetCurrentTime().AddDays(-days);

            if (file.CreationTime >= deleteOlderThan) continue;
            _myFileSystemService.DeleteFile(file);
        }
    }
}
公共类MyFileInfo
{
公共字符串文件名{get;set;}
公共日期时间CreationTime{get;set;}
}
公共接口IDateTimeProvider
{
DateTime GetCurrentTime();
}
公共接口IMyFileSystemService
{
IEnumerable GetFileInfos(字符串文件夹);
作废删除文件(MyFileInfo MyFileInfo);
}
公共类MyService
{
私有只读IMyFileSystemService\u myFileSystemService;
私有只读IDateTimeProvider\u dateTimeProvider;
公共MyService(IMyFileSystemService myFileSystemService,IDateTimeProvider dateTimeProvider)
{
_myFileSystemService=myFileSystemService;
_dateTimeProvider=dateTimeProvider;
}
公共作废删除(字符串文件夹,整数天)
{
var files=\u myFileSystemService.GetFileInfos(文件夹);
foreach(文件中的var文件)
{
var deleteOlderThan=\u dateTimeProvider.GetCurrentTime().AddDays(-days);
如果(file.CreationTime>=deleteOlderThan)继续;
_myFileSystemService.DeleteFile(文件);
}
}
}
我认为接口
IDateTimeProvider
IMyFileSystemService
的实现不应该成为问题


现在您可以为
MyService编写干净的单元测试了。删除

如果您想要单元测试您已经展示过的方法,那么当前编写的方法将很困难,但是我们可以模拟
文件
类,并传递一个接口供其使用:

public class FileDeleter
{

    private readonly IFileOperator _fileOperator;

    public FileDeleter(IFileOperator fileOperator)
    {
        _fileOperator= fileOperator
    }

    public void Delete(string folder, int days)
    {
        var files = _fileClass.GetFiles(folder);

        foreach (var file in files)
        {
            var fi = _fileClass.GetFileInfo(file);
            var fiCreationTime = fi.CreationTime;
            var deleteOlderThan= DateTime.Now.AddDays(-days);

            if (fiCreationTime >= deleteOlderThan)
                continue;
            fi.Delete();
        }
    }
}

public interface IFileClass 
{
    IEnumerable<string> GetFiles(string path);
    IFileInfo GetFileInfo(string filePath);
}

public interface IFileInfo 
{
    DateTime CreationTime { get; }
    void Delete();
}
公共类文件删除程序
{
专用只读IFileOperator\u fileOperator;
公共文件删除程序(IFileOperator文件操作员)
{
_fileOperator=fileOperator
}
公共作废删除(字符串文件夹,整数天)
{
var files=\u fileClass.GetFiles(文件夹);
foreach(文件中的var文件)
{
var fi=\u fileClass.GetFileInfo(文件);
var fiCreationTime=fi.CreationTime;
var deleteOlderThan=DateTime.Now.AddDays(-days);
如果(fiCreationTime>=deleteOlderThan)
继续;
fi.删除();
}
}
}
公共接口IFileClass
{
IEnumerable GetFiles(字符串路径);
IFileInfo GetFileInfo(字符串文件路径);
}
公共接口IFileInfo
{
DateTime CreationTime{get;}
作废删除();
}
之后,只需使用如下库模拟这两个类:

编写单元测试来测试所需的逻辑


编辑:正如其他人所指出的,datetime.now可能也是一件值得模仿的好事情,但也可以用同样的方法来完成。

另一种可能性是使用静态帮助器类

public static class FileEx
{
    public static Func<string, IEnumerable<string>> EnumerateFiles { set; get; }
       = Directory.EnumerateFiles;
}
通过这种方式,您可以在单元测试中更改方法

[Test]
public void Test()
{
    FileEx.EnumerateFiles = (_) => new [] { "file1", "file2" };

    // your test here

    // Reset the method:
    FileEx.EnumerateFiles = Directory.EnumerateFiles;
}
这适用于大多数静态帮助器方法,并且更容易重构每个类,以便可以将其注入

缺点
  • 您将丢失函数重载
  • 只适用于静态类(在您的示例中,它不适用于FileInfo)
正面
  • 真的很容易
  • 易于实现
  • 测试时易于更改
  • 易于使用
更新评论中的评论: 在单元测试中,可以将系统方法替换为
Directory.EnumerateFiles

因为您正在测试
Delete
方法,可以假设Microsoft已经测试了框架代码。因此,单元测试必须证明的唯一一件事是,
Delete
方法必须纠正输出和副作用。

对于您的代码来说,完美的解决方案是创建一个包含所有文件操作方法的接口,然后模拟这些方法

但您也可以在示例类中为这些文件操作创建虚拟方法,并在单元测试中模拟该方法

下面是实际代码的代码实现

public class Sample
{
    public void Delete(string folder, int days)
    {
        var files = GetFiles(folder);

        foreach (var file in files)
        {
            var fi = GetFileInfo(file);
            var fiCreationTime = fi.CreationTime;
            var deleteOlderThan = DateTime.Now.AddDays(-days);

            if (fiCreationTime >= deleteOlderThan) continue;
            DeleteFile(fi);
        }
    }

    public virtual void DeleteFile(FileInfo f)
    {
        f.Delete();
    }

    public virtual string[] GetFiles(string path)
    {
        return Directory.GetFiles(path);
    }

    public virtual FileInfo GetFileInfo(string file)
    {
        return new FileInfo(file);
    }
}
下面是您的单元测试类

public class NUnitTest
{
    [TestFixture]
    public class UnitTest1
    {
        private Mock<Sample> _sample;
        private FileInfo _fileInfo;
        [SetUp]
        public void Setup()
        {
            _sample = new Mock<Sample>();

        }

        [Test]
        public void File_Should_Not_Delete()
        {
            _fileInfo = new FileInfo("file");
            _fileInfo.Create();

            _sample.Setup(x => x.GetFiles(It.IsAny<string>())).Returns(() => new[] {"file1"});
            _sample.Setup(x => x.GetFileInfo(It.IsAny<string>())).Returns(() => _fileInfo);
            _sample.Setup(x => x.DeleteFile(It.IsAny<FileInfo>())).Verifiable();
            _sample.Object.Delete("file1",2);

            _sample.Verify(x => x.DeleteFile(It.IsAny<FileInfo>()), Times.Never);

        }

        [Test]
        public void File_Should_Delete()
        {
            _fileInfo = new FileInfo("file1");
            _fileInfo.Create();

            _sample.Setup(x => x.GetFiles(It.IsAny<string>())).Returns(() => new[] { "file1" });
            _sample.Setup(x => x.GetFileInfo(It.IsAny<string>())).Returns(() => _fileInfo);
            _sample.Setup(x => x.DeleteFile(It.IsAny<FileInfo>())).Verifiable();
            _sample.Object.Delete("file1", -2);

            _sample.Verify(x => x.DeleteFile(It.IsAny<FileInfo>()), Times.Once);

        }
    }
}
公共类NUnitTest
{
[测试夹具]
公共类UnitTest1
{
私人模拟样本;
私人文件信息(FileInfo);;
[设置]
公共作废设置()
{
_样本=新模拟();
}
[测试]
公共无效文件\u不应\u删除()
{
_fileInfo=新的fileInfo(“文件”);
_Create();
_Setup(x=>x.GetFiles(It.IsAny())。返回(()=>new[]{“file1”});
_sample.Setup(x=>x.GetFileInfo(It.IsAny())。返回(()=>\u fileInfo);
_sample.Setup(x=>x.DeleteFile(It.IsAny()).Verifiable();
_sample.Object.Delete(“file1”,2);
_Verify(x=>x.DeleteFile(It.IsAny()),Times.Never);
}
[测试]
公共无效文件\u应\u删除()
{
_fileInfo=新的fileInfo(“file1”);
_Create();
_Setup(x=>x.GetFiles(It.IsAny())。返回(()=>new[]{“file1”});
_sample.Setup(x=>x.GetFileInfo(It.IsAny())。返回(()=>\u fileInfo);
_sample.Setup(x=>x.DeleteFile(It.IsAny()).Verifiable();
_sample.Object.Delete(“file1”,-2);
_验证(x=>x.DeleteFile(It.IsAny()),次.Once);
}
}
}
我知道这不是一个好的设计实践,但我
public class NUnitTest
{
    [TestFixture]
    public class UnitTest1
    {
        private Mock<Sample> _sample;
        private FileInfo _fileInfo;
        [SetUp]
        public void Setup()
        {
            _sample = new Mock<Sample>();

        }

        [Test]
        public void File_Should_Not_Delete()
        {
            _fileInfo = new FileInfo("file");
            _fileInfo.Create();

            _sample.Setup(x => x.GetFiles(It.IsAny<string>())).Returns(() => new[] {"file1"});
            _sample.Setup(x => x.GetFileInfo(It.IsAny<string>())).Returns(() => _fileInfo);
            _sample.Setup(x => x.DeleteFile(It.IsAny<FileInfo>())).Verifiable();
            _sample.Object.Delete("file1",2);

            _sample.Verify(x => x.DeleteFile(It.IsAny<FileInfo>()), Times.Never);

        }

        [Test]
        public void File_Should_Delete()
        {
            _fileInfo = new FileInfo("file1");
            _fileInfo.Create();

            _sample.Setup(x => x.GetFiles(It.IsAny<string>())).Returns(() => new[] { "file1" });
            _sample.Setup(x => x.GetFileInfo(It.IsAny<string>())).Returns(() => _fileInfo);
            _sample.Setup(x => x.DeleteFile(It.IsAny<FileInfo>())).Verifiable();
            _sample.Object.Delete("file1", -2);

            _sample.Verify(x => x.DeleteFile(It.IsAny<FileInfo>()), Times.Once);

        }
    }
}