TDD:是否有集成测试,但没有单元测试

本文关键字:单元测试 集成测试 是否 TDD | 更新日期: 2023-09-27 18:24:56

技术堆栈:.NET 4,C#,NUnit

我正在尝试将测试驱动的开发应用于一个执行图像处理的新项目。我有一个基类,它包含执行各种特定处理算法的共享文件I/O方法和子类。据我所知,单元测试不涉及文件系统或其他对象,并模拟发生这种情况的行为。我的基类只包含简单的访问器和直接的文件系统I/O调用。

public class BaseFile
{
    public String Path { get; set; }
    public BaseFile()
    {
        Path = String.Empty;
    }
    public BaseFile(String path)
    {
        if (!File.Exists(path))
        {
            throw new FileNotFoundException("File not found.", path);
        }
        Path = path;
    }
}

测试这些方法有什么价值吗?如果是这样的话,我该如何提取对文件系统的调用?

我的另一个问题是如何测试特定于图像文件类型(~200MB)的子类。我搜索过这个网站,发现了类似的问题,但没有一个涉及我在这个项目中使用的文件大小。类有集成测试(使用"黄金文件"),但没有单元测试,这合理吗?在这种情况下,我怎么能严格遵循TDD方法并首先编写一个失败的测试呢?

TDD:是否有集成测试,但没有单元测试

在回答您的第一个问题时,是的,测试这些方法是有价值的。我发布了一个库,它可以在不影响文件系统的情况下方便地做到这一点:https://bitbucket.org/mylesmcdonnell/mpm.io/wiki/Home

在(不是)回答您的第二个问题时,我需要查看一些代码,但我怀疑您可能需要对上述lib采取类似的方法。即定义接口,将代理定义为具体,将工厂定义为返回代理或mock。

我怎么能严格遵循TDD方法并首先编写一个失败的测试呢在这种情况下?

轻松!您模拟文件系统:)

这听起来可能需要做很多工作,但通常只需要实现一些方法,并根据需要进行扩展。在上述情况下。。。你只需要一个。

public interface IFileStore
{
    Boolean FileExists(String path);
}

其想法是将您的文件工作委派到接口后面,并创建一个具体的实现来完成繁重的工作。基本上是适配器模式。

对于这种事情,我甚至不反对"穷人的DI",因为如果你的应用程序稍后调用容器,你就可以实现它。。。您可能会一直使用真实的文件系统。

public class BaseFile
{
    private IFileStore _fileStore
    public IFileStore FileStore
    {
        get
        {
            return _fileStore ?? (_fileStore = new ConcreteFileStore());
        }
        set
        {
            _fileStore = value;
        }
    }
    //SNIP...
}

现在您有了一个可测试的实现,并且不必依赖任何"Golden"文件。

测试这些方法有价值

虽然现在这看起来像是为了微不足道的收益而做的额外工作,但添加BaseFile这样的测试应该在文件不存在时抛出FileNotFoundException至少可以实现两个目标:

定义预期行为列表

项目的新成员可以查看测试名称,以确定类的操作方式。他们将知道在每种情况下会发生什么——异常、null、默认结果等。

它还迫使你用通俗易懂的英语思考和定义你希望事情如何运作,而不是只在这里和那里加入条件和例外。这应该会在整个项目中产生一个非常一致的理念。

开发一套自动化回归测试

假设有人看到一些代码在特定条件下抛出异常,但他们认为做其他事情更明智(接受错误,但添加一个新的IsValid属性,这样消费者就可以知道构造/初始化是否成功)。如果他们做出这样的改变,测试将很快引起人们对这种改变的关注。事情的背后有一个有意识和有意的决定,人们可能已经开始依赖现有的行为——这种变化需要进一步讨论才能被接受。

至于你问题的第二部分,我认为Josh和Myles都已经提供了很好的建议。

使用接口模拟简单的文件系统调用似乎有些过头了。使用ITimeService模拟当前时间也是如此。我倾向于使用Func或Action,因为它要简单得多:

public static Func<string, bool> FileExists = System.IO.File.Exists;
public static Func<DateTime> GetCurrentTime = () => DateTime.Now;

由于这些都是公开的,我可以在单元测试中轻松地模拟。代码保持简单,不需要为简单的事情注入各种接口。对于流,我通常在单元tets中使用MemoryStream。

集成测试本身就有价值。如果像Joshs的回答中所解释的那样模拟文件系统,那么您真的不确定您的代码是否真的会在生产中运行。文件系统有很多隐藏的契约,这些契约对模拟来说并不重要。如果你的mock/fake显示出稍微不同的行为,你的代码可能会在你不知情的情况下开始依赖它。

只有集成测试才能确定某些事情。(集成测试也有缺点!)。