由try catch处理的单元测试异常

本文关键字:单元测试 异常 处理 try catch | 更新日期: 2023-09-27 18:06:46

我已经满足了在业务逻辑中没有设置配置键的情况,如下所示:

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为了记录日志而吞食了异常。没有试接,测试通过了。这是与AppSettings["SomeKey"]手动设置为空字符串。

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

任何帮助是非常感谢,因为我是新的单元测试。

由try catch处理的单元测试异常

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

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属性的有效值或空字符串,这取决于任何给定的测试需要什么。然后验证条件语句产生的副作用。(我认为是"友好的错误消息"answers"日志记录"。这可能涉及到进一步的模拟,我现在还不知道。

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

// arrange
// no mocks to set up
// act
var presenter = new Presenter(null);
// assert
Assert.IsNotNull(presenter.Configuration);