单位测试应测试方法的功能



我正在编写单元测试,但最让我感到困惑的部分是它是否应该测试功能?

例如,如果有一种方法可以做两件事

  1. 从文件夹中删除文件
  2. 返回文件夹是否为空
public bool DeleteFTPFiles(string xyz)
{
    ...
    path = GetFTPPath(xyz);
    DeleteFolderFiles(path);
    return IsFtpFolderEmpty(path);
}

DeleteFolderFiles-基于某些逻辑删除文件。

现在,如果我必须为此方法进行单元测试(DeleteFTPFiles(。我是否必须创建文件夹结构并通过我的单元测试作为安排测试添加一些文件?

断言是否根据条件删除文件?

另外,测试IsFtpFolderEmpty是根据是否为空的true还是False?

如果是这样,这与集成测试有何不同?

例如,如果有一种方法可以做两件事

您选择编写DeleteFTPFiles()的方法是一个糟糕的选择,因为结果与名称不匹配。如果未删除该文件,则该方法仍然可能返回true?那是错误的逻辑。如果我使用该代码,我会认为结果是如果文件未删除,则目录是空的

如果我要编写它,它将只是DeleteAllFiles(),因为它不需要知道它在哪里发生的,而是它在哪里。然后,我将通过另一个班级,该课程具有完成工作所需的方法。

public class MySpaceManager()
{
  private readonly IFileManager _fileManager;
  public MySpaceManager(IFileManager fileManager)
  {
    _fileManager = fileManager;
  }

  public bool TryDeleteAllFiles1(logicalDirectory)
  {
    var files = _fileManager.GetFiles(logicalDirectory);
    var result = true;
    foreach(var file in files)
      result = result && _fileManager.Delete(file);
    return result;
  }
  // or maybe
  public bool TryDeleteAllFiles2(logicalDirectory)
  {
    var files = _fileManager.GetFiles(logicalDirectory);
    foreach(var file in files)
      _fileManager.Delete(file);
    var result = _fileManager.GetFiles(logicalDirectory).Count() == 0;
    return result;
  }
}

单位测试应该测试方法的功能吗?

这是我的解释:

单位检验只能测试其封装的意义。这可能包括以下一个或多个(不一定是详尽的列表(:

  1. 运行到完成
  2. 抛出异常
  3. 某种类型的逻辑(例如,AddTwoNumber()确实可以做该逻辑(
  4. 执行一些外部依赖关系
  5. 不执行某些外部依赖关系

让我们参加这个假设的班级,分解每个测试的内容和原因:

public class MySpaceManagerTests
{
  // First simple, best good path for code
  public void TryDeleteAllFiles2_WithEmptyPath_ThrowsNoException()
  {
    /// ** ASSIGN **
    // I'm using NSubstitute here just for an example
    // could use Moq or RhinoMocks, whatever doesn't  
    // really matter in this instance
    // the important part is that we do NOT test dependencies
    // the class relies on.
    var fileManager = Substitute.For<IFileManager>();
    fileManager
      .GetFiles(Args.Any<string>())
      .Returns(new List<IFile>());
    var mySpaceManager = new MySpaceManager(fileManager);
    // ** ACT && ASSERT**
    // we know that the argument doesn't matter so we don't need it to be
    // anything at all, we just want to make sure that it runs to completion
    Asser.DoesNotThrow(() => mySpaceManager.TryDeleteAllFiles2(string.Empty);
  }
  // This looks VERY similar to the first test but
  // because the assert is different we need to write a different
  // test.  Each test should only really assert the name of the test
  // as it makes it easier to debug and fix it when it only tests
  // one thing.
  public void TryDeleteAllFiles2_WithEmptyPath_CallsFileManagerGetFiles()
  {
    /// ** ASSIGN **
    var fileManager = Substitute.For<IFileManager>();
    fileManager
      .GetFiles(Args.Any<string>())
      .Returns(new List<IFile>());
    var mySpaceManager = new MySpaceManager(fileManager);
    // ** ACT **
    mySpaceManager.TryDeleteAllFiles2(string.Empty)
    // ** ASSERT **
    Assert.DoesNotThrow(fileManager.Received().GetFiles());
  }
  public void TryDeleteAllFiles2_With0Files_DoesNotCallDeleteFile
  {
    /// ** ASSIGN **
    var fileManager = Substitute.For<IFileManager>();
    fileManager
      .GetFiles(Args.Any<string>())
      .Returns(new List<IFile> { Substitute.For<IFile>(); });
    var mySpaceManager = new MySpaceManager(fileManager);
    // ** ACT **
    mySpaceManager.TryDeleteAllFiles2(string.Empty)
    // ** ASSERT **
    Assert.DoesNotThrow(fileManager.DidNotReceive().GetFiles());
  }
  public void TryDeleteAllFiles2_With1File_CallsFileManagerDeleteFile
  {
    // etc
  }
  public void TryDeleteAllFiles2_With1FileDeleted_ReturnsTrue()
  {
    /// ** ASSIGN **
    var fileManager = Substitute.For<IFileManager>();
    fileManager
      .GetFiles(Args.Any<string>())
      .Returns(new List<IFile> { Substitute.For<IFile>(); }, 
        new list<IFile>());
    var mySpaceManager = new MySpaceManager(fileManager);
    // ** ACT **
    var actual = mySpaceManager.TryDeleteAllFiles2(string.Empty)
    // ** ASSERT **
    Assert.That(actual, Is.True);
  }
  public void TryDeleteAllFiles2_With1FileNotDeleted_ReturnsFalse()
  {
    /// ** ASSIGN **
    var fileManager = Substitute.For<IFileManager>();
    fileManager
      .GetFiles(Args.Any<string>())
      .Returns(new List<IFile> { Substitute.For<IFile>(); }, 
        new List<IFile> { Substitute.For<IFile>(); });
    var mySpaceManager = new MySpaceManager(fileManager);
    // ** ACT **
    var actual = mySpaceManager.TryDeleteAllFiles2(string.Empty)
    // ** ASSERT **
    Assert.That(actual, Is.False);
  }
}

单位测试可能会测试此代码,但应以其他方式写入。

查看此代码而不是说集成测试而不是单位测试是有意义的。

有能力编写单元测试,需要将代码与具体实现相结合。您要测试您的代码而不是FTP服务,不是吗?

使代码可测试以下步骤重构代码:

介绍 ifilestorage -abstraction

public interface IFileStorage
{
    string GetPath(string smth);
    void DeleteFolder(string name);
    bool IsFolderEmpty(string path);    
}
public sealed class FtpFileStorage : IFileStorage
{
    public string GetPath(string smth) { throw new NotImplementedException(); }
    public void DeleteFolder(string name) { throw new NotImplementedException(); }
    public bool IsFolderEmpty(string path) { throw new NotImplementedException(); }
}

代码应取决于抽象而不是具体实现:

public class SmthLikeServiceOrManager
{
    private readonly IFileStorage _fileStorage;
    public SmthLikeServiceOrManager(IFileStorage fileStorage)
    {
        _fileStorage = fileStorage;
    }    
    public bool DeleteFiles(string xyz)
    {
        // ...
        var path = _fileStorage.GetPath(xyz);
        _fileStorage.DeleteFolder(path);
        return _fileStorage.IsFolderEmpty(path);
    }
}

现在您可以使用模仿库之一来编写真实的单位测试,例如

  • MOQ

  • nsubstitute

stackoverflow上的相关文章:

  • 使用c#

  • 中使用量
  • ..

相关内容

最新更新