代码之家  ›  专栏  ›  技术社区  ›  user2739418

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

  •  3
  • user2739418  · 技术社区  · 6 年前

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

        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中单元测试这种方法的最佳方法是什么#

    5 回复  |  直到 6 年前
        1
  •  2
  •   Deleted    6 年前

    实际上,您无法对方法进行单元测试,因为它依赖于外部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);
            }
        }
    }
    

    我认为接口的实现 IDateTimeProvider IMyFileSystemService 这不应该是个问题。

    现在,您可以为 MyService.Delete

        2
  •  1
  •   Jamie Twells    6 年前

    如果你想对你展示的方法进行单元测试,那么它目前的编写方式会很困难,但我们可以模拟 File 初始化并传递一个接口供其使用:

    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();
    }
    

    之后,只需使用如下库模拟这两个类: https://github.com/Moq/moq4/wiki/Quickstart

    编写单元测试,测试所需的逻辑。

    编辑:正如其他人指出的,日期时间。现在嘲笑可能也是件好事,但也可以用同样的方式。

        3
  •  1
  •   Iqon    6 年前

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

    public static class FileEx
    {
        public static Func<string, IEnumerable<string>> EnumerateFiles { set; get; }
           = Directory.EnumerateFiles;
    }
    

    然后只使用助手类:

    var files = FileEx.EnumerateFiles(...);
    

    通过这种方式,您可以在单元测试中更改方法。

    [Test]
    public void Test()
    {
        FileEx.EnumerateFiles = (_) => new [] { "file1", "file2" };
    
        // your test here
    
        // Reset the method:
        FileEx.EnumerateFiles = Directory.EnumerateFiles;
    }
    

    这适用于大多数静态助手方法,并且更容易重构每个类,以便可以注入。

    缺点

    • 你将失去函数重载。
    • 只适用于静态类(在您的示例中,它不适用于FileInfo)。

    正面

    • 真的很容易
    • 易于实现
    • 测试时易于更改
    • 易于使用

    更新评论中的评论:

    作为替代系统方法是可行的 Directory.EnumerateFiles 在单元测试中。 因为你在测试你的 Delete 方法,可以假设微软已经测试了框架代码。因此,单元测试必须证明的唯一一件事是 删去 方法必须纠正输出和副作用。

        4
  •  1
  •   Deleted    6 年前

    100%的解决方案是,必须注入大量这类物质,因为它要么有副作用,要么是非决定性的:

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

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

        5
  •  0
  •   Niraj Trivedi    6 年前

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

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

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

    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);
    
            }
        }
    }
    

    我知道这不是很好的设计实践,但我只想向大家展示一种使用虚拟方法进行单元测试的不同方式。

    最好的方法是创建一个接口,并模拟我创建的虚拟接口方法