单元测试:何时停止?

4

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

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

但问题在于:我必须在我的测试中使用依赖项的“真实”实现。那么怎么办呢?测试?不测试?

以这个例子为例:

我在应用程序中需要的路径上有一些依赖项。所以我提取了一个接口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;
        }
    }
}

你会测试这样的代码吗?如果会,如何测试呢?


以下是关于单元测试的一些值得思考的内容:http://www.rbcs-us.com/documents/Why-Most-Unit-Testing-is-Waste.pdf - Big Daddy
3个回答

2

PathProvider 这个类很像一个数据库存储库类——它与外部系统(例如文件系统、数据库等)集成。

这些类存在于您系统的边界——其他类依赖它们,并且它们仅依赖外部系统。它们将您的系统连接到外部世界。

因此,它们适用于集成测试——而不是单元测试。

我通常只是使用标签将我的测试 (例如 xUnit 的 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))
}

我同意你的说法,但这并不是问题的合适答案。他并没有问这将是什么类型的测试... - Jamie Rees
@JamieRees 我想我回答了“你测试这样的代码吗”的部分,但没有回答“如何”部分。 - dcastro
顺便说一句:你可以找到很多关于单元测试的好文献,但我找不到一些针对集成测试的(就像这种情况)。 - joerg
关于连接器的管理 - 这实际上取决于项目以及我醒来时的状态。 我曾经在它们自己的命名空间中有DAO,也不得不将DAO分离为一个全新的项目,因为它们将被其他项目重用,这取决于情况。 但是我会从小的分离程度开始(即命名空间)。 另外,我不会将所有连接器都放在一个命名空间中 - 它们彼此并不相关。 - dcastro
@dcastro:感谢您的示例,我明白了您的意思...在这种特殊情况下,您的示例将失败,因为Assembly.GetEntryAssembly()是在测试环境之外调用的;同时也感谢您对分离DAO的想法。 - joerg
显示剩余6条评论

0

这两个答案都非常有价值,但我想再补充一点,我认为这很重要。

我觉得人们经常陷入讨论什么应该被测试,什么不应该被测试的争论中。某些东西应该被视为单元还是不应该。它是集成还是不是。

通常,单元测试的目标是确保我们的代码按照预期工作。在我看来,我们应该测试所有可能出现问题的地方(只要我们能够测试到)。将其组织成某种形式的测试并命名是必要的,但对于测试本身而言,这是次要的。我的建议是,如果你有任何疑问,总是问自己一个问题:“我是否担心它会出问题?”那么...“停止的位置”?当你不再担心功能出现问题时

回到你的具体情况。正如@dcastro所指出的那样,这看起来更像是集成测试而不是经典逻辑单元测试。无论我们如何称呼它 - 它能被破坏吗?是的,它可以。应该进行测试吗?可能需要测试,尽管我远远不会将其称为至关重要。它几乎没有自己添加的逻辑,复杂性也很低。如果我有时间,我会测试它吗?是的。我会怎么做?@dcastro的示例代码是我解决问题的方式。


0

答案取决于您对“单元”的定义。如果将类作为一个单元使用,则答案是肯定的。然而,测试单个类除非您正在开发某种类库否则其商业价值非常有限。

另一种选择是将单位定义为用户级别(或系统)要求。这种单位的测试将是这样的:给定用户输入X,测试系统输出Y。以这种方式定义的单位具有明确的、可衡量的商业价值,可以追溯到用户要求。

例如,当使用user stories时,您可能会有如下要求:

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

这可能有许多相关场景来覆盖不同的允许和禁止文件夹。每一个这些场景都将有一个单元测试来提供用户输入并验证输出。

给定:用户已将“C:\Program Files\MyApp”指定为安装文件夹。
当:安装程序运行时。
然后:在驱动器上存在“C:\Program Files\MyApp”,并且所有应用程序文件都位于该位置。
当您在类接口级别进行单元测试时,就会出现软件设计紧密耦合于单元测试框架和实现,而不是用户需求的情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接