1

我有一个服务调用包装器,它只在调用中将参数转发给服务。包装器的原因是我们可以使用 DI 容器注入包装器,从而模拟单元测试。

这是包装器的样子

public class WeatherChannelWrapper : IWeatherServiceWrapper
{
    public GetLocalWeather(string zipcode)
    {
        TWCProxy.GetCurrentCondition(zipcode, DateTime.Now.ToString("MM/dd/yyyy hh:mm"));
    }
}

一切正常,现在我需要吞下 TWCProxy 崩溃的异常。所以现在我的包装看起来像这样。

public class WeatherChannelWrapper : IWeatherServiceWrapper
{
    private readonly IExceptionLogger exceptionLogger;

    public WeatherChannelWrapper(IExceptionLogger logger)
    {
        this.exceptionLogger = logger;
    }
    public GetLocalWeather(string zipcode)
    {
        try 
        {
            TWCProxy.GetCurrentCondition(zipcode, DateTime.Now.ToString("MM/dd/yyyy hh:mm"));
        }
        catch (Exception e)
        {
            exceptionLogger.Log(e);
        }
    }
}

我必须编写以下单元测试

  1. GetLocalWeather() 不会在发生内部异常时使应用程序崩溃
  2. GetLocalWeather() 记录异常

现在要测试这两种情况,我需要以某种方式引发崩溃;如何使用 NUnit 和 Automoq/Moq 做到这一点?

4

2 回答 2

1

在不重构代码的情况下,您需要做的是在TWCProxy类上使用 shim。Moq 不提供 shimming,因此您可能需要使用第三方库。

我不确定你是否可以使用 Microsoft Fakes 之类的东西,但如果你这样做了,这可以在你的单元测试中很容易地实现。

https://msdn.microsoft.com/en-us/library/hh549175.aspx

首先,引用您的 3rd 方库并创建一个 Fake 库。在 VS(我使用企业版,不确定哪些版本具有该功能)中,这可以在您的测试项目中通过引用库来完成,然后在引用中,右键单击库和Add Fake Assembly. 然后构建你的项目。

然后,在您的单元测试中,您可以按如下方式设置 shim

[TestMethod]
public void MyTestMethod()
{
    using (ShimsContext.Create())
    {
        // fake assembly will have a long method name for the signature of the call. omitted for simplicity
        ShimTWCProxy.GetCurrentCondition[...] = () => {throw new Exception("message")};

        // instantiate and execute test code
        var logger = new Mock<IExceptionLogger>();
        var testedClass = new WeatherChannelWrapper (logger.Object);
        testedClass.GetLocalWeather("...");
        svc.Verify(x => x.Log(It.IsAny<string>()), Times.Once);

    }
}

另一种方法是向类传递一个方法委托,它可以默认为您的静态类,然后允许您在运行时覆盖它,尽管这可能有点混乱。如果你想要一个例子,请告诉我。

请注意,MS Fakes 不能很好地与某些测试运行程序/框架配合使用。例如,它似乎可以与带有 MSTest 适配器的 NUnit 以及 MSTest 和 Resharper 测试运行器一起使用,但不能与 NUnit 和 Resharper 运行器一起使用。

可能还有其他匀场/伪造解决方案,但我没有立即意识到它们。

于 2017-07-31T01:30:05.590 回答
1

更改 WeatherChannelWrapper 以便您可以覆盖它以引发异常。例如,通过添加受保护的虚拟方法,如下面的 CallTwcProxy:

public class WeatherChannelWrapper : IWeatherServiceWrapper
{
    private readonly IExceptionLogger exceptionLogger;

    public WeatherChannelWrapper(IExceptionLogger logger)
    {
        this.exceptionLogger = logger;
    }

    public GetLocalWeather(string zipcode)
    {
        try 
        {
            CallTwcProxy(zipcode);
        }
        catch (Exception e)
        {
            exceptionLogger.Log(e);
        }
    }

    protected virtual void CallTwcProxy(string zipcode)
    {
        TWCProxy.GetCurrentCondition(zipcode, DateTime.Now.ToString("MM/dd/yyyy hh:mm"));
    }
}

然后,您可以通过覆盖测试项目中的 CallTwcProxy 方法来伪造它以引发异常:

public class FakeWeatherChannelWrapper : WeatherChannelWrapper
{
    protected override virtual void CallTwcProxy(string zipcode)
    {
        throw new Exception("force exception to throw");
    }
}

现在你有一个假的 WeatherChannelWrapper ,它会抛出一个异常,你可以在测试中使用它:

public class WeatherChannelWrapperTests
{
    [Test]
    public void GetLocalWeather_WhenInternalExceptionOccurs_LogsException_Test()
    {
        var loggerMock = new Mock<IExceptionLogger>();
        var sut = new FakeWeatherChannelWrapper(loggerMock.Object);

        sut.GetLocalWeather("12345");

        // Assert Exception is logged
        loggerMock.Verify(logger => logger.Log(It.IsAny<Exception>()), Times.Once);

        // And practically this also tests that it doesn't crash even if exception is thrown
    }
}
于 2017-08-02T19:07:39.623 回答