ASP.NET MVC中的单元测试:最小化每次测试的断言数

时间:2010-06-16 22:27:00

标签: unit-testing

我正在ASP.NET MVC的绿地爱好应用程序上尝试TDD,并开始获得以下测试方法:

[Test]
public void Index_GetRequest_ShouldReturnPopulatedIndexViewModel()
{
    var controller = new EmployeeController();
    controller.EmployeeService = GetPrePopulatedEmployeeService();

    var actionResult = (ViewResult)controller.Index();

    var employeeIndexViewModel = (EmployeeIndexViewModel)actionResult.ViewData.Model;
    EmployeeDetailsViewModel employeeViewModel = employeeIndexViewModel.Items[0];

    Assert.AreEqual(1, employeeViewModel.ID);
    Assert.AreEqual("Neil Barnwell", employeeViewModel.Name);
    Assert.AreEqual("ABC123", employeeViewModel.PayrollNumber);
}

现在我知道理想情况下测试只会有一个Assert.xxx()调用,但这是否意味着我应该重构以上内容以使用以下名称分隔测试:

  • Index_GetRequest_ShouldReturnPopulatedIndexViewModelWithCorrectID
  • Index_GetRequest_ShouldReturnPopulatedIndexViewModelWithCorrectName
  • Index_GetRequest_ShouldReturnPopulatedIndexViewModelWithCorrectPayrollNumber

...大多数测试都是重复的代码(因此不止一次被测试并且违反了“快速测试”建议)?这似乎对我来说是极端的,所以如果我和我一样正确,那么“每个测试的一个断言”建议的真实含义是什么?

3 个答案:

答案 0 :(得分:3)

对我来说这似乎是极端的,这就是为什么我还要为每个测试编写多个断言。我已经进行了> 500次测试,每次测试只写一个断言就会将其打到至少2500次,我的测试需要10分钟才能运行。

由于良好的休息跑步者(例如Resharper's)可以让你快速看到测试失败的线路,你仍然可以轻松找出测试失败的原因。如果你不介意额外的努力,你也可以添加一个断言描述(“断言工资单号码正确”),这样你甚至可以看到这个甚至没有查看源代码。有了这个,每次测试只有一个断言的理由很少。

答案 1 :(得分:2)

在他的“单位测试艺术”一书中,Roy Osherove谈到了这个问题。他也赞成在单元测试中只测试一个事实,但他指出这并不总是只意味着一个断言。在这种情况下,您正在测试给定GetRequest Index方法ShouldReturnPopulatedIndexViewModel。在我看来,填充的视图模型应该包含一个ID,一个名称,一个PayrollNumber,因此对此所有这些内容进行断言测试是完全合理的。

但是,如果你真的想要拆分断言(例如,如果你正在测试需要类似设置但在逻辑上不一样的各个方面),那么你可以这样做而不需要太多努力:< / p>

[Test] 
public void Index_GetRequest_ShouldReturnPopulatedIndexViewModel() 
{
    var employeeDetailsViewModel = SetupFor_Index_GetRequest();
    Assert.AreEqual(1, employeeDetailsViewModel.ID);
}

[Test] 
public void Index_GetRequest_ShouldReturnPopulatedIndexViewModel() 
{
    var employeeDetailsViewModel = SetupFor_Index_GetRequest();
    Assert.AreEqual("Neil Barnwell", employeeDetailsViewModel.Name); 
}

[Test] 
public void Index_GetRequest_ShouldReturnPopulatedIndexViewModel() 
{
    var employeeDetailsViewModel = SetupFor_Index_GetRequest();
    Assert.AreEqual("ABC123", employeeDetailsViewModel.PayrollNumber); 
}

private EmployeeDetailsViewModel SetupFor_Index_GetRequest()
{
    var controller = new EmployeeController(); 
    controller.EmployeeService = GetPrePopulatedEmployeeService(); 

    var actionResult = (ViewResult)controller.Index(); 

    var employeeIndexViewModel = (EmployeeIndexViewModel)actionResult.ViewData.Model; 
    var employeeDetailsViewModel = employeeIndexViewModel.Items[0]; 

    return employeeDetailsViewModel;
}

也可以说,由于这些测试需要相同的设置,因此他们应该获得自己的夹具并使用单个[SetUp]方法。但是这种方法有一个缺点。它可能导致比实际的真实类更多的单元测试类,这可能是不可取的。

答案 2 :(得分:1)

我使用辅助类来包含断言。这使测试方法保持整洁,并专注于他们实际尝试建立的内容。它看起来像:

public static class MvcAssert
{
    public static void IsViewResult(ActionResult actionResult)
    {
        Assert.IsInstanceOfType<ViewResult>(actionResult);
    }

    public static void IsViewResult<TModel>(ActionResult actionResult, TModel model)
    {
        Assert.IsInstanceOfType<ViewResult>(actionResult);
        Assert.AreSame(model, ((ViewResult) actionResult).ViewData.Model);
    }

    public static void IsViewResult<TModel>(ActionResult actionResult, Func<TModel, bool> modelValidator)
        where TModel : class
    {
        Assert.IsInstanceOfType<ViewResult>(actionResult);
        Assert.IsTrue(modelValidator(((ViewResult) actionResult).ViewData.Model as TModel));
    }

    public static void IsRedirectToRouteResult(ActionResult actionResult, string action)
    {
        var redirectToRouteResult = actionResult as RedirectToRouteResult;
        Assert.IsNotNull(redirectToRouteResult);
        Assert.AreEqual(action, redirectToRouteResult.RouteValues["action"]);
    }
}