我正在编写单元测试,但是最让我困惑的部分是它是否应该测试功能?
例如,如果有一种方法可以做两件事
public bool DeleteFTPFiles(string xyz)
{
...
path = GetFTPPath(xyz);
DeleteFolderFiles(path);
return IsFtpFolderEmpty(path);
}
DeleteFolderFiles
-根据某些逻辑删除文件。
现在,如果我必须对此方法进行单元测试(DeleteFTPFiles
)。
我是否必须通过我的单元测试创建文件夹结构并添加一些文件作为整理测试?
根据情况确定是否删除文件?
还要根据其是否为空来测试IsFtpFolderEmpty
是否返回true或false?
如果是这样,这与集成测试有何不同?
答案 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;
}
}
单元测试应该测试方法的功能吗?
这是我的解释:
单元测试只能测试封装的含义。这可能包括以下一项或多项(不一定是详尽的清单):
AddTwoNumber()
确实做到了这种逻辑)让我们参加这个假想的课程,并细分每种测试的内容和原因:
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的相关文章: