一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29


一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29
 
本文根据《Professional ASP.NET MVC 1.0》中微软牛人Scott Guthrie 提供免费下载的第一章,一步一步演示如何通过ASP.NET MVC 1.0 正式版创建NerdDinner 范例程序。对了解如何使用最新的ASP.NET MVC 1.0框架创建Web Application 非常有帮助。本文由http://forum.entlib.com 开源论坛小组提供。
 
 
本文继续学习之旅,一步一步通过ASP.NET MVC 1.0 实现NerdDinner 范例程序。恭喜恭喜,本文终于是本系列文章的最后一篇了。在此,感谢 美女程序员Jacky 的友情协助。
 
创建Edit Action方法的单元测试
下面创建DinnersController的编辑功能的单元测试。首先,测试Edit Action方法的HTTP-GET版本:
        //
        // GET: /Dinners/Edit/2
        public ActionResult Edit(int id)
        {
            Dinner dinner = dinnerRepository.GetDinner(id);
 
            if (!dinner.IsHostedBy(User.Identity.Name))
                return View("InvalidOwner");
            // 使用ViewData
            // ViewData["Countries"] = new SelectList(PhoneValidator.Countries, dinner.Country);
            // return View(dinner);
 
            // 使用ViewModel
            return View(new DinnerFormViewModel(dinner));
 
        }
备注:上述代码中DinnerFormViewModel是之前我们定义的一个Model类。
 
我们将创建一个测试:当请求一个有效的Dinner对象时,验证返回的DinnerFormViewModel对象。
        [TestMethod]
        public void EditAction_Should_Return_View_For_ValidDinner()
        {
            // Arrage
            var controller = CreateDinnersController();
            // Act
            var result = controller.Edit(2) as ViewResult;
            // Assert
            Assert.IsInstanceOfType(result.ViewData.Model, typeof(DinnerFormViewModel));
        }
如果现在运行测试,发现测试会失败,这是因为Edit方法在访问User.Identity.Name属性,执行Dinner.IsHostedBy()检查时,抛出null引用异常。
Controller基类的User对象封装了登录用户的详细信息,在运行时创建Controller时,ASP.NET MVC填充该对象。因为我们测试DinnersController时,没有运行在web-server的环境,因此User 对象没有设置,导致null引用异常。
 
模仿User.Identity.Name属性
Mocking Framework可以帮忙我们动态创建虚假的依赖对象,支持测试工作。例如,在Edit Action 方法的测试中,我们可以使用一个Mocking Framework,动态创建User对象,DinnersController 将使用该对象来模拟一个用户名。这样在运行测试时,可以避免null引用的发生。
 
下载后,在NerdDinner.Tests项目中添加对Moq.dll 程序集的引用。
 
 
接着在测试类中添加一个重载的CreateDinnersControllerAs(username) 辅助方法,接收username参数,该参数模仿DinnersController实例中的User.Identity.Name 属性。
        DinnersController CreateDinnersControllerAs(string userName)
        {
            var mock = new Mock<ControllerContext>();
            mock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(userName);
            mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);
            var controller = CreateDinnersController();
            controller.ControllerContext = mock.Object;
 
            return controller;
        }
 
上述代码使用Moq创建一个Mock对象,虚拟一个ControllerContext对象(该对象是ASP.NET MVC传递给Controller类,公布运行时对象,如User、Request、Response和Session)。调用Mock的SetupGet方法,表示ControllerContext的HttpContext.User.Identity.Name 属性应该返回username字符串,该字符串是传递给辅助方法的参数。
 
我们可以模拟ControllerContext的任何属性和方法。为了证明这一点,我们也向Request.IsAuthenticated 属性添加了SetupGet() 的调用(该属性对于下面的测试是不需要的,但是可以证明如何模拟Request属性)。最后,将模拟的ControllerContext实例赋值给辅助方法需要返回DinnersController对象。
 
下面使用上述辅助方法编写单元测试,用不同的用户测试Edit方法:
        [TestMethod]
        public void EditAction_Should_Return_View_For_ValidDinner()
        {
            // Arrage
            var controller = CreateDinnersControllerAs("EntLib.com");
            // Act
            var result = controller.Edit(2) as ViewResult;
            // Assert
            Assert.IsInstanceOfType(result.ViewData.Model, typeof(DinnerFormViewModel));
        }
 
        [TestMethod]
        public void EditAction_Should_Return_InvalidOwnerView_When_InvalidOwner()
        {
            // Arrange
            var controller = CreateDinnersControllerAs("NotOwnerUser");
            // Act
            var result = controller.Edit(2) as ViewResult;
            // Assert
            Assert.AreEqual(result.ViewName, "InvalidOwner");
        }
 
现在通过所有测试:
 
 
测试UpdateModel()
我们已经创建了测试HTTP-GET版本的Edit Action方法,下面继续创建测试HTTP-POST版本的Edit Action方法:
        //
        // POST: /Dinners/Edit/2
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Edit(int id, FormCollection formValues)
        {
            Dinner dinner = dinnerRepository.GetDinner(id);
 
            if (!dinner.IsHostedBy(User.Identity.Name))
                return View("InvalidOwner");
 
            try
            {
                UpdateModel(dinner);
                dinnerRepository.Save();
                return RedirectToAction("Details", new { id = dinner.DinnerID });
            }
            catch
            {
                ModelState.AddRuleViolations(dinner.GetRuleViolations());
                // 使用ViewData
                // ViewData["countries"] = new SelectList(PhoneValidator.Countries, dinner.Country);
                // return View(dinner);
 
                // 使用ViewModel
                return View(new DinnerFormViewModel(dinner));
            }
        }
 
上述Action方法使用了Controller基类的UpdateModel() 辅助方法,使用该辅助方法绑定表单提交的值到Dinner对象实例。
 
下面的2个测试演示了如何提供表单提交的值给UpdateModel() 辅助方法使用。通过创建和填充一个FormCollection对象,接着赋值给Controller的ValueProvider属性,来实现测试。测试方法代码如下:
        [TestMethod]
        public void EditAction_Should_Redirect_When_Update_Successful()
        {
            // Arrange
            var controller = CreateDinnersControllerAs("EntLib.com");
            var formValues = new FormCollection() {
                            { "Title", "Another value" },
                            { "Description", "Another description" }
                            };
            controller.ValueProvider = formValues.ToValueProvider();
            // Act
            var result = controller.Edit(1, formValues) as RedirectToRouteResult;
            // Assert
            Assert.AreEqual("Details", result.RouteValues["Action"]);
        }
 
        [TestMethod]
        public void EditAction_Should_Redisplay_With_Errors_When_Update_Fails()
        {
            // Arrange
            var controller = CreateDinnersControllerAs("EntLib.com");
            var formValues = new FormCollection() {
                            { "EventDate", "Bogus date value!!!"}
                            };
            controller.ValueProvider = formValues.ToValueProvider();
            // Act
            var result = controller.Edit(1, formValues) as ViewResult;
            // Assert
            Assert.IsNotNull(result, "Expected redisplay of view");
            Assert.IsTrue(result.ViewData.ModelState.Count > 0, "Expected errors");
        }
 
其中第一个测试验证:当成功保存后,浏览器重定向到Details Action方法。第二个测试验证:当提交无效的表单参数值时,重新显示带错误消息的Edit视图。现在,再次运行测试,结果如下:
 
 
单元测试总结
我们已经完成了对Controller类进行单元测试的核心概念。我们可以使用这些技术轻松创建好几百简单测试,验证应用程序的功能。
因为Controller和Model测试不需要真实的数据库,这样可以非常快和容易运行。我们可以在几秒钟执行几百个自动化测试,并立即获得信息 – 是否更新破坏了现有的逻辑。这样,让我们有信心持续改进、重构和优化应用程序。
在本章的最后部分,我们介绍了测试相关技术,但并不表示测试是开发流程的最后阶段。相反,你应该在开发流程中尽早编写自动化测试。这样,你可以在开发过程中及时得到反馈结果,帮助你仔细思考应用程序的业务场景,并指导你设计清晰分层的、松耦合的应用程序。
《Professional ASP.NET MVC 1.0》这本书的随后章节将介绍Test Driven Development(TDD),已经如何在ASP.NET MVC中使用。TDD是一个迭代的开发过程。通过TDD,首先编写验证将要实现的业务功能的测试。编写单元测试,可以帮助你清晰理解功能和如何工作。仅仅在完成测试代码的编写之后,才可是实现对应的实际功能。因为你已经思考了这些功能如何工作的业务场景,你可以更好地理解需求,以及如何最好地实现。当你完成了业务代码的编写之后,你可以重新运行测试,立即获得关于功能是否工作正常的反馈。
 
NerdDinner范例程序总结
NerdDinner范例应用程序终于完成了,已经可以发布了。
 
 
我们使用了大量的ASP.NET MVC功能来创建NerdDinner范例程序。希望这一开发过程演示了ASP.NET MVC核心功能是如何工作的,已经如何在一个应用程序中集成这些功能。
 

 

发表 @ 2009年4月16日 22:16

打 印

评论

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by myjunc at 2009/4/17 0:02
Gravatar
请问楼主有没有要做一个实际应用程序作为例子的想法啊,呵呵,期待中……

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by entlibforum at 2009/4/17 8:29
Gravatar
觉得还没有掌握ASP.NET MVC 1.0的一些技术细节,水平还比较嫩,希望能够完整看完《Professional ASP.NET MVC 1.0》这本书之后,再考虑在具体项目中使用。

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by mvcbird at 2009/4/17 16:25
Gravatar
一口气看完了 ,支持下楼主精神 !
假如有空能搞的PDF就更好了 !谢谢 !

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by entlibforum at 2009/4/17 17:45
Gravatar
mvcbird,多谢支持!

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by badboy at 2009/4/23 16:30
Gravatar
看完了,非常好,谢谢

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by FinKL at 2009/5/5 18:03
Gravatar
我也看完了 不错的文章

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by Luck at 2009/7/10 15:15
Gravatar
非常好啊!感谢楼主。只可以只有这一章,都有了买这本书的冲动了,可惜原版太贵了,需要$31.9.

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by Striker at 2009/7/21 10:39
Gravatar
还是看原版英文更能加深理解。
而且感觉,遇到的问题会比较容易解决,开始我是看中文的,后来遇到很多问题,看了英文原版才恍然大悟的,后来就先看英文原版后,做程序的时候再看看中文,效果好多了。

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by feifeiyaqi3 at 2009/7/26 11:56
Gravatar
问题:

我在作nerdDinner1.0时如果设置default。aspx为启动页的话,这个就可以正常浏览的,如果我设置dinner下的details。aspx时就会发生
这个错误
说明: HTTP 404。您正在查找的资源(或者它的一个依赖项)可能已被移除,或其名称已更改,或暂时不可用。请检查以下 URL 并确保其拼写正确。

请求的 URL: /Views/Dinners/Deleted.aspx


请问如何解决呀?谢谢

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by aaa at 2009/8/10 11:32
Gravatar
修改Views下的web.config 中
<httpHandlers>
<add path="*" verb="*"
type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
修改 path="*" 不为空

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by at 2009/8/23 0:24
Gravatar
由于技术不高,所得有些吃力!细节不讲!

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by at 2009/8/23 0:27
Gravatar
现在已经有MVC 2.0 了 不过教程好少!
如果边做实例边开着幕录制的软件,这样多好呀,发布出来一定很多人下载!
我个人就喜欢看录制的,因为技术有些菜!

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by abcsw12 at 2009/10/4 17:02
Gravatar
楼主!辛苦了
谢谢楼主

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by Yinner at 2010/1/26 22:42
Gravatar
对楼主所做的辛苦而高级的劳动表示,受益非浅!

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by flysnow at 2010/2/2 15:01
Gravatar
谢谢楼主的辛苦,我也全部建立完成了。

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by Someone at 2010/4/20 15:36
Gravatar
前两天看完的, 感谢楼主!

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by cknd at 2010/5/11 22:24
Gravatar
做完留名=v=

# re: 一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 29

Left by 凌绝顶 at 2010/7/30 1:03
Gravatar
很好的系列教程,感谢!

您的评论:



 (不显示)


 
 
 
Please add 5 and 8 and type the answer here:
    
 

评论预览窗口:

 
«九月»
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789