Hello World
Spiga

对ASP.NET MVC项目中的视图做单元测试

2009-02-25 01:01 by 老赵, 10445 visits

关于视图的单元测试

说到ASP.NET MVC,我们似乎始终都在关注对于Controller的测试——虽然Stephen Walther也写过如何脱离Web Server对View进行单元测试,但是他的方法可看而不可用。复杂的构造和预备,以及对生成的HTML字符串作判断——这真是在对视图做单元测试吗?仔细分析他的代码可以发现,这其实是在对ViewEngine做单元测试。而且,如果真要对ViewEngine做单元测试,也不应该像他那样依赖外部文件。在我看来,他的做法什么都不是……似乎美观,似乎能博得一些“掌声”,但是这个掌声是来自于他的解决方案,还是大家一时的冲动呢?

如果要对视图做单元测试,还是要将内容呈现在浏览器中才行。在对网页做单元测试时,我们一般会使用WatiN等工具操作浏览器,打开页面,再对其DOM元素结构及内容作断言。不过……这是单元测试吗?可惜这只能算是一种回归测试或用户验收测试。因为,我们在打开一个页面的时候,从表现层到业务逻辑再到数据访问,应用程序的每个部件都在忙碌着。而单元测试讲究的是“分离”,分离一切关注,分离一切依赖。因为分离,我们才能准确定位错误;因为分离,我们才能在测试中使用我们准备好的数据。

既然要分离,我们就必须遵循一定的使用规范。在《ASP.NET MVC单元测试最佳实践》中我提到,在View中只能使用ViewData中的数据,而不该依赖其他内容(包括HttpContext)。这样我们就可以自行构造ViewData并注入一个视图对象中。事实上,这个约定在ASP.NET MVC自带的项目模板中就被破坏了。请看Views\Shared\LogOnUserControl.ascx,其中通过this.User来查看当前用户的登陆状态。这是个定义在传统Page对象上的属性,从当前HttpContext上直接获取。如果使用这种方式,我们在单元测试时就难以“模拟”当前用户的登陆状态,进而难以使测试覆盖到测试的各种情况了。

Lightweight Test Automation Framework

在这里,老赵推荐使用ASP.NET Team提供的Lightweight Test Automation Framework(下文称之为LTAF)作为测试工具,它目前已经在CodePlex上更新至Feb Update版本。这个框架的作用与WatiN和Selenium类似,可操作浏览器对应用程序编写回归测试。虽然在某些方面(例如DOM元素的选取)不如“竞争对手”,但是LTAF自有其独到之处:

  • 由于直接在浏览器中运行,它天生便支持现有的——以及未来可能出现的任意浏览器。
  • 由于直接部署在被测试的网站中,因此测试代码和网站页面是在同一个进程中。

第一点优势自不必说,而第二点更是关键。试想WatiN和Selenium,都是通过编写代码在浏览器中打开页面。这意味着我们的在测试代码和被测试的网页分别在不同的进程中。在这个前提下,如果我们要将测试代码中定义的数据传递给被测试的网页(也就是视图对象),我们就必须进行跨进程的通信。而无论怎么实现,都逃不过“序列化”一途,这无疑增加了复杂度。而使用LTAF之后,这个问题瞬间烟消云散了,因为我们可以直接在内存中“传递”测试数据,一切都只是个引用而已。

不过任何事物都具有两面性,LTAF也有一些难以天生的,而且是永远无法弥补的缺点。例如:

  • 由于LTAF将待测试的页面放置在Frame中,因此该页面上的window.top等基于浏览器frame结构的属性会被改变。
  • 由于LTAF的本质是使用JavaScript来操作DOM,这意味着任何会阻塞程序进行的操作(例如alert)都不能使用,否则将阻塞整个测试过程。

不过幸运的是,这两点都不回成为严重的问题。对于第一种,我们只需要编写一个自定的getTop方法来替换直接访问windows.top的做法即可。而第二种情况——老赵从来不喜欢alert或confirm这种“纯浏览器功能”,因为它们会带来很差的用户体验,更何况现在的JavaScript类库/框架都能很轻松的做出这种效果,您觉得呢?

LTAF的具体使用方式可参考其Release Note。令人奇怪的是,老赵发现直接在项目中使用LTAF会有一些小问题(不过它的示例为什么就一切正常呢?),因此进行了一些细微的修改。请注意~\UnitView\DriverPage.aspx文件尾部的一些JavaScript代码。

UnitView的使用

于是老赵编写了一个组件UnitView,方便我们构造一个单元测试时所需的数据。有了数据,便能够直接将视图在浏览器中加以呈现了。例如:

[WebTestClass]
public class HomeTests
{
    [WebTestMethod]
    public void LoggedOnIndexTest()
    {
        var data = new TestViewData<IndexModel>
        {
            ControllerName = "Home",
            ActionName = "Index",
            Model = new IndexModel
            {
                Message = "Welcome guys!",
                Identity = new UserIdentity
                {
                    IsAuthenticated = true,
                    Name = "Jeffrey Zhao"
                }
            }
        };

        HtmlPage page = new HtmlPage(TestViewData.GenerateHostUrl(data));

        // Assert title
        Assert.AreEqual("Home Page", page.Elements.Find("title", 0).GetInnerText());

        // Assert head element
        var mainContent = page.Elements.Find("main");
        var head2 = mainContent.ChildElements.FindAll("h2").Single();
        Assert.AreEqual(data.Model.Message, head2.GetInnerText(), "Message should be displayed.");

        var loginTabInnerText = page.Elements.Find("logindisplay").GetInnerTextRecursively();
        Assert.IsTrue(loginTabInnerText.Contains("Welcome"), "'Welcome' missed.");
        Assert.IsTrue(loginTabInnerText.Contains(data.Model.Identity.Name), "Login name missed.");
    }
}

自然,Web Server是不可或缺的。幸运的是,分离让我们的视图只会涉及最简单的测试数据,这样VS自带的简单Web Server就足够了。在上面的代码中,我们直接构造了强类型的TestViewData对象,它包含呈现一个视图所需要的所有数据:

  • Cotroller和Action名称。从理论上说,由不同的Controller和Action进入同样的视图可能会得到不同的结果。
  • View和Master名称。如果省略,则表明将使用默认的视图,即通过Controller和Action的值来确定。
  • ViewData和Model。

TestViewData.GenerateHostUrl方法会把data保存起来,并返回一个URL。访问该URL便能够得到对应的视图内容。

如果您想使用UnitView,可以从上面的链接中下载UnitView的源代码和示例在本机进行尝试。使用UnitView时主要有以下几个注意点:

  1. 将Tests项目的输出路径指向被测试网站的bin目录,这样既可以在运行时得到正确的程序集,又不必为网站添加多余的引用。
  2. 将~\UnitView目录复制到您的网站根目录下(在发布网站时,请剔除该目录)。如果想使用其它目录,请关注接下来UnitView实现分析。
  3. 编辑~\UnitView\Web.config文件,将MvcApp.Tests.dll修改为您自己的包含测试代码的程序集。

UnitView实现分析

UnitView组件非常简单,简单地几乎不值一提。TestViewData类型包含了测试需要的所有数据,而TestViewData<TModel>继承了TestViewData,提供了强类型的Model属性访问方式。它们就不作分析了。

此外,TestViewData还有一些静态方法:

public class TestViewData
{
    static TestViewData()
    {
        PersistentProvider = new InProcPersistentProvider();
    }

    public static IPersistentProvider PersistentProvider { get; set; }

    public static string GenerateHostUrl(TestViewData data)
    {
        var key = PersistentProvider.Save(data);
        return ViewHostHandlerUrl + "?key=" + HttpUtility.UrlEncode(key);
    }

    private static string ViewHostHandlerUrl
    {
        get
        {
            return ConfigurationManager.AppSettings["UnitView_ViewHostHandlerUrl"]
                ?? "/UnitView/ViewHostHandler.ashx";
        }
    }

    internal static TestViewData Load(string key)
    {
        return PersistentProvider.Load(key);
    }
    ...
}

GenerateHostUrl方法将委托PersistentProvider保存对象,并得到一个key。这个key将拼接在ViewHostHandlerUrl属性上,这便是被测试的路径。从代码中可以看出,如果您不想使用默认的测试路径,只需在web.config的AppSettings节点中添加一个目标地址即可。

PersistentProvider属性为IPersistentProvider接口类型,其中定义了Save/Load/Remove三个方法。IPersistentProvider在项目中只有一个实现:InProcPersistentProvider,它会将TestViewData存放在内存中的一个字典里。这个实现已经足够让UnitView结合LTAF运行(LTAF的同进程特性起到了关键的作用)。不过,如果您还是希望使用WatiN等独立进程的测试工具,就必须实现自己的IPersistentProvider类型。例如您可以实现一个FilePersistentProvider,将TestViewData序列化至一个外部文件中,这样就可以在合适的时候将它取回了。

另一个较为关键的类型是UnitView.Engine.ViewHostHandler:

public class ViewHostHandler : IHttpHandler
{
    private HttpContext Context { get; set; }

    public void ProcessRequest(HttpContext context)
    {
        this.Context = context;

        ControllerContext controllerContext = new ControllerContext(
            new HttpContextWrapper(context),
            this.Data.RouteData,
            new MockController());

        new ViewResult
        {
            MasterName = this.Data.MasterName,
            ViewName = this.Data.ViewName,
            TempData = this.Data.TempData,
            ViewData = this.Data.ViewData,
        }.ExecuteResult(controllerContext);
    }

    private string Key
    {
        get
        {
            string key = this.Context.Request.QueryString["key"];
            if (String.IsNullOrEmpty(key))
            {
                throw new ArgumentNullException("key");
            }

            return key;
        }
    }

    private TestViewData m_data;
    private TestViewData Data
    {
        get
        {
            if (this.m_data == null)
            {
                this.m_data = TestViewData.Load(this.Key);
                if (this.m_data == null)
                {
                    throw new ArgumentNullException("Cannot retrieve the data.");
                }
            }

            return this.m_data;
        }
    }

    public bool IsReusable { get { return false; } }
}

首先,在ProcessRequest方法会取回TestViewData,并根据这些数据构造一个ViewResult对象,最后执行它的ExecuteResult方法来输出视图内容。由于ExecuteRequest方法的需要,我们还必须构造一个ControllerContext对象,也就意味着我们还必须提供一个Controller对象和HttpContext的封装。从代码中可以看出,我们这里使用了最简单的数据。由于视图遵守“约定”,它只会从ViewData中获取数据,所以无论Controller或HttpContext是什么值都已经无关紧要了。

您可能会想,为什么会有这样的“约定”,不让视图从HttpContext对象中获取数据呢?Mock一个HttpContext对象也不是那么困难(这里要感谢各种强大的Mock框架)啊。可惜,Mock后的HttpContext很难进行序列化,这样就几乎杜绝了跨进程通信的可能,这对于使用WatiN和Selenium进行测试的朋友们无疑是一种灾难。权衡之下,老赵决定放弃对HttpContext的支持。

 

注1:目前UnitView基于ASP.NET MVC RC构建,当RTM发布后我会进行必要的更新。请关注老赵这篇文章和托管在MSDN Code Gallery上的代码(http://code.msdn.microsoft.com/UnitView)。

注2:在《ASP.NET MVC单元测试最佳实践》中我也包含了UnitView组件,实现略有不同——请以本篇文章为主。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

46 条回复

  1. 老赵
    admin
    链接

    老赵 2009-02-24 01:29:00

    怎么英文都变小写了?好丑。

  2. Anders Cui
    *.*.*.*
    链接

    Anders Cui 2009-02-24 02:03:00

    @Jeffrey Zhao
    没关系,我看了一下,文章内容没问题,应该很快就会好的:-}

  3. Anders Liu
    *.*.*.*
    链接

    Anders Liu 2009-02-24 06:59:00

    打死我也不信只用小写字母就能写程序……

  4. 老赵
    admin
    链接

    老赵 2009-02-24 07:59:00

    --引用--------------------------------------------------
    Anders Cui: @Jeffrey Zhao
    没关系,我看了一下,文章内容没问题,应该很快就会好的:-}
    --------------------------------------------------------
    嗯,是样式,不是内容的问题

  5. 老赵
    admin
    链接

    老赵 2009-02-24 08:00:00

    --引用--------------------------------------------------
    Anders Liu: 打死我也不信只用小写字母就能写程序……
    --------------------------------------------------------
    语法上总归没有问题的,恩恩

  6. ssseewew[未注册用户]
    *.*.*.*
    链接

    ssseewew[未注册用户] 2009-02-25 00:24:00

    写得不错!谢谢了! (本人站点http://www.0663bx.com/yellow)

  7. Ryan Gene
    *.*.*.*
    链接

    Ryan Gene 2009-02-25 00:26:00

    .NET MVC现在貌似有点火,但个人觉得.net真的需要再加mvc这个概念吗?因为.net天生就有code behind的这个东东,以前将ror(ror的mvc的确不错,不过后来没深入下去)和.net做对比,感觉.net的aspx就是view,aspx.cs就是controller,一些datasource之类的就是model。

    随便说说,可能是我对mvc理解不深,呵呵

  8. T2噬菌体
    *.*.*.*
    链接

    T2噬菌体 2009-02-25 00:31:00

    先顶再看

  9. 老赵
    admin
    链接

    老赵 2009-02-25 00:57:00

    @Ryan Gene
    组件之间交互方式是不同的。
    aspx/aspx.cs组成了Page Controller模式,而现在的mvc框架其实是Front Controller模式。
    其实在webform里使用良好的分离也可以实现很好的view/controller特性,可惜大家多在乱用。
    个人认为死扣“MVC”模式意义不大,Web平台上实现真正的MVC本就难以作到,所以我一直认为唯一不变的是“分离”,而不是到底mvc不mvc。
    很明显Web Form和mvc框架两种东西是不同的驱动方式,选择一多总是好事……当然前提是思路要清晰。

  10. Ryan Gene
    *.*.*.*
    链接

    Ryan Gene 2009-02-25 08:50:00

    谢谢你这么晚了还回复,的确,滥用的确是一个问题,其中一个原因可能是微软提的让web开发像winform那样简单吧,导致很多.net程序员对http,html,css了解不够。补充点我现在的做法,首先尽量把页面切分成user control,一方面为重用,另一方面化烦为简,每个user control实现必须的接口,例如savedata,loaddata等,aspx.cs代码组织好object或object数组,提供给页面上的user controls 进行自我绑定。当然实现可以是多样的,这是编程的乐趣之一,也是体力劳动和脑力劳动的区别,呵呵

  11. RawMan
    *.*.*.*
    链接

    RawMan 2009-02-25 08:58:00

    楼主很强,很伟大

  12. 老赵
    admin
    链接

    老赵 2009-02-25 09:14:00

    @Ryan Gene
    嗯,这是个好习惯,构建自己的编程模型/约定等等。

  13. 假正经哥哥
    *.*.*.*
    链接

    假正经哥哥 2009-02-25 09:24:00

    意义不大。。

  14. 老赵
    admin
    链接

    老赵 2009-02-25 09:31:00

    --引用--------------------------------------------------
    假正经哥哥: 意义不大。。
    --------------------------------------------------------
    为啥?

  15. 重典
    *.*.*.*
    链接

    重典 2009-02-25 09:50:00

    5555555
    快枪赵真是快

    HttpContext。。的问题
    我觉得在View之下只会读取,一般不写
    我仿了一下MonoRail的方法将
    QueryString/Form/Session/RouteData、ServerVariables都在OnActionExecuting时堆到ViewData里了至于其它的方法可以重写或调用静态方法

  16. 老赵
    admin
    链接

    老赵 2009-02-25 10:00:00

    @重典
    View读取HttpContext也不好啊。
    至于推到ViewData里……我还是认为要尽可能使用强类型,所以对于ViewData的感觉不是很好。

  17. 重典
    *.*.*.*
    链接

    重典 2009-02-25 10:08:00

    @Jeffrey Zhao
    强类型是可以直接以编译错误找出问题所在,不过如果传递的类型比较多也繁杂的话,那样只能单独建立Model类来实现了

  18. 老赵
    admin
    链接

    老赵 2009-02-25 10:26:00

    @重典
    对,我建议每个View都使用强类型的Model。

  19. 重典
    *.*.*.*
    链接

    重典 2009-02-25 10:51:00

    @Jeffrey Zhao
    请注意~\UnitView\Default.aspx文件尾部的一些JavaScript代码。

    -》应为

    请注意~\UnitView\DriverPage.aspx文件尾部的一些JavaScript代码。

  20. T2噬菌体
    *.*.*.*
    链接

    T2噬菌体 2009-02-25 10:54:00

    不错,学到不少东西。
    现在我调试视图都是得现在浏览器里运行。。。

  21. 老赵
    admin
    链接

    老赵 2009-02-25 11:09:00

    @重典
    我错了……

  22. 海洋——海纳百川,有容乃大.
    *.*.*.*
    链接

    海洋——海纳百川,有容乃大. 2009-02-25 11:14:00

    老赵,看了你的留言,看来你的FANS 很多啊!我也留一个。
    .NET 技术大会上,你好像讲过这个,不过还是非常感谢分享。

  23. xjb
    *.*.*.*
    链接

    xjb 2009-02-25 11:21:00

    不错,关注ing

  24. 老赵
    admin
    链接

    老赵 2009-02-25 11:30:00

    --引用--------------------------------------------------
    海洋——海纳百川,有容乃大.: 老赵,看了你的留言,看来你的FANS 很多啊!我也留一个。
    .NET 技术大会上,你好像讲过这个,不过还是非常感谢分享。
    --------------------------------------------------------
    我讲过“要对View做单元测试”,没有说过该怎么测试,以及UnitView是怎么做的。

  25. T2噬菌体
    *.*.*.*
    链接

    T2噬菌体 2009-02-25 11:32:00

    非常同意老赵“视图仅应依赖ViewData”这一观点。不过,个人还是觉得视图应该做成模板形式,不要承担逻辑

  26. 老赵
    admin
    链接

    老赵 2009-02-25 11:38:00

    @T2噬菌体
    aspx本来就是一种模板形式。
    说到逻辑——模板中肯定也是会包含逻辑的,呈现上的逻辑。例如,试图会根据“是否有上一页”来确定是不是要显示这个链接。
    当然确定“是否有上一页”是Controller/Model的职责,视图只负责读取“是否有上一页”这一情况。

  27. 假正经哥哥
    *.*.*.*
    链接

    假正经哥哥 2009-02-25 12:03:00

    当我在公司推行MVC的时候,连Controller的单元测试推行都不被接受(认为成本太高),何况是View的测试呢。
    况且View测试断言并非是在哪里可以拿到什么数据,或这显示什么数据,
    对页面问题,往往是:如下
    1:表格的宽度顶出的边框。。
    2:下拉图层没有遮住select(IE6)
    3:...............

    还是需要眼睛去看。。。光靠断言不行啊。。。
    但是对Controller的测试我倒是认为非常有必要。。。。怒啊,环境,领导。。!

  28. 老赵
    admin
    链接

    老赵 2009-02-25 12:09:00

    @假正经哥哥
    对View做单元测试,测试的是“数据”和“结构”和“行为”;而眼睛看的是“样式”,两者都不能省。如果没有自动的测试,测试人员还必须在页面上查看数十个链接是否正确。
    还例如,现在可以省下检查客户端输入校验功能是否正常的工作,只需要用自动测试的方式:“输入错误信息,点击提交按钮,检查错误提示是否出现”就可以了。
    至于你说单元测试的功效不能被接受,那么我的看法是,你的团队不适合用MVC框架,以及……不适合写长期的项目……

  29. 妖居
    *.*.*.*
    链接

    妖居 2009-02-25 12:46:00

    日发一篇啊。老赵辛苦了。开头对Stephen的评论深有同感。我当时看得时候也感觉他那篇文章写得不伦不类。

  30. 老赵
    admin
    链接

    老赵 2009-02-25 12:48:00

    --引用--------------------------------------------------
    妖居: 日发一篇啊。老赵辛苦了。开头对Stephen的评论深有同感。我当时看得时候也感觉他那篇文章写得不伦不类。
    --------------------------------------------------------
    嗯嗯,我们要擦亮眼睛,一味跟风是不好滴……

  31. 零点
    *.*.*.*
    链接

    零点 2009-02-25 14:10:00

    每次读老赵的文章都很受益哦!

  32. 哦ioi[未注册用户]
    *.*.*.*
    链接

    哦ioi[未注册用户] 2009-02-25 17:31:00

    老赵:
    有空翻译基本国外经典技术书籍吧,看看这位仁兄翻译的书籍:

    下面是我在其译章里随意(绝对是鼠标随意找的句子),你们看看,这样的书籍,呵呵呵-----------------一本《C#3.0 in a nutshell》多好的书呀,却被。。。
    (我强烈呼吁:非行内人不要翻译技术书,至少你好好读读行业术语)

    extend interface居然翻译成 拓展接口?
    一个适配器包裹了一个流,跟修饰模式一样
    流必须在使用后关闭或者处置掉
    第12章 处置与回收
    一些对象需要特定的,具有粉碎性的代码来释放资源,
    并且描述C#终止器(finalizers)

    就算意思通,你也应该润饰一下呀

  33. 老赵
    admin
    链接

    老赵 2009-02-25 17:34:00

    @哦ioi
    没空啊,好多东西要写要学

  34. 奥地利[未注册用户]
    *.*.*.*
    链接

    奥地利[未注册用户] 2009-02-25 17:40:00

    透露一下最近学习内容?呵呵

  35. ads的[未注册用户]
    *.*.*.*
    链接

    ads的[未注册用户] 2009-02-25 17:54:00

    --引用--------------------------------------------------
    奥地利: 透露一下最近学习内容?呵呵
    --------------------------------------------------------
    感兴趣

  36. 老赵
    admin
    链接

    老赵 2009-02-25 17:55:00

    @奥地利
    大学课本翻出来从头学

  37. RogerTong
    *.*.*.*
    链接

    RogerTong 2009-02-25 21:10:00

    JeffreyZhao换头像了?哈哈,好像还是以前那个靓仔点:)

  38. airwolf2026
    *.*.*.*
    链接

    airwolf2026 2009-02-25 21:47:00

    还没有接触ASP.NET MVC的东东
    不过还是能从老赵这篇blog获取知识哈.比如你说的'单元测试'要分离,这个俺以前没有认识到,可能还停留于'单元测试'的肤浅认识吧,另外了解到了几个相关的测试工具,还是第一次了解,感觉比vs里面带的web测试工具更方便.

  39. 老赵
    admin
    链接

    老赵 2009-02-25 22:42:00

    @airwolf2026
    这些工具应该算是对VS的补充,功能上没有太大可比性

  40. 老鼠
    *.*.*.*
    链接

    老鼠 2009-02-28 09:37:00

    您好,能否告诉我如何下你的mvc视屏吗?我没有找到!!可以发邮件告诉我吗?weilian_520@126.com 谢谢

  41. 水果阿生
    *.*.*.*
    链接

    水果阿生 2009-03-01 10:24:00

    抛开一切不谈,老赵你的瘦身计划很成功。另外,老苏也已经成功瘦下5公斤了。

  42. 老赵
    admin
    链接

    老赵 2009-03-01 14:42:00

    @水果阿生
    怎么做到的亚?

  43. Nick Wang
    *.*.*.*
    链接

    Nick Wang 2009-03-23 23:58:00

    个人感觉,意义不大。每个页面都这么折腾要花很多功夫,收到的效果却未必有多大,也就是说投入产出比不大。完全可以用其他方式的测试代替。

    另外waitN和selenium似乎都是做acceptance test的,和Unit test还是不一样的吧。

    有太多人把注意力放在controller和view的测试上了,虽然没啥不好,但是model的测试才是最复杂和最重要的啊。只有model正确了,controller和view才有意义,可惜的是这么多人都在追mvc,silverlight这些表现层的东西,却对model不上心啊。(感叹一下)

  44. 老赵
    admin
    链接

    老赵 2009-03-24 00:03:00

    @Nick Wang
    1、没有必要每个页面都这么折腾,对于一些客户端功能测试,例如输入验证,有个自动测试可以省很多事情。
    2、WaitN和Selenium也可以做验收测试的,你可以搜以下前两个和UnitTest,可以有一大堆结果,也常用来做UnitTest。
    3、不是不重视Model,而是Domain的单元测试已经不用强调了,从单元测试已开始测试的几乎都是领域模型,类库,框架云云;反之,表现层的测试一直是个麻烦问题,因此现在一直在强调——兄弟不用觉得世道变了,呵呵。

  45. Nick Wang
    *.*.*.*
    链接

    Nick Wang 2009-03-24 00:18:00

    @Jeffrey Zhao
    你不是天天都这么晚睡吧?牛人就是不一样啊。

    3. model的单元测试虽然已经有历史了,但是感觉国内或者园子内还不够重视,model的UT还不会写呢就开始写表现层了,这个有点头重脚轻了。不是和你有相反的见解,只是感慨一下。也希望借你的影响力,强调一下,希望大家都能写出高质量的代码,早点回家休息。

  46. 老赵
    admin
    链接

    老赵 2009-03-24 00:21:00

    @Nick Wang
    睡的早觉得浪费时间啊……
    我觉得其实倒不是不够重视Model的单元测试,而是不重视单元测试本身,这的确是一个问题……

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我