简体   繁体   English

如何正确进行单元测试

[英]How to do unit tests correctly

I'm trying to write an application using the Test Driven Design approach - I'm quite new to unit tests, so I'm just wondering what the right way to test for correct inputs and exceptions is. 我正在尝试使用“测试驱动设计”方法编写应用程序-我对单元测试还很陌生,所以我只是想知道测试正确输入和异常的正确方法是什么。

I have this class that's used to load a config file: 我有用于加载配置文件的此类:

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");
        }
    }
}

So, there are 3 tests that can be performed here: 因此,可以在此处执行3个测试:

  1. load an actual file and make sure there are no errors 加载实际文件并确保没有错误
  2. try to load an invalid file (one that doesnt exist) 尝试加载无效的文件(一个不存在的文件)
  3. do not specify an filename argument 不指定文件名参数

Is the correct way to test these, to write 3 test methods, like so?: 测试这些并编写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.");
    }

Also, should all 3 of these tests have try {} catch () {} statements? 此外,所有这三个测试都应具有try {} catch(){}语句吗? As in the first test, the correctness is implied, and in the 2nd and 3rd tests, I'm checking for an exception anyway, so an exception is of no consequence to the tests. 像第一个测试一样,它也暗示着正确性,而在第二个和第三个测试中,无论如何我都在检查异常,因此异常对测试没有影响。

You're on the right path but not quite there yet. 您处在正确的道路上,但还没有到达正确的道路。

It is pretty rare to have a situation where you have to call Assert.Inconclusive and in your situation it isn't necessary: when you expect an exception and the exception is thrown, it will work as it should (aka: it should show up as a green result). 这种情况很少发生,您必须致电Assert.Inconclusive ,在您的情况下是没有必要的:当您期望一个异常并且引发了异常时,它将按预期运行(又名:它应该显示作为绿色结果)。 When you expect an exception and none is thrown, it will show up as a fail (aka: red result). 当您预期发生异常且未引发任何异常时,它将显示为失败(又称红色结果)。 More on that here . 这里更多。

As a matter of fact, a method that returns void can be tested . 实际上, 可以测试返回void的方法 Instead of returning a value it might change the state of something. 除了返回值外,它可能会更改某些状态。 In your case the variable configFile . 在您的情况下,变量configFile The way to test this is by retrieving the value (for example by providing a getter) and/or by using dependency injection and substituting the variable for a fake/mock/stub (choose your jargon) in the test. 测试此方法的方法是通过获取值(例如,通过提供getter方法)和/或使用依赖项注入,然后在测试中将变量替换为假/模拟/存根(选择行话)。

There should be no try-catch blocks: it would only hide any problems your code might have. 不应有try-catch块:它只会隐藏您的代码可能存在的任何问题。 As to your original code: don't catch the actual exception and rethrow it as a FileNotFoundException . 至于您的原始代码:不要捕获实际的异常并将其重新抛出为FileNotFoundException Look at all the possible causes you are hiding . 查看您隐藏的所有可能原因

Expanding on the comments: 扩展评论:

I wouldn't want a develper to mess around with the configfile property directly, so should I make it public for the test, then change it back to private? 我不想让开发人员直接弄乱configfile属性,所以我应该将其公开以进行测试,然后再将其更改回private吗?

This is a good concern to have and every developer faces it when doing tests. 这是一个很好的考虑,每个开发人员在进行测试时都会面对它。 What's important to realize is that inner workings of a unit are not something you should usually test. 重要的是要认识到,单元的内部工作通常不需要测试。 Implementation details are exactly what they are: implementation details. 实现细节正是它们的本质:实现细节。 However sometimes the alternatives are just even less wanted so you have to make the comparison whether or not you want to do it after all. 但是,有时甚至根本不需要替代方案,因此无论是否要进行比较,您都必须进行比较。

This can be a legitimate usecase and luckily there is a fairly nice workaround for this! 这可能是一个合法的用例,幸运的是,有一个相当不错的解决方法! I go in more detail in it here but I would suggest to give internal access to configfile whether it it using an internal constructor, method, property or just the field. 我在这里对其进行了更详细的介绍,但是我建议无论是使用内部构造函数,方法,属性还是仅使用字段,都对configfile提供internal访问权限。 By applying the [InternalsVisibleTo] attribute you can provide access to it from your unit test project while still hiding it from the public. 通过应用[InternalsVisibleTo]属性,您可以从单元测试项目中访问它,同时仍对公众隐藏。

with regards to using stubs to test what's in configfile, it looks like I have to change everything to be interface dependant? 关于使用存根测试configfile中的内容,看来我必须更改所有内容以依赖于接口? Doesn't this just add tons more complexity that isn't needed? 难道这不只是增加了不必要的复杂性吗?

Define "what is needed". 定义“需要什么”。 It is true that by defining interfaces and injecting these you have an extra layer of abstraction in your code but this is done for a reason: due to the loosely coupled nature of your class you can now test it more easily by injecting another implementation. 的确,通过定义接口并注入接口,您在代码中有了额外的抽象层,但这是有原因的:由于类的松散耦合性质,您现在可以通过注入另一个实现来更轻松地对其进行测试。

This process - Dependency Injection - is the backbone of unit testing and will also help with your first remark. 这个过程-依赖注入-是单元测试的基础,也将有助于您的第一句话。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM