单元测试:在哪里停止?

时间:2015-08-13 11:08:54

标签: c# unit-testing

如果您要在要测试的类中具有依赖项的具体实现,那么经典的解决方案是:在您完全控制的位置添加一个间接层。

因此,需要添加越来越多的间接层(在单元测试中可以是存根/模拟的接口)。

但是:在我的测试的某个地方,我必须拥有"真实"实现我的依赖。那么这个怎么样?测试?不要测试吗?

举个例子:

我对应用程序中需要的路径有一些依赖关系。所以我提取了一个接口IPathProvider(我可以在测试中假装)。以下是实施:

public interface IPathProvider
{
    string AppInstallationFolder { get; }
    string SystemCommonApplicationDataFolder { get; }
}

具体实现PathProvider如下所示:

public class PathProvider : IPathProvider
{
    private string _appInstallationFolder;
    private string _systemCommonApplicationDataFolder;

    public string AppInstallationFolder
    {
        get
        {
            if (_appInstallationFolder == null)
            {
                try
                {
                    _appInstallationFolder =
                        Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
                }
                catch (Exception ex)
                {
                    throw new MyException("Error reading Application-Installation-Path.", ex);
                }
            }
            return _appInstallationFolder;
        }
    }

    public string SystemCommonApplicationDataFolder
    {
        get
        {
            if (_systemCommonApplicationDataFolder == null)
            {
                try
                {
                    _systemCommonApplicationDataFolder =
                        Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
                }
                catch (Exception ex)
                {
                    throw new MyException("Error reading System Common Application Data Path.", ex);
                }
            }
            return _systemCommonApplicationDataFolder;
        }
    }
}

您是否测试过此类代码以及是否如何?

3 个答案:

答案 0 :(得分:1)

PathProvider类看起来很像数据库存储库类 - 它与外部系统(例如,文件系统,数据库等)集成。

这些类位于系统的边界 - 其他类依赖于它们,它们仅依赖于外部系统。它们将您的系统连接到外部世界。

因此,他们需要integration testing - 而不是单元测试。

我通常只是标记我的测试(例如使用xUnit' s Trait属性)以使它们与常规测试分开,因此我可以在隔离运行时禁用它们(例如,没有数据库)。另一种方法是在相同的解决方案中将它们分成一个全新的项目,但我个人认为这有点过分。

[Fact, Trait("Category", "Integration")]
public void ReturnsCorrectAppInstallationFolder()
{
    // Arrange
    var assemblyFilename = Path.GetFilename(typeof(SomeType).Assembly.Location);
    var provider = new PathProvider();

    // Act
    var location = provider.AppInstallationFolder;

    // Assert
    Assert.NotEmpty(Directory.GetFiles(location, assemblyFilename))
}

答案 1 :(得分:0)

答案取决于您对“单位”的定义。在使用类作为一个单元的情况下,答案是肯定的。但是,除非您正在开发某种类库,否则测试单个类的业务价值非常有限。

另一种替代方法是将单元定义为用户级(或系统)要求。对这种单元的测试将遵循:给定用户输入X,测试系统输出Y.以这种方式定义的单位具有明确的,可测量的商业价值,可以追溯到用户要求。

例如,使用user stories时,您可能会遇到以下要求:

  

作为管理员,我想将软件包安装到特定文件夹。

这可能有许多相关的方案来涵盖不同的允许和禁止的文件夹。其中每个都有一个单元测试,提供用户输入并验证输出。

  

给定:用户已指定C:\Program Files\MyApp作为安装   文件夹中。

     

何时:安装程序已运行。

     

然后:驱动器上存在C:\Program Files\MyApp和所有   应用程序文件存在于该位置。

在您正在进行的类接口级别的单元测试时,您可以轻松地将您的软件设计与您的单元测试框架和实现紧密结合,而不是根据用户的要求。

答案 2 :(得分:0)

这两个答案有很多价值,但我还要补充一点,我认为这个问题非常重要。

我觉得人们经常会讨论应该测试什么,不应该测试什么。那些东西应该被视为单位或不应该被视为单位。它是否是一种整合。

通常,单元测试的目标是确保我们的代码按预期工作。 IMO我们应该测试一切(无论我们能做什么),这可能会被打破。将其组织成一些正式的测试并命名它是必要的,但对测试本身来说是次要的。我的建议是,如果你有任何疑问,总要问自己一个问题:"我是否害怕它可以被打破?" 。所以..."在哪里停止"? 你不怕功能被破坏

回到特定案例。正如@dcastro注意到的那样,这看起来确实是集成测试的一个例子,而不是经典的逻辑单元测试。不管我们如何命名它 - 它可以被打破吗?是的,它可以。它应该测试吗?  它可能会被测试,虽然我还远未命名它至关重要。几乎没有自己增加的逻辑,复杂性很低。我会测试一下吗?是的,如果我有时间这样做的话。我该怎么办? @dcastro的示例代码是我解决问题的方式。