单元测试是否应该测试方法的功能?

时间:2019-05-13 23:38:26

标签: c# unit-testing testing automated-tests

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

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

  1. 从文件夹中删除文件
  2. 返回文件夹是否为空
public bool DeleteFTPFiles(string xyz)
{
    ...

    path = GetFTPPath(xyz);
    DeleteFolderFiles(path);
    return IsFtpFolderEmpty(path);
}

DeleteFolderFiles-根据某些逻辑删除文件。

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

根据情况确定是否删除文件?

还要根据其是否为空来测试IsFtpFolderEmpty是否返回true或false?

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

2 个答案:

答案 0 :(得分:3)

  

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

您选择编写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);
  }
}

答案 1 :(得分:1)

单元测试可以测试此代码,但是应该以另一种方式编写。

看这段代码更有意义,是谈论集成测试而不是单元测试。

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

要使代码可测试,需要按照以下步骤重构代码:

介绍 IFileStorage -抽象

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

现在您可以使用其中一个模拟库(例如

)编写真实的单元测试

有关StackOverflow的相关文章: