zoukankan      html  css  js  c++  java
  • Asp.net MVC 单元测试 简要笔记

    首先要啰嗦几句。

    单元测试是TDD的重要实践方法,也是代码质量的一种保证手段。在项目的工程化开发中,研发人员应该尽量保证书写Unit Test,即使不使用TDD。

    (VS中,我们可以直接使用微软提供的一套单元测试框架,一般使用足够了,特别需求的话,可以使用其他更好的框架。)

    书写单元测试时,我们并不一定真的要去连接数据库,毕竟就算只使用自己计算机上的研发数据库,也不能保证数据正确性和完备性,毕竟自己经常会操作些垃圾数据。

    这个时候就需要模拟一个“数据库”来构造我们想要的一些数据。这个就是Mock最直接的需求。然后当我们进一步实践,会发现我们的Service层等,也可以用Mock模拟对象,而不是非要去new一个真实对象。真实场景是当我们研发小组合作时,你作为更高层的研发人员,可能只拿到了服务层的接口,具体的实现类你的同事还在研发中,这个时候你要做Unit Test,就只有模拟一个假的Service实现了。

    C#的单元测试框架中,有一套Mock框架就叫Moq。

    Moq可以直接在VS 2013及以后的版本中通过Nuget获取。以前的版本的VS可以到github上下载Moq的dll。

    Moq的github地址为:Moq

    Moq的QuickStart页面为:QuickStart 深入学习,可以直接看此文档。

    MVC中,最直接需要模拟的应该就是HttpContext相关对象,如HttpRequest、Server、Session等对象。以HttpRequest为例。

    首先,我们要知道, controller中相关HttpContext的对象是ControllerContext,它就是HttpContextBase。模拟的HttpContext通过它绑定给Controller。

    controller.ControllerContext = new ControllerContext(mockContext.Object, new RouteData(), controller);
    

     mockContext.Object就是我们用Moq模拟的HttpContextBase。

    这里是绑定的代码,倒推回去,我们应该先生成mockContext,如下:

    private Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
    

     实际我们代码可能使用的是Request["xxx"]、Session["yyy"],这些对象又依赖于HttpContextBase,所以我们需要模拟它们,然后绑定到 mockContext。如下

     var mockRequest = new Mock<HttpRequestBase>();
                //模拟Request["xxx"]
                if (dataIndexed != null)
                {
                    foreach (var pair in dataIndexed)
                    {
                        mockRequest.Setup(x => x[pair.Key]).Returns(pair.Value);
                    }
                }
    mockContext.SetupGet(x => x.Request).Returns(mockRequest.Object);
    

     注意最后一句就是将模拟的Request绑定到模拟的HttpContextBase上。代码含义是:当通过mockContext.Request(即它的get方法)得到Request对象时,把mockRequest模拟的HttpRequest对象返回。 

    Moq的方法都是比较直白的含义,如上就是:SetupGet(x => x.Request).Returns($$$),针对对象Request使用Get方法时,Returns相应的对象$$$。其他的对象,不管是HttpContextBase的还是Request的再子层对象,都通过这样的方法设置,前提是相应的类中有此属性(get,set)。

    回过头继续看上面代码这段mockRequest.Setup(x => x[pair.Key]).Returns(pair.Value); 这个就是直接针对值进行设置,这里是针对类的Indexedr进行设置。

    还有一些其他方法,需要时就看QuickStart了。

    另外,针对全局的HttpContext对象,在单元测试中,它是null的,所以为了保证单元测试可进行,需要对其进行包装,在项目中使用包装的类进行访问。这样,单元测试时,就注入自己模拟的HttpContext对象。然而HttpContext是sealed类,是不能被Mock的。所以我们可以在包装类中,使用两个对象,分别指向Mock的对象和真实的HttpContext,依据是否模拟的判断在代码中选择调用。也可以使用HttpContextWrapper来包装HttpContext,因为HttpContextWrapper是HttpContextBase的实现。如:

     /// <summary>
        /// 全局HttpContext的包装类,以便单元测试
        /// </summary>
        public class CmsHttpContext
        {
            /// <summary>
            /// 当前单例对象 
            /// </summary>
            private static CmsHttpContext _instance = new CmsHttpContext();
            /// <summary>
            ///  包装的HttpContext
            /// </summary>
            private static HttpContextBase wrapper = null;
            /// <summary>
            /// 是否被包装
            /// </summary>
            private static bool IsWrap = false;
    
            private CmsHttpContext()
            {
    
            }
            /// <summary>
            /// 当前HttpContext对象
            /// </summary>
            public static HttpContextBase Current
            {
                get
                {
                    if (!IsWrap)
                    {
                        wrapper = new HttpContextWrapper(HttpContext.Current);
                    }
                    return wrapper;
                }
            }
    
            /// <summary>
            /// 包装外部 HttpContext,仅用于单元测试中
            /// </summary>
            /// <param name="context"></param>
            public static void Wrap(HttpContextBase context)
            {
                IsWrap = true;
                wrapper = context;
            }
        }
    

     其他要注意的点:

    1.Action的方法直接调用即可以执行。针对ViewBag.XXX,使用Controller对象调用,如mController.ViewBag.XXX

    2.JsonResult中Json(XXX),如果XXX是动态类型的话,它在传输后会变成object,单元测试中无法识别它相应的属性,可以使用框架ExposedObject(Nuget中可以直接下载)进行包装,将其包装回dynamic进行测试,如下:

    var jsonData = Exposed.From(result.Data);
    
                Assert.IsTrue(jsonData.total > 0);
                Assert.IsTrue(jsonData.list.Count > 0);
    

      

  • 相关阅读:
    【Azure Redis 缓存】Azure Redis 功能性讨论二
    【Azure Developer】如何用Microsoft Graph API管理AAD Application里面的Permissions
    【Azure 环境】通过Python SDK收集所有订阅简略信息,例如订阅id 名称, 资源组及组内资源信息等,如何给Python应用赋予相应的权限才能获取到信息呢?
    【Azure 应用服务】App Service与APIM同时集成到同一个虚拟网络后,如何通过内网访问内部VNET的APIM呢?
    【Azure 云服务】如何从Azure Cloud Service中获取项目的部署文件
    【Azure Redis 缓存】Azure Redis 异常
    【Azure 微服务】基于已经存在的虚拟网络(VNET)及子网创建新的Service Fabric并且为所有节点配置自定义DNS服务
    【Azure Redis 缓存】遇见Azure Redis不能创建成功的问题:至少一个资源部署操作失败,因为 Microsoft.Cache 资源提供程序未注册。
    【Azure Redis 缓存】如何得知Azure Redis服务有更新行为?
    【Azure API 管理】在 Azure API 管理中使用 OAuth 2.0 授权和 Azure AD 保护 Web API 后端,在请求中携带Token访问后报401的错误
  • 原文地址:https://www.cnblogs.com/dev2007/p/5774578.html
Copyright © 2011-2022 走看看