Joe*_*Joe 6 model-view-controller tdd asp.net-mvc
我正在开始一个新项目(好吧,重新启动现有项目),并尝试采用TDD(第n次)来获得它应该带来的所有好处.
我相信TDD将导致我的测试驱使我只编写我需要编写的代码,但它会驱使我编写我需要的代码,而不是留下一些代码.
这就是我目前的不确定状态.
考虑这个故事:
"用户必须能够添加小部件,这样做才能查看新添加的小部件的详细信息."
好吧,所以从UI工作(就像用户将从中添加他们的小部件,而不是使用Visual Studio和我写的一组程序集)......我从下面的测试开始,编写非常小的,以便测试通过.
所以我开始使用控制器抛出一个NotImplementedException,然后返回一个View()......以下是我写的最少的第一点,我可以让测试通过.
[TestFixture]
public class WidgetControllerTester
{
[Test]
public void Create_IfBusinessModelIsValid_ReturnRedirectToRouteResultToDetailsAction()
{
// Arrange
var currentUser = new User
{
DisplayName = "Fred",
Email = "fred@widgets.com",
Password = "pass",
Status = UserStatus.Active
};
var model = new WidgetModel();
var controller = new WidgetController();
// Act
var actionResult = controller.Create(currentUser, model);
// Assert
actionResult.AssertActionRedirect().ToAction("Details");
}
}
public class WidgetModel
{
}
public class WidgetController: Controller
{
public ActionResult Create()
{
return View("Create");
}
[HttpPost]
public ActionResult Create(User currentUser, Widget model)
{
return RedirectToAction("Details");
}
}
Run Code Online (Sandbox Code Playgroud)
现在我意识到无效模型的额外测试和模型状态的检查将从其他故事演变而来.
但是,我无法看到如何利用其他测试来驱动控制器中的更多代码的明确路径.
例如,我知道在某些时候我会想要从Create操作中调用WidgetService.我是否遗漏了一些显而易见的东西(无法看到树木的那些东西)我如何通过额外的测试来推进控制器代码?
谈到WidgetService,我希望我会编写一个WidgetServiceTester,因为控制器内的引用很可能会被模拟.
我有些想法......
感谢您的阅读,我将非常感谢您对最佳进展的任何反馈和见解.
乔.
编辑2010年2月27日
我发现下面的文章迭代#6 -使用测试驱动开发(上asp.net)(http://www.asp.net/%28S%28ywiyuluxr3qb2dfva1z5lgeg%29%29/learn/mvc/tutorial-31-cs. aspx)演示了我所追求的那种东西,然而他们似乎考虑将控制器/服务添加到控制器作为重新分解...我个人不同意,我错了吗?:)
我将考虑编写一个检查详细信息操作的ViewData的测试,并在我拥有后更新此问题.
乔. 有时我也会感到很多同样的不确定性。但我也认为你所缺少的大部分是那些预先的故事。首先,您应该稍微分解您的故事以创建实际的开发人员需求。这就是我的意思:
“用户必须能够添加小部件,这样他们才能查看新添加的小部件的详细信息。”
好的,对我来说,将问题分解如下,可以帮助您思考测试:
“用户”——用户从哪里来?他们如何登录?(如果您使用默认的 AccountController 和测试,那么它已经存在,如果没有,您将需要测试来获取登录表单、登录、验证成功和失败的登录等)
“添加一个小部件”——什么(我不是指实现,我只是指出这意味着您要么要访问存储库或服务,除非“添加”只是意味着将其添加到正在运行的实例,你不需要持久性)?此时小部件是否必须有效,或者可以保存无效的小部件并在以后使其有效吗?对我来说,这意味着测试存储库或服务是否有一个方法命中(save(),insert(),add(),等等(不是方法的内部,直到你开始测试你的服务/存储库,只是控制器通过调用它来完成其工作),检查有效/无效小部件上发生的情况(您需要稍微扩展您的故事或添加一个故事以涵盖有效/无效小部件上应该发生的情况)
“这样做,他们会被带到查看新添加的小部件的详细信息”——稍微改写一下,但基本上就是你所说的。总是?还是只看成功?此视图是可编辑还是只读(即“编辑”操作或“详细信息”操作)?是否有一条消息告诉用户他们已经成功了,或者他们应该从他们正在查看小部件的事实中推断出他们已经成功了?这应该驱动测试执行诸如检查返回的 actionresult 的属性、检查存储在 TempData(状态消息)中的值以及检查两个路径中发生的情况(成功或失败)等操作。
这只是一个快速的镜头,但基本上这就是思考过程。您还可以对其他故事执行相同的操作,并为此生成新故事以涵盖更多应用程序行为。
关于您未来设计的一些想法。
您的下一个测试应该首先查看我上面写的内容,即控制器的创建 POST 操作应该 1) 接收所需的数据(您的参数),2) 调用该服务/存储库来“添加”小部件,3) 可能如果该添加失败,则执行某些操作(这是在您的设计中;我已经达到了我的控制器假设一切都会顺利的位置,并且我通过属性处理失败,但这是个人设计决策),4)重定向到详细信息。
因此,您的下一个测试将使用模拟(我更喜欢谷歌代码上的起订量库,但无论您拥有什么都可以)。您需要一些接口来描述控制器将调用的服务,并将其模拟实现传递给正在测试的控制器,以确保它调用正确的方法。在起订量中,看起来像这样:
[Test]
public void Create_CallsRepository()
{
// Arrange
var currentUser = new User
{
DisplayName = "Fred",
Email = "fred@widgets.com",
Password = "pass",
Status = UserStatus.Active
};
var model = new WidgetModel();
var mockService = new Mock<IService<WidgetModel>();
mockService.Setup(s=>s.Add(model)); //.Returns(whatever) if it returns something
var controller = new WidgetController(mockService.Object);
// Act
var actionResult = controller.Create(currentUser, model);
// Assert
mockService.Verify(s=>s.Add(model));
}
Run Code Online (Sandbox Code Playgroud)
当然,这会做出一些设计假设,但是如何编写测试与如何调用对象/处理事物之间的紧张关系是 TDD 如此有价值的部分原因。