Cha*_*ion 67 c# unit-testing controller asp.net-mvc-3
我正在寻找有关.NET mvc控制器的有效单元测试的建议.
在我工作的地方,许多这样的测试使用moq来模拟数据层并断言某些数据层方法被调用.这似乎对我没用,因为它基本上验证了实现没有改变而不是测试API.
我还阅读了一些文章,建议检查返回的视图模型的类型是否正确.我可以看到提供一些价值,但仅凭它似乎不值得编写许多行代码的努力(我们的应用程序的数据模型非常庞大和复杂).
任何人都可以建议一些更好的控制器单元测试方法或解释为什么上述方法是有效/有用的?
谢谢!
dan*_*wig 50
控制器单元测试应该在操作方法中测试代码算法,而不是在数据层中.这是模拟这些数据服务的一个原因.控制器期望从存储库/服务/等接收某些值,并且当它从它们接收不同信息时采取不同的行为.
您编写单元测试以断言控制器在非常特定的场景/环境中以非常特定的方式运行.您的数据层是应用程序的一部分,它将这些情况提供给控制器/操作方法.断言控制器调用服务方法很有价值,因为您可以确定控制器从其他位置获取信息.
检查返回的viewmodel的类型很有价值,因为如果返回错误类型的viewmodel,MVC将抛出运行时异常.您可以通过运行单元测试来防止在生产中发生这种情况.如果测试失败,则视图可能会在生产中抛出异常.
单元测试很有价值,因为它们使重构更容易.您可以更改实现,并通过确保所有单元测试通过来断言行为仍然相同.
回答评论#1
如果更改被测方法的实现需要更改/删除下层模拟方法,则单元测试也必须更改.但是,这不应该像你想象的那样经常发生.
典型的红绿重构工作流程要求在编写测试方法之前编写单元测试.(这意味着在短时间内,您的测试代码将无法编译,这也是许多年轻/缺乏经验的开发人员难以采用红绿色重构的原因.)
如果您首先编写单元测试,那么您将知道控制器需要从较低层获取信息.你怎么能确定它试图获取这些信息?通过模拟提供信息的下层方法,并断言控制器调用下层方法.
当我使用"改变实施"这个术语时,我可能会错过任务.当必须更改控制器的操作方法和相应的单元测试以更改或删除模拟方法时,您实际上是在改变控制器的行为.根据定义,重构意味着在不改变整体行为和预期结果的情况下改变实施.
红绿重构是一种质量保证方法,有助于防止代码出现之前的错误和缺陷.通常,开发人员会在实施后更改实施以删除错误.重申一下,你担心的案件不应该像你想象的那样频繁发生.
Blu*_*uds 10
是的,您应该一直测试数据库.你进行模拟的时间越少,你从模拟中获得的价值就越小(你的系统中80%的可能错误都不能通过模拟来挑选).
当您从控制器到数据库或Web服务的所有方式进行测试时,它不会被称为单元测试而是集成测试.我个人认为集成测试与单元测试相反(即使它们都用于不同的目的).我能够通过集成测试(场景测试)成功地进行测试驱动开发.
以下是我们团队的工作方式.开头的每个测试类都会重新生成数据库,并使用最少的数据集(例如:用户角色)填充/播种表.根据控制器的需要,我们填充DB并验证控制器是否完成了它的任务.这样设计使得其他方法留下的数据库损坏数据永远不会失败.除了时间运行之外,几乎所有单元测试的质量(即使它是一个理论)都是可以得到的.使用容器可以减少顺序运行所花费的时间.对于容器,我们不需要重新创建DB,因为每个测试在容器中都有自己的新DB(在测试后将被删除).
当我被迫使用模拟/存根时,我的职业生涯中只有2%的情况(或很少),因为无法创建更真实的数据源.但在所有其他情况下,集成测试是可能的.
我们花了一些时间用这种方法达到成熟水平.我们有一个很好的框架,处理测试数据填充和检索(一等公民).它耗费大量时间!第一步是告别模拟和单元测试.如果嘲笑没有意义,那么它们不适合你!集成测试可以让您睡个好觉.
===================================
在下面的评论后编辑:演示
集成测试或功能测试必须直接处理DB/source.没有嘲笑.所以这些是步骤.您想测试getEmployee(emp_id).以下所有这5个步骤都是在一个测试方法中完成的.
现在Assert()/验证返回的数据是否正确
这证明了getEmployee()的工作原理.直到3的步骤要求您只有测试项目使用的代码.第4步调用应用程序代码.我的意思是创建一个员工(第2步)应该通过测试项目代码而不是应用程序代码来完成.如果存在创建employee的应用程序代码(例如:CreateEmployee()),则不应使用此代码.同样,当我们测试在CreateEmployee() ,然后getEmployee的()不应该用于应用程序代码.我们应该有一个测试项目代码,用于从表中获取数据.
这样没有嘲笑!删除和创建数据库的原因是为了防止数据库损坏数据.使用我们的方法,无论我们运行多少次,测试都会通过.
特别提示:在步骤5中,getEmployee()返回一个雇员对象.如果稍后开发人员删除或更改字段名称,则测试会中断.如果开发人员稍后添加新字段会怎样?他/她忘记为它添加测试(断言)?测试不会捡起来.解决方案是添加字段计数检查.例如:Employee对象有4个字段(名字,姓氏,名称,性别).因此,断言员工对象的字段数为4.因此,当添加新字段时,我们的测试将因计数而失败,并提醒开发人员为新添加的字段添加断言字段.
这是一篇很好的文章,讨论集成测试对单元测试的好处,因为"单元测试会导致死亡!" (它说)
小智 9
单元测试的要点是基于一组条件单独测试方法的行为.您可以使用模拟设置测试的条件,并通过检查方法与其周围的其他代码的交互方式来断言方法的行为 - 通过检查它尝试调用哪些外部方法,特别是通过检查它在给定条件时返回的值.
因此,对于返回ActionResults的Controller方法,检查返回的ActionResult的值非常有用.
有关使用Moq的一些非常明确的示例,请查看"为控制器创建单元测试" 部分.
这是来自该页面的一个很好的示例,它测试当Controller尝试创建联系人记录并且失败时返回适当的视图.
[TestMethod]
public void CreateInvalidContact()
{
// Arrange
var contact = new Contact();
_service.Expect(s => s.CreateContact(contact)).Returns(false);
var controller = new ContactController(_service.Object);
// Act
var result = (ViewResult)controller.Create(contact);
// Assert
Assert.AreEqual("Create", result.ViewName);
}
Run Code Online (Sandbox Code Playgroud)
I don't see much point in unit testing the controller, since it is usually just a piece of code that connects other pieces. Unit testing it typically includes lots of mocking and just verifies that the other services are connected correctly. The test itself is a reflection of the implementing code.
I prefer integration tests -- I start not with a concrete controller, but with an Url, and verify that the returned Model has the correct values. With the help of Ivonna, the test might look like:
var response = new TestSession().Get("/Users/List");
Assert.IsInstanceOf<UserListModel>(response.Model);
var model = (UserListModel) response.Model;
Assert.AreEqual(1, model.Users.Count);
Run Code Online (Sandbox Code Playgroud)
I can mock the database access, but I prefer a different approach: setup an in-memory instance of SQLite, and recreate it with each new test, together with the required data. It makes my tests fast enough, but instead of complicated mocking, I make them clear, e.g. just create and save a User instance, rather than mock the UserService (which might be an implementation detail).
| 归档时间: |
|
| 查看次数: |
62164 次 |
| 最近记录: |