发布新日志

  • sql常用语句

    2009-07-21 11:35:43

    1. Select & distinct

    select colum1,colum2 ...

    from tablename

    where colum1=value1 and colum2=value2

    slect distinct colum1,colum2...

    from tablename

    2.Insert

    insert into tablename

    values (value1,value2...)

    insert into tablename (colum1,colum2...)

    values (value1,value2...)

    3.update

    update tablename

    set colum1=value1, colum2=value2...

    where somecolum=somevalue

    4.delete

    delete from tablename

    where colum1=value1

    5.Top & order by

    select top 10

    from tablename

    order by colum1 asc|desc

    6.like与通配符

    select * from tablename

    where colum1 like 's%'

    7.count & distinct

    select count(distinct colum1)

    from tablename

    8.in & (between and)

    select column(s)

    from tablename

    where column1 in (value1,value2...)

    select column(s)

    from tablename

    where column1 between value1 and value2

    9.create database & create table

    create database DB_Name

    create table tablename

    (

    column_name1 datatype,

    column_name2 datatype,

    ...

  • 名词解释

    2009-07-20 22:14:59

    http: hyper Text Transfer Protocol 超文本传输协议

    URL: Uniform. Resource Locator 唯一资源定位

    TCP/IP:Transmission Control Protocol/Internet Protocol 传输控制协议/网际协议

    UDP:User Datagram Protocol 用户数据报协议

    pop3:post office protocol--version 3 邮局协议版本3

    smtp:simple mail trasfer protocol 简单邮件传输协议

    ftp:file transfer protocol 文件传输协议

    DNS: domain name server 域名服务器

    ARP: Address resolution protocol 地址解析协议(通过ip获取对方物理地址的协议)

    XML: Extensible Markup Language 可扩展的标志性语言

    J2EE:java 2 Platform. Enterprise Edition java2平台企业版

    jdk: java development kit java开发工具包

    CVS: concurrent version system 协作版本系统

    c2c:consumer/consumer 电子商务

    API: application program interface 应用程序接口

    SQL: structured query language 结构化查询语言

  • java中读取配置文件

    2009-07-15 16:24:23

    这两天把自己的测试用例重新修改了一番,把原来一些不太规范的用法、写法做了更新,今天先记录java中读取配置文件的应用。

    定义一个Config类,包含多个static final的成员变量(全局静态一次初始化后不可被修改的变量),这些变量包括测试数据所在路径,测试中用多次用到的一些常量。config类中还包含一个private static的getProperty方法,用以获取config.ini文件中的属性值,并赋给static final常量。

    下面是getProperty方法,用到了java.util.Properties类:

    private static String getProperty(String property){
      String ConfigFile = "config.ini";//该文件默认位于.project文件同级目录下

         Properties p = new Properties();
         try {
           p.load(new FileInputStream(ConfigFile));
         } catch (FileNotFoundException e) {
          e.printStackTrace();
         } catch (IOException e) {
           e.printStackTrace();
         }
         String value = p.getProperty(property);
         //System.out.println(value);
      return value;
      
     }

    下面是config.ini配置文件:

    /*Soap服务器地址*/
    host=http://192.9.200.59:8000
    /*登录用户名*/
    user=admin
    /*登录密码*/
    pwd=123456
    /*资源路径*/
    resource=d:\data\

    调用getProperty获取配置文件:

    String serverhost=getProperty("host")//获取到配置文件中host所对应的等号后面的值;

  • 网络学习日志(二)---http协议四个方面的特性

    2009-07-14 17:32:42

    由于有个项目涉及到TCP、HTTP、Soap的一些东西,于是回顾了一下以前曾经学过的东西,并学到了一些新东西,以此记录。根据我的逐步学习过程,分了以下5个部分。

    1.由netstat查看网络情况,引出的TCP建立连接、终止连接过程,以及TCP状态分析;

    2.Soap=XML+HTTP引出的HTTP协议回顾;

    3.Soap(Simple Object Access Protocol)简单对象访问协议分析及应用;

    4.番外一:awk的使用

    5.番外二:Linux下抓包工具

    -----------------------------篇二----------------------------------------------

    一:HTTP协议几个要点

      参考文献:http://www.cnblogs.com/gpcuster/archive/2009/05/25/1488749.html

      HTTP协议,像pop3、smtp、ftp协议一样,都是应用层的一些通信规范,它运行在TCP之上,对通信数据格式、连接方式等做了一定的规范:

    1.Request和Response的格式

    Request格式:

    HTTP请求行(get还是post,协议版本)

    (请求)头(包括host、charset、connection等属性)

    空行

    消息体

     

    Response格式:

    HTTP应答行(协议版本、返回状态码:200还是404等)

    (应答)头(包括content-type、encoding、length等属性)

    空行

    消息体

     

      详细的信息请参考:RFC 2616

        HTTP headers的简要介绍,请查看:Quick reference to HTTP headers

    2.建立连接的方式

      从应用层看,HTTP协议是无连接的,不需要connect才传输数据,直接向服务器发送Request,然后服务器返回结果。

      HTTP支持2中建立连接的方式:非持久连接和持久连接(HTTP1.1默认的连接方式为持久连接)。是否持久连接,可以通过抓包看到,如果Request或者是Response的消息头中包含:Connection: Keep-Alive,表示持久连接;Connection: close表示非持久连接。

        非持久连接:

    举例说明:非持久连接情况下,客户端向服务器端请求一个web页面的过程:

    a.客户端初始化一个与服务器主机中http服务器的TCP连接;

    b.HTTP客户端经由与TCP相关联的本地套接字发出一个HTTP请求消息;

    c.HTTP服务器经由与TCP相关联的本地套接字接收这个请求,再经由一个套接字发出响应消息;

    d.HTTP服务器告知TCP关闭这个TCP连接;

    e.HTTP客户端经由一个套接字接收到这个响应消息,TCP连接随后终止;

    f.假设该响应消息中经分析封装的为一个html页面,其中包含10个jpeg对象的引用,则为每一个引用重复以上五步;

        持久连接:

    非持久连接的缺点:

    a.客户端需要为每个请求建立并维护一个新的TCP连接,对于每个TCP连接,客户端和服务器端都需要分配TCP缓冲区,并维持TCP变量;

    b.每个请求对象,都有2个RTT(Round-Trip Time)延迟,一个在建立TCP连接时发生,另一个在数据发送及应答时发生。(TCP连接建立的三次握手的前两次,是一个RTT,最后一次会与发送数据一起发送至对方服务器)。

    在持久连接情况下,服务器在发出响应后让TCP连接继续打开着。同一对客户/服务器之间的后续请求和响应可以通过这个连接发送。HTTP服务器在某个连接闲置一段特定时间后关闭它,而这段时间通常是可以配置的。持久连接分为不带流水线(without pipelining)和带流水线(with pipelining)两个版本。

        HTTP1.1协议中,默认使用持久连接,如果需要非持久连接,则需要在http头中设置connetion参数为:Connection: close

    3.缓存机制

    (略)

    4.响应授权激发机制

    (略)

  • 网络学习日志(一)---TCP的三次握手、四次握手及状态图

    2009-07-14 13:44:56

    由于有个项目涉及到TCP、HTTP、Soap的一些东西,于是回顾了一下以前曾经学过的东西,并学到了一些新东西,以此记录。根据我的逐步学习过程,分了以下5个部分。

    1.由netstat查看网络情况,引出的TCP建立连接、终止连接过程,以及TCP状态分析;

    2.Soap=XML+HTTP引出的HTTP协议分析;

    3.Soap(Simple Object Access Protocol)简单对象访问协议分析及应用;

    4.番外一:awk的使用

    5.番外二:Linux下抓包工具

    -----------------------------篇一-----------------------------------------------------

    引子:Linux上soap服务器在多并发、长时间运行后,客户端出现:connection reset by peer, broken pipe的错误。

    在网上搜了一些结果,基本上是说,如果浏览器向服务器发送一个请求后,在还没有收到返回结果时,点击了刷新、后退、停止、关闭等操作,在服务器端就会看到该错误。这是具体到了B/S结构的系统,如果通用一点讲的话,是指pipe(管道)读数据的一端没有在读,但是写数据的一端有线程在写,就会在写数据端看到该错误。

    以上问题目前还未解决。

    一:TCP建立连接

    文字描述:

    1.客户端首先向服务器端发送一个带有SYN(Synchronize)标记的TCP报文,向服务器申请打开某个端口;

    2.服务器接收到SYN后,返回一个ACK(ACKnowledge)确认收到SYN报文,并同时发送一个SYN报文(之前的SYN序号+1)向客户端确认(我已准备好数据发送,你呢?);

    3.客户端最后再返回一个ACK确认收到服务器端的信息,并做好数据发送的准备。

    至此,客户端、服务器均做好了数据发送准备,TCP连接的建立(三次握手)完成。

    图片描述:

    二:TCP连接关闭

    文字描述:

    由于TCP的连接是全双工的(可以同时发送和接受),因此在关闭连接时,也必须确认两端的连接均关闭。

    1.客户端向服务器端发送一个带有FIN段的报文,表示客户端已终止发送,客户端close;

    2.收到FIN后,服务器端返回一个ACK报文应答对方;

    3.服务器端发送一个FIN报文(之前的FIN序号+1),表示服务器端已终止发送,服务器端close;

    4.客户端收到FIN后,向对方发送一个ACK确认应答;

    至此,双方均已相互确认终止数据发送,TCP连接的关闭(四次握手)完成。

    图片描述:

    三:TCP状态图

    RFC 793: Transmission Control Protocol - Functional Specification
    http://www.rfc-editor.org/rfc/rfc793.txt
                                 
        
         +---------+ ---------\ active OPEN | CLOSED | \ ----------- +---------+<---------\ \ create TCB | ^ \ \ snd SYN passive OPEN | | CLOSE \ \ ------------ | | ---------- \ \ create TCB | | delete TCB \ \ V | \ \ +---------+ CLOSE | \ | LISTEN | ---------- | | +---------+ delete TCB | | rcv SYN | | SEND | | ----------- | | ------- | V +---------+ snd SYN,ACK / \ snd SYN +---------+ | |<----------------- ------------------>| | | SYN | rcv SYN | SYN | | RCVD |<-----------------------------------------------| SENT | | | snd ACK | | | |------------------ -------------------| | +---------+ rcv ACK of SYN \ / rcv SYN,ACK +---------+ | -------------- | | ----------- | x | | snd ACK | V V | CLOSE +---------+ | ------- | ESTAB | | snd FIN +---------+ | CLOSE | | rcv FIN V ------- | | ------- +---------+ snd FIN / \ snd ACK +---------+ | FIN |<----------------- ------------------>| CLOSE | | WAIT-1 |------------------ | WAIT | +---------+ rcv FIN \ +---------+ | rcv ACK of FIN ------- | CLOSE | | -------------- snd ACK | ------- | V x V snd FIN V +---------+ +---------+ +---------+ |FINWAIT-2| | CLOSING | | LAST-ACK| +---------+ +---------+ +---------+ | rcv ACK of FIN | rcv ACK of FIN | | rcv FIN -------------- | Timeout=2MSL -------------- | | ------- x V ------------ x V \ snd ACK +---------+delete TCB +---------+ ------------------------>|TIME WAIT|------------------>| CLOSED | +---------+ +---------+
                             TCP Connection State Diagram
                                   Figure 6.
    
  • C++学习笔记2

    2009-05-21 11:58:00

    今天要讨论关于指针的问题。

    char globlestr1[]="abcde";

    char globlestr2[]="abcde";

    int mian()
    {
      char *p = new char[10];
      strcpy(p,"abcde");  
        //指针的赋值方式;除了初始化时可以使用char* p="abcde"外,
        //其他情况是不正确的。例如*p= "abcd"是不对的,不可能将一个
        //字符串付给*p,因为*p是char类型
      printf("%s %d\n",p,p);
        //结果是: abcde 以及 p中存放的地址(即abcde位置)
      printf("%c %d %c\n",*p,*p,*(p+1));
        //结果是: a  97(a的整数值) 以及 b
      char str[]="ABCDE";
      printf("%s %d",str,str);
        //结果是: ABCDE 存放ABCDE的地址

        //另外看一下这个列子:

      char* p1="abcde";

      char* p2="abcde";

      printf("%d %d",p1,p2)

        //p1,p2输出结果相同,是同一个地址;在这个列子里,在栈里为p1、

        //p2分配两块不同的内存,单都指向同一个地址abcde;

      printf("%d %d",globlestr1,p1,globlestr2);

        //输出结果不同,虽然都是在数据段分配,但不是同一块地址;在这个

        //列子里,在数据段里为globlestr1和globlestr2分配了两块不同

        //的内存,但是存放的内容是相同的。
    }

  • C++学习笔记1

    2009-05-18 21:34:02

    1.using namespace std;和#include <iostream>还是#include<iostream.h>的问题

    一般都知道,要使用cout等方法必须 std::cout或者在前面使用using namespace std;但前提是需要包含头文件#include<iostream>,而不是iostream.h(有些编译器已经不再支持C头文件了,比如vs2008)。

    使用#include < iostream >,得到的是置于名字空间std下的iostream库的元素;如果使用#include < iostream.h >,得到的是置于全局空间的同样的元素。在全局空间获取元素会导致名字冲突,而设计名字空间的初衷正是用来避免这种名字冲突的发生。


    2.boolalpha的用法:
    int main()
    {
     using namespace std;
     bool a=true;
     cout<<a<<endl; //输出1
     boolalpha(cout); 
     cout<<a<<endl; //输出true
     noboolalpha(cout);
     cout<<a<<endl; //输出1
     cout<<boolalpha<<a<<endl; //输出true
    }

    3.内存划分问题-代码段、数据段、堆、栈

    代码段,顾名思义,代码存放于代码段;

    数据段,用于存放全局变量,全局变量初始化后不能改变;

    堆,程序中new出来的空间存放于堆中;

    栈,存放程序中的局部变量(跳出该段程序后空间被释放);

    4.举例说明3中各类内存段

    int a = 0; //全局变量,存放于数据段

    char *p1; //全局变量,存放于数据段;

    int main()

    {

    int b;//局部变量,存放于栈;

    char s[] = "abc"; //局部变量,存放于栈;

    char *p2; //局部变量,存放于栈;

    char *p3 = "abcd"; //p3存放于栈,“abcd”存放于数据段;

    static int c = 0; //全局变量,数据段;

    p1 = (char *)malloc(10); //malloc得来10字节存放于堆;

    strcpy(p1, "abcd"); //"abcd"存放于数据段,编译器可能会将它与p3所指向的"abcd"优化成一个地方

    return 0;

    }

    有了上面的说明,下面这个问题就可以轻易解决了:

    以下三条输出语句分别输出什么?

    char str1[]       = "abc";
    char str2[]       = "abc";
    const char str3[] = "abc";
    const char str4[] = "abc";
    const char* str5  = "abc";
    const char* str6  = "abc";
    cout << boolalpha << ( str1==str2 ) << endl; //
    false
    cout << boolalpha << ( str3==str4 ) << endl; // false

    cout << boolalpha << ( str5==str6 ) << endl; //  true


     

  • Loadrunner学习积累(不断整理中)

    2008-06-03 17:31:11

    1.loadrunner中可以对事物名称做参数化;

    2.lr中的函数可以多看看,有些很有用的函数,比如当前线程id(vuserid): lr.get_vuser_id();

     

  • 初识性能测试

    2008-06-02 21:46:41

    初次接触性能测试,从测试方案到测试执行,再到结果分析,确实不是一件易事,时间耗费之长,在我记忆中实属少见,粗略记录一下我的体验心得,希望对自己以后的测试有所启发。

    1.明确被测产品功能结构;

    这一点还是蛮重要的,本文中也多次提到。

    2.明确本次性能测试的需求;

    与开发组、测试组讨论本次测试的目标,毕竟有了目标才能根据目标制定相应的测试方案。首先需要确定的是,本次性能测试是需要获取性能指标?单纯的压力测试(服务器可承受多大压力)?还是一定压力下的稳定性测试?

    对于获取性能指标的测试,首先需要明确,性能指标是什么。这一点我走弯路了,一直以为性能指标就是获取多个并发数量下单个事物的响应时间,实则不然,要看用户关注的是什么,以及该产品的特点。一方面,如若用户并不关注响应时间,关心的是一定时间内的数据量(可称为吞吐量),那么我最初确定获取响应时间的路线就是错误的;另一方面,如若产品本身的特性并不能通过单个事物的响应时间很好的体现出来,那么获取了这些响应时间就是无意义的,根本不能体现产品的特点。这些东西都需要在制定测试策略前掌握清楚。

    我接手的这次性能测试主要是获取性能指标,但是当我将获取的指标与大家一起讨论时,却没有被大家认同,获取单个事物响应时间的指标,不是用户需要的。

    重做的代价是很大的,测试策略变了,测试脚本要修改,测试数据也要重新配置,包括测试环境。总之,这是很令人恼火,但又不得不做的事情。

    3.测试数据、测试环境的确认。

    根据已知的测试需求,要求预先整理好测试数据,测试数据的选取也是需要斟酌的事情,并非什么数据都好,也并非只要数据多样化就没问题,并且,数据量的大小,是否可以重复使用,也是需要预先明确的,其实这就要求做性能测试的朋友,一定要先了解产品,很了解产品才可以。

    测试环境的确认问题,客户端(loadrunner及客户端程序)、服务器端(只运行服务器),怎样的配置(一般来说使用现有的最好配置就ok了)、客户端及服务器端网卡,网络带宽如何都要明确在心,并明确在测试方案中。

    有些客户端程序,要求测试服务器与客户端在一台服务器上的情况,一来为了验证客户端性能对服务器的影响,二来可以验证网络传输对性能测试结果的影响。

    4.录制、编写测试脚本

    如果需求有了,数据、环境也搭建好了,方案定下来了,编写测试脚本就比较容易了。

    这里说一下,我使用的是java vuser编写测试脚本,首先需要掌握loadrunner的运行原理,否则很难在它的action中编写自己的测试脚本,也就是说很难通过代码控制loadrunner。

    因此有时间的话,loadrunner的手册要读一下,不要只根据tutorial学会怎么用loadrunner就开始测,当然前提是时间允许的话。

    另外一点,loadrunner给我们提供了不少内部方法可以直接调用(lr.*),适时的看一下VuGen里的Function Reference,可以增强测试脚本。

    5.结果获取后的分析

    获取性能指标,多大压力下就可以停止测试,不再加压?是否存在性能瓶颈,瓶颈在哪?有时候蛮迷茫的。

    一种情况,如若在压力不断增大的情况下,吞吐量响应增大到一个峰值,接着回落,这种情况估计谁都可以一眼判断出最优并发在哪,如果这时cpu占用率仍在70%以下,我们可以考虑被测服务器对cpu时间利用率没有控制好,cpu没有被充分的利用,那么应该还有存在调优的可能。

    如果当cpu时间已占用80%以上了(一般情况下认为75%以上就算cpu满负荷),如若再给服务器增加压力,并且该情况下,吞吐量还是一直处于增长状态,直至cpu100%,那么这种状态下,我们是否可以认为cpu就是性能瓶颈呢?或者应该认为被测程序应当削减效率,减少cpu使用率?

    我这里引用的关键词“吞吐量”是与TPS(每秒完成事物个数)密切相关的,如果只看吞吐量并不能确认最优并发的情况,可以看一下响应时间,即使用户对响应时间没有概念,或者说没有要求,引用某个朋友的话叫做:我们应也当给予一些专业性的建议。

    6.关于脚本录制协议的选择。

    因为此次测试没有录制脚本,所以一直对此比较不解,如果录制脚本时协议选择不合适,会是什么结果?协议的选择,是否具有不唯一性?比如上层为soap协议,底层为socket协议,该选择哪个?

     

  • 在QC主页面(qcbin页面)实现查看当前在线的功能定制(一)

    2008-03-04 08:59:23

    第一部分:调用SAClient.dll编写查看当前在线的功能函数(C++)

    "SAClient.dll"类库,使用方法:

    #import "SAClient.dll" no_namespace named_guids

    下面简单说明如何与qc服务器建立连接,并返回当前用户在线情况。

     

    //QC208上用户使用情况,将返回结果保存到磁盘

         CoInitialize(NULL);//连接QC前初始化操作

        // QC Site Admin

        ISAapi2Ptr sa;

    _bstr_t ret; // user to write the return of query from saclientchar*类型的变量来保存返//回值

       sa.CreateInstance(CLSID_SAapi);

       sa->Login("http://<HostName>:<Port>/sabin","admin","password"); 

       ret = sa->GetConnections();//返回值为xml文件

    // Create an XML file for saclient results

        pFile = fopen("c:\\temp\\temp.xml","w+");

    fprintf(pFile,"%s\n", ret);//写入文件

    fclose(pFile);

    CoUninitialize();//终止连接

     

    说明:返回的xml文件包含每个在线用户的登陆模块、最后操作时间等多个属性,如果需要显示这些属性,可使用XML Dom分析xml文件,给出最终结果,此处不作说明。

  • Junit搭建的测试框架

    2008-02-04 18:00:38

    最近使用Junit对某个java应用的api进行测试,有一点点小心得,特地跑上来写写

    明确测试目的

    使用Junit测试应用程序接口,是仅仅为了测试接口的正确性么?

    之前我也是一直这样认为的,现在发现这样不够深入,至少作为一个有经验的测试人员而言绝对是不允许的。接口的正确性,按接口文档描述走一遍就可以了,检查接口功能是不是都实现了,是不是与文档说明一致,很简单的。但是,一般来说接口测试的要求是通过应用的接口检查应用的实现情况,这个可能就需要对测试程序框架、测试数据、测试过程进行详细的设计。

    框架:

    一个类对应一个TestCase;

    一个功能中的多个类组成一个TestSuite;

    所有测试程序组合成一个最外面的TestSuite;

    示例:

    一个包含有两个测试用例TestOne和TestTwo的suite:

    import junit.framework.Test;

    import junit.framework.TestSuite;

    public Class oneTestSuite{

    public static Test suite(){

    TestSuite suite=new TestSuite("name the suite");

    suite.addTestSuite(TestOne.class);

    suite.addTestSuite(TestTwo.class);

    //如果需要显示这个test suite里的测试用例数,可以用suite.countTestCase()来获得,如下面所示,在测试suite名称后面显示测试用例格式:

    suite.setName("TestABS 共"+suite.countTestCases()+"个用例");

    }

    }

    测试用例TestOne:

    import junit.framework.TestCase;

    public Class TestOne extends TestCase{

    public void test***(){  //以test开头junit才能识别为用例,另外测试函数需设置为public

    do some test...

    }

    }

    一般来说应该是这样设计的,但不排除其他情况,比如,某些情况下能不能按功能划分测试suite呢?(先保留意见,实践实践再说吧)。

    测试数据设计:

    如果想要很好的测试一个功能,需要全面、丰富的数据,尽可能的准备各种不同的数据,验证该功能对这些数据处理的正确性、准确性是一方面,还需要考虑容错性,所以,数据一定要全。

    另外说下数据的存放位置:

    1.不能使用d:\data类似的绝对位置,一个是测试用例换到其他机器上执行时需要手动将测试数据拷贝到这个位置,另外一个,如果在linux上运行也会不识别windows文件系统的路径表示方式。

    2.可以使用相对路径data\\data.txt,但需要将\\使用file.separator代替:

    String dir="data"+file.separator+"data.txt";

    3.还有介绍另外一种方法,这里还没验证过,先不写。

    应用程序的异常捕获

    测试代码中不需要手工捕获异常,捕获异常的这些工作已经全部由Junit代劳了,已经验证过确实如此,而且改过之后的代码明显清晰多了。

    public void exampletest () {
    try {
    // do some test
    } catch (someapplicationexception e) {
    fail ("caught someapplicationexception exception");
    }
    }
    所以上面的代码很冗余,完全可以写成下面等效却简洁得多的代码:
    public void exampletest () throws someapplicationexception {
    // do some test
    }
    执行时如果发生异常,junit会提示异常出现的位置,很容易发现是哪段代码的异常。

    使用eclipse自带junit自动生成suite:

    File->New->Other->JUnit->JUnit Test Suite->选择需要包含的Class,finish。

  • JUnit best practices (V)

    2008-02-01 21:11:02

    JUnit best practices

    Techniques for building resilient, relocatable, multithreaded JUnit tests

    Page 5 of 5

    If we built a test case for the entire system, it would consist of all the other test cases, already defined. The test case would define the suite() method, which would add all test cases defined in the system to a TestSuite. This test suite would then be returned from the suite() method. If you had many test cases, building such a test suite would be time-consuming. In addition, you would have to update the universal test case when new test cases were added or existing test cases were renamed or deleted. Instead of manually building and maintaining the test suite, build a test case that automatically builds a TestSuite from all of your system's test cases. Here is an outline of the requirements for such a test case:

    • It should not be self-loading; that would cause recursion. As such, we need to mark test cases as not loadable.
    • It should not load classes derived from TestCases that are meant to be subclasses, and not directly executed.
    • It should distinguish between unit tests and other tests, like load or stress tests. That will let different tests run at different times.
    • It should recurse down a directory structure, looking for test cases to add to the test suite.


    We can use the Java type system to determine what sort of test a test case represents. We can have test cases extend classes like UnitTest, StressTest, LoadTest, and so on. However, this would make test case classes difficult to reuse between test types, because the test type decision is made near the root of the inheritance hierarchy; it should be made at each leaf instead. As an alternative, we can distinguish tests using a field: public static final String TEST_ALL_TEST_TYPE. Test cases will be loaded if they have this field declared with a value matching a string that the automatic test case has been configured with. To build this, we'll implement three classes:

    • ClassFinder recursively searches a directory tree for classfiles. Each classfile is loaded and the class's full class name is extracted. That class name is added to a list for later loading.
    • TestCaseLoader loads each class in the list found by ClassFinder and determines if it is a test case. If it is, it is added to a list.
    • TestAll is a subclass of TestCase with an implementation of suite() that will load in a set of test cases by TestCaseLoader.


    Let's look at each class in turn.

    ClassFinder

    ClassFinder locates the classes within the system to be tested. It is constructed with the directory that holds the system's classes. ClassFinder then finds all the classes in the directory tree and stores them for later use. The first part of ClassFinder's implementation is below:

    public class ClassFinder {
       // The cumulative list of classes found.
       final private Vector classNameList = new Vector ();
       /**
        * Find all classes stored in classfiles in classPathRoot
        * Inner classes are not supported.
        */
       public ClassFinder(final File classPathRoot) throws IOException {
        findAndStoreTestClasses (classPathRoot);
       }
       /**
        * Recursive method that adds all class names related to classfiles it finds in
        * the currentDirectory (and below).
        */
       private void findAndStoreTestClasses (final File currentDirectory) throws IOException {
          String files[] = currentDirectory.list();
          for(int i = 0;i < files.length;i++) {
             File file = new File(currentDirectory, files[i]);
             String fileBase = file.getName ();
             int idx = fileBase.indexOf(".class");
             final int CLASS_EXTENSION_LENGTH = 6;
             if(idx != -1 && (fileBase.length() - idx) == CLASS_EXTENSION_LENGTH) {
    


    In the code above, we iterate over all the files in a directory. If a filename has a ".class" extension, we determine the fully qualified class name of the class stored in the classfile, as seen here:

                JcfClassInputStream inputStream = new JcfClassInputStream(new FileInputStream (file));
                JcfClassFile classFile = new JcfClassFile (inputStream);
                System.out.println ("Processing: " + classFile.getFullName ().replace ('/','.'));
                classNameList.add (classFile.getFullName ().replace ('/','.'));
    


    This code uses the JCF package to load the classfile and determine the name of the class stored within it. The JCF package is a set of utility classes for loading and examining classfiles. (See Resources for more information.) The JCF package allows us to find each class's full class name. We could infer the class name from the directory name, but that doesn't work well for build systems that don't store classes according to this structure. Nor does it work for inner classes.

    Lastly, we check to see if the file is actually a directory. (See the code snippet below.) If it is, we recurse into it. This allows us to discover all the classes in a directory tree:

             } else if(file.isDirectory()) {
                findAndStoreTestClasses (file);
             }
          }
       }
    /**
     * Return an iterator over the collection of classnames (Strings)
     */
    public Iterator getClasses () {
       return classNameList.iterator ();
    }
    }
    


    TestCaseLoader

    TestCaseLoader finds the test cases among the class names from ClassFinder. This code snippet shows the top-level method for adding a class that represents a TestCase to the list of test cases:

    public class TestCaseLoader {
       final private Vector classList = new Vector ();
       final private String requiredType;
       /**
        * Adds testCaseClass to the list of classdes
        * if the class is a test case we wish to load. Calls
        * shouldLoadTestCase () to determine that.
        */
       private void addClassIfTestCase (final Class testCaseClass) {
          if (shouldAddTestCase (testCaseClass)) {
             classList.add (testCaseClass);
          }
       }
       /**
        * Determine if we should load this test case. Calls isATestCaseOfTheCorrectType
        * to determine if the test case should be
        * added to the class list.
        */
       private boolean shouldAddTestCase (final Class testCaseClass) {
          return isATestCaseOfTheCorrectType (testCaseClass);
       }
    


    You'll find the meat of the class in the isATestCaseOfTheCorrectType() method, listed below. For each class being considered, it:

    • Determines whether it is derived from TestCase. If not, it is not a test case.
    • Determines whether the field public final static TEST_ALL_TEST_TYPE has a value matching that specified in the member field requiredType.


    Here's the code:

       private boolean isATestCaseOfTheCorrectType (final Class testCaseClass) {
          boolean isOfTheCorrectType = false;
          if (TestCase.class.isAssignableFrom(testCaseClass)) {
             try {
                Field testAllIgnoreThisField = testCaseClass.getDeclaredField("TEST_ALL_TEST_TYPE");
                final int EXPECTED_MODIFIERS = Modifier.STATIC | Modifier.PUBLIC | Modifier.FINAL;
                if (((testAllIgnoreThisField.getModifiers() & EXPECTED_MODIFIERS) != EXPECTED_MODIFIERS) ||
                   (testAllIgnoreThisField.getType() != String.class)) {
                   throw new IllegalArgumentException ("TEST_ALL_TEST_TYPE should be static private final String");
                }
                String testType = (String)testAllIgnoreThisField.get(testCaseClass);
                isOfTheCorrectType = requiredType.equals (testType);
             } catch (NoSuchFieldException e) {
             } catch (IllegalAccessException e) {
                throw new IllegalArgumentException ("The field " + testCaseClass.getName () + ".TEST_ALL_TEST_TYPE is not accessible.");
             }
          }
          return isOfTheCorrectType;
       }
    


    Next, the loadTestCases() method examines each class name. It loads the class (if it can be loaded); if the class is a test case and of the required type, the method adds the class to its list of test cases:

       public void loadTestCases (final Iterator classNamesIterator) {
          while (classNamesIterator.hasNext ()) {
             String className = (String)classNamesIterator.next ();
             try {
                Class candidateClass = Class.forName (className);
                addClassIfTestCase (candidateClass);
             } catch (ClassNotFoundException e) {
                System.err.println ("Cannot load class: " + className);
             }
          }
       }
       /**
       * Construct this instance. Load all the test cases possible that derive
       * from baseClass and cannot be ignored.
       * @param classNamesIterator An iterator over a collection of fully qualified class names
       */
      public TestCaseLoader(final String requiredType) {
          if (requiredType == null) throw new IllegalArgumentException ("requiredType is null");
          this.requiredType = requiredType;
       }
       /**
       * Obtain an iterator over the collection of test case classes loaded by loadTestCases
       */
       public Iterator getClasses () {
          return classList.iterator ();
       }
    


    TestAll

    TestCall pulls everything together. It uses the aforementioned classes to build a list of test cases defined in the system. It adds those test cases to a TestSuite and returns the TestSuite as part of its implementation of the suite() method. The result: a test case that automatically extracts every defined test case in the system, ready for execution by JUnit.

    public class TestAll extends TestCase {
    


    The addAllTests() method iterates over the classes loaded by the TestCaseLoader and adds them to the test suite:

       private static int addAllTests(final TestSuite suite, final Iterator classIterator)
       throws java.io.IOException {
          int testClassCount = 0;
          while (classIterator.hasNext ()) {
             Class testCaseClass = (Class)classIterator.next ();
             suite.addTest (new TestSuite (testCaseClass));
             System.out.println ("Loaded test case: " + testCaseClass.getName ());
             testClassCount++;
          }
          return testClassCount;
       }
    


    With suite(), the test cases are added to the TestSuite, then returned to JUnit for execution. It obtains, from the system property "class_root", the directory where the classes are stored. It obtains, from the system property "test_type", the type of test cases to load. It uses the ClassFinder to find all the classes, and the TestCaseLoader to load all the appropriate test cases. It then adds these to a new TestSuite:

       public static Test suite()
       throws Throwable {
          try {
             String classRootString = System.getProperty("class_root");
             if (classRootString == null) throw new IllegalArgumentException ("System property class_root must be set.");
             String testType = System.getProperty("test_type");
             if (testType == null) throw new IllegalArgumentException ("System property test_type must be set.");
             File classRoot = new File(classRootString);
             ClassFinder classFinder = new ClassFinder (classRoot);
             TestCaseLoader testCaseLoader = new TestCaseLoader (testType);
             testCaseLoader.loadTestCases (classFinder.getClasses ());
             TestSuite suite = new TestSuite();
             int numberOfTests = addAllTests (suite, testCaseLoader.getClasses ());
             System.out.println("Number of test classes found: " + numberOfTests);
             return suite;
          } catch (Throwable t) {
             // This ensures we have extra information. Otherwise we get a "Could not invoke the suite method." message.
             t.printStackTrace ();
             throw t;
          }
       }
      /**
       * Basic constructor - called by the test runners.
       */
       public TestAll(String s) {
          super(s);
       }
    }
    


    To test an entire system using these classes, execute the following command (in a Windows command shell):

    java -cp C:\project\classes;C:\junit3.2\junit.jar:C:\jcf\jcfutils.zip -Dclass_root=C:\project\classes -Dtest_type=UNIT junit.ui.TestRunner bp.TestAll
    


    This command loads and runs all test cases of type UNIT that have classes stored under C:\project\classes.

    Test thread safety

    You'll want to guarantee the status of supposedly thread-safe classes by testing them. Such tests prove difficult using Junit 3.2's existing set of facilities. You can use junit.extensions.ActiveTest to run a test case in a different thread. However, TestSuite assumes that a test case is complete when it returns from run(); with junit.extensions.ActiveTest, it is not. We could work hard to define a properly working ActiveTestSuite; instead, let's look at a simpler solution: MultiThreadedTestCase. First, I'll show how MultiThreadedTestCase assists with multithreaded testing. Then I'll show how MultiThreadedTestCase is implemented.

    To use MultiThreadedTestCase, we implement the standard elements of a TestCase, but we derive from MultiThreadedTestCase. The standard elements are the class declaration, the constructor, and since we're using TestAll, the definition of the test type:

    public class MTTest extends MultiThreadedTestCase {
       /**
        * Basic constructor - called by the test runners.
        */
       public MTTest(String s) {
          super (s);
       }
       public static final String TEST_ALL_TEST_TYPE = "UNIT";
    


    A multithreaded test case needs to spawn a number of threads that perform some operation. We need to start those threads, wait until they've executed, and then return the results to JUnit -- all done in the code below. The code is trivial; in practice, this code would spawn multiple threads that performed different operations on the class under test. After each operation the class invariants and post-conditions would be tested to ensure that the class was behaving properly.

       public void testMTExample ()
       {
          // Create 100 threads containing the test case.
          TestCaseRunnable tct [] = new TestCaseRunnable [100];
          for (int i = 0; i < tct.length; i++)
          {
             tct[i] = new TestCaseRunnable () {
                public void runTestCase () {
                   assert (true);
                }
             };
          }
          // Run the 100 threads, wait for them to complete and return the results to JUnit.
          runTestCaseRunnables (tct);
       }
    }
    


    Now that I've shown how to use MultiThreadedTestCase, I'll examine the implementation. First, we declare the class and add an array where the running threads will be stored:

    public class MultiThreadedTestCase extends TestCase {
       /**
        * The threads that are executing.
        */
       private Thread threads[] = null;
    


    testResult, seen below, holds the testResult that declares that the test case's run() will be passed. We override run() so we can store the testResult for later population by the test threads:

       /**
        * The tests TestResult.
        */
       private TestResult testResult = null;
       /**
        * Simple constructor.
        */
       public MultiThreadedTestCase(final String s) {
          super(s);
       }
       /**
        * Override run so we can save the test result.
        */
       public void run(final TestResult result) {
          testResult = result;
          super.run(result);
          testResult = null;
    


    runTestCaseRunnables() runs each TestCaseRunnable in a seperate thread. All the threads are created and then started at the same time. The method waits until every thread has finished and then returns:

       protected void runTestCaseRunnables (final TestCaseRunnable[] runnables) {
          if(runnables == null) {
             throw new IllegalArgumentException("runnables is null");
          }
          threads = new Thread[runnables.length];
          for(int i = 0;i < threads.length;i++) {
             threads[i] = new Thread(runnables[i]);
          }
          for(int i = 0;i < threads.length;i++) {
             threads[i].start();
          }
          try {
             for(int i = 0;i < threads.length;i++) {
                threads[i].join();
             }
          }
          catch(InterruptedException ignore) {
             System.out.println("Thread join interrupted.");
          }
          threads = null;
       }
    


    Exceptions caught in the test threads must be propagated into the testResult instance we saved from the run() method. handleException(), below, does just that:

       /**
        * Handle an exception. Since multiple threads won't have their
        * exceptions caught the threads must manually catch them and call
        * handleException().
        * @param t Exception to handle.*/
       private void handleException(final Throwable t) {
          synchronized(testResult) {
             if(t instanceof AssertionFailedError) {
                testResult.addFailure(this, (AssertionFailedError)t);
             }
             else {
                testResult.addError(this, t);
             }
          }
       }
    


    Finally, we define the class that each test thread extends. The purpose of this class is to provide an environment (runTestCase()) where thrown exceptions will be caught and passed to JUnit. The implementation of this class is:

       /**
        * A test case thread. Override runTestCase () and define
        * behaviour of test in there.*/
       protected abstract class TestCaseRunnable implements Runnable {
          /**
           * Override this to define the test*/
          public abstract void runTestCase()
                     throws Throwable;
          /**
           * Run the test in an environment where
           * we can handle the exceptions generated by the test method.*/
          public void run() {
             try {
                runTestCase();
             }
             catch(Throwable t) /* Any other exception we handle and then we interrupt the other threads.*/ {
                handleException(t);
                interruptThreads();
             }
          }
       }
    }
    


    The implementation above helps to develop multithreaded test cases. It handles exceptions thrown in the multiple testing threads and passes them back to JUnit. JUnit only sees a test case that behaves like a single-threaded test. The unit test developer can extend that test case to develop multithreaded tests, without spending much time developing thread-handling code.

    Conclusion

    Using JUnit to develop robust tests takes some practice (as does writing tests). This article contains a number of techniques for improving your tests' usefulness. Those techniques range from avoiding basic mistakes (such as not using setUp()) to more design-level issues (avoiding intertest coupling). I've covered some basic ideas to help you use JUnit to test parts of your UI or Web application. I've also shown how to build an automated test suite that removes the overhead of maintaining hand-coded test suites and a mechanism for reducing the effort of developing multithreaded JUnit test cases.

    JUnit is an excellent framework for unit-testing Java applications. One final thought: If you just started using JUnit to produce unit tests, stick at it. For the first few weeks, you may not see any real reward for your labors. In fact, you may feel that the whole process slows you down. However, after a few weeks, you'll begin to enhance existing code. Then you'll run your tests, pick up new bugs, and fix them. You'll be far more confident in your code base and you will see the value of unit testing.

    Author Bio

  • JUnit best practices (IV)

    2008-02-01 21:09:50

    JUnit best practices

    Techniques for building resilient, relocatable, multithreaded JUnit tests

    Page 4 of 5

    Swing

    When testing a Swing-based UI, you can write tests to ensure that:

    • All the components reside in the correct panels
    • You've configured the layout managers correctly
    • Text widgets have the correct fonts


    A more thorough treatment of this can be found in the worked example of testing a GUI, referenced in the Resources section.

    XML

    When testing classes that process XML, it pays to write a routine that compares two XML DOMs for equality. You can then programmatically define the correct DOM in advance and compare it with the actual output from your processing methods.

    Servlets

    With servlets, a couple of approaches can work. You can write a dummy servlet framework and preconfigure it during a test. The framework must contain derivations of classes found in the normal servlet environment. These derivations should allow you to preconfigure their responses to method calls from the servlet.

    For example:

    • HttpServletRequest can be subclassed to allow the test class to specify the header, method, path info, and other data
    • HttpServletResponse can be subclassed to return an output stream that stores the servlets' responses in a string for later checking


    A simpler solution is to use HttpUnit to test your servlets. HttpUnit provides a DOM view of a request's results, which makes it relatively simple to compare actual data with expected results.

    You can avoid visual inspection in many ways. However, sometimes it is more cost-effective to use visual inspection or a more specialized testing tool. For example, testing a UI's dynamic behavīor within JUnit is complicated, but possible. It may be a better idea to purchase one of the many UI record/playback testing tools available, or to perform some visual inspection as part of testing. However, that doesn't mean the general rule -- don't visually inspect -- should be ignored.

    Keep tests small and fast

    Executing every test for the entire system shouldn't take hours. Indeed, developers will more consistently run tests that execute quickly. Without regularly running the full set of tests, it will be difficult to validate the entire system when changes are made. Errors will start to creep back in, and the benefits of unit testing will be lost. This means stress tests and load tests for single classes or small frameworks of classes shouldn't be run as part of the unit test suite; they should be executed separately.

    Use the reflection-driven JUnit API

    Allowing TestSuite to populate itself with test cases using reflection reduces maintenance time. Reflection ensures that you don't need to update the suite() implementation whenever a new test is added.

    Build a test case for the entire system

    It is important to build a test case for the entire system. If one test case exercises the whole system, then developers can test the impact their changes will have on every class in the system. This increases the chance of errors resulting from unanticipated side effects being caught earlier. Without a universal test case, developers tend to test only the class they have modified. Also, running all the tests for the system becomes a painstaking manual process.  (Continued)

  • JUnit best practices (III)

    2008-02-01 21:08:32

    JUnit best practices

    Techniques for building resilient, relocatable, multithreaded JUnit tests

    Page 3 of 5

    Do not load data from hard-coded locations on a filesystem(不要从一个程序很难定位的文件系统加载测试数据)

    Tests often need to load data from some location in the filesystem. Consider the following:

    public void setUp () {
       FileInputStream inp ("C:\\TestData\\dataSet1.dat");
       ...
    }
    


    The code above relies on the data set being in the C:\TestData path. That assumption is incorrect in two situations: (上面的一段程序依赖存放于C:\TestData 下的数据。但这种假设从以下两点来讲是不正确的)

    • A tester does not have room to store the test data on C: and stores it on another disk
    • The tests run on another platform, such as Unix


    One solution might be(解决方法之一):

    public void setUp () {
       FileInputStream inp ("dataSet1.dat");
       ...
    }
    


    However, that solution depends on the test running from the same directory as the test data. If several different test cases assume this, it is difficult to integrate them into one test suite without continually changing the current directory.

    To solve the problem, access the dataset using either Class.getResource() or Class.getResourceAsStream(). Using them, however, means that resources load from a location relative to the class's origin.

    Test data should, if possible, be stored with the source code in a configuration management (CM) system. However, if you're using the aforementioned resource mechanism, you'll need to write a scrīpt that moves all the test data from the CM system into the classpath of the system under test. A less ungainly approach is to store the test data in the source tree along with the source files. With this approach, you need a location-independent mechanism to locate the test data within the source tree. One such mechanism is a class. If a class can be mapped to a specific source directory, you could write code like this:

    InputStream inp = SourceResourceLoader.getResourceAsStream (this.getClass (), "dataSet1.dat");
    


    Now you must only determine how to map from a class to the directory that contains the relevant source file. You can identify the root of the source tree (assuming it has a single root) by a system property. The class's package name can then identify the directory where the source file lies. The resource loads from that directory. For Unix and NT, the mapping is straightforward: replace every instance of '.' with File.separatorChar.

    Keep tests in the same location as the source code

    If the test source is kept in the same location as the tested classes, both test and class will compile during a build. This forces you to keep the tests and classes synchronized during development. Indeed, unit tests not considered part of the normal build quickly become dated and useless.

    Name tests properly

    Name the test case TestClassUnderTest. For example, the test case for the class MessageLog should be TestMessageLog. That makes it simple to work out what class a test case tests. Test methods' names within the test case should describe what they test:

    • testLoggingEmptyMessage()
    • testLoggingNullMessage()
    • testLoggingWarningMessage()
    • testLoggingErrorMessage()


    Proper naming helps code readers understand each test's purpose.

    Ensure that tests are time-independent(保证测试用例不依赖于时间)

    Where possible, avoid using data that may expire; such data should be either manually or programmatically refreshed. It is often simpler to instrument the class under test, with a mechanism for changing its notion of today. The test can then operate in a time-independent manner without having to refresh the data.

    Consider locale when writing tests(考虑测试用例的本地化兼容问题)

    Consider a test that uses dates. One approach to creating dates would be:

    Date date = DateFormat.getInstance ().parse ("dd/mm/yyyy");
    


    Unfortunately, that code doesn't work on a machine with a different locale. Therefore, it would be far better to write:

    Calendar cal = Calendar.getInstance ();
    Cal.set (yyyy, mm-1, dd);
    Date date = Calendar.getTime ();
    


    The second approach is far more resilient to locale changes.

    Utilize JUnit's assert/fail methods and exception handling for clean test code(利用JUnit的assert/fail方法和异常处理机制,使测试代码更简洁)

    Many JUnit novices make the mistake of generating elaborate try and catch blocks to catch unexpected exceptions and flag a test failure. Here is a trivial example of this:

    public void exampleTest () {
       try {
          // do some test
       } catch (SomeApplicationException e) {
          fail ("Caught SomeApplicationException exception");
       }
    }
    


    JUnit automatically catches exceptions. It considers uncaught exceptions to be errors, which means the above example has redundant code in it.

    Here's a far simpler way to achieve the same result:

    public void exampleTest () throws SomeApplicationException {
       // do some test
    }
    


    In this example, the redundant code has been removed, making the test easier to read and maintain (since there is less code).

    Use the wide variety of assert methods to express your intention in a simpler fashion. Instead of writing:

    assert (creds == 3);
    


    Write:

    assertEquals ("The number of credentials should be 3", 3, creds);
    


    The above example is much more useful to a code reader. And if the assertion fails, it provides the tester with more information. JUnit also supports floating point comparisons:

    assertEquals ("some message", result, expected, delta);
    


    When you compare floating point numbers, this useful function saves you from repeatedly writing code to compute the difference between the result and the expected value.

    Use assertSame() to test for two references that point to the same object. Use assertEquals() to test for two objects that are equal.

    Document tests in javadoc

    Test plans documented in a word processor tend to be error-prone and tedious to create. Also, word-processor-based documentation must be kept synchronized with the unit tests, adding another layer of complexity to the process. If possible, a better solution would be to include the test plans in the tests' javadoc, ensuring that all test plan data reside in one place.

    Avoid visual inspection

    Testing servlets, user interfaces, and other systems that produce complex output is often left to visual inspection. Visual inspection -- a human inspecting output data for errors -- requires patience, the ability to process large quantities of information, and great attention to detail: attributes not often found in the average human being. Below are some basic techniques that will help reduce the visual inspection component of your test cycle.   (Continued)

  • JUnit best practices (II)

    2008-02-01 21:07:05

    JUnit best practices

    Techniques for building resilient, relocatable, multithreaded JUnit tests

    Page 2 of 5

    Don't assume the order in which tests within a test case run

    You should not assume that tests will be called in any particular order. Consider the following code segment:

    public class SomeTestCase extends TestCase {
       public SomeTestCase (String testName) {
          super (testName);
       }
       public void testDoThisFirst () {
         ...
       }
       public void testDoThisSecond () {
       }
    }
    


    In this example, it is not certain that JUnit will run these tests in any specific order when using reflection. Running the tests on different platforms and Java VMs may therefore yield different results, unless your tests are designed to run in any order. Avoiding temporal coupling will make the test case more robust, since changes in the order will not affect other tests. If the tests are coupled, the errors that result from a minor update may prove difficult to find.

    In situations where ordering tests makes sense -- when it is more efficient for tests to operate on some shared data that establish a fresh state as each test runs -- use a static suite() method like this one to ensure the ordering:

    public static Test suite() {
       suite.addTest(new SomeTestCase ("testDoThisFirst";));
       suite.addTest(new SomeTestCase ("testDoThisSecond";));
       return suite;
    }
    


    There is no guarantee in the JUnit API documentation as to the order your tests will be called in, because JUnit employs a Vector to store tests. However, you can expect the above tests to be executed in the order they were added to the test suite.

    Avoid writing test cases with side effects

    Test cases that have side effects exhibit two problems:

    • They can affect data that other test cases rely upon
    • You cannot repeat tests without manual intervention


    In the first situation, the individual test case may operate correctly. However, if incorporated into a TestSuite that runs every test case on the system, it may cause other test cases to fail. That failure mode can be difficult to diagnose, and the error may be located far from the test failure.

    In the second situation, a test case may have updated some system state so that it cannot run again without manual intervention, which may consist of deleting test data from the database (for example). Think carefully before introducing manual intervention. First, the manual intervention will need to be documented. Second, the tests could no longer be run in an unattended mode, removing your ability to run tests overnight or as part of some automated periodic test run.

    Call a superclass's setUp() and tearDown() methods when subclassing

    When you consider:

    public class SomeTestCase extends AnotherTestCase {
       // A connection to a database
       private Database theDatabase;
       public SomeTestCase (String testName) {
          super (testName);
       }
       public void testFeatureX () {
          ...
       }
       public void setUp () {
          // Clear out the database
          theDatabase.clear ();
       }
    }
    


    Can you spot the deliberate mistake? setUp() should call super.setUp() to ensure that the environment defined in AnotherTestCase initializes. Of course, there are exceptions: if you design the base class to work with arbitrary test data, there won't be a problem.   (Continued)

  • JUnit best practices (I)

    2008-02-01 20:57:09

    JUnit best practices

    Techniques for building resilient, relocatable, multithreaded JUnit tests

    JUnit is a typical toolkit: if used with care and with recognition of its idiosyncrasies, JUnit will help to develop good, robust tests. Used blindly, it may produce a pile of spaghetti instead of a test suite. This article presents some guidelines that can help you avoid the pasta nightmare. The guidelines sometimes contradict themselves and each other -- this is deliberate. In my experience, there are rarely hard and fast rules in development, and guidelines that claim to be are misleading.

    We'll also closely examine two useful additions to the developer's toolkit:

    • A mechanism for automatically creating test suites from classfiles in part of a filesystem
    • A new TestCase that better supports tests in multiple threads



    When faced with unit testing, many teams end up producing some kind of testing framework. JUnit, available as open source, eliminates this onerous task by providing a ready-made framework for unit testing. JUnit, best used as an integral part of a development testing regime, provides a mechanism that developers can use to consistently write and execute tests. So, what are the JUnit best practices?

    Do not use the test-case constructor to set up a test case

    Setting up a test case in the constructor is not a good idea. Consider:

    public class SomeTest extends TestCase
       public SomeTest (String testName) {
          super (testName);
          // Perform test set-up
       }
    }
    



    Imagine that while performing the setup, the setup code throws an IllegalStateException. In response, JUnit would throw an AssertionFailedError, indicating that the test case could not be instantiated. Here is an example of the resulting stack trace:

    junit.framework.AssertionFailedError: Cannot instantiate test case: test1       at
    junit.framework.Assert.fail(Assert.java:143)     at
    junit.framework.TestSuite.runTest(TestSuite.java:178)  at
    junit.framework.TestCase.runBare(TestCase.java:129)      at
    junit.framework.TestResult.protect(TestResult.java:100)        at
    junit.framework.TestResult.runProtected(TestResult.java:117)     at
    junit.framework.TestResult.run(TestResult.java:103)      at
    junit.framework.TestCase.run(TestCase.java:120)  at
    junit.framework.TestSuite.run(TestSuite.java, Compiled Code)     at
    junit.ui.TestRunner2.run(TestRunner.java:429)
    



    This stack trace proves rather uninformative; it only indicates that the test case could not be instantiated. It doesn't detail the original error's location or place of origin. This lack of information makes it hard to deduce the exception's underlying cause.

    Instead of setting up the data in the constructor, perform test setup by overriding setUp(). Any exception thrown within setUp() is reported correctly. Compare this stack trace with the previous example:

    java.lang.IllegalStateException: Oops     at bp.DTC.setUp(DTC.java:34)   at
    junit.framework.TestCase.runBare(TestCase.java:127)      at
    junit.framework.TestResult.protect(TestResult.java:100)        at
    junit.framework.TestResult.runProtected(TestResult.java:117)     at
    junit.framework.TestResult.run(TestResult.java:103)
    ...
    



    This stack trace is much more informative; it shows which exception was thrown (IllegalStateException) and from where. That makes it far easier to explain the test setup's failure.   (Continued)

  • JUNIT框架中用例的执行顺序问题

    2008-02-01 20:53:43

        初出接触JUNIT,本以为每个test case的执行顺序会按java文件中的书写顺序进行,其实不然。测试中发现,有时莫名其妙执行结果与预期根本不一致,又查不到原因,最后将两个有耦合关系测试用例解除耦合性之后,重新运行用例,测试终于通过。google上搜了一下,确实如此:

    public class SomeTestCase extends TestCase { 
    public void testDoThisFirst () { 
    ... 

    public void testDoThisSecond () { 


    在上面这个例子中,当使用映射时,JUnit执行测试用例的顺序是不确定的。在不同的平台及Java VM上,可能产生不同的结果,除非你的测试被事先设计为按某种顺序执行。避免测试用例之间的短暂耦合会使你的测试用例更加健壮,因为即使改变测试用例的执行顺序也不会影响其它测试。如果测试耦合在一起,即使一个小的更新,也可能引发难于发现的错误。 

    在某些情况下,测试的顺序还是有意义的————例如,测试用例可以使用一些共享数据时来提高效率。这些共享数据对于每个测试用例在运行时都会建立一个新的状态。————可以使用一个静态的 suite() 方法来保证执行顺序,如下: 

    public static Test suite() { 
    suite.addTest(new SomeTestCase ("testDoThisFirst";)); 
    suite.addTest(new SomeTestCase ("testDoThisSecond";)); 
    return suite; 

    上面的两段内容是JUNIT Best Practice一文中写的,文章写的很不错,有空一定得仔细读一读,忙啊~~

573/3<123
Open Toolbar