7

単体テスト方法について 2 つの懸念があります。

  1. 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

「Arrange」と「Act」のコードをすべてSetup()メソッドに移動し、残りを 3 つのテストに分割することをお勧めします。これにより、個々のテストが非常に読みやすくなり、各テストに含まれる実際のアサートにより適切に対応する名前を付けることができます。

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 に答える
2

はい、あまりにも多くのことをテストしていると思います。

テストメソッドの名前を変更することから始めます。メソッド シグネチャは、アクション、シナリオ、および期待される結果を記述する必要があります。

メソッドの名前を変更すると、次のようになります。

public void Create_DuplicateTemplate_ModelStateIsInvalidAndReturnsPartialViewResultAndPartialViewResultOfTypeTemplateViewModel() 
{ 
}

あなたのテストは、1 つではなく 3 つのことに関係しています。失敗すると、なぜ失敗したのかすぐにはわかりません。

これを小さなテストにリファクタリングし、配置ロジックの一部をカプセル化して再利用できるようにすることを検討してください。

編集:

単一のアサーションを持つ単一のテストメソッドに関するコメントで、あなたは良い点を指摘しました。その点については同意しますが、よく聞こえますが、多くの場合、十分ではありません。

次のアクション メソッドがあるとします。

[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 が 2 回呼び出されることを期待しています
  • UserManagementService が一度呼び出されることを期待しています

テストの最後に、ユーザーが正しいルートにリダイレクトされたかどうかを確認する単一のアサーションがあります。

では、どこでアサーションを確認すればよいでしょうか? 私の期待が満たされていることを確認する別のメソッドを作成します。

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

おっしゃる通り、1 つではなく 3 つのアサーションがありますが、アサーションが 1 つしかないように見えるようにコードを構成しています。

于 2012-09-05T15:14:49.333 に答える