【问题标题】:Test if save method is hit in unit test测试单元测试中是否命中了保存方法
【发布时间】:2012-08-06 15:05:29
【问题描述】:

我开始在 asp.net mvc 4 框架中进行单元测试。

我有一个包含基本 crud 方法和 save 方法的存储库。当我创建一个单元测试时,我创建一个测试存储库并测试例如将项目添加到集合中。一切都很顺利,但我无法测试保存方法是否被击中。

我尝试向测试存储库添加一个布尔属性,如果 .save() 被命中,该属性将设置为 true。但随后我需要更改界面以及数据库存储库。在我看来,这既不实用也不是最佳实践。

测试这个的最佳方法是什么?提前感谢您的回答。

我的代码:

假存储库:

public class TestUserRepository : IUserManagementRepository
    {
        /// <summary>
        /// entries used used for testing
        /// </summary>
        private List<User> _entities;

        /// <summary>
        /// constructor
        /// </summary>
        public TestUserRepository()
        {
            _entities = new List<User>();
            _entities.Add(new User
            {
                Id = 1,
                InsertDate = DateTime.Now,
                LastUpdate = DateTime.Now,
                Username = "TestUserName",
                Password = "TestPassword"
            });
        }
...

public void Create(User task)
        {
            _entities.Add(task);
        }

public void Save()
        {
            //do nothing
        }
    }

要测试的控制器:

[HttpPost]
        public ActionResult Create(User user)
        {
            if (ModelState.IsValid)
            {
                _repository.Create(user);
                _repository.Save();
                return RedirectToAction("Index");
            }
            else
            {
                return View(user);
            }

        }

和测试

[TestMethod()]
        public void CreateTest()
        {
            IUserManagementRepository repository = new TestUserRepository();
            UserController controller = new UserController(repository);
            User user = new User { Username = "UnitTestUserName", InsertDate = DateTime.Now, LastUpdate = DateTime.Now, Password = "Password" };
            ActionResult actionResult = controller.Create(user);
        User returnedUser = repository.FindBy(u => u.Username == "UnitTestUserName").First<User>();

            Assert.IsNotNull(actionResult);
            Assert.AreEqual(user, returnedUser);            
        }

【问题讨论】:

  • 看看 mocking 和 BDD。 BDD 就是为这类事情而设计的。

标签: asp.net-mvc unit-testing repository


【解决方案1】:

您必须注意不要编写一堆只测试您的测试存储库的单元测试。

考虑以下场景:

  1. 您有一个服务方法,应该将项目添加到您的存储库。
  2. 您的单元测试调用此方法,并且您应该验证是否在存储库上调用了适当的“AddX”方法。

这是一个有效的单元测试场景,要对其进行测试,您可以使用您的测试存储库。由于它是您的测试对象,因此您可以完全控制它。您可以公开诸如“AddXMethodCallCount”之类的属性或类似的东西。

随着时间的推移,您会发​​现自己编写了大量几乎是样板的测试代码。我强烈推荐的替代方法是使用模拟框架:

https://stackoverflow.com/questions/37359/what-c-sharp-mocking-framework-to-use

这需要一些时间来适应,但是一旦你掌握了它,它将显着加快你的单元测试。

如果您还不想使用模拟,但仍想实现验证是否调用 Save() 的目标,我建议您添加一个公开的 SaveMethodCallCount 属性:

public class TestUserRepository : IUserManagementRepository
{

...


public SaveMethodCallCount {get; set;}

...
public void Save()
    {
        SaveMethodCallCount++;
    }
}

这行得通,因为在你的单元测试中你实际上可以说:

TestUserRepository 存储库 = new TestUserRepository();

UserController 不关心,只要传入的参数实现了 IUserManagementRepository 接口即可。控制器通过接口与存储库对象交互,但单元测试不必这样做,并且作为测试类的 TestUserRepository 被允许具有更多功能,而不必通过接口公开。

所以你的测试可能看起来像:

[TestMethod()]
    public void CreateTest()
    {
        TestUserRepository repository = new TestUserRepository();
        UserController controller = new UserController(repository);
        User user = new User { Username = "UnitTestUserName", InsertDate = DateTime.Now, LastUpdate = DateTime.Now, Password = "Password" };
        ActionResult actionResult = controller.Create(user);
    User returnedUser = repository.FindBy(u => u.Username == "UnitTestUserName").First<User>();

        Assert.IsNotNull(actionResult);
        Assert.AreEqual(user, returnedUser);  
        Assert.AreEqual(1, repository.SaveMethodCallCount);
    }

为了使我的示例完整,让我向您展示如果您使用模拟框架(例如 Moq)会是什么样子。你可以看到更多的例子here。示例测试方法使用 Moq 和Arrange/Act/Assert,并且只测试一件事——调用 Create() 时调用 Save()。

[TestMethod()]
public void Test_SaveCalledWhenCreateCalled()
{    
    // Arrange
    // First, instead of creating an instance of your test class, you create a mock repository. 
    // In fact, you don't need to write any code, the mocking framework handles it. 
    var mockRepository = new Mock<IUserManagementRepository>();
    // and pass the mock repository (which implements the IUserManagementRepository) to your controller
    UserController controller = new UserController(mockRepository);

    // Act
    ActionResult actionResult = controller.Create(user);

    // Assert
    // see how easy it is to do with a mocking framework:
    mockRepository.Verify(rep => rep.Save(), Times.AtLeastOnce());
}

【讨论】:

  • 在我的存储库中,我有一个 create 方法,该方法将一个项目添加到集合中,一个 save 方法将更改保存到数据库中。控制器的create方法应该使用repository的create和save方法。我可以使用测试存储库测试 create 方法,因为在集合中添加了一个项目。但是我无法测试控制器是否命中了 save 方法以将更改写入数据库,这在我看来是非常必要的。
  • 发布您的代码将帮助我更好地回答您的问题。我的回答的意思是,如果您的测试调用了 TEST 存储库上的方法,那么您并没有真正测试任何东西。您的测试调用的生产代码是什么?
  • 感谢您的帮助!我添加了代码。我想测试的是控制器是否点击了存储库的 save() 方法。
  • 感谢您的回答。现在一切都清楚了。我会明确地研究 Moq 框架,但现在我想通过简单的测试来练习更多,首先了解基础知识。
猜你喜欢
  • 1970-01-01
  • 2021-11-16
  • 1970-01-01
  • 2017-07-29
  • 2010-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多