郁闷的PP

博客园 首页 新随笔 联系 订阅 管理

(代码截图为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 WindsorObjectBuilder的代码示例。

 

Start Me Up(向滚石乐队致歉)

首先安装好VS2008以及ASP.NET MVC,打开Visual Studio 2008并选择File|New Project,在对话窗口中选择ASP.NET MVC Application模板。

 

然后选择单元测试项目选择对话框。

 

默认安装下,只有Visual Studio Unit Test项目选项可用。但是安装了MbUnitxUnit等等其他框架也会在这里显示出来。

 

你可能会猜想,我会从建立一个很权威的blog例子,其实我会从没有数据库的示例开始,我们可以以后慢慢添加。

 

第一件我想做的事是添加一些类文件到主项目中去。我不会添加任何实现,能编译就行。我首先添加这些:

 

Controllers目录下BlogController.cs

Models目录下IPostRespository.cs

Models目录下Post.cs

MvcApplicationTest项目下BlogControllerTests

 

完成后,我的项目文件树像这样:

 


 

现在我想写足够多的代码让我们可以写一个测试。首先,我定义了容器接口。

 

 

对真正的博客帖子容器来说这点内容是不够的,但是这里只是做一个demo而已。当你准备好写一个很强悍的blog引擎的时候,你可以添加更多的方法。

 

现在我仍然不管Post类,让它继续空着,可以以后再来实现。先实现blogController

 



 

好了,在这里打住。我们已经能够开始写单元测试了。毕竟我准备演示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)。

 

posted on 2008-09-04 10:38  郁闷的PP  阅读(1600)  评论(0编辑  收藏  举报