TDD:进行集成测试是否合理,但没有单元测试?

时间:2012-02-15 16:50:14

标签: c# .net unit-testing nunit

技术堆栈:.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;
    }
}

测试这些方法有什么价值吗?如果是这样,我怎样才能抽象出对文件系统的调用?

我的另一个问题是如何测试特定于某种图像文件的子类(~200 MB)。我搜索了网站并找到similar questions,但没有处理我正在处理此项目的文件大小。一个类是否有可能进行集成测试(使用“黄金文件”),但没有单元测试?在这种情况下,我怎样才能严格遵循TDD方法并首先编写失败的测试?

5 个答案:

答案 0 :(得分:4)

在回答您的第一个问题时,是的,测试这些方法是有价值的。我已经发布了一个库,可以在不实际访问文件系统的情况下完成这项工作:https://bitbucket.org/mylesmcdonnell/mpm.io/wiki/Home

在(不是)回答你的第二个问题我需要看一些代码,但我怀疑你可能需要对上面的lib采取类似的方法。即;定义接口,定义代理到具体,定义工厂以返回代理或模拟。

答案 1 :(得分:3)

  

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

轻松!你嘲笑文件系统:)

这可能听起来像很多工作,但通常你只需要实现一些方法,并根据需要进行扩展。在上面的例子中......你只需要一个。

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

我们的想法是将您的文件工作委托给界面,并创建一个具体的实现来完成繁重的任务。基本上是Adapter pattern

我甚至不反对“穷人的DI”这类事情,因为如果您的应用程序需要它,您可以稍后实现容器。在这种情况下......你可能总是会使用真正的文件系统。

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

    //SNIP...
}

现在你有一个可测试的实现,你不必依赖任何“Golden”文件。

答案 2 :(得分:1)

测试这些方法有价值

虽然现在看起来似乎是微不足道的额外工作,但是当文件不存在时,添加像 BaseFile这样的测试会抛出FileNotFoundException 实现至少两个目标:

定义预期行为列表

项目的新手可以查看测试名称,以确定您的课程的运作方式。他们将知道在每种情况下会发生什么 - 例外,空,默认结果等。

它还会迫使你思考并用简单的英语定义你想要的东西如何操作,而不是只是在这里和那里抛出条件和例外。这应该会在您的项目中应用非常一致的理念。

开展一系列自动化回归测试

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

关于你问题的第二部分,我认为Josh和Myles已经提供了合理的建议。

答案 3 :(得分:1)

使用接口模拟简单的文件系统调用似乎有点矫枉过正。对于使用ITimeService模拟当前时间的事情也是如此。我倾向于使用Func或Action,因为它更简单:

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

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

答案 4 :(得分:0)

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

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