7

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

  1. 我是否在一种测试方法中测试太多?
  2. 我的测试方法名称如何反映所有测试期望?

当我的方法名称说: 时,我问自己ReturnInvalidModelState,然后我的另外 2 个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);
}
4

2 回答 2

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
        {                
        }
}

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

于 2012-09-05T15:14:49.333 回答
2

我建议将所有“排列”和“执行”代码移动到一个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);
}
于 2012-09-07T13:23:32.210 回答