2007-10-30

    谁会想到,因为kugoo 2007,Flex应用不能正常浏览了......

    原因很简单,kugoo把windows的注册表改了(改了哪里就无从得知了),让IE6晕得找不到系统信息,连它的“关于Internet Explorer”对话框也抛出了“错误53:空间不足”。 而Flex2的官方JS中,刚刚好用到系统信息来判断Flash的版本,它也就这么地挂了。  

    真是恼火,我为了这个问题白白忙活了一个晚上,360卫士、雅虎助手都在kugoo面前倒下了。    

    虽然不知道kugoo到底改了注册表哪些键值,但我还是找到了一个修复的办法,虽然比较麻烦,不过还是有效的。解决办法如下:
    (1)下载并安装Windows xp版本的IE7;
    (2)安装完毕,并重启系统后运行IE7;
    (3)开启 工具->Internet选项->高级->还原设置,完成最彻底的还原。
     执行完毕上述操作后,卸载IE7并重新启动系统,IE6恢复正常。

     这个办法我写在kugoo的在线客户留言版上了,但愿其他受害者能暂时用这个自救。算是做了件好事,挺开心

2007-10-24

关键字:AS3 ActionScript3 图形 两条直线交点 

今天在做数据关系图表时,需要求两条直线相交的交点坐标,代码整理如下:

package org.hadronforce.charts.utils.math{
 import flash.geom.Point;
 import org.hadronforce.charts.baseobjects.Line;
 
 /**
  * 点计算类
  * @Author:Robin
  */
 public class PointCalculator{
  
/**
   * 求两条直线交点.
   * 如果两直线无交点,则返回null。
   */
  public static function getIntersection(line1:Line, line2:Line):Point{
   var x:Number;//被求交点的x轴坐标
   var y:Number;//被求交点的y轴坐标
   
   var k1:Number;//line1的斜率
   var b1:Number;//line1的纵截距
   
   var k2:Number;//line2的斜率
   var b2:Number;//line2的纵截距
   
   /*
     分别求出line1和line2的斜率
     公式:斜率k= tan(a) = y/x
   */
   
   k1 = (line1.startPoint.y - line1.endPoint.y) / (line1.startPoint.x - line1.endPoint.x);
   k2 = (line2.startPoint.y - line2.endPoint.y) / (line2.startPoint.x - line2.endPoint.x);
   
   /*
    根据斜截式 y = kx + b
    可得推导式:b = y - kx
    将k1,k2代入推导式,分别求b1,b2
   */
      b1 = line1.startPoint.y - k1 * line1.startPoint.x;
      b2 = line2.startPoint.y - k2 * line2.startPoint.x;
     
   if (k1 == k2){//当两条直线平行时返回null;
    return null;
   }else if ((k1 == Number.POSITIVE_INFINITY || k1 == Number.NEGATIVE_INFINITY) &&
       (k2 == Number.NEGATIVE_INFINITY || k2 == Number.POSITIVE_INFINITY)){
        //当两条直线平行时(特例,均垂直于y轴)返回null;
       return null;
   }else if (k1 == Number.POSITIVE_INFINITY || k1 == Number.NEGATIVE_INFINITY){
    //当line1垂直于y轴时(斜率为正无穷或负无穷),进行如下计算
    x = line1.startPoint.x;
    y = k2 * x + b2;
   }else if (k2 == Number.POSITIVE_INFINITY || k2 == Number.NEGATIVE_INFINITY){
    //当line2垂直于y轴时(斜率为正/负无穷),进行如下计算
    x = line2.startPoint.x;
    y = k1 * x + b1;
   }else{
       /*
      连例 y = k1 * x + b1
          y = k2 * x + b2
      得到推导式:x = (b1 - b2)/(k2 - k1)
    */
       x = (b1 - b2) / (k2 - k1);
      
     /*
       将x值带入斜截式方程,可求y值
     */
       y = k1 * x + b1;
   }
   
   return new Point(x,y);
  }
 }

}

Tag:
2007-07-07
选择风险

          近期我们在产品前台重构中引入了RIA特性。虽然Ajax比其他RIA技术对于开发人员技术水平的要求较高,开发代价更大,但由于产品特性需要我们仍然采用了Ajax技术,并在众多框架中选择了Prototype(1.5.1)Ext(1.0.1a)组合。在企业级应用开发中做出这样的抉择是需要承担一定的风险的。

 

如期而至的问题

          不出所料,重构工作开展不过两个星期,客户端浏览器的性能问题便凸现出来,使重构进程陷入僵持状态。暂时撇开客户端代码运行速度不说,IE(6 sp2)Fx(2.0.0.4)浏览器出现了不同程度的JavaScript内存泄露,使得客户端浏览器在浏览系统页面一段时间后会消耗大量客户端资源以至浏览器的浏览速度低下甚至瘫痪。使框架问题还是我们编写的客户端代码存在问题呢?我们首先带着这个问题展开了一系列的测试。

         经过初步观察,在系统主界面中每打开一个包含了Ext.Grid(以下简称grid)控件的Ext.TabPanelItem(以下简称tab),浏览器(IEFx)占用的内存资源就会飙升14M,且关闭Ext.TabPanelItem后内存并不会被回收。如此,每次开启10Ext.TabPanleItem再将之关闭,反复数次之后,浏览器所占用的资源已经高达数百兆。浏览器内存占用提高的问题暂不考虑,因为tab中嵌入了iframe(由于系统页面结构的特殊性和接口问题,没有利用ajax请求向tab中装载页面内容),而iframe中的页面加载了PrototypeExtjs文件(总共大约600K),假设框架加载需要分配14M的内存空间也是合理的。但关闭tab后内存不被回收,且开启新tab时浏览器内存占用继续增长,则可以肯定地判断为内存泄露。是何种原因导致了内存泄露呢?

 

IE/Fxbugiframe惹祸

         经过进一步的观察,我们发现IEFx的内存泄露现象存在着差异,而浏览器瘫痪的时机也有着很大不同。

         具体表现如下:

         IE:每开启一个tabIE的内存占用量即提高14M。关闭tab,内存占用量不会下降。继续操作,直至浏览器瘫痪,内存占用量仍不下降。

         Fx:每开启一个tabFx的内存占用量提高14M。关闭tab,内存占用量不会下降。但连续关闭几个tab后,浏览器陷入假死状态,cpu占用率达到50%,数秒后恢复正常。tab全部关闭后,继续操作,随着tab的添加Fx的内存占用量又逐渐增加。

 

解决办法

         综上,可以得到这样的结论,IE下确实存在着内存泄露问题,而Fx下并非内存泄露导致浏览器假死。

         随后,我在Ext的官方网站论坛上找到了关于iframetab混用时会发生Memory leak的帖子。Ext核心开发人员Jack的回答是,TabPanelItem在关闭时并不会对自定义到tab中的元素做特殊处理,这部分工作必须在控件外来完成。另一方面,相关资料称IEiframe元素的回收方面存在着bug,在通常情况下应该将该元素的src属性值修改为"abort:blank",并手工将其从DOM树上移除,然后把脚本中引用它的变量置空并调用CollectGarbage()就可以避免iframe不能正常回收所造成的内存泄露。

         按照上述方法,在tabclose事件中添加了iframe的销毁代码,IE的内存泄露问题得以解决。

         但是,Fx的问题仍然无法得到解决,即关闭tabCPU占用率激增并持续数秒。无奈之下我测试了Linux下的Firefox以及Windows版本的Firefox 3 alpha,并未出现此问题。初步断定,着个问题应该是Firefox 2.0.0.4DOM元素回收性能方面的BUG。至于在Fx下关闭tab并不会立即释放内存也得到了官方解释,Fx会缓存一部分页面数据在内存中,以得到较好的性能。

 

“至尊”级浏览器,Safari

       此前我对苹果发布的windows版本Safai3还持有不屑一顾的态度,因为他们的发言人实在太过狂妄的发言,自称Safari是世界上最好的浏览器,未把任何浏览器放在眼里。我一向不喜欢自吹的人,却又对他们发言人的狂言感觉好奇,另一方面希望测试一下产品的跨浏览器能力,便索性下载了Safari 3 for windows。结果令我瞠目结舌,Safari处理JavaScript的速度绝非IEFx(包括他们的最新版本IE7Fx3 alpha)所能及,绝对可以堪称一流的页面渲染能力。为程序员说句心里话,如果所有浏览器的页面渲染速度能达到Safari的水准,我们在AJAX RIA应用构建上将有更大的发挥空间。我着实为Safari带来的RIA极速浏览体验快感兴奋了一把。不夸张地说,Safari至少可以算个浏览器的准至尊吧。

 

Extjs包使用注意事项

      在通常情况下,使用ext-all.js就可以了,但是当一个页面需要包含多个iframe,且iframe的资源指向需要使用Ext的页面时,被嵌入的页面尽量不要使用Ext-all.js了,而是使用ext-core.js并从Extpackage目录中加载你所需要的js包。除非你确定你的客户使用Safari或者Fx3来浏览,否则性能会大打折扣。

2007-05-31

    IPIS就爱搞这些无聊的东西,更让我吃惊的是我竟然把70道测试题都答完了,看来我蜕化了
     今天很忙,没时间细看测试结果,改天来看吧,粘上先。

你的气质类型,更接近于创业者手艺者,是艺术创造者的一个分支。你在拥有艺术创造者类型共性的同时还兼具创业者的特性。 
  ......

2007-05-27

      最近做发财梦真的有些发疯了,把FCat Studio(森猫工作室)改名为HadronForce Software Studio(海创力量工作室),延续大学时代的创业梦想。设计了新的Logo还有主页草图。我不得不承认自己确实是个急性子,还没开始做什么就把域名申请了,http://www.hadronforce.com/,是有一点浮躁吧。
      Logo还算可以,主页就很难让自己满意,背景太过杂乱了。不过也没有时间再投入到这方面了,暂时如此吧。

  HadronForce Logo
      海创LOGO

HadronForce Home Page

海创Home Page

2007-05-27

     我今天在豆瓣上看到JSON讨论群中有人提问$.getJSON的使用方法,他不明白回调函数中json参数的用法。我对此做了简单的回答。
      其实,这个json对象其实我们都非常熟悉。
  不说那些难以理解的概念了,先简单说一下它的结构。
   -以左大括号({)开始
   -一个或多个属性,属性名和值之间用冒号隔开,属性之间使用逗号间隔
   -以右大括号(})结束
  对象中每个属性的值可以为任何JavaScript中允许的类型的数据,例如字符串、数组、数字、对象等。
   举一个简单的例子:
   {
   companyname:"SUN CO.",
   addresses:["address1","address2"],
   country:"USA",
   }
   很明显,其实JSON结构也就是一个哈希表结构。
   OK,了解了JSON的结构,我们就可以回来看一下JQuery中的$.getJSON()方法了。该方法有三个参数,$.getJSON(String url, Hash params, Function callback)。其中,url是获取JSON结构数据的请求地址,即访问该地址会得到一段JSON结构的文本(这里被JQuery封装了,这一段文本是通过请求对象的responseText属性得到的)。params是向url地址的服务发送的参数,服务器可以根据这些参数做出响应。参数的结构跟JSON的结构类似,都是形似“{key1:value1,key2:value2}”的结构。callback为回调函数,即function(json){//code...},在这个函数中做客户端处理。在callback函数中,参数json就是通过向url请求得来的JSON结构文本构造出来的对象。json参数的使用很简单,它是一个哈希表对象,可以通过json.key的方式来使用其中定义的属性。例如,如果我们传入的是前面所述例子的JSON结构,我们可以通过json.addresses[0]来获取addresses的第一个值。
   JSON不神秘,说白了其实就是个哈希表结构,在JavaScript中可以直接使用,非常方便。

2007-05-26
          关键字:AJAX 请求 并发 Firefox IE

    最近在开发中遇到一个棘手的问题,在FireFox浏览器中连续展开AJAX树(XTREE+XLoadTree)的多个节点会导致Hibernateorg.hibernate.exception.GenericJDBCExceptioncan't load entry异常(我们的系统用Ajax树控制数据节点,数据持久层采用Hibernate),但是在IE浏览器下进行同样的操作则不会抛任何异常。

        通过异常日志和代码分析发现,在这个异常后面隐藏着Hibernate Session的线程安全问题。当去掉引起该异常的代码时,线程安全的异常警告便会被显示出来。也就是说这个异常只是表象,其实真正导致系统异常的原因是Hibernate Session线程安全。当然,这个不能怪Hibernate,确实是我们的代码在存在并发时重写Hibernate Session导致冲突的可能。修正代码后问题得以解决。但令人费解的是,为什么在FireFox会导致Hibernate抛异常,在IE下却完全正常呢?

        两种浏览器都是发送Ajax请求到服务器,结果却大相径庭。虽说是服务器端的响应导致了该异常,但不难发现,只有当两个请求同时进行时才可能引起上述的Hibernate的线程安全问题,那么我们就必须假设这样的情况,当使用Firefox浏览器进行AJAX请求时同时发送两个甚至多个相同session id的请求线程到服务器,而IE仅发送一个。

        在网上找到一个非常有意思的测试,最终得出这样的结论:IE每次执行一个请求,Firefox一次执行两个请求。这一测试通过验证,结果属实。

三个文件,一个客户端(其中包含 prototype.js 文件):

 

下载: client.html

      <script type="text/javascript" src="prototype.js">script>      <script type="text/javascript" src="func.js">script> 

 

      <a href="javascript:ajax_get('A')">Click Aa>

      <hr />

      <a href="javascript:ajax_get('B')">Click Ba>

      <hr /> 

      <a href="javascript:ajax_get('C')">Click Ca>

      <hr />

      <div id="result">Result:<br />div>

 

一个 JS:

下载: func.js

     var i = 0;

     function ajax_get(action){  

         i++;

         var rand_num = Math.random();

         var myajax=new Ajax.Request("server.php?action=" + action + "&order=" + i +"&rand=" + rand_num,{method:'get',onComplete:show_result});

         $('result').innerHTML += get_date() + " , " + action + i + " : client post.
\n";

     }

     function show_result(response){

        var r_text = response.responseText;

        $('result').innerHTML += r_text;

     }

    function get_date(){

       var date = new Date();

      return date.getMinutes() + "m" + date.getSeconds();

     }

一个服务器端,PHP 文件:

下载: server.php

   

    //session_start();

     $action = $_GET['action'];

     $order = $_GET['order'];

     

     echo date("i\ms") . " , " . $action . $order . " : server is start.
\n";

     switch($action){

        case 'A':

            sleep(5);

            break;

       case 'B': 

           sleep(2);

           break; 

       case 'C':

           break;

     }

     echo date("i\ms") . " , " . $action . $order . " : server is end.
\n";

第一段代码是点 A,B,C 三个链接,分别发送不同的 action 的请求到服务器端。

第二段代码是接收到请求后,A 是等 5 秒后结束,B 是等 2 秒,C 是马上结束,用来模拟 PHP 执行代码的时间。

测试环境:IE6,FireFox,Opera,Windows XP,Apache 2,PHP 5.2.0

 

测试方法 1:

分别点 A,B,C,慢点点,等前一个回来后再点下一个:

结果:

IE6,FireFox 以及 Opera 结果都类似:

42m52 , A1 : client post.

42m52 , A1 : server is start.

42m57 , A1 : server is end.

42m57 , B1 : client post.

42m57 , B1 : server is start.

42m59 , B1 : server is end.

43m01 , C1 : client post.

43m01 , C1 : server is start.

43m01 , C1 : server is end.

结论:没啥惊讶的,点一个返回一个嘛。

测试方法 2:

分别点 A,C,快点点,不能前一个回来就点后面的,模拟并发:

结果和上面一样,恩,处理两个并发木问题。

测试方法 3:

分别点 A,B,C,快点点,不能前一个回来就点后面的,模拟并发:

结果:

IE6 和 Opera 结果类似:

51m30 , A1 : client post.

51m30 , B2 : client post.

51m30 , C3 : client post.

51m30 , C3 : server is start.

51m30 , C3 : server is end.

51m30 , B2 : server is start.

51m32 , B2 : server is end.

51m30 , A1 : server is start.

51m35 , A1 : server is end.

FireFox:

52m20 , A1 : client post.

52m20 , B2 : client post.

52m20 , C3 : client post.

52m20 , B2 : server is start.

52m22 , B2 : server is end.

52m22 , C3 : server is start.

52m22 , C3 : server is end.

52m20 , A1 : server is start.

52m25 , A1 : server is end.

结论:嘎嘎,有意思了吧,IE6 和 Opera 似乎不受影响,但 FireFox 的结果确是 C 要等 B 执行执行完毕后才开始(看时间,A 和 B 在服务器端都是 52m20 秒开始的,而 C 确是在 B 结束后的时间 5m22 开始执行的),这样可以排除服务器端 PHP 的影响,因为 IE,FireFox 以及 Opera 并没有改变服务器端什么,而返回的结果是不同的,所以 PHP 端顺序执行代码的结论应该是不成立的。

测试方法 4:

分别点 A,A,A,A,C 快点点,不能前一个回来就点后面的,模拟并发:

结果:

IE6:

56m06 , A1 : client post.

56m06 , A2 : client post.

56m06 , A3 : client post.

56m07 , A4 : client post.

56m07 , B5 : client post.

56m07 , B5 : server is start.

56m09 , B5 : server is end.

56m06 , A1 : server is start.

56m11 , A1 : server is end.

56m06 , A2 : server is start.

56m11 , A2 : server is end.

56m06 , A3 : server is start.

56m11 , A3 : server is end.

56m07 , A4 : server is start.

56m12 , A4 : server is end.

FireFox:

56m33 , A1 : client post.

56m33 , A2 : client post.

56m33 , A3 : client post.

56m34 , A4 : client post.

56m34 , B5 : client post.

56m33 , A1 : server is start.

56m38 , A1 : server is end.

56m33 , A2 : server is start.

56m38 , A2 : server is end.

56m38 , A3 : server is start.

56m43 , A3 : server is end.

56m38 , A4 : server is start.

56m43 , A4 : server is end.

56m43 , B5 : server is start.

56m45 , B5 : server is end.

Opera:

57m18 , A1 : client post.

57m19 , A2 : client post.

57m19 , A3 : client post.

57m19 , A4 : client post.

57m20 , B5 : client post.

57m18 , A1 : server is start.

57m23 , A1 : server is end.

57m19 , A2 : server is start.

57m24 , A2 : server is end.

57m19 , A3 : server is start.

57m24 , A3 : server is end.

57m19 , A4 : server is start.

57m24 , A4 : server is end.

57m23 , B5 : server is start.

57m25 , B5 : server is end.

结论:

从上面可以看出,IE6 似乎还是不受影响,而 FireFox 同时发送请求,但执行却是要两个两个执行, Opera 是四个四个执行。所以得到结论是 IE 可以同时发送无数个请求,FireFox 同时发送 2 个,Opera 是 4 个。

从网上查了一下,貌似是和浏览器的进程并发数有关。

更有趣的一个测试:

把上面的 PHP 文件的第 2 行的 session_start 的注释去掉,每次请求都打开一次 session,然后进行上面的测试。

测试方法 2:

IE6,FireFox 以及 Opera 结果类似:

15m03 , A1 : client post.

15m03 , C2 : client post.

15m08 , C2 : server is start.

15m08 , C2 : server is end.

15m03 , A1 : server is start.

15m08 , A1 : server is end.

结论:

貌似 session_start 也占了一个进程,A 和 C 同时发出去,但 C 要等 A 执行完后才开始执行。

测试方法 3:

分别点 A,B,C,快点点,不能前一个回来就点后面的,模拟并发:

IE6 和 FireFox 结果类似:

01m59 , A1 : client post.

01m59 , B2 : client post.

01m59 , C3 : client post.

01m59 , A1 : server is start.

02m04 , A1 : server is end.

02m04 , B2 : server is start.

02m06 , B2 : server is end.

02m06 , C3 : server is start.

02m06 , C3 : server is end.

Opera:

05m07 , A1 : client post.

05m08 , B2 : client post.

05m08 , C3 : client post.

05m07 , A1 : server is start.

05m12 , A1 : server is end.

05m14 , C3 : server is start.

05m14 , C3 : server is end.

05m12 , B2 : server is start.

05m14 , B2 : server is end.

结论:

从上面感觉 session 也占了浏览器的一个进程。从网上找了一下,说是 session 是发送一个 session ID 放到客户端,但我感觉既然是这么发送,应该不可能老占着一个进程啊。

2007-05-24

关键字:Tapestry4  单元测试  EasyMock  SpringMock

1        

Tapestry4中进行单元测试不是一件容易的事情。我们知道,一个完整的MVC框架内的单元测试,仅仅像纯粹简单java类那样测试是不够的。除了需要模拟出被测试类所依赖的类或接口实现以外,还要模拟必须的Servlet组件对象,以及Session等,较为繁琐。此外,由于Tapestry4采用了抽象类和抽象方法来实现页面,又给它的单元测试带来了不便。更糟糕的是,Tapestry4自身并未提供完整的单元测试解决方案,同时又欠缺第三方测试框架的支持,因此在互联网上有人抨击Tapestry4,“它的单元测试简直不可能”。

其实,如果我们对Tapestry的框架内部结构足够熟悉,如果对Servlet容器的内部机制足够了解,实现在Tapestry4上的单元测试不会太难,但是太多条件对于大多数Tapestry Programmer尚不具备,依我们目前对Tapestry4框架结构的了解程度,仍然无法实现像StrutsTapestry5那样完整的单元测试,但应该基本能够满足我们的测试需要。当然,随着对框架研究的逐步深入,这一情况应该会逐步得到改善。

接下来,本文将针对Tapestry4 + Hibernate + Spring框架组合中,Tapestry4的单元测试做简要说明。

 


2         工具介绍
2.1       EasyMock简介

EasyMock是一个Mock对象的类库。最新的EasyMock版本是2.2,可在http://sourceforge.net/projects/下载到。Mock 对象能够模拟领域对象的部分行为,并且能够检验运行结果是否和预期的一致。领域类将通过与Mock 对象的交互,来获得一个独立的测试环境。EasyMock可以动态地生成Mock 对象而不需要编写它们,因为也不会产生多余代码。说明:默认情况下,EasyMock只支持为接口生成Mock。如果还需要为类生成Mock,可在上述网址下载EasyMock的扩展包以完成此功能。EasyMock 2.2只能在Java 5.0以上的版本中运行。 

 

3         单元测试方法
3.1       方法一:EasyMock辅助方式 

大多数情况下,前端的单元测试需要与后端分离。而且为了测试方便,又会希望摆脱对Servlet容器的依赖。在这种情况下,以往的实现手段是手工编写所需的Mock对象来辅助完成测试,但这样会大幅增加测试代码编写量。幸运的是,这部分工作现在可以通过EasyMock来帮助我们完成,从而提高单元测试效率。

这种测试方式下,使用JUnitTestNG均可。

下面,以Metaone前端的登陆页面类(Login.java,为了增加例子可读性,代码已删减)的单元测试为例,讲解EasyMock辅助Tapestry4单元测试的方法。

被测试类Login.java的被测方法代码如下:

//Login.java

package com.carnation.metaone.page; 
import …… 
public abstract class Login extends BasePage implements PageBeginRenderListener {   
   // 用户名   
   public abstract String getUsername();    
   
// 密码   
  
public abstract String getPassword();    
  
public abstract UserService getUserService();    
  
public abstract HttpServletRequest getHttpServletRequest();    
   
public abstract User getUser();    
  
public abstract void setUser(User user);    
  
public abstract void setApplication(Application app);    
  
public abstract Application getApplication();    
  
// 登陆表单的监听方法   
  
public IPage loginSubmit(IRequestCycle cycle) {       
       
User user =
new User();
       user.setUsername(getUsername());
       user.setPassword(getPassword());
       Application application = getApplication();
       user.setApplication(application);
        try {
           user = getUserService().authenticate(user);
           user.setLocale(this.getLocale());
           user.setApplication(application);
           setUser(user);
           Date myDate = new Date();
           String ip = getHttpServletRequest().getRemoteAddr().trim();
            /*
            * 跳转页面,其中空URL代表当前应用的根目录
            */
           String url = "Home.html";
           if (application.getId().intValue()>1) {
              url = "application/Home.html";
           }
           return cycle.getPage(url);
       } catch (AuthenticationException e) {
           IValidationDelegate delegate = getDelegate();
           delegate.setFormComponent(null);
           delegate.record("认证失败,请重新登录!", null);
           return null;
       }
    }
测试代码编写步骤:
(1)                EasyMock包引入测试类,当然如果使用JDK1.5,可以使用静态引入,使用更加方便。
         import static org.easymock.EasyMock; //JDK1.5环境下可以静态引入
         import static org.easymock.EasyMock.createMock;
         import static org.easymock.EasyMock.expect;
         import static org.easymock.EasyMock.replay;
         import static org.easymock.EasyMock.verify; 

(2)                模拟后端Service接口和Servlet接口。
         UserService userService = createMock(UserService.class); 
         HttpServletRequest httpServletRequest = create(HttpServletRequest); 
         IRequestCycle cycle = create(IRequestCycle.class);
         Home home = create(Home.class); 

(3)                Login类实例化,并将需要初始化的变量进行初始化。Tapestry框架提供了一个org.apache.tapestry.test.Creator类,可以创建页面和组件抽象类的实例。
         Creator creator = new Creator();Map propertyMap =               new HashMap();propertyMap.add(“username”,”admin”);
         propertyMap.add(“password”,”admin”);
         propertyMap.add(“userService”,userService);
         Application application = new Application();
        
application.setId(new Long(“1”));
         
propertyMap.add(“application”,application);creator.newInstance(Login.class, propertyMap); 
(4)                编写Mock行为逻辑。EasyMock测试注重行为,即通过类似于录制、回放的过程来进行测试。在“录制”过程中,我们编写期望Mock对象发生的行为,例如返回值、被调用次数等。在回放过程中,EasyMock将自动检查实际发生行为与预期是否相符。那么,对应本例而言,代码如下:
     
expect(httpServletReques.getRemoteAddr()).andReturn(“192.168.0.1”);
        
try{
             
expect(userService.authenticate(not(eq(user)))).andReturn(getMockUser());
         
}catch(AuthenticationException e){   ……} 
        
expect(cycle.getPage(“application/Home.html”)).andReturn(home).anyTimes();
        
expect(cycle.getPage(“Home.html”)).andReturn(home).anyTimes();
       
 replay(httpServletReques);replay(userService);replay(cycle); 
        
Creator creator = new Creator();
        
Login loginPage = (Login) creator.newInstance(Login.class, propertyMap); 
(5)                断言
    
assert loginPage.loginSubmit(cycle).equals(home); 

3.2       方法二:SpringMock辅助方式(前后端集成)

在一些实际情况中,后端的一些方法内部对Hibernate Session进行操作,在这种情况下,对于那些无返回值的方法调用,在前端使用EasyMock是无法模拟的。

幸运的是,利用Spring Mock可以辅助这类测试。但需要注意,这并不意味这不再需要EasyMock Servlet接口的模拟仍然需要使用EasyMock来实现。

若采用这种方式进行测试,对测试工具的选择会有局限,仅支持JUnit,因为Spring Mock提供的基类继承自JUnit中的测试类。

该方式的实现步骤如下:

(1)    使测试类继承AbstractDependencyInjectionSpringContextTests

(2)    覆写getConfigLocations方法。以读取Spring上下文。

@Override

                    protected String[] getConfigLocations() {
         
                String[] str = new String[]{"applicationContext.xml"};                   
                         
return str;
                    
}

(3)    分别在onSetUp()onTearUp()方法中开启和关闭HibernateSession,这样延迟加载(Lazy Initialization)的集合也可以被正常调用。

                   private SessionFactory sf;

@Override
 protected void onSetUp() throws Exception {
                        
super.onSetUp();
                        
SessionFactory sessionFactory = (SessionFactory) applicationContext.getBean("sessionFactory");
                       
sf = sessionFactory;Session s = sessionFactory.openSession();
                       
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(s));
                  
}
 
@Override
protected void onTearDown() throws Exception {
      
super.onTearDown();
       
SessionHolder holder = (SessionHolder)TransactionSynchronizationManager.getResource(sf);
       
Session s = holder.getSession();
       
s.flush();TransactionSynchronizationManager.unbindResource(sf);
        
SessionFactoryUtils.closeSessionIfNecessary(s, sf);} 

(4)    编写测试方法。与使用EasyMock不同,此时通过application对象的getBean()方法来获取Spring上下文中装配的Bean。例如

                    UserService userService =  (UserService)applicationContext.getBean("userService");

        通过上述两种方法,可以满足大部分单元测试需要,但仍然无法覆盖到界面操作方面的测试。这样的做法在理论上是可行的,但从实际角度出发,对于Web层质量的提升意义并不大,不如用Selenium做集成测试来的实在。:)

共1页 1