Unit testing 有些事情你可以';t检验?

Unit testing 有些事情你可以';t检验?,unit-testing,frameworks,Unit Testing,Frameworks,当进行单元测试时,总是很难知道框架到底有多低 如果我们有一个直接依赖于.NET Framework的类,即System.IO.File类,那么我们就不能单独测试它 当然,我们可以包装它并将其注入依赖类,但随后我们开始包装每个.NET类!如果包装器不是一个直接的呼叫通过呢?也许它首先执行一些检查/业务逻辑,您希望测试这些逻辑 目前,我们将封装到一个特定的级别,然后就不用麻烦对包装器进行单元测试了。也许这是可以的,因为稍后将通过集成和探索性测试对其进行测试 下面是C#中的一个例子来说明我的观点: 此

当进行单元测试时,总是很难知道框架到底有多低

如果我们有一个直接依赖于.NET Framework的类,即System.IO.File类,那么我们就不能单独测试它

当然,我们可以包装它并将其注入依赖类,但随后我们开始包装每个.NET类!如果包装器不是一个直接的呼叫通过呢?也许它首先执行一些检查/业务逻辑,您希望测试这些逻辑

目前,我们将封装到一个特定的级别,然后就不用麻烦对包装器进行单元测试了。也许这是可以的,因为稍后将通过集成和探索性测试对其进行测试

下面是C#中的一个例子来说明我的观点:

此类与.NET Framework紧密耦合。。这很好,但是现在我不能单独测试它,它需要文件存在等等

public class PathResolver
{
    public string Resolve(string filename)
    {
        string completePath = string.Empty;
        if(!File.Exists(filename))
        {
            return Path.Combine(@"D:\MyExample", filename);
        }
        return completePath;
    }
}
我们可以通过如下操作进行单元测试:

public class PathResolver
{
    private readonly IFileSystem _fileSystem;

    public PathResolver(IFileSystem fileSystem)
    {
        _fileSystem = fileSystem;
    }

    public string Resolve(string filename)
    {
        string completePath = string.Empty;
        if(!_fileSystem.Exists(filename))
        {
            return _fileSystem.Combine(@"D:\MyExample", filename);
        }
        return completePath;
    }
}
但是现在我们不能测试“文件系统”类


其他人的想法是什么?

一般来说,测试像.NET这样的框架不是你的责任,而是发布框架的人的责任。

你想测试System.IO吗?如果不是,为什么不使用模拟/存根方法来摆脱依赖关系?

如果使用依赖关系注入,则可以确保类的外部调用指向存根,而不是外部库。

不幸的是,在进行单元测试时,无法测试应用程序中的所有内容。这就是为什么在测试时,您的目标是让您的测试在项目中完成90%的代码覆盖率


将此测试作为集成测试的一部分,甚至是端到端测试的一部分,因为测试此测试的操作将非常昂贵

没有必要删除您对整个框架的依赖关系-只是那些妨碍您对代码进行单元测试的部分。因此,我认为您可以随意删除System.IO.File操作以及数据库调用。我还发现,有一个IClock接口来告诉时间而不是调用DateTime.UtcNow非常有用——这使我们能够在单元测试中控制时间的流逝

事实上,在某些时候,您必须实现像
IFileSystem
这样的接口,从而将应用程序粘到.NET framework上。这种“中间的粘合代码”不能进行单元测试。只要粘合代码非常简单,这就不是什么大问题

如果粘合代码没有您希望的那么简单,您仍然可以执行自动化集成测试,例如,以某种自检模式运行组装好的应用程序


单元测试之所以更受欢迎,是因为它们更容易创建,也不那么脆弱。运行单元测试不需要准备数据库、web服务器或文件系统。如果您更改了类B,您不必担心类A的单元测试会中断。集成测试可以测试更多的东西,但它们没有这些优点。

您只需要测试您的类,而不需要测试下面的框架

为此,您需要一个testdriver来设置一个testcase,在您的示例中,创建一个要解析的文件,实例化您的类并让它完成它的工作


一个好的测试驱动程序也会清理测试场景,并使系统保持与测试前相同的状态。

如果您不能测试一个类,但可以使用隔离框架(Rhino Mocks、Typemock、Moq)对其进行模拟,那么您可以伪造“不稳定”的细节这是单元测试和集成测试之间区别的一个很好的例子。对于单元测试您的PathResolver,您需要传递一个手动创建的或使用模拟框架(如my favorite)创建的模拟对象。使用模拟对象,您将能够测试是否调用了Combine方法

单元测试如下所示(使用Moq):


当使用持续集成软件在新提交上创建软件构建时,通常会调用集成测试。由于使用外部依赖项,它们的速度可能较慢。

+1。你不需要测试已经测试过的东西,除非你怀疑框架代码中有bug,这是极不可能的。是的,我同意这一点,但我不想测试框架。。我正在测试以确保我的代码调用它,而不是确保它正确执行其任务!框架提供了一个有趣的案例,因为它们有效地将从头开始的应用程序中的单元测试转化为集成测试(因为您现在使用的是框架中的“单元”,而不是您自己的代码)。是的,这正是我问题的目的!问题是,我们将.NET与我们的应用程序紧密地结合在一起(这很好),尽管这阻止了很多类的单元可测试性…例如。与.NET消息队列、数据库、文件系统类等交互的类。是的,这就是我展示的示例。。。这就是问题所在。。是否值得对所有.NET类进行模拟/存根(即包装)以确保我们的代码与之交互?是的,一般来说,您只想测试您的代码。更重要的是,如果你测试一个依赖于B类方法的a类方法,那么你真的应该模拟B类,所以你只测试a类方法的逻辑(好吧,当你测试模块之间的连接时,有一些特殊情况——它被称为集成测试)。对不起,我同意使用模拟框架。。。我不想测试System.IO!模拟的问题是NMock2只支持接口,所以我们需要一个文件接口(我们没有)。。因此,我们需要将其包装并注入。。。然而,这意味着我们开始包装大量的.NET类,这感觉有点错误。。。这是我的问题。。我们是否将所有内容都打包?不进行测试?还是有其他方法?尝试Rhinomock而不是NMock2是的,我们正在将其用于新项目。。。我们无法更改现有ap中的数百行代码
[Test]
public void ShouldCombinePath()
{
    IFileSystem fs = new Mock<IFileSystem>();

    PathResolver resolver = new PathResolver(fs.Object);

    resolver.Resolve("Test.filename");

    fs.Verify(fs => fs.Combine());
}
[Test]
public void ShouldCombinePath()
{
    DotNetFileSystem fileSystem = new DotNetFileSystem();

    string resultPath = fileSystem.Combine("Test.filename");

    Assert.That(resultPath, Text.Contains("@D:\MyExample\Test.filename"));
}