• 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);
    

      

  • 相关阅读:
    数据探索
    String的相关操作总结
    2015/6/10 按位运算 补码求法
    15年6月8号 jsp内置对象总结
    投资小故事
    定投出来的千万富翁
    股票PE的应用
    幸福人生的资产配置
    定投6年,以亏损收场,她到底做错了什么?
    一则故事让你轻松了解保险的实质
  • 原文地址:https://www.cnblogs.com/dev2007/p/5774578.html
Copyright © 2020-2023  润新知