此文为转载:http://www.cnblogs.com/darkdawn/archive/2008/09/04/1283692.html 非常好的文章。
(代码截图为ASP.NET MVC Preview 5版本)
原文地址:http://haacked.com/archive/2007/12/07/tdd-and-dependency-injection-with-asp.net-mvc.aspx
在设计ASP.NET MVC Framework的时候,指导原则之一是要能使用TDD(测试驱动开发)建立web应用程序。本文使用ASP.NET MVC CodePlex Preview 4为例(经过测试,代码可以在Preview 5中运行,翻译版截图全部为Preview 5),我将试着保持这篇文章的内容适用于最新的ASP.NET MVC Framework,但是需要多一点点的时间。
本文提供一个稍具测试驱动开发(TDD)风格的web程序,同时介绍把StructureMap DI(依赖注入)框架集成到这个ASP.NET MVC示例中。在本文结尾处你可以下载代码。
我选择了StructureMap 2.0依赖注入框架,因为我对它比较熟悉,并且它只需要做很少的代码和配置。如果你想把这个示例换成使用Spring.NET,可以到Fredrik Normen’s post查看。以后我可能会写点使用Castle Windsor和ObjectBuilder的代码示例。
Start Me Up!(向滚石乐队致歉)
首先安装好VS2008以及ASP.NET MVC,打开Visual Studio 2008并选择File|New Project,在对话窗口中选择ASP.NET MVC Application模板。
然后选择单元测试项目选择对话框。
默认安装下,只有Visual Studio Unit Test项目选项可用。但是安装了MbUnit,xUnit等等其他框架也会在这里显示出来。
你可能会猜想,我会从建立一个很权威的blog例子,其实我会从没有数据库的示例开始,我们可以以后慢慢添加。
第一件我想做的事是添加一些类文件到主项目中去。我不会添加任何实现,能编译就行。我首先添加这些:
在Controllers目录下BlogController.cs
在Models目录下IPostRespository.cs
在Models目录下Post.cs
在MvcApplicationTest项目下BlogControllerTests
完成后,我的项目文件树像这样:
现在我想写足够多的代码让我们可以写一个测试。首先,我定义了容器接口。
对真正的博客帖子容器来说这点内容是不够的,但是这里只是做一个demo而已。当你准备好写一个很强悍的blog引擎的时候,你可以添加更多的方法。
现在我仍然不管Post类,让它继续空着,可以以后再来实现。先实现blog的Controller。
好了,在这里打住。我们已经能够开始写单元测试了。毕竟我准备演示TDD嘛。让我们先来写个测试。
Let’s Get Test Started, In Here.(向黑眼豆豆致歉)
从最简单的测试开始,确定Recent这个行为(Action)没有指定视图(View),因为我看到默认行为的运行结果。(这段代码假设你已经引用了所有需要的名称空间)
运行这个测试,会失败。
但这正是我们所期望的,因为我们并没有实现Recent方法。这是TDD红绿重构韵律之红色部分。
还是让我们来把这个方法实现了吧:
注意:我们是把精力集中在行为上,而不是在UI上。这和使用ASP.NET WebForms是不同的。这两者没有孰优孰劣,只是风格不同而已。
现在当我运行测试,会通过。
很好,现在是TDD生命周期的绿色部分了!也只个非常非常简单的TDD例子。现在该我们进入介绍依赖注入阶段了。
It’s Refactor Time(向读者致歉,扯得太远了)
为了获得最近的博客帖子,我想为我的博客Controller提供一个“服务”实例,它可以请求这些帖子。
这时,我不能确定我怎么去存储博客帖子,用什么好呢?SQL?XML?还是其他?
都不是。暂时不要去想它吧。
我们可以把这个讨论延迟到最后的时刻。现在我要创建一个抽象容器IPostRepository,用来描述我想怎么存储和取回这些博客帖子。我们来为blogController写些代码,让它可以在它的构造器中接受一个这个接口的实例。
这是依赖注入的依赖(Dependency)部分。这个Controller现在有一个对IPostResitory的依赖。注入(Injection)部分是一种机制:传递依赖给所需要依赖的类,直接创建类的实例,并绑定类到所指定的接口的实现。
现在修改BlogController类。
很好。注意我并没有改变Recent方法。我需要先写另一个测试,要确保它传递正确的数据给view。
注意:你现在会发现刚才我们写的测试不能通过,先注释掉,我们一会再修正它。
我们现在要使用mock框架,在我写测试之前,我需要引用Moq.dll到我的测试项目中,在这里下载MoQ。
注意:在本示例项目中已经引用了这个程序集。
这个测试动态创建IPostRepository接口的实现。我们告诉它:不管是什么参数传递给了ListRecentPosts,返回两篇帖子。
注意:我们现在不需要这个接口的实现。我们感兴趣的是把测试Action的逻辑孤立出来,所以在测试的时候我们直接使用了接口实例化。
开始测试,失败了,看来我们需要重构Recent方法,让它正确运行:
重新测试一次,成功了!
Inject That Dependency
当我使用浏览器尝试访问这个action的时候(如http://localhost:14963/Blog/Recent),会出现如下的错误页面:
出现这样的错误很正常,默认情况下,ASP.NET MVC需要Controller有一个public的、无参的构造器,让它自己可以创建Controller的实例。但是我们的构造器需要一个IPostRepository实例作为参数。我们需要给Controller传递一个这样的参数才行。
StructureMap(或其他依赖注入框架)来救援!
注意:记得下载和引用StructureMap.dll程序集。我在示例代码中已经引用了。
首先要在应用程序根目录下创建StructureMap.config文件,文件内容:
这里不对这个文件内容做详细解释,如果你想了解更多,请查看StructureMap文档。
它只暴露了你所需要知道的最少细节,每个PluginFamily节点描述一个接口类型和一个节点的键(key)。Plugin节点描述一个具体的类型:框架实例化接口类型需要创建的具体类型。
比如说,第二个PluginFamily节点中,接口类型是IPostRepository,具体的类型是InMemoryPostRepository。当我们使用StructureMap构造一个包含对IPostRepository依赖的类型的实例时,StructureMap会传递一个InMemoryPostReposity实例。
平时我使用SqlPostRepository。但是这个demo的目的并不需要那么做,所以我打算用个静态集合,把存储这些博客帖子到内存中。我们总是可以不急着实现使用SQL版本。
注意:本来应该写一个InMemoryPostRepository的测试,但是这篇文章已经够长了。不过也别担心,我会把单元测试放到示例代码中去。
快,我们需要一个工厂!
快完成了。我们需要实现IControllerFactory接口,把StructureMap连接到ASP.NET MVC上。Controller工厂的职责是创建Controller的实例。我们可以在自己的工厂中用这些逻辑:
最后,我们吧他们都连接起来,在Global.asax.cs中的Application_Start方法添加方法调用:
一切搞定!现在我们把依赖注入框架引入到了我们的程序中,我们可以重新访问站点测试一下了(别忘记编译)。我们得到这个页面:
很好很强大!黄屏去死吧,不过在这里是个好现象:我们的依赖对象已经注入到对象中了,这是另一个错误提示,因为我们没有view创建view造成的。
不好意思,跑题了。
这里我就不做了,你们可以自己做一个,或者你们可以在傻瓜示例源代码中看到。
这个示例有点简单得可笑,不过这种原则在做更大的项目中也是通用的。它只是用来学习技术的,希望在你在TDD路上走得更好。
本文所有内容的源代码下载(ASP.NET MVC Preview 5/StructureMap 2.0)。