如何正确进行单元测试

3

我正在尝试使用测试驱动设计方法编写应用程序 - 我对单元测试还很新,所以我想知道正确的测试正确输入和异常的方法。

我有一个用于加载配置文件的类:

class Config
{
    private XmlDocument configfile;

    public Config()
    {
        configfile = new XmlDocument();
    }

    public void LoadConfigFile(string filename)
    {
        if(string.IsNullOrEmpty(filename)) 
            throw new System.ArgumentException("You must specify a filename");

        try
        {
            configfile.Load(filename);
        }
        catch (Exception ex)
        {
            throw new System.IO.FileNotFoundException("File could not be loaded");
        }
    }
}

因此,这里可以进行三个测试:

  1. 加载实际文件并确保没有错误
  2. 尝试加载无效文件(不存在的文件)
  3. 不指定文件名参数

正确的测试方法是编写3个测试方法,像这样吗?

    /// <summary>
    ///A test for LoadConfigFile
    ///</summary>
    [TestMethod()]
    public void LoadConfigFileTest()
    {
        Config target = new Config(); // TODO: Initialize to an appropriate value
        string filename = "config.xml"; // TODO: Initialize to an appropriate value
        target.LoadConfigFile(filename);
        Assert.Inconclusive("A method that does not return a value cannot be verified.");
    }

    /// <summary>
    ///A test for LoadConfigFile
    ///</summary>
    [TestMethod()]
    [ExpectedException(typeof(System.ArgumentException))]
    public void LoadConfigFileTest1()
    {
        Config target = new Config(); // TODO: Initialize to an appropriate value
        string filename = ""; // TODO: Initialize to an appropriate value
        target.LoadConfigFile(filename);
        Assert.Inconclusive("A method that does not return a value cannot be verified.");
    }

    /// <summary>
    ///A test for LoadConfigFile
    ///</summary>
    [TestMethod()]
    [ExpectedException(typeof(System.IO.FileNotFoundException))]
    public void LoadConfigFileTest2()
    {
        Config target = new Config(); // TODO: Initialize to an appropriate value
        string filename = "blah.xml"; // TODO: Initialize to an appropriate value
        target.LoadConfigFile(filename);
        Assert.Inconclusive("A method that does not return a value cannot be verified.");
    }

此外,这3个测试中是否都应该加上try {} catch () {}语句? 就像第一个测试一样,正确性是暗示的,在第二个和第三个测试中,我无论如何都要检查异常,因此异常对测试没有影响。


2
我认为在无法加载XmlDocument时,抛出一个 throw new System.IO.FileNotFoundException("File could not be loaded") 非常误导人。我会直接传播原始异常或至少将其添加为内部异常,这样当问题是格式错误的xml文档时,不会产生FileNotFound的混淆。此外,为什么要使用 Assert.Inconclusive("A method that does not return a value cannot be verified.")?你可以检查配置文件是否符合预期,例如,一个测试通过给定良好的文档,另一个测试检查根据输入加载的内容是否“合理”。 - tolanj
2
如果你有要测试的代码,那么你并没有在进行测试驱动开发。在TDD中,你会先写测试,然后再编写代码来满足测试。 - Daniel Mann
1个回答

5
你已经在正确的道路上,但还没有完全到达目的地。
调用Assert.Inconclusive的情况非常罕见,在你的情况下并不必要:当你期望抛出异常并且异常被抛出时,它将按照预期工作(也就是说:它应该显示为绿色结果)。当你期望抛出异常但没有抛出任何异常时,它将显示为失败(也就是说:红色结果)。更多信息请参见这里
事实上,一个返回void的方法可以被测试。它可能不会返回值,但可能会改变某些东西的状态。在你的情况下,是变量configFile。测试的方法是通过检索该值(例如通过提供getter)和/或使用依赖注入,并在测试中将变量替换为假/模拟/存根(选择你的行话)。
不应该有try-catch块:它只会隐藏你的代码可能存在的任何问题。至于你的原始代码:不要捕获实际异常并将其重新抛出为FileNotFoundException查看你隐藏的所有可能原因
在评论中进行扩展:

我不想让开发人员直接操作configfile属性,所以我应该为测试将其公开,然后再将其更改为私有?

这是一个很好的问题,每个开发人员在进行测试时都会遇到这个问题。重要的是要意识到,单元的内部工作通常不是你应该测试的东西。实现细节就是它们的含义:实现细节。但有时候其他选择只会更加不受欢迎,所以你必须比较是否真的想这样做。
这可能是一个合法的用例,幸运的是,有一个相当不错的解决方法!我在这里详细介绍了它,但我建议为configfile提供internal访问权限,无论是使用内部构造函数、方法、属性还是仅仅是字段。通过应用[InternalsVisibleTo]属性,你可以从单元测试项目中访问它,同时仍然将其隐藏在公共之外。

关于使用存根测试configfile中的内容,看起来我必须将所有内容更改为接口依赖?这不会增加更多不必要的复杂性吗?

定义“所需的是什么”。定义接口并进行注入确实会在代码中多出一层抽象,但这是有原因的:由于类之间的松耦合性,您现在可以通过注入另一个实现来更轻松地测试它。

这个过程 - 依赖注入 - 是单元测试的支柱,也将有助于您的第一个备注。


1
对于捕获并重新抛出的地方,或者使用 throw new System.IO.FileNotFoundException("文件无法加载", ex); 这样你可以得到原始异常作为内部异常。 - Scott Chamberlain
@Jeroen Vannevel 这是一个很好的回答... 但我有两个问题 (1) 我不希望开发人员直接操作configfile属性,所以我应该将其设置为public进行测试,然后再改回private吗?(2) 关于使用存根来测试configfile属性中的内容,看起来我需要将所有东西都改为接口依赖?这难道不会增加更多不必要的复杂性吗?我只是对这两点感到好奇,因为我想按照正确的方式进行单元测试。 - binks
1
@binks:我对你的问题进行了扩展。 - Jeroen Vannevel

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