ASP.NET MVC编程——单元测试

1自动化测试基本概念

自动化测试分为:单元测试,集成测试,验收测试。

单元测试

检验被测单元的功能,被测单元一般为低级别的组件,如一个类或类方法。

单元测试要满足四个条件:自治的,可重复的,独立的,快速的。

自治的是指:关注于验证某个单一功能,例如只关注于类的某个方法的功能。

可重复的是指:无论何时允许同一段测试代码都应该得到相同的结果。

独立的是指:不依赖与其他任何系统或单元测试。

快速的是指:所有测试都应快速地完成,

 

集成测试

验证两个或多个组件之间的交互。

验收测试

确保已构建的系统实现了既定的全部功能。

2准备进行单元测试

创建单元测试项目并执行测试应该依据一定的准则,运用一些技巧或工具,下面列举了常用的技巧和工具。

命名规则

测试类应以被测试的单元命名,测试方法的名称应能够描述待验证的行为。

 

使用特性

TestClassAttribute:标识包含测试方法的类。

TestMethodAttribute:用于标识测试方法。

TestInitializeAttribute:标识在测试之前要运行的方法,从而分配并配置测试类中的所有测试所需的资源。

ExpectedExceptionAttribute:表示测试方法的执行过程中应引发异常,用来判断抛出的异常是否符合预期。

 

Arrange-Act-Assert模式

此模式又被称为3A模式,Arrange,准备测试环境;Act,调用被测方法;Assert,断言。

例1:标准的3A模式,且只测试一个功能,即返回视图对象是否为null,虽然待验证的点有好几个,但我们一次只验证一个。

    [TestClass]
    public class HomeTest
    {
        [TestMethod]
        public void TestCacheExeActionResultNull()
        {
            //Arrange
            HomeController hc = new HomeController();

            //Act
            ViewResult vr = hc.CacheExe();

            //Assert
            Assert.IsNotNull(vr);
        }
     }

例2:验证参数为null时,是否会抛出预期的异常类型,即ArgumentNullException类型

    [TestClass]
    public class AccountTest
    {
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void TestLogin()
        {
            AccountController ac = new AccountController();

            ac.Login(null);
        }
    }

模拟依赖

为达到测试目的,使用假的组件模拟真实组件。有两种方式模拟依赖:一种是创建模拟对象,另一种是使用框架。为能够模拟依赖,使用存储库模式。

例1:自定义模拟对象。

控制器:

    public class BookController : Controller
    {
        private IRepository repository;
        public BookController()
            : base()
        { }
        public BookController(IRepository repository)
        {
            this.repository = repository;
        }
        // GET: Book
        public ViewResult GetBook(int id)
        {
            var book = repository.GetBook(id);
            return View(book);
        }

        //其他代码
    }

实现Repository

public class BookRepository:IRepository
{

        public Book GetBook(int id)
        {
            throw new NotImplementedException();
        }

        //其他代码
}

定义IRepository接口

public interface IRepository
{
      Book GetBook(int id);

       //其他代码
}

实体

public class Book
{
        public int Id { set; get; }
}

模拟对象

public class MocBookRepository : IRepository
{
        private Book bk;

        public MocBookRepository(Book bk)
        {
            this.bk = bk;
        }
        public Book GetBook(int id)
        {
            return bk;
        }
}

测试类

    [TestClass]
    public class BookTest
    {
        [TestMethod]
        public void TestGetBook()
        {
            Book exceptedBk = new Book
            {
                Id = 1
            };
            BookController bc = new BookController(new MocBookRepository(exceptedBk));

            ViewResult result = bc.GetBook(exceptedBk.Id);

            Assert.AreEqual(exceptedBk,result.Model);
        }
    }

例2:使用模拟框架Moq

使用nuget下载Moq,截图如下:

使用Moq:

        [TestMethod]
        public void TestGetBook()
        {
            Book exceptedBk = new Book
            {
                Id = 1
            };

            var mokRepository = new Moq.Mock<IRepository>();
            mokRepository.Setup(rep => rep.GetBook(exceptedBk.Id)).Returns(exceptedBk);

            BookController bc = new BookController(mokRepository.Object);
            var result = bc.GetBook(exceptedBk.Id);

            Assert.AreEqual(exceptedBk, result.Model);
        }

重构:去除重复代码

例:

[TestClass]
public class HomeTest
{
        [TestMethod]
        public void TestCacheExeActionResultNull()
        {
            //Arrange
            HomeController hc = new HomeController();

            //Act
            ViewResult vr = hc.CacheExe();

            //Assert
            Assert.IsNotNull(vr);
        }

        [TestMethod]
        public void TestCacheExeActionValue()
        {
            //Arrange
            HomeController hc = new HomeController();

            //Act
            ViewResult vr = hc.CacheExe();

            //Assert
            Assert.AreEqual("缓存部分",vr.ViewBag.Sign);
        }
}

上面面的两个测试方法含有共同的代码,应将其提取,并作为测试所需的资源,先于测试方法执行。下面是改进后的代码。

    [TestClass]
    public class HomeTest
    {
        private HomeController hc;
        private ViewResult vr;

        [TestInitialize]
        public void InitializeContext()
        {
            //Arrange
            hc = new HomeController();

            //Act
            vr = hc.CacheExe();
        }
        [TestMethod]
        public void TestCacheExeActionResultNull()
        {
            //Assert
            Assert.IsNotNull(vr);
        }

        [TestMethod]
        public void TestCacheExeActionValue()
        {
            //Assert
            Assert.AreEqual("缓存部分",vr.ViewBag.Sign);
        }
    }

3 测试ASP.NET MVC项目

3.1模拟HttpContext对象

        public void HttpContextForController(Controller controller)
        {
            var contextBaseMock = new Mock<HttpContextBase>();
            contextBaseMock.Setup(c=>c).Returns(new CustomHttpContext());
            controller.ControllerContext = new ControllerContext(new RequestContext(contextBaseMock.Object, new RouteData()), controller);
        }

        public class CustomHttpContext : HttpContextBase
        {

        }

3.2模拟Request对象

var contextBaseMock = new Mock<HttpContextBase>();
var method = "get";
contextBaseMock.Setup(c => c.Request.HttpMethod).Returns(method);
var mockHttpContext = contextBaseMock.Object;
或
var request = new Mock<HttpRequestBase>();
var headerValue = new NameValueCollection(){};//替换为具体实现
request.Setup(c =>c.Headers).Returns(headerValue);
var mockRequest = request.Object;

3.3模拟HttpResponse对象

var contextBaseMock = new Mock<HttpContextBase>();
contextBaseMock.Setup(c => c.Response.StatusCode).Returns(200);
var mockHttpContext = contextBaseMock.Object;
或
var response = new Mock<HttpResponseBase>();
var headerValue = new NameValueCollection(){};//替换为具体实现
response.Setup(c => c.Headers).Returns(headerValue);
var mockRequest = response.Object;

3.4模拟缓存对象

模拟Session对象

var contextBaseMock = new Mock<HttpContextBase>();
contextBaseMock.Setup(c => c.Session.Timeout).Returns(10);
var mockHttpContext = contextBaseMock.Object;

模拟Cache对象

var contextBaseMock = new Mock<HttpContextBase>();
contextBaseMock.Setup(c => c.Session.Timeout).Returns(10);
var mockHttpContext = contextBaseMock.Object;

3.5测试控制器

基本代码如下,其中断言部分会根据下面的测试项不同而不同

        public void TestGetBook()
        {
            Book exceptedBk = new Book
            {
                Id = 1
            };

            var mokRepository = new Moq.Mock<IRepository>();
            mokRepository.Setup(rep => rep.GetBook(exceptedBk.Id)).Returns(exceptedBk);

            BookController bc = new BookController(mokRepository.Object);
            var result = bc.GetBook(exceptedBk.Id);
            //断言部分

        }

测试控制器操作的返回类型

Assert.IsInstanceOfType(result, typeof(ViewResult));

测试返回的视图模型数据

Assert.AreEqual(exceptedBk, result.Model);
//或
Assert.AreEqual(exceptedBk.Id,result.Model.Id);

测试重定向

控制器操作:

public RedirectResult Turn()
{
     return Redirect("~/home/index");
}

测试方法:

        [TestMethod]
        public void TestTurn()
        {
            BookController bc = new BookController();

            var result = bc.Turn();

            Assert.AreEqual("~/home/index", result.Url);
        }

3.6测试过滤器

虽然可能对控制器应用了过滤器,但单元测试调用控制器时是不会调用过滤器的;此外我们注册的全局过滤器也不会被调用。要测试过滤器,就要模拟HTTP上下文、请求等。此外,建议将具体的验证逻辑代码封装起来,这样可以将其作为普通的类来测试。

例:

动作过滤器定义:

        public class CustomActionFilterAttribute : ActionFilterAttribute
        {
            public override void OnActionExecuted(ActionExecutedContext filterContext)
            {
                //具体实现
            }

            public override void OnActionExecuting(ActionExecutingContext filterContext)
            {
                //具体实现
            }
        }

权限过滤器定义:

public class CustomAuthorizeAttribute : AuthorizeAttribute
{
        private UserRole role;
        public CustomAuthorizeAttribute(UserRole role)
        {
            this.role = role;
        }
        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            //具体实现
        }
        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            //具体实现
        }
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            base.OnAuthorization(filterContext);
        }
}

public enum UserRole
{
        Org = 1,
        Vip = 2,
        Guest = 3
}

验证动作过滤器CustomActionFilterAttribute

            //模拟Request
            var request = new Mock<HttpRequestBase>();
            request.SetupGet(r => r.HttpMethod).Returns("GET");
            request.SetupGet(r => r.Url).Returns(new Uri("http://basesit/controller/action"));

            //设置HttpContext,用模拟的Request设置HttpContext
            var httpContext = new Mock<HttpContextBase>();
            httpContext.SetupGet(c => c.Request).Returns(request.Object);

            //模拟ActionExecutedContext
            var actionExecutedContext = new Mock<ActionExecutedContext>();
            actionExecutedContext.SetupGet(c => c.HttpContext).Returns(httpContext.Object);

            //实例化待测试过滤器CustomActionFilterAttribute
            var customActionFilter = new CustomActionFilterAttribute();
            //调用执行方法,执行测试
            customActionFilter.OnActionExecuted(actionExecutedContext.Object);

            //模拟ActionExecutingContext
            var actionExecutingContext = new Mock<ActionExecutingContext>();
            actionExecutingContext.SetupGet(c => c.HttpContext).Returns(httpContext.Object);

            //调用执行方法,执行测试
            customActionFilter.OnActionExecuting(actionExecutingContext.Object);

验证权限过滤器CustomAuthorizeAttribute

            //模拟Request
            var request = new Mock<HttpRequestBase>();
            request.SetupGet(r => r.HttpMethod).Returns("GET");
            request.SetupGet(r => r.Url).Returns(new Uri("http://basesit/controller/action"));

            //设置HttpContext,用模拟的Request设置HttpContext
            var httpContext = new Mock<HttpContextBase>();
            httpContext.SetupGet(c => c.Request).Returns(request.Object);
            //模拟AuthorizationContext
            var authorizationContext = new Mock<AuthorizationContext>();
            authorizationContext.SetupGet(c => c.HttpContext).Returns(httpContext.Object);

            //实例化待测试权限过滤器:CustomAuthorizeAttribute
            var authorizationFilter = new CustomAuthorizeAttribute(UserRole.Guest);
            //调用待测试方法
            authorizationFilter.OnAuthorization(authorizationContext.Object);

3.7测试视图

视图的测试主要通过实际运行,然后观察浏览器渲染出来的结果,由于浏览器种类繁多,适配是也随之变成了比较繁重的任务,依靠自动化测试不是最佳选择,至少目前不是最佳选择,但在此还是给出一个自动化测试的例子,这里使用WatiN测试套件,使用NuGet下载测试套件:

测试代码

        [TestMethod]
        public void TestGetBookView()
        {
            string url = "http://localhost/MVCPointApp/Book/GetBook/1";
            using (var browser = new FireFox(url))
            {
                var bookDiv = browser.Div(Find.ByClass("pro_book"));
                var title = bookDiv.Element(Find.First()).Text;

                Assert.IsFalse(string.IsNullOrWhiteSpace(title));
                Assert.AreEqual("机器学习算法原理与编程实践", title);
            }
        }

3.8测试路由

配置的路由模板为:

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );

测试被忽略的路由

        [TestMethod]
        public void TestIgnoreRoute()
        {
            var mock = new Mock<HttpContextBase>();
            mock.Setup(m => m.Request.AppRelativeCurrentExecutionFilePath).Returns("~/book.axd");
            var routes = new RouteCollection();

            var routeData = routes.GetRouteData(mock.Object);

            Assert.IsNull(routeData);
            Assert.IsInstanceOfType(routeData.RouteHandler,typeof(StopRoutingHandler));
        }

测试可匹配的路由

        [TestMethod]
        public void TestMatchedRoute()
        {
            var mock = new Mock<HttpContextBase>();
            mock.Setup(m => m.Request.AppRelativeCurrentExecutionFilePath).Returns("~/book/getbook/1");
            var routes = new RouteCollection();

            var routeData = routes.GetRouteData(mock.Object);

            Assert.IsNull(routeData);
            Assert.AreEqual("Book", routeData.Values["controller"]);
            Assert.AreEqual("GetBook", routeData.Values["action"]);
            Assert.AreEqual(UrlParameter.Optional, routeData.Values["id"]);

        }

4启发:开发可测试的程序

即使对下面的概念没有感觉,当实施一次单元测试以后就会深有体会。

基于接口编程

基于接口的编程,使得可以在测试的时候指定具体的类型,这样解除了依赖,方便模拟组件。我们常见的相关概念是控制反转(依赖注入)

使用IoC框架

使用成熟稳定的Ioc框架减少待测试的代码量,减轻测试任务量。

存储库模式

使用存储库模式,将数据访问逻辑与业务逻辑、控制器分离开来,测试控制器时可以借助此模式方便地模拟依赖,这样将模块合理地切分,实现测试只关注单一功能。

面向切面编程(APO)

面向切面编程是面向对象编程的有力补充,降低业务处理中各个部分之间的耦合性,便于实施单元测试。

测试驱动开发(TDD)

遵循“红灯-绿灯-重构”的原则:从失败的情况开始测试,然后编写最少的代码让测试通过。为了能尽快地通过测试,编写的最少量的代码可能是未经过深思熟虑的,这种情况下就要重构。

参考:

1.Jess Chadwick/Todd Snyder/Hrusikesh Panda,徐雷/徐扬译。ASP.NET MVC4 Web编程

2.Jon Galloway/Phil Haack/Brad Wilson/K. Scott Allen,孙远帅/邹权译  ASP.NET MVC4 高级编程(第四版)

3.Dino Esposito著,潘丽臣译,ASP.NET MVC5编程实战

----------------------------------------------------------------------

转载与引用请注明出处。

时间仓促,水平有限,如有不当之处,欢迎指正。

原文地址:https://www.cnblogs.com/hdwgxz/p/8707878.html

时间: 2024-08-02 16:27:40

ASP.NET MVC编程——单元测试的相关文章

ASP.NET MVC 编程参考

ASP.NET MVC 编程参考 转载请注明出处:http://surfsky.cnblogs.com MVC    参考 http://msdn.microsoft.com/zh-cn/dd408813.aspx    MVC 的基本思想        ·Module      : 模型.可以看做实体类,可用各种技术实现,例如Microsoft Entity Framework.NHibernate等        ·View        : 视图.可以与某个实体类关联,此时视图则负责展示该

ASP.NET MVC编程——模型

1 ViewModel 是一种专门提供给View使用的模型,使用ViewModel的理由是实体或领域模型所包含的属性比View使用的多或少,这种情况下实体或领域模型不适合View使用. 2模型绑定 默认模型绑定器 通过DefaultModelBinder解析客户端传来的数据,为控制器的操作参数列表赋值.   显示模型绑定 使用UpdateModel和TryUpdateModel显示绑定模型,不会检验未绑定字段. 使用UpdateModel方法绑定模型时,如果绑定失败就会抛异常,而TryUpdat

ASP.NET MVC编程——错误处理与日记

ASP.NET MVC的错误处理应考虑到这几个方面:模型绑定期间发生的错误,未能路由到指定操作,针对控制器的错误处理.使用配置文件可以帮助我们处理异常,但是不够灵活和全面:使用HandleErrorAttribute.自定义错误过滤器或重写控制器OnException方法只能解决针对控制器的错误,无法解决模型绑定期间发生的错误,也无法处理404错误,即使将错误过滤器注册为全局过滤器也是如此.有时候需要多种方法配合使用. 在捕获错误的地方,可以将有用的信息记录下来,便于我们查出引起问题的原因和纠正

ASP.NET MVC编程入门--Excel上传

参考博客:ASP.NET MVC下使用文件上传 参考博客:NPOI使用手册 参考博客:ASP.Net MVC利用NPOI导入导出Excel 参考博客:C# NPOI 导入与导出Excel文档 兼容xlsx, xls 文件上传代码块 #region EXCEL上传 /// <summary> /// EXCEL上传 /// </summary> /// <param name="fileData"></param> /// <retu

ASP.NET MVC之单元测试

1.新建项目添加单元测试项目 2.鼠标右键直接可以断点调试 3.public static class Assert类

ASP.NET MVC编程入门--网站访问统计

参考文章:Asp.Net MVC3.0网站统计登录认证的在线人数 参考文章:ASP.net中网站访问量统计方法代码 Global 代码: protected void Application_Start() { Application["online"] = 0; ///在应用程序第一次启动时初始化在线人数为0 AreaRegistration.RegisterAllAreas(); RouteConfig.RegisterRoutes(RouteTable.Routes); log4

ASP.NET MVC编程入门--MVC5 传递参数与初始化数据

传递参数格式: $(".limit").live("click", function () { top.location = "/Product/Index?id='0'"; }); 后台接收参数如下: [HttpGet] public ActionResult Index() { string name = Request.Params["id"]; models.Product product = new models.P

[ASP.NET MVC 小牛之路]02 - C#知识点提要--转载

本篇博文主要对asp.net mvc开发需要撑握的C#语言知识点进行简单回顾,尤其是C# 3.0才有的一些C#语言特性.对于正在学asp.net mvc的童鞋,不防花个几分钟浏览一下.本文要回顾的C#知识点有:特性.自动属性.对象集合初始化器.扩展方法.Lambda表达式和Linq查询.C#资深“玩家”可路过. 本文目录 1.特性(Attributes) 特性(Attributes),MSDN的定义是:公共语言运行时允许你添加类似关键字的描述声明,叫做attributes, 它对程序中的元素进行

C# 6 与 .NET Core 1.0 高级编程 - 41 ASP.NET MVC(上)

译文,个人原创,转载请注明出处(C# 6 与 .NET Core 1.0 高级编程 - 41 ASP.NET MVC(上)),不对的地方欢迎指出与交流. 章节出自<Professional C# 6 and .NET Core 1.0>.水平有限,各位阅读时仔细分辨,唯望莫误人子弟. 附英文版原文:Professional C# 6 and .NET Core 1.0 - Chapter 41 ASP.NET MVC ------------------------------------ 本