庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理

1.安装
首先,我是使用mysql进行测试的,你的机器上需要安装mysql数据库。
然后执行:
gem install mysql

到rubyforge下载ruby-DBI,解压后cd到目录运行如下命令:
ruby setup.rb config --with=dbi,dbd_mysql
ruby setup.rb setup
ruby setup.rb install

完整的setup命令参数参考DBI的doc

2.完整例子
DBI是一类似于ODBC的开发式的统一的数据库编程接口,结构层次上可以分为两层:
1.Database Interface——数据库接口层,与数据库无关,提供与数据库无关的标准接口
2.Database Driver——数据库驱动,与数据库相关

DBI也是很简单易用的,一个完整的使用例子,对于初学者可能有点帮助:
require 'dbi'
begin
  
#连接数据库
  dbh=DBI.connect("DBI:Mysql:dbi_test:localhost","root","")
  
  dbh.columns(
"simple").each do |h|
    p h
  end
  
#示范3种事务处理方式
  #手动commit
  dbh["AutoCommit"]=false
  
1.upto(10) do |i|
    sql 
= "insert into simple (name, author) VALUES (?, ?)"
    dbh.do(sql, 
"Song #{i}""#{i}")
  end
  dbh.commit
  
  
#使用transaction方法
  dbh.transaction do |dbh|
    
1.upto(10) do |i|
      sql 
= "insert into simple (name, author) VALUES (?, ?)"
      dbh.do(sql, 
"Song #{i}""#{i}")
    end
  end
  
  
#使用SQL语句
  dbh.do("SET AUTOCOMMIT=0")
  dbh.do(
"BEGIN")
  dbh[
"AutoCommit"]=false
  dbh.do(
"UPDATE simple set name='test' where id='1'")
  dbh.do(
"COMMIT")
  
  
#查询
  sth=dbh.execute("select count(id) from simple")
  puts 
"bookCount:#{sth.fetch[0]}"
  sth.finish
  begin
    sth
=dbh.prepare("select * from simple")
    sth.execute
    
while row=sth.fetch do
      p row
    end
    sth.finish
  rescue
  end
  
  
#上面这段查询可以改写为:
  #dbh.select_all("select * from simple") do |row|
  #   p row
  #end   
  
  
  
#使用工具类输出xml格式结果集以及测量查询时间
  sql="select * from simple"
  mesuretime
=DBI::Utils::measure do
    sth
=dbh.execute(sql)
  end 
  puts 
"SQL:#{sql}"
  puts 
"Time:#{mesuretime}"
  rows
=sth.fetch_all
  col_names
=sth.column_names
  sth.finish
  puts DBI::Utils::XMLFormatter.table(rows)
  
  dbh.do(
"delete from simple")
rescue  DBI::DatabaseError
=>e
  puts 
"error code:#{e.err}"
  puts 
"Error message:#{e.errstr}"
ensure
  dbh.disconnect 
if dbh
end   


posted @ 2007-04-10 20:21 dennis 阅读(4645) | 评论 (1)编辑 收藏

    这个包的类主要用于spring框架的异常处理和一些核心的助手类(与框架具体部分无关的)。
    这个包中主要应用到了简单工厂模式,用于判断jdk版本,根据jdk版本不同提供不同的集合类、当前方法栈信息等。我们来看看是如何判断当前用户的jdk版本的:

package org.springframework.core;

public class JdkVersion {
    
    
public static final int JAVA_13 = 0;
    
    
public static final int JAVA_14 = 1;
    
    
public static final int JAVA_15 = 2;

    
private static String javaVersion;

    
private static int majorJavaVersion = JAVA_13;
    
    
static {
        javaVersion 
= System.getProperty("java.version");
        
// should look like "1.4.1_02"
        if (javaVersion.indexOf("1.4."!= -1) {
            majorJavaVersion 
= JAVA_14;
        }
        
else if (javaVersion.indexOf("1.5."!= -1) {
            majorJavaVersion 
= JAVA_15;
        }
        
// else leave as 1.3 default
    }

    
/**
     * Return the full Java version string, as returned by
     * <code>System.getProperty("java.version")</code>.
     
*/
    
public static String getJavaVersion() {
        
return javaVersion;
    }

    
/**
     * Get the major version code. This means we can do things like
     * <code>if (getMajorJavaVersion() < JAVA_14)</code>.
     * 
@return a code comparable to the JAVA_XX codes in this class
     * 
@see #JAVA_13
     * 
@see #JAVA_14
     * 
@see #JAVA_15
     
*/
    
public static int getMajorJavaVersion() {
        
return majorJavaVersion;
    }

}

直接获取系统的java.version属性来进行jdk版本的判断。而CollectionFactory依据这个类来创建不同的集合类型,如果是jdk1.4就优先使用jdk1.4的集合框架,再次选择Commons Collections,最后才不得已就使用jdk1.3的集合框架,这里比较有趣的是判断Commons Collections的方法就是尝试Class.forName一个Commons集合框架中的对象,如果成功,当然证明classpath有commons-collections.jar包:
static {
        
// Check whether JDK 1.4+ collections and/or
        
// Commons Collections 3.x are available.
        if (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_14) {
            logger.info(
"JDK 1.4+ collections available");
        }
        
try {
            Class.forName(COMMONS_COLLECTIONS_CLASS_NAME);
            commonsCollections3xAvailable 
= true;
            logger.info(
"Commons Collections 3.x available");
        }
        
catch (ClassNotFoundException ex) {
            commonsCollections3xAvailable 
= false;
        }
    }

然后就是一系列的getXXXIfPossible()方法用以获取最优版本的集合类型,比如getLinkedHashMapIfPossible():
public static Map createLinkedMapIfPossible(int initialCapacity) {
        
if (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_14) {
            logger.debug(
"Creating [java.util.LinkedHashMap]");
            
return Jdk14CollectionFactory.createLinkedHashMap(initialCapacity);
        }
        
else if (commonsCollections3xAvailable) {
            logger.debug(
"Creating [org.apache.commons.collections.map.LinkedMap]");
            
return CommonsCollectionFactory.createLinkedMap(initialCapacity);
        }
        
else {
            logger.debug(
"Falling back to [java.util.HashMap] for linked map");
            
return new HashMap(initialCapacity);
        }
    }
其中的Jdk14CollectionFactory 和CommonsCollectionFactory 也都是工厂类。可以看到,一个优秀的通用框架对于版本的兼容性非常重视。

    这个包中另外一个需要注意的就是用于spring AOP功能实现的辅助类——ControlFlow。ControlFlow按照rod johnson的说法就是用于获取当前调用的方法栈的具体信息。ControlFlow是一个接口,拥有3个方法用于判断当前方法栈的位置:
public interface ControlFlow {

    
/**
      查找当前方法调用是否则在某类中
     * 
@param clazz the clazz to look for
     
*/
    
boolean under(Class clazz);

    
/**
     * 查找当前方法调用是否则在某类的某个方法中
     * according to the current stack trace.
     * 
@param clazz the clazz to look for
     * 
@param methodName the name of the method to look for
     
*/
    
boolean under(Class clazz, String methodName);

    
/**
     * 当前栈帧是否包含传入的记号
     * 
@param token the token to look for
     
*/
    
boolean underToken(String token);

}

然后根据jdk版本的不同采用不同的方式实现这个接口:Jdk14ControlFlow和Jdk13ControlFlow。这是典型的策略模式的应用。需要注意的是,这两个具体类的是放在工厂类ControlFlowFactory中作为内部类实现的:
public abstract class ControlFlowFactory {
   
   
static class Jdk13ControlFlow implements ControlFlow {
  
   

    
static class Jdk14ControlFlow implements ControlFlow {
   
}

在这里,我们可以学到的东西就如何去判断当前方法栈的信息?jdk1.4之前只能通过对StackTrace的字符串进行分析,而jdk1.4引入了java.lang.StackTraceElement用于获取当前方法调用所处的栈帧的信息,看看spring的使用方法,相当简单:
static class Jdk14ControlFlow implements ControlFlow {

        
private StackTraceElement[] stack;

        
public Jdk14ControlFlow() {
            
this.stack = new Throwable().getStackTrace();
        }

        
/**
         * Searches for class name match in a StackTraceElement.
         
*/
        
public boolean under(Class clazz) {
            Assert.notNull(clazz, 
"Class must not be null");
            String className 
= clazz.getName();
            
for (int i = 0; i < stack.length; i++) {
                
if (this.stack[i].getClassName().equals(className)) {
                    
return true;
                }
            }
            
return false;
        }

        
/**
         * Searches for class name match plus method name match
         * in a StackTraceElement.
         
*/
        
public boolean under(Class clazz, String methodName) {
            Assert.notNull(clazz, 
"Class must not be null");
            Assert.notNull(methodName, 
"Method name must not be null");
            String className 
= clazz.getName();
            
for (int i = 0; i < this.stack.length; i++) {
                
if (this.stack[i].getClassName().equals(className) &&
                        
this.stack[i].getMethodName().equals(methodName)) {
                    
return true;
                }
            }
            
return false;
        }

        
/**
         * Leave it up to the caller to decide what matches.
         * Caller must understand stack trace format, so there's less abstraction.
         
*/
        
public boolean underToken(String token) {
            
if (token == null) {
                
return false;
            }
            StringWriter sw 
= new StringWriter();
            
new Throwable().printStackTrace(new PrintWriter(sw));
            String stackTrace 
= sw.toString();
            
return stackTrace.indexOf(token) != -1;
        }
}

获取当前栈帧的信息,对于一般的java开发者没有什么意义,对于AOP的实现和框架开发者可能有比较重要的作用,我还未研读spring的aop部分,不敢妄言,留待以后解答,如果您已经研读过这部分代码,不吝赐教。

这个包另外的一个特点就是将java的反射API演示了一遍,特别是Constant.java(用于提取某个类public static final定义的常量)和ReflectiveVisitorHelper (反射助手类),对于学习java反射技术也有不小的帮助。

posted @ 2007-04-10 16:56 dennis 阅读(4535) | 评论 (2)编辑 收藏

    网上其实有一大堆这样的资料了,我再写也没多大价值,谈下几个注意点吧。
1.在windows上安装Rmagic,如果你是通过gem安装的,
require 'Rmagic'
要修改为:
require 'rubygems'
require 
'Rmagick'
才能正确引入。

2.网上那个例子,画布是使用Rmagic内置的图像格式,Rmagic内置的图像格式还有:

gradient*

梯度,比如gradient:red-blue

granite

花岗石,比如: "granite:".

logo

logo型的图像. 如: "logo:"后面会多显示一个五角星^_^

netscape

非常漂亮的彩条。如: "netscape:"

null*

空白 使用方式: "null:"

rose


玫瑰 使用方式 : "rose:"

xc*

设置一个背景色,比如"xc:green"


一个修改的例子,在rails的models下存为noisy_image.rb,在Controller就可以这样调用NoisyImage.new(6) :


require 'rubygems'
require 
'Rmagick'
class NoisyImage
  include Magick
  attr_reader :code, :code_image
  Jiggle 
= 15
  Wobble 
= 15
  
  
def initialize(len)
    chars 
= ('a'..'z').to_a - ['a','e','i','o','u']
    code_array
=[]
    
1.upto(len) {code_array << chars[rand(chars.length)]}
    granite 
= Magick::ImageList.new('xc:#EDF7E7')
    canvas 
= Magick::ImageList.new
    canvas.new_image(
32*len, 50, Magick::TextureFill.new(granite))
    text 
= Magick::Draw.new
    text.font_family 
= 'times'
    text.pointsize 
= 40
    cur 
= 10
    
    code_array.each{
|c|
      rand(
10> 5 ? rot=rand(Wobble):rot= -rand(Wobble)
      rand(
10> 5 ? weight = NormalWeight : weight = BoldWeight
      text.annotate(canvas,0,0,cur,
30+rand(Jiggle),c){
        self.rotation
=rot
        self.font_weight 
= weight
        self.fill 
= 'green'
      }
      cur 
+= 30
    }
    @code 
= code_array.to_s
    @code_image 
= canvas.to_blob{
      self.format
="JPG" 
    }
  end
  
end

3.与rails应用的结合,和一般的验证码原理一样,将产生的随机数存储在session或者request范围内,提交的时候进行比较验证即可。比如产生图片的时候将随机字母存储在session[:code]中:

 session[:noisy_image] = NoisyImage.new(6)

 session[:code] = session[:noisy_image].code

验证的时候,比较提交的type_code与session[:code]即可,为了安全性考虑,最好还是不考虑使用客户端验证。

 unless session[:code]==params[:type_code]
      flash[:notice]
='验证码填写错误,请重新注册,谢谢!'
      
return redirect_to :action=>:new  
 end

在页面显示图片,类似servlet一样直接调用Controller的action:

 def code_image
    image 
= session[:noisy_image].code_image
    send_data image, :type 
=> 'image/jpeg', :disposition => 'inline'
  end

<img height='30' src="/test/code_image">




posted @ 2007-04-09 17:13 dennis 阅读(3410) | 评论 (3)编辑 收藏

    开源工具已经有NUnit,微软这次给VS增加的单元测试功能不知道是不是直接借鉴NUnit?不过总也是好事,还集成了静态分析和代码覆盖工具等,值的小小期待下。从这点上看,单元测试已经非常深入人心咯。消息来自infoq中文站

posted @ 2007-04-09 08:37 dennis 阅读(1893) | 评论 (1)编辑 收藏

    《Programming Ruby中文版》前3部分我并不准备细看,毕竟我接触ruby也有一段时间了,只准备快速地掠过一遍,查缺补漏;重点放在第3部分的核心内容上,至于第四部分的参考手册更多作为工具书了。仅在此记录下一些值的注意的东西。

1.全局变量$_,默认当gets方法返回输入的行时,同时保存在全局变量$_,并且正则表达式如果作为条件语句(if或者while)时默认是跟这个全局变量进行匹配,而print参数为空时也是打印这个全局变量。这是早期ruby向perl语言学习的结果。可以看看这个例子:
while gets
  
if /Ruby/
      print
  end
end

这样的风格不值的提倡,全局变量的使用应该尽力减少,ruby也在逐渐脱离perl主义的风格

2.ruby中的单例模式:
class Logger
  private_class_method:new
  @@logger
=nil
  
def Logger.create
    @@logger
=new unless @@logger
    @@logger
  end
end
log1
=Logger.create
log2
=Logger.create

puts log1.object_id
puts log2.object_id

3.ruby中的block作用:
1)迭代器,通常是内部迭代器
2)事务Blocks,c#的using语句倒是跟这个有点像,其实就是让对象自身负责资源的打开和关闭,这是通过Kernel.block_given?实现的,比如File.open方法,当后面跟着一个block的时候,就会自动关闭打开的文件资源,如果不是,就需要自己处理。
3)作为闭包,与javascript和其他语言中的闭包概念一致,一个例子:
def n_times(thing)
  
return lambda {|n| thing*n}
end
p1
=n_times(23)
puts p1.call(
3)
puts p1.call(
2)
通过lambda方法将一个block转为Proc对象,尽管参数thing在block被真正调用时已经离开了作用范围,但是仍然可以使用

4.ruby中数字的最大长度取决于系统,这跟java,C#通过虚拟机规范的不同,数字类型的几个常用迭代器:times,upto,downto,step,如:
2.step(10,2){|i| print i,' '}  =>2,4,6,8,10

5.ruby中的字符串是8字节的序列,可以存储可打印的字符和二进制数据。比较有趣3种构建字符串常量方式:%q(对应于单引号定义的字符串),%Q(双引号)以及here documents,比如:
s=<<END_OF_STRING
   测试测试啦
END_OF_STRING

6.Range,书中翻译为区间,我倒更喜欢范围这个词。区间的3个用途:
1)用作序列,最常见的,如1..2,a..z等,可以定义自己的区间,只要实现succ和<=>比较方法
2)作为条件,书中的例子很经典:
while line=gets
   puts line 
if line=~/start/..line=~/end/
end

#利用全局变量简化为,不建议这样写
while gets
   
print if /start/../end/
end

3)作为间隔,看看某个值是否落入区间范围内,使用===操作符比较

7.正则表达式,这是重头戏。ruby中的perl风格的正则表达式,其实也是内建在ruby中的正则表达式对象的外部包装,关键的就是两个类Regexp类和MatchData类。一些peri程序员熟悉的记号:
$&    匹配的字符串
$`    匹配前的字符串
$'    匹配后的字符串
$1    第一个分组,$2,$3...类似
详细的就不抄书了,正则表达式我在学习javascript的时候已经系统地学过,倒是不感觉吃力。

8.在方法中定义可变长度参数,只要参数前加*号即可,java1.5也已经支持可变参数,比如Object...obj。
另外,在方法中,将数组展开为参数,可以在数组前加一个*号,比如:
def three(a,b,c)
   
print "this is #{a},#{b},#{c}"
end

three([
1,2,3)]
#上面这样调用报参数数目错误,正确的用法如下:
three(*[1,2,3)] =>this is 1,2,3
将hash列表直接做为参数,可能在2.0支持,目前采用的要求散列数组在正常的参数之后,并位于任何的block或者数组之前

9.ruby中的多线程:
1)ruby创建线程,见下面这个例子,开3个线程分别访问3个站点,并且对3个线程通过调用join方法,直到3个线程都结束,主线程才结束,来自书中例子:
require 'net/http'
pages
=%w(www.javaeye.com www.sina.com.cn www.blogjava.net)
$proxy_addr 
= 'x.x.x.x'
$proxy_port 
= 80
threads
=[]
for page_to_fetch in pages
  threads
<<Thread.new(page_to_fetch) do |url|
    h
=Net::HTTP.Proxy($proxy_addr, $proxy_port).new(url,80)
    puts 
"Fetcing:#{url}"
    resp
=h.get('/',nil)
    puts 
"Got #{url}:#{resp.message}"
  end
end    
threads.each{
|thr| thr.join}

2)线程中如何共享变量?可以通过[]=简单地把当前线程看成一个散列表,这里没有考虑同步问题:
count=0
threads
=[]
10.times do |i|
  threads[i]
=Thread.new do 
    sleep(rand(
0.1))
    Thread.current[
"mycount"]=count
    count
+=1
  end
end
threads.each{
|t| t.join;print t["mycount"],""}
puts 
"count =#{count}"

3)通过设置abort_on_exception,如果是true,未处理的线程异常将杀死所有正在运行的线程,如果是false,则杀死当前运行的线程,其他线程继续运行。修改上面的例子查看下:
count=0
threads
=[]
10.times do |i|
  threads[i]
=Thread.new(i) do |j|
    
raise "boom!" if j==4 
    sleep(rand(
0.1))
    Thread.current[
"mycount"]=count
    count
+=1
  end
end
threads.each do 
|t|
  begin
    t.join
    
print t["mycount"],""
  rescue RuntimeError
=>e
    puts 
"Failed:#{e.message}"
  end
end
puts 
"count =#{count}"
输出(随机的):
8, 1, 6, 3, Failed:boom!
2, 4, 7, 0, 5, count =9

在开头加上:
Thread.abort_on_exception=true
杀死所有的运行进程,报出异常,而不会产生输出。

4)通过线程的一系列方法:pass,join,value,stop来进行线程的调度
5)互斥的实现,与其他语言一样,不外乎加锁、信号量、队列的方式。看看加锁是如何做的,通过monitor库的关键字synchronize实现,如下面这个例子,两个线程递增同一个变量,似乎结果应该是20000:
#require 'monitor'
class Counter#<Monitor
  attr_reader:count
  
def initialize
    @count
=0
  
#  super
  end
  
def tick
  
#  synchronize do
      @count+=1
  
#  end
  end
end
c
=Counter.new
t1
=Thread.new{10000.times{c.tick}}
t2
=Thread.new{10000.times{c.tick}}

t1.join;t2.join

print c.count
很遗憾,结果不会是20000,而是比它小的一个数值,这里的问题就是因为访问共享资源没有进行同步的缘故,使用monitor库,请将上面代码中的注释去掉,可以得到正确的结果
使用monitor,不一定要使用继承,也可以使用mixin,甚至:
lock=Monitor.new
t1
=Thread.new{10000.times{lock.synchronize{c.tick}}}
还可以把特定的对象放入monitor,比如:
c=Counter.new
c.extend(MonitorMixin)
t1
=Thread.new{10000.times{c.synchronize{c.tick}}}
.

6)条件变量和队列的方式不准备抄书了,ruby中对线程的操作都是直接调用操作系统的命令,特别是*nix支持的非常好,可惜我对linux也是个初哥。

10.ruby中表达式很重要的一个特点是:任何表达式都有返回值,包括赋值语句、条件语句、循环语句之类。
1)ruby中对布尔表达式的规定是:任何不是nil或者常量false的值都为真
2)注意,在方法中调用访问属性的函数,需要写上调用者self,否则将处理为局部变量
3)defined?方法用于返回参数的描述,如果未定义,返回nil
4)逻辑表达式中,and和or的优先级低于&&,||
5)ruby没有for语句,因为ruby通过内建在对象中的迭代器提供了循环访问的能力,最简单的内建迭代器:loop do ....end
6)只要你的类支持each方法,你就可以使用for ... in ..语句循环它
7)对循环可以使用break(打断跳出),redo(从头重新循环,当前迭代),next进行调度。另外,还有retry,用于完全重新开始循环
8)while,until和for循环内建到了ruby语言中,但没有引入新的作用域:前面存在的局部变量可以在循环中使用,而循环中新创建的局部变量也可以在循环后使用。而被迭代器使用的block则不同,在block中创建的局部变量无法在block外访问。

11.ruby的异常处理
类似于java的try...catch...finnaly,ruby对应的是begin...rescue...ensure...end,将产生异常的代码放在这个块中进行处理。可以通过$!得到异常信息,或者提供局部变量名,我改写了一下我的google在线翻译机,增加异常处理,并用exit代替break:
require 'net/http'
def translate
  txt
=STDIN.gets
  exit 
if txt.strip=='e' or txt.strip=='exit'
  temp
=txt.split(' ')
  
if temp[1]=='1' or temp.size==1
    langpair
='en|zh-CN'
  
else
    langpair
='zh-CN|en'
  end
  
#使用代理  
  begin 
    $proxy_addr 
= 'localhost'
    $proxy_port 
= 80
    response 
= Net::HTTP.Proxy($proxy_addr,$proxy_port).post_form(URI.parse("http://translate.google.com/translate_t"),{'text'=>temp[0],'langpair'=>langpair})
    response.body 
=~ /<div id=result_box dir=ltr>(.*)<\/div>/
  rescue  StandardError 
=>e
    $stderr.
print "网络错误:"+e
  
else
    result 
= $1 
    puts 
'翻译内容:'+temp[0]
    puts 
'google返回:'+result
    puts 
'-------------------退出请打e或者exit---------------'
    translate
  end
end
translate
引发一个异常使用raise语句,重新引发当前异常,如果没有,就引发一个RuntimeError,常见使用方式:
raise InterfaceException,"keyboard failure",caller
其中的caller生成了栈的信息。另外,catch...throw语句用于在异常发生时从深度嵌套的结构中跳转出来。

12。关于模块,作用有二:作为命名空间和Mixin机制。模块的Mixin机制可以说是ruby的一个精华所在,通过Mixin,可以变相地实现了多重继承,并且可以动态地为类添加和删除功能。这一部分注意两点:
1)模块中定义的实例变量可能与包含模块的类的实例变量产生名称冲突。可以使用模块一级的散列表,以当前对象的ID做索引,来保存特定于当前模块的实例变量解决这个问题。比如:
module Test
  State
={}
  
def state=(value)
    State[object_id]
=value
  end
  
def state
    State[object_id]
  end
end
class Client
  include Test
end
c1
=Client.new
c2
=Client.new
c1.state
='A'
c2.state
='B'

puts c1.state
puts c2.state
2)是关于方法的查找路径,顺序是:当前类-》类的mixin模块-》超类-》超类的mixin,另外mixin的模块,最后混入的同名方法将覆盖前面混入的。

13.irb的配置和命令,今天发现irb原来也是可以玩出很多花样的。记录些有趣的:
1)可以使用按tab键两次来自动补全,要求加载irb/completaion库。比如这样启动irb:
 
irb -r irb/completion

或者进入irb后手工require:
require 'irb/completation'

当然,还有更好的方法,呆会介绍
2)子会话,在irb中使用irb可以创建子会话,通过命令jobs可以查看所有的子会话。创建子会话的时候指定一个对象,子会话的self将绑定该对象,比如:
irb 'test'
reverse
=>"tset"
length
=>4
self
=>"test"
irb_quit

3)在linux下可以通过配置.irbrc配置文件来进行初始化定制,在windows环境你可以在ruby安装目录下的bin看到一个irb.bat文件,通过配置文件来定制irb,比如我们为irb增加ri和tab自动补齐功能:
@echo off
goto endofruby
#!/bin/ruby
#
#
   irb.rb - intaractive ruby
#
       $Release Version: 0.9.5 $
#
       $Revision: 1.2.2.1 $
#
       $Date: 2005/04/19 19:24:56 $
#
       by Keiju ISHITSUKA(keiju@ruby-lang.org)
#

require 
"irb"
require 
'irb/completion'
def ri(*names)
  system(
%{ri.bat #{names.map{ |name| name.to_s}.join(" ")}})
end
if __FILE__ == $0
  IRB.start(
__FILE__)
else
  
# check -e option
  if /^-e$/ =~ $0
    IRB.start(
__FILE__)
  
else
    IRB.setup(
__FILE__)
  end
end
__END__
:endofruby
"%~d0%~p0ruby" -"%~f0" %*

4)常用命令:
exit,quit,irb_exit,irb_quit——退出
conf,context,irb_context——查看配置信息
irb <obj>——创建子会话,如果提供obj,作为self
jobs,irb_jobs——列出irb的子会话
irb_fg,fg n——切换子会话
kill n,irb_kill n——杀死一个irb子会话

14.类的实例变量,类除了类变量、实例变量外,还有一个类的实例变量的概念:
class Test
  #类的实例变量
  @cls_var 
= 123
  def Test.inc
    @cls_var 
+= 1
  end
  
class<<self
    attr_accessor:cls_var
  end
end
Test.inc
Test.inc


15.子类竟然可以改变父类定义方法的访问级别:
class Base
  
def aMethod
    puts 
"Got here"
  end
  private :aMethod
end

class Derived1 < Base
  public :aMethod
end

class Derived2 < Base
  
def aMethod(*args)
    super
  end
  public:aMethod  
end

d1
=Derived1.new
d2
=Derived2.new
d1.aMethod
d2.aMethod

不知道ruby是基于什么样的考虑允许这样的行为。













posted @ 2007-04-07 10:30 dennis 阅读(2329) | 评论 (0)编辑 收藏

    这个包的说明是:Support for styling values as Strings, with ToStringCreator as central class.
这个包简单来说就是提供一个pretty-printing功能的辅助类,而ToStringCreator就是用于产生一个可以输出经过美化的value信息的toString()方法。使用方法参照spring的Test可以看到是这样:   
        int[] integers = new int[] { 01234 };
        String str 
= new ToStringCreator(integers).toString();
        assertEquals(
"[@" + ObjectUtils.getIdentityHexString(integers) + " array<Integer>[0, 1, 2, 3, 4]]", str);
或者写个简单例子感受下:
int [] a={1,2,3,4,5,6,7,8,9};
System.out.println(
new ToStringCreator(a).toString());
    
输出:
[@18558d2 array<Integer>[123456789]]

    如果你接触过ruby,你应该很熟悉Object.inpsect这个功能,这里通过ToStringCreator包装的toString()方法也是产生类似的能够清晰显示对象内部结构信息的方法。spring应该是使用这些辅助类来报告清晰的错误信息或者提示信息。
    看看这个包的UML类图:

    首先,你需要理解ToStringStyler和ValueStyle两个接口,ToStringStyler定义了描述一个输入的Value信息的基本模板方法:
public interface ToStringStyler {

    
/**
     * Style a <code>toString()</code>'ed object before its fields are styled.
     * 
@param buffer the buffer to print to
     * 
@param obj the object to style
     
*/
    
void styleStart(StringBuffer buffer, Object obj);

    
/**
     * Style a <code>toString()</code>'ed object after it's fields are styled.
     * 
@param buffer the buffer to print to
     * 
@param obj the object to style
     
*/
    
void styleEnd(StringBuffer buffer, Object obj);

    
/**
     * Style a field value as a string.
     * 
@param buffer the buffer to print to
     * 
@param fieldName the he name of the field
     * 
@param value the field value
     
*/
    
void styleField(StringBuffer buffer, String fieldName, Object value);

    
/**
     * Style the given value.
     * 
@param buffer the buffer to print to
     * 
@param value the field value
     
*/
    
void styleValue(StringBuffer buffer, Object value);

    
/**
     * Style the field separator.
     * 
@param buffer buffer to print to
     
*/
    
void styleFieldSeparator(StringBuffer buffer);

}
    这是典型的Template Method模式,而两个接口ToStringStyler、ValueStyler和它们的相应实现DefaultToStringStyler、DefaultValueStyler又是策略模式(Strategy)的应用体现。ValueStyler和DefaultValueStyler之间不仅仅是策略模式,同时也是visitor模式,请看DefaultValueStyler中一系列重载的visit方法,这些visit方法访问不同类型Value的内部结构并构造pretty格式的String返回,提供给ToStringStyler使用。
    ToStringCreator是ToStringStyler的客户,它使用ToStringStyler调用产生优美格式打印,而ToStringStyler 其实又是使用ValueStyler是访问每个不同类型的子元素并返回优美格式的String。实现的相当精巧和灵活:
   
public ToStringCreator(Object obj, ToStringStyler styler) {
        Assert.notNull(obj, 
"The object to be styled is required");
        
this.object = obj;
        
this.styler = (styler != null ? styler : DEFAULT_TO_STRING_STYLER);
//开始        this.styler.styleStart(this.buffer, this.object);
    }

public ToStringCreator append(String fieldName, byte value) {
        
return append(fieldName, new Byte(value));
    }
   一系列不同类型的append方法
   
public String toString() {
//结束,并返回优美格式的String        this.styler.styleEnd(this.buffer, this.object);
        return this.buffer.toString();
    }

posted @ 2007-04-06 15:18 dennis 阅读(1114) | 评论 (0)编辑 收藏

    其实我这系列小文,名为源码分析,其实是自己读《设计模式》的读书笔记。Decorator模式在java的IO库中得到应用,java的IO库看起来复杂,其实理解了Decorator模式再回头看可以很好理解并使用。
    Decorator模式,也就是装饰器模式,是对象结构型模式之一。

1.意图:动态地给一个对象添加一些额外的职责。给对象添加功能,我们首先想到的是继承,但是如果每增一个功能都需要继承,类的继承体系将无可避免地变的庞大和难以理解。面向对象设计的原则:优先使用组合,而非继承,继承的层次深度最好不过三。

2.适用场景:
1)在不影响其他对象的情况下,以动态、透明的方式给单个对象添加额外的责任
2)处理可以撤销的职责
3)为了避免类的数目爆炸,或者不能采用生成子类的方法进行扩展时

3.UML图和协作:



Component——定义一个对象接口,可以给这些对象动态地添加职责

ConcreteComponent——定义一个对象,可以给这个对象添加职责

Decorator——维持一个指向Component的引用,并定义一个与Component一致的接口,作为装饰类的父类

ConcreteDecorator——具体装饰类

4.效果:
1)与静态继承相比,Decorator可以动态添加职责,更为灵活
2)避免产生复杂的类,通过动态添加职责,而不是一次性提供一个万能的接口
3)缺点是将产生比较多的小对象,对学习上有难度,显然,java.io就是这个问题

我们以一个例子来实现Decorator模式,假设这样一个场景:在某个应用中需要打印票据,我们写了一个PrintTicket接口,然后提供一个实现类(DefaultPrintTicket)实现打印的功能:

package com.rubyeye.design_pattern.decorator;
//抽象component接口
public interface PrintTicket {
    
public void print();
}


//默认实现类,打印票据
package com.rubyeye.design_pattern.decorator;

public class DefaultPrintTicket implements PrintTicket {

    
public void print() {
        System.out.println(
"ticket body");
    }

}

OK,我们的功能已经实现,我们还体现了针对接口编程的原则,替换一个新的打印方式很灵活,但是客户开始提需求了——人生无法避免的三件事:交税、死亡和需求变更。客户要求打印页眉,你首先想到的是继承:
package com.rubyeye.design_pattern.decorator;

public class AnotherPrintTicket implements PrintTicket {

    
public void print() {
        System.out.println(
"ticket header");
        System.out.println(
"ticket body");
    }

}

请注意,我们这里只是简单的示例,在实际项目中也许意味着添加一大段代码,并且需要修改打印票据本体的功能。需求接踵而至,客户要求添加打印页码,要求增加打印花纹,要求可以联打......你的类越来越庞大,直到你看见这个类都想吐的地步!-_-。让我们看看另一个方案,使用Decorator模式来动态地给打印增加一些功能,首先是实现一个Decorator,它需要保持一个到PrintTicket接口的引用:

package com.rubyeye.design_pattern.decorator;

public class PrintTicketDecorator implements PrintTicket {

    
protected PrintTicket printTicket;

    
public PrintTicketDecorator(PrintTicket printTicket) {
        
this.printTicket = printTicket;
    }

    
//默认调用PrintTicket的print
    public void print() {
        printTicket.print();
    }

}

然后,我们实现两个具体的装饰类——打印页眉和页脚:
package com.rubyeye.design_pattern.decorator;

public class HeaderPrintTicket extends PrintTicketDecorator {

    
public HeaderPrintTicket(PrintTicket printTicket){
        
super(printTicket);
    }
    
    
public void print() {
        System.out.println(
"ticket header");
        
super.print();
    }
}

package com.rubyeye.design_pattern.decorator;

public class FooterPrintTicket extends PrintTicketDecorator {
    
public FooterPrintTicket(PrintTicket printTicket) {
        
super(printTicket);
    }

    
public void print() {
        
super.print();
        System.out.println(
"ticket footer");
    }
}

    使用起来也很容易:
   
package com.rubyeye.design_pattern.decorator;

public class DecoratorTest {

    
/**
     * 
@param args
     
*/
    
public static void main(String[] args) {
        PrintTicket print
=new HeaderPrintTicket(new FooterPrintTicket(new DefaultPrintTicket()));
        print.print();
    }

}

输出:
ticket header
ticket body
ticket footer

    了解了Decorator模式,我们联系了下JUnit里面的应用。作为一个测试框架,应该方便地支持二次开发,也许用户开发自己的TestCase,添加自定义的功能,比如执行重复测试、多线程测试等等。动态添加职责,而又不想使用静态继承,这正是Decorator使用的地方。在junit.extensions包中有一个TestDecorator,正是所有装饰类的父类,也是作为二次开发的基础,它实现了Test接口,而Test接口就是我们定义的抽象接口:
public class TestDecorator implements  Test {
    
//保有一个指向Test的引用
        protected Test fTest;
       
    
public TestDecorator(Test test) {
        fTest
= test;
    }
        
        
public void basicRun(TestResult result) {
          fTest.run(result);
        }
        
public void run(TestResult result) {
           basicRun(result);
        }
        
}

Junit已经提供了两个装饰类:junit.extensions.ActiveTest用于处理多线程,junit.extensions.RepeatedTest用于执行重复测试,看看RepeatedTest是怎么实现的:
public class RepeatedTest extends  TestDecorator {
        
//重复次数
    private int fTimesRepeat;

    
public RepeatedTest(Test test, int repeat) {
        
super(test);
        fTimesRepeat
= repeat;
    }
        
public void run(TestResult result) {
        
//重复执行
                for (int i= 0; i < fTimesRepeat; i++) {
            
if (result.shouldStop())
                
break;
            
super.run(result);
        }
    }
        
}

    RepeatedTest继承TestDecorator ,覆写run(TestReult result)方法,重复执行,super.run(result)将调用传入的TestCase的run(TestResult result)方法,这已经在TestDecorator默认实现。看看使用方式,使用装饰模式的好处不言而喻。

TestSuite suite = new TestSuite();
suite.addTest(
new TestSetup(new RepeatedTest(new Testmath("testAdd"),12)));

posted @ 2007-04-06 12:39 dennis 阅读(2546) | 评论 (3)编辑 收藏

    在JUnit执行测试时,我们经常需要初始化一些环境供测试代码使用,比如数据库连接、mock对象等等,这些初始化代码应当在每一个测试之前执行并在测试方法运行后清理。在JUnit里面就是相应的setUp和tearDown方法。如果没有这两个方法,那么我们要在每个测试方法的代码内写上一大堆重复的初始化和清理代码,这是多么愚蠢的做法。那么JUnit是怎么让setUp和tearDown在测试执行前后被调用的呢?
    如果你查看下TestCase方法,你会发现TestCase和TestSuite的run()方法都是将执行测试的任务委托给了TestResult,由TestResult去执行测试代码并收集测试过程中的信息(这里用到了Collecting Parameter模式)。
   
    public TestResult run() {
        TestResult result
= createResult();
        run(result);
        
return result;
    }
    
/**
     * Runs the test case and collects the results in TestResult.
     * This is the template method that defines the control flow
     * for running a test case.
     
*/
    
public void run(TestResult result) {
        result.run(
this);
    }
   
    我们直接找到TestResult,看看它的run方法:
/**
     * Runs a TestCase.
     
*/
    
protected void run(final TestCase test) {
        startTest(test);
        Protectable p 
= new Protectable() {
            
public void protect() throws Throwable {
                test.runBare();
            }
        };
        runProtected(test, p);
        endTest(test);
    }

    这里实例化了一个内部类,内部类实现了Protectable接口的 protect()方法,并执行传入的TestCase的runBare()方法,显然,真正的测试代码在TestCase的runBare()方法中,让我们来看下:


        //将被子类实现
    protected void setUp() throws Throwable {
    }
    
//同上,将被具体的TestCase实现
    protected void tearDown() throws Throwable {
    }
     /**
     * 模板方法
     * Runs the bare test sequence.
     * 
@exception Throwable if any exception is thrown
     
*/
    
public void runBare() throws Throwable {
        setUp();
        
try {
            runTest();
        }
        
finally {
            tearDown();
        }
    }

真相水落石出,对于每一个测试方法,都遵循这样的模板:setUp->执行测试 runTest()->tearDown。这正是模板方式模式的一个应用例子。什么是template method模式呢?

Template Method模式

类行为模式的一种
1.意图:定义一个操作中的算法的骨架,而将一些延迟步骤到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些步骤。
2.适用场景:
1)一次性实现算法的不变部分(基本骨架),将可变的行为留给子类来完成
2)子类中的公共部分(比如JUnit中的初始化和清理)被抽取到一个公共父类中以避免代码重复。
3)控制了子类的扩展,这里其实也有类似回调函数的性质,具体步骤先在骨架中注册,在具体执行时被回调。

3.UML图和结构
   
  抽象父类定义了算法的基本骨架(模板方法),而不同的子类实现具体的算法步骤,客户端由此可以与算法的更改隔离。

4.效果:
1)模板方法是代码复用的基本技术,在类库中经常使用,可以减少大量的代码重复
2)通过隔离算法的不变和可变部分,增加了系统的灵活性,扩展算法的某些步骤将变的很容易。

    了解了Template Method模式之后,让我们回到JUnit的源码,看看runTest()方法,这里主要应用的是java的反射技术,对于学习反射技术的有参考价值:
protected void runTest() throws Throwable {
        Method runMethod
= null;
        
try {
            runMethod
= getClass().getDeclaredMethod(fName, new Class[0]);
        } 
catch (NoSuchMethodException e) {
            fail(
"Method \""+fName+"\" not found");
        }
        
if (runMethod != null && !Modifier.isPublic(runMethod.getModifiers())) {
            fail(
"Method \""+fName+"\" should be public");
        }

        
try {
            runMethod.invoke(
thisnew Class[0]);
        }
        
catch (InvocationTargetException e) {
            e.fillInStackTrace();
            
throw e.getTargetException();
        }
        
catch (IllegalAccessException e) {
            e.fillInStackTrace();
            
throw e;
        }
    }

   

posted @ 2007-04-06 09:54 dennis 阅读(2651) | 评论 (0)编辑 收藏

    今天在javaeye论坛上看到有人有这个需求,顺手写了下。原理就是通过遍历Controllers目录,并用正则表达式取出Controller名和它所有的action。

    @controllers=Hash.new
    path
="#{RAILS_ROOT}/app/controllers/"
    Dir
.new(path).entries.each do |f|
      
if !f.index('.rb').nil? and f.index('.rb')>0
        
        controller
=File.open(path+f.to_s)
        s
=controller.read
         
/class\s(.*)\s\</.match(s)
        controller_name
=$1.to_s
        actions
=[]
        s
.scan(/def\s(.*)\s/).each|action| actions<<(action[0]) }
        
@controllers[controller_name]=actions
        controller
.close
      end
    end
    
    
@controllers.each_pair do |name, actions|
      actions
.each do |action| 
        puts 
|name<<" "<<action
      end
    end


posted @ 2007-04-05 20:21 dennis 阅读(963) | 评论 (0)编辑 收藏


    我们知道JUnit支持不同的使用方式:swt、swing的UI方式,甚至控制台方式,那么对于这些不同的UI我们如何提供统一的接口供它们获取测试过程的信息(比如出现的异常信息,测试成功,测试失败的代码行数等等)?我们试想一下这个场景,当一个error或者exception产生的时候,测试能够马上通知这些UI客户端:发生错误了,发生了什么错误,错误是什么等等。显而易见,这是一个订阅-发布机制应用的场景,应当使用观察者模式。那么什么是观察者模式呢?

观察者模式(Observer)

Observer是对象行为型模式之一

1.意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发现改变时,所有依赖于它的对象都得到通知并被自动更新

2.适用场景:
1)当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,通过观察者模式将这两者封装在不同的独立对象当中,以使它们可以独立的变化和复用
2)当一个对象改变时,需要同时改变其他对象,并且不知道其他对象的具体数目
3)当一个对象需要引用其他对象,但是你又不想让这个对象与其他对象产生紧耦合的时候

3.UML图:
                 
   Subject及其子类维护一个观察者列表,当需要通知所有的Observer对象时调用Nitify方法遍历Observer集合,并调用它们的update方法更新。而具体的观察者实现Observer接口(或者抽象类),提供具体的更新行为。其实看这张图,与Bridge有几分相似,当然两者的意图和适用场景不同。

4.效果:
1)目标和观察者的抽象耦合,目标仅仅与抽象层次的简单接口Observer松耦合,而没有与具体的观察者紧耦合
2)支持广播通信
3)缺点是可能导致意外的更新,因为一个观察者并不知道其他观察者,它的更新行为也许将导致一连串不可预测的更新的行为

5.对于观察者实现需要注意的几个问题:
1)谁来触发更新?最好是由Subject通知观察者更新,而不是客户,因为客户可能忘记调用Notify
2)可以通过显式传参来指定感兴趣的更新
3)在发出通知前,确保Subject对象状态的一致性,也就是Notify操作应该在最后被调用
4)当Subject和Observer的依赖关系比较复杂的时候,可以通过一个更新管理器来管理它们之间的关系,这是与中介者模式的结合应用。


    讨论完观察者模式,那我们来看JUnit是怎么实现这个模式的。在junit.framework包中我们看到了一个Observer接口——TestListener,看看它的代码:
package junit.framework;

/**
 * A Listener for test progress
 
*/
public interface TestListener {
   
/**
     * An error occurred.
     
*/
    
public void addError(Test test, Throwable t);
   
/**
     * A failure occurred.
     
*/
     
public void addFailure(Test test, Throwable t);  
   
/**
     * A test ended.
     
*/
     
public void endTest(Test test); 
   
/**
     * A test started.
     
*/
    
public void startTest(Test test);
}

    接口清晰易懂,就是一系列将测试过程的信息传递给观察者的操作。具体的子类将接受这些信息,并按照它们的方式显示给用户。
     比如,我们看看swing的UI中的TestRunner,它将这些信息显示在一个swing写的UI界面上:
    public void startTest(Test test) {
        showInfo(
"Running: "+test);
    }

    public void addError(Test test, Throwable t) {
        fNumberOfErrors.setText(Integer.toString(fTestResult.errorCount()));
        appendFailure(
"Error", test, t);
    }
    
public void addFailure(Test test, Throwable t) {
        fNumberOfFailures.setText(Integer.toString(fTestResult.failureCount()));
        appendFailure(
"Failure", test, t);
    }
    public void endTest(Test test) {
        setLabelValue(fNumberOfRuns, fTestResult.runCount());
        fProgressIndicator.step(fTestResult.wasSuccessful());
    }

可以看到,它将错误信息,异常信息保存在List或者Vector集合内,然后显示在界面上:
private void showErrorTrace() {
        
int index= fFailureList.getSelectedIndex();
        
if (index == -1)
            
return;
    
        Throwable t
= (Throwable) fExceptions.elementAt(index);
        
if (fTraceFrame == null) {
            fTraceFrame
= new TraceFrame();
            fTraceFrame.setLocation(
100100);
           }
        fTraceFrame.showTrace(t);
        fTraceFrame.setVisible(
true);
    }
    
private void showInfo(String message) {
        fStatusLine.setFont(PLAIN_FONT);
        fStatusLine.setForeground(Color.black);
        fStatusLine.setText(message);
    }
    
private void showStatus(String status) {
        fStatusLine.setFont(BOLD_FONT);
        fStatusLine.setForeground(Color.red);
        fStatusLine.setText(status);
    }

而Junit中的目标对象(Subject)就是TestResult对象,它有添加观察者的方法:

/**
     * Registers a TestListener
     
*/
    
public synchronized void addListener(TestListener listener) {
        fListeners.addElement(listener);
    }

而通知观察者又是怎么做的呢?请看这几个方法,都是循环遍历观察者列表,并调用相应的更新方法:

/**
     * Adds an error to the list of errors. The passed in exception caused the
     * error.
     
*/
    
public synchronized void addError(Test test, Throwable t) {
        fErrors.addElement(
new TestFailure(test, t));
        
for (Enumeration e = fListeners.elements(); e.hasMoreElements();) {
            ((TestListener) e.nextElement()).addError(test, t);
        }
    }

    
/**
     * Adds a failure to the list of failures. The passed in exception caused
     * the failure.
     
*/
    
public synchronized void addFailure(Test test, AssertionFailedError t) {
        fFailures.addElement(
new TestFailure(test, t));
        
for (Enumeration e = fListeners.elements(); e.hasMoreElements();) {
            ((TestListener) e.nextElement()).addFailure(test, t);
        }
    }

    
/**
     * Registers a TestListener
     
*/
    
public synchronized void addListener(TestListener listener) {
        fListeners.addElement(listener);
    }

    
/**
     * Informs the result that a test was completed.
     
*/
    
public synchronized void endTest(Test test) {
        
for (Enumeration e = fListeners.elements(); e.hasMoreElements();) {
            ((TestListener) e.nextElement()).endTest(test);
        }
    }


使用这个模式后带来的好处:
1)上面提到的Subject与Observer的抽象耦合,使JUnit可以支持不同的使用方式
2)支持了广播通信,目标对象不关心有多少对象对自己注册,它只是通知注册的观察者

最后,我实现了一个简单的ConsoleRunner,在控制台执行JUnit,比如我们写了一个简单测试:
package junit.samples;

import junit.framework.*;

/**
 * Some simple tests.
 * 
 
*/
public class SimpleTest extends TestCase {
    
protected int fValue1;

    
protected int fValue2;

    
public SimpleTest(String name) {
        
super(name);
    }

    
public void setUp() {
        fValue1 
= 2;
        fValue2 
= 3;
    }

    
public void testAdd() {
        
double result = fValue1 + fValue2;
        
assert(result == 5);
    }


    
public void testEquals() {
        assertEquals(
1212);
        assertEquals(
12L12L);
        assertEquals(
new Long(12), new Long(12));

        assertEquals(
"Size"1212);
        assertEquals(
"Capacity"12.011.990.01);
    }
}

使用ConsoleRunner调用这个测试,代码很简单,不多做解释了:
package net.rubyeye.junit.framework;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import junit.framework.Test;
import junit.framework.TestListener;
import junit.framework.TestResult;
import junit.samples.SimpleTest;
//实现观察者接口
public class ConsoleRunner implements TestListener {

    
private TestResult fTestResult;

    
private Vector fExceptions;

    
private Vector fFailedTests;

    
private List fFailureList;

    
public ConsoleRunner() {
        fExceptions 
= new Vector();
        fFailedTests 
= new Vector();
        fFailureList 
= new ArrayList();
    }

    
public void endTest(Test test) {
        System.out.println(
"测试结束:");
        String message 
= test.toString();
        
if (fTestResult.wasSuccessful())
            System.out.println(message 
+ " 测试成功!");
        
else if (fTestResult.errorCount() == 1)
            System.out.println(message 
+ " had an error");
        
else
            System.out.println(message 
+ " had a failure");

        
for (int i = 0; i < fFailureList.size(); i++) {
            System.out.println(fFailureList.get(i));
        }
        
for (int i = 0; i < fFailedTests.size(); i++) {
            System.out.println(fFailureList.get(i));
        }
        
for (int i = 0; i < fExceptions.size(); i++) {
            System.out.println(fFailureList.get(i));
        }

        System.out.println(
"------------------------");
    }

    
public void startTest(Test test) {
        System.out.println(
"开始测试:" + test);
    }

    
public static TestResult createTestResult() {
        
return new TestResult();
    }

    
private String truncateString(String s, int length) {
        
if (s.length() > length)
            s 
= s.substring(0, length) + "";
        
return s;
    }

    
public void addError(Test test, Throwable t) {
        System.out.println(fTestResult.errorCount());
        appendFailure(
"Error", test, t);
    }

    
public void addFailure(Test test, Throwable t) {
        System.out.println(fTestResult.failureCount());
        appendFailure(
"Failure", test, t);
    }

    
private void appendFailure(String kind, Test test, Throwable t) {
        kind 
+= "" + test;
        String msg 
= t.getMessage();
        
if (msg != null) {
            kind 
+= ":" + truncateString(msg, 100);
        }
        fFailureList.add(kind);
        fExceptions.addElement(t);
        fFailedTests.addElement(test);
    }

    
public void go(String args[]) {
        Method[] methods 
= SimpleTest.class.getDeclaredMethods();

        
for (int i = 0; i < methods.length; i++) {
            
//取所有以test开头的方法
            if (methods[i].getName().startsWith("test")) {
                Test test 
= new SimpleTest(methods[i].getName());
                fTestResult 
= createTestResult();
                fTestResult.addListener(ConsoleRunner.
this);
                
//执行测试
                test.run(fTestResult);
            }
        }

    }

    
public static void main(String args[]) {
        
new ConsoleRunner().go(args);
    }

}

posted @ 2007-04-05 17:19 dennis 阅读(2896) | 评论 (0)编辑 收藏

仅列出标题
共56页: First 上一页 42 43 44 45 46 47 48 49 50 下一页 Last