【问题标题】:How to unit test try-catch block?如何对 try-catch 块进行单元测试?
【发布时间】:2013-12-19 05:12:21
【问题描述】:

如何对 try-catch 块进行单元测试? 我正在使用 Moq 作为单元测试对象的 ASP.NET MVC 4.0。我想弄清楚如何覆盖 try-catch 块。

下面是我的控制器操作方法的屏幕截图,包括 try-catch 块:

public ActionResult ProductDelete(int id)
{
    try
    {
        ViewBag.Title = "Delete Product";
        ProductDetailDto dto = ProductService.GetProductDetail(id);
        return View("ProductDelete", BuildProductDetailModel(dto));
    }
    catch (Exception)
    {
        throw; // cannot test this line
    }
}

您可以看到,当我从测试资源管理器中启用代码覆盖着色选项时,它会显示未覆盖的 catch 块。

下面是我的单元测试方法的屏幕截图:

[TestMethod]
public void ProductDeleteTest()
{            
    ProductController controller = new ProductController();
    var existingProductDetail = _ProductService.GetAllProductsDetails().ToList().FirstOrDefault();

    if (existingProductDetail != null)
    {
        TestControllerBuilder builder = new TestControllerBuilder();
        builder.InitializeController(controller);
        // Act
        var actual = controller.ProductDelete(existingProductDetail.ProductDetailId) as ViewResult;
        var viewModel = (ProductDetailModel)actual.Model;
        // Assert
        Assert.IsNotNull(actual);
        Assert.IsInstanceOfType(viewModel, typeof(ProductDetailModel));
        Assert.AreEqual(actual.ViewName, "ProductDelete");
    }
}

我想弄清楚如何在单元测试方法中覆盖 try-catch 块。

【问题讨论】:

  • 请下次粘贴您的代码而不是粘贴图像 - 它可以帮助其他人复制粘贴您的代码,并且当图像从主机中删除时,您的问题不会过时。
  • 当然,lazyberezovsky 我会处理的。感谢您的更改。

标签: unit-testing asp.net-mvc-4 mocking try-catch


【解决方案1】:

你当前的测试有几个问题

  • 您没有单独测试控制器(我相信您使用的是产品服务的真实实现)。您应该对正在测试的类的所有依赖项使用模拟。因此,您可以避免其他类错误的影响,并且可以轻松地模拟 SUT 和依赖项之间的任何通信。您已经遇到了抛出异常时无法重现场景的问题。
  • 您的测试中有条件逻辑,这使其不可重复。请记住 - 测试应该始终产生相同的结果。
  • 您的前两个断言没有验证任何内容,因为测试会提前失败。如果结果是null,那么在访问Model 属性期间您将拥有NullReferenceException。并且如果模型不是ProductDetailModel 类型,你将有强制转换异常。

因此,您应该模拟您的依赖项,设置控制器和模拟程序之间的通信,然后采取行动。您的测试可能如下所示(此处使用Moq 库):

private ProductController controller;
private Mock<IProductService> productServiceMock;
private Random random = new Random();

[TestInitialize]
public void Init()
{
    productServiceMock = new Mock<IProductService>();
    controller = new ProductController(productServiceMock.Object);
}

[TestMethod]
public void ShouldReturnDetailsOfDeletedProduct()
{
    int id = random.Next();
    var dto = new ProductDetailDto { ProductDetailId = id };
    productServiceMock.Setup(s => s.GetProductDetail(id)).Returns(dto);

    // Act
    var actual = controller.ProductDelete(id) as ViewResult;
    var viewModel = (ProductDetailModel)actual.Model;
    // Assert
    Assert.AreEqual("ProductDelete", actual.ViewName);
    Assert.AreEqual(id, viewModel.Id);
    productServiceMock.VerifyAll();
}

如您所见,这里使用了模拟服务。您设置要返回的 dto,并验证是否调用了服务。还要记住,在断言中,您首先输入预期值,然后才是实际值。

所以,下面是如何使用 mock 设置异常抛出:

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void ShouldNotReturnResultWhenExceptionOccurs()
{
    int id = random.Next();
    var dto = new ProductDetailDto { ProductDetailId = id };
    productServiceMock.Setup(s => s.GetProductDetail(id))
                      .Throws(new ArgumentException());
    // Act
    controller.ProductDelete(id);
}

设置服务模拟以抛出一些异常,然后使用ExpectedException 属性来验证您的控制器重新从服务中抛出异常。

【讨论】:

  • 谢谢你的建议对我真的很有帮助。我是单元测试的新手,所以你能推荐一些好文章或其他东西来学习吗?
  • @Pawan 我可以推荐的最好的东西是Growing Object-Oriented Software Guided by Tests 的书,它通过示例展示了如何通过 TDD 方法创建应用程序。您还可以阅读有关编写有效单元测试的文章FIRST。强烈建议阅读 Bob 大叔的SOLID 编写可测试代码的原则
【解决方案2】:

您想明确在 try 块中的哪一行引发异常。然后使用下面的方法

Assert exception from NUnit to MS TEST

更新: 对不起在火车上,所以我赶紧写下来。这是完整的答案

你的测试应该这么简单

    [TestMethod]
    public void ProductDeleteAction_GetProductDetail_ThrowsException()
    {
        // Arrange
        var productServiceMock = new Mock<IProductService>();
        productServiceMock.Setup(x => x.GetProductDetail(It.IsAny<int>())).Throws(new Exception("some"));
        var sut = new ProductController(productServiceMock.Object);

        // Act
        AssertException.Throws<Exception>(() => sut.ProductDelete(It.IsAny<int>()));
    }

正如我之前指出的,我还将创建一个简单的辅助方法

public static class AssertException
{
    public static T Throws<T>(Action action) where T : Exception
    {
        try
        {
            action();
        }
        catch (T ex)
        {
            return ex;
        }

        Assert.Fail("Expected exception of type {0}.", typeof(T));

        return null;
    }
}

你的控制器):

    public ProductController(IProductService productService) {
        _productService = productService;
    }

    public ActionResult ProductDelete(int id)
    {
        try
        {
            ViewBag.Title = "Delete Product";
            ProductDetailDto dto = _productService.GetProductDetail(id);
            return View("ProductDelete", BuildProductDetailModel(dto));

        }
        catch (Exception)
        {
            throw;
        }
    }

有几点需要注意:

您的测试方法名称写得不好。避免包含“测试”方法名称,因为您知道它是单元测试。始终使用易读的测试方法名称。您的单元测试中有一个“if”语句。避免测试中的任何逻辑,这可能会导致测试中的错误。您的测试应该“非常”简单。由于您要测试异常,因此您的断言是无关紧要的。它们可以在单独的单元测试中。

【讨论】:

    猜你喜欢
    • 2018-10-29
    • 2020-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多