我的单位测试过多了吗?

时间:2012-09-05 08:31:05

标签: c# asp.net-mvc unit-testing

我对我的单元测试方法有两个担忧:

  1. 我是否在一种测试方法中测试过多?
  2. 我的测试方法名称如何反映所有测试期望?
  3. 我问自己,我的方法名称是:ReturnInvalidModelState,然后我的另外两个Asserts不正确。至少关于方法名称......

    [Test]
    public void Create_TemplateAlreadyExists_ReturnInvalidModelState()
    {
        // ARRANGE
        TemplateViewModel templateViewModel = new TemplateViewModel { 
            Name = "MyTest" 
        };
    
        Mock<ITemplateDataProvider> mock1 = new Mock<ITemplateDataProvider>();
        Mock<IMappingEngine> mock2 = new Mock<IMappingEngine>();
    
        TemplateController controller = 
            new TemplateController(mock1.Object, mock2.Object);
        mock1.Setup(m => m.TemplateExists("MyTest")).Returns(true);
        // Set ModelState.IsValid to false
        controller.ModelState.AddModelError("Name", 
                                            "This name already exists.");
    
        // ACT
        ActionResult result = controller.Create(templateViewModel);
    
        // ASSERT
        Assert.IsFalse(controller.ModelState.IsValid);
        Assert.IsInstanceOfType(typeof(PartialViewResult), result);
        Assert.AreEqual(templateViewModel, ((PartialViewResult)result).Model);
    }
    
    [HttpPost]
    public ActionResult Create(TemplateViewModel templateViewModel)
    {
        if (ModelState.IsValid
            && !_templateDataProvider.TemplateExists(templateViewModel.Name))
        {
    
            Template template = 
                Mapper.Map<TemplateViewModel, Template>(templateViewModel);
    
            _templateDataProvider.AddTemplate(template);
            return new JsonNetResult(new { success = true });
        }
        ModelState.AddModelError("Name", "This name already exists.");
        return PartialView(templateViewModel);
    }
    

2 个答案:

答案 0 :(得分:2)

是的,我认为你正在测试太多东西。

从重命名测试方法开始。您的方法签名应描述操作,方案和预期结果。

如果我要重命名你的方法,那么我最终会得到以下内容:

public void Create_DuplicateTemplate_ModelStateIsInvalidAndReturnsPartialViewResultAndPartialViewResultOfTypeTemplateViewModel() 
{ 
}

您的测试涉及三件事,而不是一件事。当它失败时,你不会马上知道它失败的原因。

考虑将其重新分解为较小的测试并封装一些排列逻辑,以便可以重复使用。

修改

您对单一测试方法的评论提出了一个很好的观点。我同意你的意见,尽管听起来不错,但通常还不够。

说我有以下操作方法:

[HttpPost]
public ActionResult Register(NewUserViewModel newUser)
{
    if (!ModelState.IsValid)
        return View(newUser);

    var newUserDTO = Mapper.Map<NewUserViewModel, NewUserDTO>(newUser);
    var userDTO = UserManagementService.RegisterUser(newUserDTO);

    var result = Mapper.Map<UserDTO, UserViewModel>(userDTO);

    TempData.Add("RegisteredUser", result);
    return RedirectToAction("RegisterSuccess");
}

我对此方法进行了以下单元测试:

        [TestMethod]
        public void Register_HttpPost_ValidViewModel_ReturnsRegisterSuccess()
        {
            // Arrange
            var autoMapperMock = this.mockRepository.DynamicMock<IMapper>();
            var userManagementServiceMock = this.mockRepository.DynamicMock<IUserManagementService>();

            var invalidRegistrationViewModel = new NewUserViewModel
            {
                LastName = "Lastname",
                FirstName = "Firstname",
                Username = null
            };

            autoMapperMock.Expect(a => a.Map<UserDTO, UserViewModel>(Arg<UserDTO>.Is.Anything)).Repeat.Once().Return(null);
            autoMapperMock.Expect(a => a.Map<NewUserViewModel, NewUserDTO>(Arg<NewUserViewModel>.Is.Anything)).Repeat.Once().Return(null);
            userManagementServiceMock.Expect(s => s.RegisterUser(Arg<NewUserDTO>.Is.Anything)).Repeat.Once();

            autoMapperMock.Replay();

            var controller = new AccountController
            {
                Mapper = autoMapperMock,
                UserManagementService = userManagementServiceMock
            };

            this.mockRepository.ReplayAll();

            // Act
            var result = (RedirectToRouteResult)controller.Register(invalidRegistrationViewModel);

            // Assert
            Assert.IsTrue((string)result.RouteValues["Action"] == "RegisterSuccess");
        }

正如您所看到的,我对我的模拟设置了多个期望:

  • 我希望AutoMapper被调用两次
  • 我希望一次调用UserManagementService

在测试结束时,我有一个断言,检查用户是否被重定向到正确的路线。

那我在哪里检查我的断言?我创建了另一种方法来确保满足我的期望:

    [TestCleanup]
    public void Cleanup()
    {
        try
        {
            this.mockRepository.VerifyAll();
        }
        finally
        {                
        }
}

所以你是对的,我有三个断言而不是一个断言,但我以这样的方式构造我的代码,所以看起来我只有一个断言。

答案 1 :(得分:2)

我建议将所有“Arrange”和“Act”代码移动到Setup()方法中,然后将其余部分分成三个测试。这将使每个单独的测试更容易阅读,并让您为每个测试提供一个更好地与其包含的实际断言相对应的名称。

private TemplateViewModel _templateViewModel;
private ITemplateDataProvider _mock2;
private IMappingEngine _mock2;
private TemplateController _controller;
private ActionResult _result;

[Setup]
public void Setup(){
    // ARRANGE
    _templateViewModel = new TemplateViewModel { Name = "MyTest" };

    _mock1 = new Mock<ITemplateDataProvider>();
    _mock2 = new Mock<IMappingEngine>();

    _controller = new TemplateController(_mock1.Object, _mock2.Object);
    _mock1.Setup(m => m.TemplateExists("MyTest")).Returns(true);

    // Set ModelState.IsValid to false
    _controller.ModelState.AddModelError("Name", 
                                        "This name already exists.");

    _result = controller.Create(_templateViewModel);
}


[Test]
public void Create_TemplateAlreadyExists_ModelStateIsInvalid()
{
    Assert.IsFalse(_controller.ModelState.IsValid);
}


[Test]
public void Create_TemplateAlreadyExists_ResultIsPartialViewResult()
{
    Assert.IsInstanceOfType(typeof(PartialViewResult), _result);
}


[Test]
public void Create_TemplateAlreadyExists_ResultModelMatchesTemplateModel()
{
    Assert.AreEqual(_templateViewModel, ((PartialViewResult)_result).Model);
}