try catch处理单元测试异常

时间:2014-06-05 11:21:17

标签: c# unit-testing exception try-catch appsettings

在我的业务逻辑中未设置配置密钥时,我已经提供了以下情况:

public class Presenter
{
private readonly IView view;     

    public Presenter(IView view) 
    {
    this.view = view;        
    }

    public void DoStuff() 
    {
        try
        {
            String someKey = ConfigurationManager.AppSettings["SomeKey"].ToString();

            if (string.IsNullOrEmpty(someKey))
            {                    
                throw new InvalidOperationException("SomeKey not set.");
            }

            // do stuff
        }
        catch (InvalidOperationException ex)
        {
            // provide view with friendly error

            // log error                
        }
    }
}

我尝试测试未设置密钥时发生此错误:

[TestMethod]
public void Presenter_DoStuff_Should_Throw_InvalidOperationException_When_SomeKey_Not_Supplied()
{
    // Arrange
    mockIView = new Mock<IView>();            
    presenter = new Presenter(mockIView.Object);

    // Act

    // Assert     
    // NUnit here as more precise       
    NUnit.Framework.Assert.Throws<InvalidOperationException>(() => presenter.DoStuff(), "SomeKey not set.");
}
  1. 如何让我的测试通过?它目前失败,因为try-catch正在吞噬异常以进行日志记录。测试通过没有try-catch。这是将AppSettings["SomeKey"]手动设置为空字符串。

  2. 其次,如何在测试中指定someKey中的DoStuff为空以实际测试此情况而无需手动删除密钥设置?

  3. 我非常感谢任何帮助,因为我是单位测试的新手。

1 个答案:

答案 0 :(得分:0)

首先,您的测试在设计上无效,因为您的方法实际上抛出调用代码的异常。这是因为您立即捕获并处理该异常。这实际上是异常的非常不正确的使用。没有必要根据条件投掷,然后在逻辑上需要做的就是检查那个条件时立即捕获。像这样:

public void DoStuff()
{
    var someKey = ConfigurationManager.AppSettings["SomeKey"];
    if (string.IsNullOrEmpty(someKey))
    {
        // provide view with friendly error
        // log error
        return;
    }
    // do stuff
}

现在问题变成了......你在测试什么?此方法的实际业务逻辑位于:

// do stuff

所以希望这是测试的关键焦点。现在,为了达到100%的代码覆盖率,您还需要测试该条件块中的内容。为此,您需要模拟条件。但是,您有一个外部依赖:

ConfigurationManager

为了测试逻辑,你需要模拟那个依赖。一般的方法是为依赖项创建一种包装器对象。在这种特殊情况下,它可以是简单的事情:

public class ConfigurationWrapper
{
    public virtual string SomeKey
    {
        get
        {
            return ConfigurationManager.AppSettings["SomeKey"];
        }
    }
}

这可以与具有DoStuff的类别分开,甚至可以嵌套在其中。取决于您想要使用它的地方/方式。当然,它可以扩展为包装其他配置依赖项。然后在具有DoStuff的类中,您将为此包装器创建一个属性。像这样:

private ConfigurationWrapper _config;
public ConfigurationWrapper Configuration
{
    get
    {
        if (_config == null)
            _config = new ConfigurationWrapper();
        return _config;
    }
    set { _config = value; }
}

并在DoStuff()

中使用它
var someKey = this.Configuration.SomeKey;

现在对ConfigurationManager的依赖包含在一个可模拟对象中。因此,在单元测试中,您要创建一个模拟ConfigurationWrapper对象并将其设置在被测对象上。类似的东西:

var mockConfig = new Mock<ConfigurationWrapper>();
presenter.Configuration = mockConfig;

您可以将mock设置为返回.SomeKey属性的有效值或空字符串,具体取决于任何给定的测试需要。然后,您将验证条件语句产生的任何副作用。 (&#34;友好的错误消息&#34;以及&#34;记录&#34;我假设。这可能涉及进一步的嘲笑,我现在不能知道。)

当然,为了达到100%覆盖率,当外部设置时,您还需要为包装器的默认情况添加另一个测试。这应该是一个相当简单的测试:

// arrange
// no mocks to set up

// act
var presenter = new Presenter(null);

// assert
Assert.IsNotNull(presenter.Configuration);