#
1 table.setSelectionBackground(Color.black); table.setSelectionForeground(Color.white);
Swing颇受欢迎的JTable类为显示大块数据提供了一种简单的机制。JTable有很多东西是用于数据的生成和编辑,其中的很多东西还可以自定义,从而更进一步增强其功能。本文会引导你一步步地进入JTable的世界。
Listing A包含了一个简单示例的代码,这个示例会说明常用JTable的行为。用户能够更改JTable的布局、拖放它的栏,或者通过拖动标题的分隔线来改变其大小。
这些列被保存在一个String数组里:
1 String[] columnNames = {"Product","Number of Boxes","Price"};
数据被初始化并保存在一个二维的对象数组里:
1 Object[][] data =
2 {
3 { " Apples " , new Integer( 5 ), " 5.00 " } ,
4 { " Oranges " , new Integer( 3 ), " 6.00 " } ,
5 { " Pears " , new Integer( 2 ), " 4.00 " } ,
6 { " Grapes " , new Integer( 3 ), " 2.00 " } ,
7 } ;
JTable是使用data和columnNames构成的:
JTable table = new JTable(data, columnNames);
查看JTable
JTable的高度和宽度按照下面的方法来设定:
table.setPreferredScrollableViewportSize(new Dimension(300, 80));
如果JTable的一个列或者JTable窗口自身的大小被重新确定,那么其他列会被相应的缩小或者放大,以适应新的窗口。使用setAutoResizeMode()方法就能够控制这种行为:
1 table.setAutoResizeMode(int mode);
mode整数字段可能的值有:
1 AUTO_RESIZE_OFF
2 AUTO_RESIZE_NEXT_COLUMN
3 AUTO_RESIZE_SUBSEQUENT_COLUMNS
4 AUTO_RESIZE_LAST_COLUMN
5 AUTO_RESIZE_ALL_COLUMNS
表格的缺省值
单元格内方格坐标线的缺省颜色是Color.gray。要更改这些方格坐标线的颜色,就要用到:
1 table.setGridColor(Color.black);
你可以用下面的方法来改变行的高度:
1 table.setRowHeight(intpixelHeight);
各个单元格的高度将等于行的高度减去行间的距离。
在缺省情况下,内容的前景颜色和背景颜色的选择都是由Swing的所见即所得的实现来确定的。你可以使用下面的方法来更改选择的颜色:
1 table.setSelectionBackground(Color.black); table.setSelectionForeground(Color.white);
你也可以隐藏单元格的方格坐标线,就像下面这样:
1 table.setShowHorizontalLines(false );
2 table.setShowVerticalLines(false);
列的宽度
JTable组件有几个控制表格特性的类和接口。TableColumn会不断追踪列的宽度,并负责列大小的调整,包括最大和最小宽度。
TableColumnModel管理着TableColumns的集合以及列的选择。要设置某个列的宽度,就要为表格列的模型设置一个参照。然后,取得想要的TableColumn并调用其setPreferredWidth()方法:
1 TableColumncolumn = table.getColumnModel().getColumn(0 );
2 column.setPreferredWidth(100);
当用户拖放列的时候,列的索引并不会发生改变。getColumn(0)方法会一直返回正确的列,无论它出现在屏幕的哪个地方。
标题
JtableHeader会处理JTable标题的显示。你可以细分JtableHeader以获得自定义的布局。例如,如果你的应用程序需要一个跨越多个列的标题,那么只用简单地细分JtableHeader并将它集成到你的JTable里就行了。
你可以通过为当前JTable的JtableHeader设置一个参照或者调用其setReorderingAllowed()方法,来指定标题的重新排序是否被允许:
1 table.getTableHeader().setReorderingAllowed(false);
类似地,你可以确信列不会因为在列标题之间拖动而改变大小。要达到这个目的,你就要使用setResizingAllowed()方法:
1 table.getTableHeader().setResizingAllowed(false);
选择模式
在缺省状况下,当用户在JTable里选择一个单元格的时候,整个行都被选中了。有多种方法能够让用户自定义选择的方式。利用ListSelectionModel接口,你可以允许用户选择单个或者多个行:
1 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
ListSelectionModel有下面这些字段:
* SINGLE_SELECTION允许一次选择一行。
* SINGLE_INTERVAL_SELECTION允许选择相邻的一系列行。
* MULTIPLE_INTERVAL_SELECTION也允许选择相邻的列,但是带有扩展功能。它允许用户使用[Ctrl]键进行多个互不相邻的选择(即选择不相邻的行)。
setCellSelectionEnabled()方法让用户能够同时选择单个单元格或者整个行:
1 table.setCellSelectionEnabled(true);
编辑单元格
我们这个简单的表格允许用户编辑表格里的任何单元格。Listing B列出了一个表格,它允许由程序员来决定哪些单元格能够被编辑。第一步是创建一个自定义的TableModel:
1 class SimpleTableModel extends AbstractTableModel {}
数据被封装在TableModel里,当JTable初始化的时候,自定义的TableModel就被作为一个参数传递给JTable的构造函数而不是那个二维的对象数组:
1 SimpleTableModelmyModel = new SimpleTableModel();
2 JTable table = new JTable(myModel);
如果想让第二列和第三列也变得可以编辑,并把第一列变成恒定的,那么你就要强制替代TableModel的isCellEditable()方法:
1 public booleanisCellEditable(int row, intcol){
2 if (col == 0) {return false ;}
3 else {return true ; }
4 }
简单的表格验证
你需要确保用户只输入整数值,假如说,向第二列(“盒子的数量”这一列)输入值来强制替代setValueAt()方法,并将验证逻辑包括进这个新方法里。首先,你要检查列是否是整数,以及这个列是否只应该包含整数值:
1 if (data[0][col] instanceof Integer && !(value instanceof Integer))
2 {… } else { data[row][col] = value;}
然后,检查被插入的值是否是个整数。如果它不是的,那么这个字段就不应该被更新,而且应该要显示一条错误信息:
1 try {
2 data[row][col] = new Integer(value.toString());
3 } catch (NumberFormatException e) {
4 JOptionPane.showMessageDialog(SimpleTable.this ,
5 "Please enter only integer values." );
6 }
背景颜色
Listing C包含了用于ColorTable.java的代码,它说明了如何向JTable加入颜色。你可以通过强制替代其prepareRenderer()方法来向JTable加入背景颜色:
1 JTable table = new JTable(data, columnNames){
2 public Component prepareRenderer(TableCellRenderer r, int row, intcol){}
3 };
然后,插入决定哪些列应该有颜色以及应该是什么颜色的逻辑:
1 if (col == 2 && ! isCellSelected(row, col)){
2 Color bg = new Color(200, 100, 30 );
3 c.setBackground(bg);
4 c.setForeground(Color.white);
5 }
要注意,当你更改单元格背景颜色的时候,你还应该更该单元格里所显示的文本的颜色,让其变得更加易读。图C显示了一个第一列和第二列加上了颜色的JTable。
一切皆在掌握中
我们的例子只是JTable其他部分的基础。通过使用这些工具,你能够快速和轻易地掌控对Java应用程序所生成的表格的格式化,这样就能够让你的用户在进行正常使用的时候不碰到障碍。
摘自:http://www.7dspace.com/doc/21/0601/20061905111047137.htm
在JAVA中使用拖拽功能
sun在java2中引入了一些新的方法来帮助实现拖拽功能,这些新的类在java.awt.dnd包中
实现一个D&D操作一般包括三个步骤:
首先实现一个拖拽源,这个拖拽源和相应的组件是关联起来的
第二步实现一个拖拽目标,这个目标用来实现拖拽物的接收
第三步实现一个数据传输对象,该对象封装拖动的数据
_____________________ _____________________
| | | |
| DragSource Component| |DropTarget Component|
|_____________________| |____________________|
| |
|____________Transferable Data_________________|
Transferable 接口实现出的对象能够保证 DropTarget Component读懂拖拽过来的对象中包含的信息
如果是在同一个虚拟机中实现拖拽的话,DragSource Component会传递一个引用给DropTarget Component
但是如果在不同的JVM中或者是在JVM和本地系统之间传递数据的话我们就必须实现一个Transferable对象来传递数据
Transferable中封装的内容存放到DataFlavors,用户可以通过访问DataFlavors来获取数据
1。创建可拖拽对象
一个对象那个如果想作为拖拽源的话,必须和五个对象建立练习,这五个对象分别是:
* java.awt.dnd.DragSource
获取DragSource的方法很简单,直接调用DragSource.getDefaultDragSource();就可以得到DragSource对象
* java.awt.dnd.DragGestureRecognizer
DragGestureRecognizer类中实现了一些与平台无关的方法,我们如果想在自己的组件上实现拖拽的话只要调用createDefaultDragGestureRecognizer()方法就可以了
该方法接收三个参数,建立组件和拖拽动作之间的关系
* java.awt.dnd.DragGestureListener
当建立了组件和拖拽动作之间的联系后,如果用户执行了拖拽操作,组件将发送一个消息给DragGestureListener监听器
DragGestureListener监听器接下来会发送一个startDrag()消息给拖拽源对象,告诉组件应该执行拖拽的初始化操作了
拖拽源会产生一个DragSourceContext对象来监听动作的状态,这个监听过程是通过监听本地方法DragSourceContextPeer来实现的
* java.awt.datatransfer.Transferable
* java.awt.dnd.DragSourceListener
DragSourceListener接口负责当鼠标拖拽对象经过组件时的可视化处理, DragSourceListener接口的显示结果只是暂时改变组件的外观
同时他提供一个feedback,当用户的拖拽操作完成之后会收到一个dragDropEnd的消息,我们可以在这个函数中执行相应的操作
再来回顾一下拖拽源的建立过程
首先、 DragGestureRecognizer 确认一个拖拽操作,同时告知 DragGestureListener.
其次、 Assuming the actions and/or flavors are OK, DragGestureListener asks DragSource to startDrag().
第三、 DragSource creates a DragSourceContext and a DragSourceContextPeer. The DragSourceContext adds itself as a DragSourceListener to the DragSourceContextPeer.
第四、 DragSourceContextPeer receives state notifications (component entered/exited/is over) from the native system and delegates them to the DragSourceContext.
第五、 The DragSourceContext notifies the DragSourceListener, which provides drag over feedback (if the DropTargetListener accepts the action). Typical feedback includes asking the DragSourceContext to change the cursor.
最后、 When the drop is complete, the DragSourceListener receives a dragDropEnd notification message
2。创建droppable Component
创建一个 droppable Component必须和下面两个对象发生关联
* java.awt.dnd.DropTarget
DropTarget构造函数使DropTarget 和 DropTargetListener objects发生关联
Droptarget对象提供 setComponent 和addDropTargetListener 两个方法
* java.awt.dnd.DropTargetListener
The DropTargetListener needs an association with the Component so that the Component can notify the DropTargetListener to display "drag under" effects during the operation. This listener, which can be conveniently created as an inner class, transfers the data when the drop occurs. Warning: The Component itself shouldn't be the listener, since this implies its availability for use as some other Component's listener.
下面的例子演示了一个从树中拖拽一个节点到文本域中
package appletandservlet;
import java.awt.*;
import javax.swing.*;
import com.borland.jbcl.layout.XYLayout;
import com.borland.jbcl.layout.*;
import java.awt.dnd.*;
import java.awt.datatransfer.*;
import java.io.*;
import javax.swing.tree.*;
public class DragAndDrop extends JFrame {
XYLayout xYLayout1 = new XYLayout();
JScrollPane jScrollPane1 = new JScrollPane();
JTextArea jTextArea1 = new JTextArea();
public DragAndDrop() {
try {
jbInit();
} catch (Exception exception) {
exception.printStackTrace();
}
getContentPane().setLayout(xYLayout1);
jScrollPane1.getViewport().setBackground(new Color(105, 38, 125));
jTextArea1.setBackground(Color.orange);
jTextArea1.setToolTipText("");
JTree jtr = new JTree();
jtr.setBackground(Color.BLUE);
jScrollPane1.getViewport().add(jtr);
this.getContentPane().add(jTextArea1,
new XYConstraints(238, 42, 140, 248));
this.getContentPane().add(jScrollPane1,
new XYConstraints(16, 42, 217, 249));
DragSource dragSource = DragSource.getDefaultDragSource(); //创建拖拽源
dragSource.createDefaultDragGestureRecognizer(jtr,
DnDConstants.ACTION_COPY_OR_MOVE,
new DragAndDropDragGestureListener()); //建立拖拽源和事件的联系
DropTarget dropTarget = new DropTarget(jTextArea1,
new DragAndDropDropTargetListener());
}
private void jbInit() throws Exception {
}
public static void main(String[] args) {
DragAndDrop dad = new DragAndDrop();
dad.setTitle("拖拽演示");
dad.setSize(400, 300);
dad.setVisible(true);
}
}
class DragAndDropDragGestureListener implements DragGestureListener {
public void dragGestureRecognized(DragGestureEvent dge) {
//将数据存储到Transferable中,然后通知组件开始调用startDrag()初始化
JTree tree = (JTree) dge.getComponent();
TreePath path = tree.getSelectionPath();
if(path!=null){
DefaultMutableTreeNode selection = (DefaultMutableTreeNode) path
.getLastPathComponent();
DragAndDropTransferable dragAndDropTransferable = new
DragAndDropTransferable(selection);
dge.startDrag(DragSource.DefaultCopyDrop, dragAndDropTransferable, new DragAndDropDragSourceListener());
}
}
}
class DragAndDropTransferable implements Transferable {
private DefaultMutableTreeNode treeNode;
DragAndDropTransferable(DefaultMutableTreeNode treeNode) {
this.treeNode = treeNode;
}
static DataFlavor flavors[] = {DataFlavor.stringFlavor};
public DataFlavor[] getTransferDataFlavors() {
return flavors;
}
public boolean isDataFlavorSupported(DataFlavor flavor) {
if(treeNode.getChildCount()==0){
return true;
}
return false;
}
public Object getTransferData(DataFlavor flavor) throws
UnsupportedFlavorException, IOException {
return treeNode;
}
}
class DragAndDropDragSourceListener implements DragSourceListener {
public void dragDropEnd(DragSourceDropEvent dragSourceDropEvent) {
if (dragSourceDropEvent.getDropSuccess()) {
//拖拽动作结束的时候打印出移动节点的字符串
int dropAction = dragSourceDropEvent.getDropAction();
if (dropAction == DnDConstants.ACTION_MOVE) {
System.out.println("MOVE: remove node");
}
}
}
public void dragEnter(DragSourceDragEvent dragSourceDragEvent) {
DragSourceContext context = dragSourceDragEvent
.getDragSourceContext();
int dropAction = dragSourceDragEvent.getDropAction();
if ((dropAction & DnDConstants.ACTION_COPY) != 0) {
context.setCursor(DragSource.DefaultCopyDrop);
} else if ((dropAction & DnDConstants.ACTION_MOVE) != 0) {
context.setCursor(DragSource.DefaultMoveDrop);
} else {
context.setCursor(DragSource.DefaultCopyNoDrop);
}
}
public void dragExit(DragSourceEvent dragSourceEvent) {
}
public void dragOver(DragSourceDragEvent dragSourceDragEvent) {
}
public void dropActionChanged(DragSourceDragEvent dragSourceDragEvent) {
}
}
class DragAndDropDropTargetListener implements DropTargetListener{
public void dragEnter(DropTargetDragEvent dtde){
}
public void dragOver(DropTargetDragEvent dtde){
}
public void dropActionChanged(DropTargetDragEvent dtde){
}
public void dragExit(DropTargetEvent dte){
}
public void drop(DropTargetDropEvent dtde){
Transferable tr=dtde.getTransferable();//使用该函数从Transferable对象中获取有用的数据
String s="";
try {
if(tr.isDataFlavorSupported(DataFlavor.stringFlavor)){
s = tr.getTransferData(DataFlavor.stringFlavor).toString();
}
} catch (IOException ex) {
} catch (UnsupportedFlavorException ex) {
}
System.out.println(s);
DropTarget c=(DropTarget)dtde.getSource();
JTextArea d=(JTextArea)c.getComponent();
if(s!=null&&s!=""){
d.append(s + "\n");
}
}
}
摘要: 通过两种方法实现Drag and Drop:
1.比较初级的D&D:只利用java.awt.datatransfer.*中的类实现.
2.高级D&D: 利用javax.awt.dnd.*中的类实现.
比较初级D&D:只利用java.awt.datatransfer.*中的类实现.
这种方法只支持对JComponent的拖拽.
&...
阅读全文
B.1 单元测试(Unit Test)
一个单元(Unit)是指一个可独立进行的工作,独立进行指的是这个工作不受前一次或接下来的工作的结果影响。简单地说,就是不与程序运行时的上下文(Context)发生关系。
如果是在Java程序中,具体来说一个单元可以是指一个方法(Method)。这个方法不依赖于前一次运行的结果,也不牵涉到后一次的运行结果。
举例来说,下面这个程序的gcd()方法可视为一个单元:
package onlyfun.caterpillar;
public class MathTool {
public static int gcd(int num1, int num2) {
int r = 0;
while(num2 != 0) {
r = num1 % num2;
num1 = num2;
num2 = r;
}
return num1;
}
下面的gcd()方法不可视为一个单元,要完成gcd的计算,必须调用setNum1()、setNum2()与gcd() 3个方法。
package onlyfun.caterpillar;
public class MathFoo {
private static int num1;
private static int num2;
public static void setNum1(int n) {
num1 = n;
}
public static void setNum2(int n) {
num2 = n;
}
public static int gcd() {
int r = 0;
while(num2 != 0) {
r = num1 % num2;
num1 = num2;
num2 = r;
}
return num1;
}
然而要完全使用一个方法来完成一个单元操作在实现上是有困难的,所以单元也可广义解释为数个方法的集合。这数个方法组合为一个单元操作,目的是完成一个任务。
不过设计时仍优先考虑将一个公开的方法设计为单元,辅助的方法则使用设定为私用,尽量不用数个公开的方法来完成一件工作,以保持接口简洁与单元边界清晰。将工作以一个单元进行设计,这使得单元可以重用,并且也使得单元可以进行测试,进而增加类的可重用性。
单元测试指的是对每一个工作单元进行测试,了解其运行结果是否符合我们的要求。例如当编写完MathTool类之后,也许会这么写一个小小的测试程序:
package test.onlyfun.caterpillar;
import onlyfun.caterpillar.MathTool;
public class MathToolTest {
public static void main(String[] args) {
if(MathTool.gcd(10, 5) == 5) {
System.out.println("GCD Test OK!");
}
else {
System.out.println("GCD Test Fail!");
}
}
在文字模式下使用文字信息显示测试结果,这个动作是开发人员经常作的事情,然而您必须一行一行看着测试程序的输出结果,以了解测试是否成功。另一方面,测试程序本身也是一个程序,在更复杂的测试中,也许会遇到测试程序本身出错,而导致无法验证结果的情况。
JUnit是一个测试框架,通过它所提供的工具,可以减少编写错误的测试程序的机会。另一方面,可以有更好的方法来检验测试结果,而不是看着一长串输出的文字来检验测试是否成功。JUnit测试框架让测试的进行更有效率且更具可靠性。
B.2 JUnit设置
JUnit最初是由Erich Gamma与Kent Beck编写,为单元测试的支持框架,用来编写与执行重复性的测试。它包括以下特性:
Ü 对预期结果作判断
Ü 提供测试装备的生成与销毁
Ü 易于组织与执行测试
Ü 图形与文字接口的测试器
要设定JUnit,可先到 JUnit官方网站(http://junit.org/)下载JUnit的zip文件,下载后解开压缩文件,其中会含有junit.jar文件,将这个文件复制到所要的数据夹中,然后设定Classpath指向junit.jar。例如:
set classpath=%classpath%;YOUR_JUNIT_DIR\junit.jar
如果是Windows 2000/XP,可以在环境变量中设定Classpath变量(可参考附录A中的Classpath设置介绍)。
B.3 第一个JUnit测试
要对程序进行测试,首先要设计测试案例(Test Case)。一个测试案例是对程序给予假定条件,然后运行程序并看看在给定的条件下,程序的运行结果是否符合要求。
在JUnit下,可以继承TestCase来编写测试案例,并定义测试方法,每一个测试方法是以testXXX()来命名。一个例子如下所示:
package test.onlyfun.caterpillar;
import onlyfun.caterpillar.MathTool;
import junit.framework.TestCase;
public class MathToolUnitTest extends TestCase {
public void testGcd() {
assertEquals(5, MathTool.gcd(10, 5));
}
public static void main(String[] args) {
junit.textui.TestRunner.run(MathToolUnitTest.class);
}
assertEquals()方法用来断定您的预期值与单元方法实际的返回结果是否相同,如果预期值与返回的结果不同则丢出异常,TestRunner会捕捉异常,并提取其中的相关信息以报告测试结果。这里使用的是文字模式的TestRunner。
接下来根据测试案例编写实际的程序,首先试着让测试案例能通过编译:
package onlyfun.caterpillar;
public class MathTool {
public static int gcd(int num1, int num2) {
return 0;
}
}
编译完MathTool.java并用javac来编译它。在编译完成之后,接着运行测试案例,会得到以下的结果:
.F
Time: 0
There was 1 failure:
1) testGcd(test.onlyfun.caterpillar.MathToolUnitTest)junit.framework.AssertionFa
iledError: expected:<5> but was:<0>
...略
FAILURES!!!
Tests run: 1, Failures: 1, Errors: 0
由于MathTool中并没有编写什么实际的逻辑,所以测试失败。在测试驱动中,测试案例所报告的结果通常是以测试失败作为开始,您的挑战就是要一步步消除这些失败的信息。接下来根据测试案例,完成所设计的程序:
package onlyfun.caterpillar;
public class MathTool {
public static int gcd(int num1, int num2) {
int r = 0;
while(num2 != 0) {
r = num1 % num2;
num1 = num2;
num2 = r;
}
return num1;
}
再次运行测试案例,会得到以下的结果,通过最后的OK信息,知道测试已经成功:
.Time: 0
OK (1 test)
不一定要在main()中指定TestRunner,而可以直接启动一个TestRunner,并指定测试案例类(继承TestCase的类)。例如启动一个Swing窗口的测试结果画面:
java junit.swingui.TestRunner test.onlyfun.caterpillar.MathToolUnitTest
执行的结果画面如图B-1所示。
在Swing窗口的测试结果显示中,如果中间的横棒是显示绿色,表示所有的测试都已经成功,如果中间的横棒显示红色,表示测试失败。JUnit的名言是Keep the bar green to keep the code clean,意思是保持绿色横棒以保证测试成功。
也可以指定文字模式的测试结果。例如:
java junit.textui.TestRunner test.onlyfun.caterpillar.MathToolUnitTest
图B-1 JUnit的Swing窗口测试结果
B.4 自动构建与测试
Ant可以进行自动化构建,而JUnit可以进行自动化测试,Ant可以与JUnit结合,使得自动化的构建与测试变得可行。
如果要让Ant能支持JUnit,建议直接将JUnit的junit.jar放置在Ant的lib目录,并记得改变Classpath中原先有关junit.jar的设定。例如将Classpath重新指向%ANT_HOME%\lib\junit.jar(假设已经如附录A中设置了ant_home的环境变量)。虽然也有其他的方式可以设定,但这是最快也是最简单的方法。
Ant使用<junit>标签来设定JUnit测试,下面是一个简单的例子:
<?xml version="1.0"?>
<project name="autoBuildTest" default="test">
<target name="setProperties">
<property name="src.dir" value="src"/>
<property name="classes.dir" value="classes"/>
</target>
<target name="prepareDir" depends="setProperties">
<delete dir="${classes.dir}"/>
<mkdir dir="${classes.dir}"/>
</target>
<target name="compile" depends="prepareDir">
<javac srcdir="${src.dir}" destdir="${classes.dir}"/>
</target>
<target name="test" depends="compile">
<junit printsummary="yes">
<test
name="test.onlyfun.caterpillar.MathToolUnitTest"/>
<classpath>
<pathelement location="${classes.dir}"/>
</classpath>
</junit>
</target>
printsummary属性会将测试的结果简单地显示出来,<test>的name属性是设定所要进行测试的测试案例类。Ant构建与调用JUnit进行测试的信息如下:
C:\workspace\B>ant
Buildfile: build.xml
setProperties:
prepareDir:
[mkdir] Created dir: C:\workspace\B\classes
compile:
[javac] Compiling 4 source files to C:\workspace\B\classes
test:
[junit] Running test.onlyfun.caterpillar.MathToolUnitTest
[junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0 sec
BUILD SUCCESSFUL
Total time: 1 second
B.5 自动生成测试报告
接上一个主题,可以将JUnit的测试过程在Ant构建过程中显示出来,只要加入< formatter>标签设定即可:
<?xml version="1.0"?>
<project name="autoBuildTest" default="test">
...
<target name="test" depends="compile">
<junit printsummary="yes">
<formatter type="plain" usefile="false"/>
<test
name="test.onlyfun.caterpillar.MathToolUnitTest"/>
<classpath>
<pathelement location="${classes.dir}"/>
</classpath>
</junit>
</target>
</project>
Ant构建与调用JUnit进行测试的信息如下:
C:\workspace\B>ant
Buildfile: build.xml
setProperties:
prepareDir:
[delete] Deleting directory C:\workspace\B\classes
[mkdir] Created dir: C:\workspace\B\classes
compile:
[javac] Compiling 4 source files to C:\workspace\B\classes
test:
[junit] Running test.onlyfun.caterpillar.MathToolUnitTest
[junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.016 sec
[junit] Testsuite: test.onlyfun.caterpillar.MathToolUnitTest
[junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.016 sec
[junit] Testcase: testGcd took 0.016 sec
BUILD SUCCESSFUL
Total time: 2 seconds
当usefile属性设定为true时,会自动将产生的结果保存在文件中,默认是TEST-*.txt。其中*是测试案例类名称。就上例而言,所产生的报告文件内容如下:
Testsuite: test.onlyfun.caterpillar.MathToolUnitTest
Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0 sec
Testcase: testGcd took 0 sec
<formatter>标签还可以设定将测试的结果,以XML文件保存下来。一个编写的例子如下,它将测试的结果保存至report目录中, 文件名称为TEST-*.xml,*是测试案例类名称:
<?xml version="1.0"?>
...
<target name="test" depends="compile">
<junit printsummary="yes">
<formatter type="xml"/>
<test
name="test.onlyfun.caterpillar.MathToolUnitTest"/>
<classpath>
<pathelement location="${classes.dir}"/>
</classpath>
</junit>
</target>
</project>
也可以将测试结果所产生的XML文件转换为HTML文件,使用Ant可以直接完成这个工作。<junitreport>标签使用 XSLT将XML文件转换为HTML文件。下面的例子将前面的说明作个总结,以完整呈现编写的实例:
<?xml version="1.0"?>
<project name="autoBuildTest" default="report">
<target name="setProperties">
<property name="src.dir" value="src"/>
<property name="classes.dir" value="classes"/>
<property name="report.dir" value="report"/>
</target>
<target name="prepareDir" depends="setProperties">
<delete dir="${report.dir}"/>
<delete dir="${classes.dir}"/>
<mkdir dir="${report.dir}"/>
<mkdir dir="${classes.dir}"/>
</target>
<target name="compile" depends="prepareDir">
<javac srcdir="${src.dir}" destdir="${classes.dir}"/>
</target>
<target name="test" depends="compile">
<junit printsummary="yes">
<formatter type="xml"/>
<test
name="test.onlyfun.caterpillar.MathToolUnitTest"/>
<classpath>
<pathelement location="${classes.dir}"/>
</classpath>
</junit>
</target>
<target name="report" depends="test">
<junitreport todir="${report.dir}">
<fileset dir="${report.dir}">
<include name="TEST-*.xml"/>
</fileset>
<report
format="frames" todir="${report.dir}/html"/>
</junitreport>
</target>
<include>设定搜寻TEST-*.xml文件,将之转换为HTML文件,而最后的结果被设定保存至report/html/目录下,在format属性中设定了HTML文件具有边框(Frame),如果不设定这个属性,则HTML报告文件就不具有边框。在运行Ant之后所产生的 HTML测试结果报告文件如图B-2所示。
图B-2 Ant结合JUnit所自动产生的测试报告
附录B只是对JUnit的一些简介,如果需要更多有关JUnit的资料,可以参考以下的网址:
http://caterpillar.onlyfun.net/Gossip/JUnit/JUnitGossip.htm
移位运算符
包括:
“>> 右移”;“<< 左移”;“>>> 无符号右移”
例子:
-5>>3=-1
1111 1111 1111 1111 1111 1111 1111 1011
1111 1111 1111 1111 1111 1111 1111 1111
其结果与 Math.floor((double)-5/(2*2*2)) 完全相同。
-5<<3=-40
1111 1111 1111 1111 1111 1111 1111 1011
1111 1111 1111 1111 1111 1111 1101 1000
其结果与 -5*2*2*2 完全相同。
5>>3=0
0000 0000 0000 0000 0000 0000 0000 0101
0000 0000 0000 0000 0000 0000 0000 0000
其结果与 5/(2*2*2) 完全相同。
5<<3=40
0000 0000 0000 0000 0000 0000 0000 0101
0000 0000 0000 0000 0000 0000 0010 1000
其结果与 5*2*2*2 完全相同。
-5>>>3=536870911
1111 1111 1111 1111 1111 1111 1111 1011
0001 1111 1111 1111 1111 1111 1111 1111
无论正数、负数,它们的右移、左移、无符号右移 32 位都是其本身,比如 -5<<32=-5、-5>>32=-5、-5>>>32=-5。
一个有趣的现象是,把 1 左移 31 位再右移 31 位,其结果为 -1。
0000 0000 0000 0000 0000 0000 0000 0001
1000 0000 0000 0000 0000 0000 0000 0000
1111 1111 1111 1111 1111 1111 1111 1111
位逻辑运算符
包括:
& 与;| 或;~ 非(也叫做求反);^ 异或
“& 与”、“| 或”、“~ 非”是基本逻辑运算,由此可以演变出“与非”、“或非”、“与或非”复合逻辑运算。“^ 异或”是一种特殊的逻辑运算,对它求反可以得到“同或”,所以“同或”逻辑也叫“异或非”逻辑。
例子:
5&3=1
0000 0000 0000 0000 0000 0000 0000 0101
0000 0000 0000 0000 0000 0000 0000 0011
0000 0000 0000 0000 0000 0000 0000 0001
-5&3=1
1111 1111 1111 1111 1111 1111 1111 1011
0000 0000 0000 0000 0000 0000 0000 0011
0000 0000 0000 0000 0000 0000 0000 0011
5|3=7
0000 0000 0000 0000 0000 0000 0000 0101
0000 0000 0000 0000 0000 0000 0000 0011
0000 0000 0000 0000 0000 0000 0000 0111
-5|3=-5
1111 1111 1111 1111 1111 1111 1111 1011
0000 0000 0000 0000 0000 0000 0000 0011
1111 1111 1111 1111 1111 1111 1111 1011
~5=-6
0000 0000 0000 0000 0000 0000 0000 0101
1111 1111 1111 1111 1111 1111 1111 1010
~-5=4
1111 1111 1111 1111 1111 1111 1111 1011
0000 0000 0000 0000 0000 0000 0000 0100
5^3=6
0000 0000 0000 0000 0000 0000 0000 0101
0000 0000 0000 0000 0000 0000 0000 0011
0000 0000 0000 0000 0000 0000 0000 0110
-5^3=-8
1111 1111 1111 1111 1111 1111 1111 1011
0000 0000 0000 0000 0000 0000 0000 0011
1111 1111 1111 1111 1111 1111 1111 1000
请注意!引用、转贴本文应注明原作者:Rosen Jiang 以及出处:http://www.blogjava.net/rosen
要改变Swing默认的LookAndFeel,网上都说用UIManager下的一个静态方法setLookAndFeel即可,但是我用了这个方法有半年的时间也没有看到真正的WindowsLookAndFeel。昨天网上无意中才看到正解,要设置LookAndFeel,不仅要调用上面提到的方法,还要调用一个SwingUtilities类中的静态方法updateComponentTreeUI。即
try{
javax.swing.UIManager.setLookAndFeel(new com.sun.java.swing.plaf.windows.WindowsLookAndFeel());
javax.swing.SwingUtilities.updateComponentTreeUI(this);
}catch(javax.swing.UnsupportedLookAndFeelException e){
e.printStackTrace();
}
后者在运行时对整个ComponentTree进行更新,应用当前的UI设置。
public static void setLookAndFeel(String className, java.awt.Component c) {
try {
UIManager.setLookAndFeel(className);
SwingUtilities.updateComponentTreeUI(c);//注意这行
}
catch (Exception ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(null,
"不好意思,setLookAndFeel时出错了:( Errormsg:" + ex,
"setLookAndFeel",
JOptionPane.INFORMATION_MESSAGE);
}
|
GridBagLayout 不是用于简单的示例程序界面。使用GridBagLayout搭建界面就像是在起居室中搭脚手架清除画钩一样。
对于简单的程序使用Boborderlayout和Gridlayout就绰绰有余了, 但如果要把程序提到实际应用上你就得考虑使用GridBagLayout。当然, 做复杂的应用程序时,一开始就使用GridBagLayout就会更有效率。
一旦你决定使用GridBagLayout,接下来一步便是要找一些纸和铅笔,只有你准确知道你的界面看上去需要成什么样子,你就可以敲键盘。这就是说,你应该在编码之前进行妥善规划。
下面将介绍一个很小的应用程序来帮助我们学习GridBagLayout,这个例子是从一个Flickr RSS fead中显示一系列照片, 最后的界面就像下面这样:
下面的是这个界面的一个原始草图:
正如你所看到的,最终的结果看上去和计划的想法完全一样。
你应该能看到在草图里有一些线,这些线是用来把总界面分成若干行和列的,这样你就很清楚每一个组件放置的格子位置。这就是GridBagLayout里"格"的那一部分,而图上的数字就是格的号码。
在某种意义上说, 我们可以把GridBagLayout想象成为早些年的HTML3和4,它们都是基于表的布局,Grid的概念就类似rowspan和colspan的意思,只不过换了个名字罢了。
随着我们的界面和表格的设置完成,是时候该进行界面布局并开始写代码了。
工作过程
这一节我假定你已经了解了基本的窗口和组件创建知识。
通过这篇文章我们最终能在一个frame中布局组件,我们将在以后的文章对界面进行改进使它更适用。因此,为了了解这整个工作的过程,我们列出了所有的目标代码。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GridBagWindow extends JFrame {
private JButton searchBtn;
private JComboBox modeCombo;
private JLabel tagLbl;
private JLabel tagModeLbl;
private JLabel previewLbl;
private JTable resTable;
private JTextField tagTxt;
public GridBagWindow() {
Container contentPane = getContentPane();
GridBagLayout gridbag = new GridBagLayout();
contentPane.setLayout(gridbag);
GridBagConstraints c = new GridBagConstraints();
//setting a default constraint value
c.fill =GridBagConstraints.HORIZONTAL;
tagLbl = new JLabel("Tags");
c.gridx = 0; //x grid position
c.gridy = 0; //y grid position
gridbag.setConstraints(tagLbl, c); //associate the label with a constraint object
contentPane.add(tagLbl); //add it to content pane
tagModeLbl = new JLabel("Tag Mode");
c.gridx = 0;
c.gridy = 1;
gridbag.setConstraints(tagModeLbl, c);
contentPane.add(tagModeLbl);
tagTxt = new JTextField("plinth");
c.gridx = 1;
c.gridy = 0;
c.gridwidth = 2;
gridbag.setConstraints(tagTxt, c);
contentPane.add(tagTxt);
String[] options = {"all", "any"};
modeCombo = new JComboBox(options);
c.gridx = 1;
c.gridy = 1;
c.gridwidth = 1;
gridbag.setConstraints(modeCombo, c);
contentPane.add(modeCombo);
searchBtn = new JButton("Search");
c.gridx = 1;
c.gridy = 2;
gridbag.setConstraints(searchBtn, c);
contentPane.add(searchBtn);
resTable = new JTable(5,3);
c.gridx = 0;
c.gridy = 3;
c.gridwidth = 3;
gridbag.setConstraints(resTable, c);
contentPane.add(resTable);
previewLbl = new JLabel("Preview goes here");
c.gridx = 0;
c.gridy = 4;
gridbag.setConstraints(previewLbl, c);
contentPane.add(previewLbl);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public static void main(String args[]) {
GridBagWindow window = new GridBagWindow();
window.setTitle("GridBagWindow");
window.pack();
window.setVisible(true);
}
}
构造方法前的代码都不是很特殊,都是一些相当标准的import和变量定义。但是进入构造方法后,事情就变得有趣了。
Container contentPane = getContentPane();
GridBagLayout gridbag = new GridBagLayout();
contentPane.setLayout(gridbag);
我们以GridBagWindow的内容面板作为开始来创建一个GridBagLayout对象,准确地说,这个方法与过去我们所创建GridLayout对象和BorderLayout对象的方法是一样的。那么,现在我们就开始来设置GridBagLayout对象使它作为内容面板的布局。
GridBagConstraints c = new GridBagConstraints();
然后我要提到这整个进程中的一个独特的对象,那就是GridBagConstraints。这个对象在GridBagLayout中控制所有被安置在其中组件的约束。为了把一个组件增加到你的GridBagLayout中去,你首先必须将它与一个GridBagConstraints对象建立连接。
GridBagConstraints可以从11个方面来进行控制和操纵,也可以给你提供一些帮助。这些内容是:
- Gridx——组件的横向坐标
- Girdy——组件的纵向坐标
- Gridwidth——组件的横向宽度,也就是指组件占用的列数,这与HTML的colspan类似
- Gridheight——组件的纵向长度,也就是指组件占用的行数,这与HTML的rowspan类似
- Weightx——指行的权重,告诉布局管理器如何分配额外的水平空间
- Weighty——指列的权重,告诉布局管理器如何分配额外的垂直空间
- Anchor——告诉布局管理器组件在表格空间中的位置
- Fill——如果显示区域比组件的区域大的时候,可以用来控制组件的行为。控制组件是垂直填充,还是水平填充,或者两个方向一起填充
- Insets——指组件与表格空间四周边缘的空白区域的大小
- Ipadx—— 组件间的横向间距,组件的宽度就是这个组件的最小宽度加上ipadx值
- ipady—— 组件间的纵向间距,组件的高度就是这个组件的最小高度加上ipady值
可能对于一个组件的每一个实例你都需要为它建立一个单独的GridBagConstraints;然而,这种方法我们并不推荐使用。最好的方法是,当你调用它的时候把对象设置为默认值,然后针对于每一个组件改变其相应的域。
这个方法具有通用性,因为在一些域中,比如insets、padx、pady和fill这些域,对于每一个组件来说一般都是相同的,因此这样对一个域进行设置就会更轻松了,也能更轻松的在另外的组件中改变某些域的值。
如果在改变了某些域值之后,你想回到原始的域值的话,你应该在增加下一个组件之前进行改变。这种方法使你更容易明白你正在修改的内容,也能使你更容易明白在一连串对象中的这11个参数的作用。
也许你现在对这些内容还是一知半解,不过事实上一旦你理解了GridBagConstraints,值得安慰的是你以后做再困难的工作都会游刃有余了。
所以,如果我们已经明白了GridBagConstraints的详细用法了,那么现在就让我们来看看在实际应用中应该如何来实现它:
tagLbl = new JLabel("Tags");
c.gridx = 0; //x grid position
c.gridy = 0; //y grid position
gridbag.setConstraints(tagLbl, c); //设置标签的限制
contentPane.add(tagLbl); //增加到内容面板
我们所做的是示例我们的标签、分配给它一个格位置,将它与一个约束对象联系起来并把它增加到我们的内容面板中。
tagModeLbl = new JLabel("Tag Mode");
c.gridx = 0;
c.gridy = 1;
gridbag.setConstraints(tagModeLbl, c);
contentPane.add(tagModeLbl);
请注意,虽然我们已经在我们的约束对象中把gridx的值设置为0,但是在这里我们仍然要对它进行重新设置——这样做没有其它原因,只是为了增加可读性。
下面,我们增加一个文本域以便能存储我们希望能搜索到的关键字,再增加一个组合框以便用来搜索多个关键字。除了我们希望的文本域有两列之外,这个概念其他的方面都与上面所说的是相同的,所以,我们需要在增加组合框之前重新设置文本域的值。
tagTxt = new JTextField("plinth");
c.gridx = 1;
c.gridy = 0;
c.gridwidth = 2;
gridbag.setConstraints(tagTxt, c);
contentPane.add(tagTxt);
String[] options = {"all", "any"};
modeCombo = new JComboBox(options);
c.gridx = 1;
c.gridy = 1;
c.gridwidth = 1;
gridbag.setConstraints(modeCombo, c);
contentPane.add(modeCombo);
做了这些之后,我们再在内容面板中增加一些其余的简单组件,这时候我们就能够浏览它了;其余的代码应该不会出现任何问题了。
到这个阶段,我们应该已经得到了一个类似于我们先前所设计的界面了。
进一步学习
当然,界面不是智能的。重新设置窗口的大小,看看将会发生些什么事情。为什么它会那样呢?那是因为我们设置了约束对象的weightx、weighty和fill的值。
关于其他类似的一些内容我们将在后面的章节中进行介绍,但是如果你希望能自己试试的话,参考GridBigLayout和GridBagConstraintsAPI文档会对扩充你的知识提供很好的帮助。
请先看下面这段程序:
public class Hello{
public static void main(String[] args){ //(1)
System.out.println("Hello,world!"); //(2)
}
}
看过这段程序,对于大多数学过Java 的从来说,都不陌生。即使没有学过Java,而学过其它的高
级语言,例如C,那你也应该能看懂这段代码的意思。它只是简单的输出“Hello,world”,一点
别的用处都没有,然而,它却展示了static关键字的主要用法。
在1处,我们定义了一个静态的方法名为main,这就意味着告诉Java编译器,我这个方法不需要创建一个此类的对象即可使用。你还得你是怎么运行这个程序吗?一般,我们都是在命令行下,打入如下的命令(加下划线为手动输入):
javac Hello.java
java Hello
Hello,world!
这就是你运行的过程,第一行用来编译Hello.java这个文件,执行完后,如果你查看当前,会发现多了一个Hello.class文件,那就是第一行产生的Java二进制字节码。第二行就是执行一个Java程序的最普遍做法。执行结果如你所料。在2中,你可能会想,为什么要这样才能输出。好,我们来分解一下这条语句。(如果没有安装Java文档,请到Sun的官方网站浏览J2SE API)首先,System是位于java.lang包中的一个核心类,如果你查看它的定义,你会发现有这样一行:public static final PrintStream out;接着在进一步,点击PrintStream这个超链接,在METHOD页面,你会看到大量定义的方法,查找println,会有这样一行:
public void println(String x)。好了,现在你应该明白为什么我们要那样调用了,out是System的一个静态变量,所以可以直接使用,而out所属的类有一个println方法。
静态方法
通常,在一个类中定义一个方法为static,那就是说,无需本类的对象即可调用此方法。如下所示:
class Simple{
static void go(){
System.out.println("Go...");
}
}
public class Cal{
public static void main(String[] args){
Simple.go();
}
}
调用一个静态方法就是“类名.方法名”,静态方法的使用很简单如上所示。一般来说,静态方法常常为应用程序中的其它类提供一些实用工具所用,在Java的类库中大量的静态方法正是出于此目的而定义的。
静态变量
静态变量与静态方法类似。所有此类实例共享此静态变量,也就是说在类装载时,只分配一块存储空间,所有此类的对象都可以操控此块存储空间,当然对于final则另当别论了。看下面这段代码:
class Value{
static int c=0;
static void inc(){
c++;
}}
class Count{
public static void prt(String s){
System.out.println(s);
}
public static void main(String[] args){
Value v1,v2;
v1=new Value();
v2=new Value();
prt("v1.c="+v1.c+"
v2.c="+v2.c);
v1.inc();
prt("v1.c="+v1.c+" v2.c="+v2.c);
}}
结果如下:
v1.c=0 v2.c=0
v1.c=1 v2.c=1
由此可以证明它们共享一块存储区。static变量有点类似于C中的全局变量的概念。值得探讨的是静态变量的初始化问题。我们修改上面的程序:
class Value{
static int c=0;
Value()
Value(int i){
c=i;
}
static void inc(){
c++;
}}
class Count{
public static void prt(String s){
System.out.println(s);
}
Value v=new Value(10);
static Value v1,v2;
static{
prt("v1.c="+v1.c+"
v2.c="+v2.c);
v1=new Value(27);
prt("v1.c="+v1.c+" v2.c="+v2.c);
v2=new Value(15);
prt("v1.c="+v1.c+" v2.c="+v2.c);
}
public static void main(String[] args){
Count ct=new Count();
prt("ct.c="+ct.v.c);
prt("v1.c="+v1.c+" v2.c="+v2.c);
v1.inc();
prt("v1.c="+v1.c+" v2.c="+v2.c);
prt("ct.c="+ct.v.c);
}}
运行结果如下:
v1.c=0 v2.c=0
v1.c=27 v2.c=27
v1.c=15 v2.c=15
ct.c=10
v1.c=10 v2.c=10
v1.c=11 v2.c=11
ct.c=11
这个程序展示了静态初始化的各种特性。如果你初次接触Java,结果可能令你吃惊。可能会对static后加大括号感到困惑。首先要告诉你的是,static定义的变量会优先于任何其它非static变量,不论其出现的顺序如何。正如在程序中所表现的,虽然v出现在v1和v2的前面,但是结果却是v1和v2的初始化在v的前面。在static{后面跟着一段代码,这是用来进行显式的静态变量初始化,这段代码只会初始化一次,且在类被第一次装载时。如果你能读懂并理解这段代码,会帮助你对static关键字的认识。在涉及到继承的时候,会先初始化父类的static变量,然后是子类的,依次类推。非静态变量不是本文的主题,在此不做详细讨论,请参考Think in Java中的讲解。
静态类
通常一个普通类不允许声明为静态的,只有一个内部类才可以。这时这个声明为静态的内部类可以直接作为一个普通类来使用,而不需实例一个外部类。如下代码所示:
public class StaticCls{
public static void main(String[] args){
OuterCls.InnerCls oi=new OuterCls.InnerCls();
}}
class OuterCls{
public static class InnerCls{
InnerCls(){
System.out.println("InnerCls");
}
}}
输出结果会如你所料:
InnerCls
EasyMock 2.3 Readme
Documentation for release 2.3 (July 9 2007)
© 2001-2007 OFFIS, Tammo Freese.
EasyMock 2 is a library that provides an easy way to use Mock Objects for given interfaces. EasyMock 2 is available under the terms of the MIT license.
Mock Objects simulate parts of the behavior of domain code, and are able to check whether they are used as defined. Domain classes can be tested in isolation by simulating their collaborators with Mock Objects.
Writing and maintaining Mock Objects often is a tedious task that may introduce errors. EasyMock 2 generates Mock Objects dynamically - no need to write them, and no generated code!
EasyMock 2 Benefits
- Hand-writing classes for Mock Objects is not needed.
- Supports refactoring-safe Mock Objects: test code will not break at runtime when renaming methods or reordering method parameters
- Supports return values and exceptions.
- Supports checking the order of method calls, for one or more Mock Objects.
EasyMock 2 Drawbacks
- EasyMock 2 does only work with Java 2 Version 5.0 and above.
EasyMock by default supports the generation of Mock Objects for interfaces only. For those who would like to generate Mock Objects for classes, there is an extension available at the EasyMock home page.
Installation
- Java 2 (at least 5.0) is required.
- Unzip the EasyMock zip file (
easymock2.3.zip
). It contains a directory easymock2.3
. Add the EasyMock jar file (easymock.jar
) from this directory to your classpath.
To execute the EasyMock tests, add tests.zip
and the JUnit 4.1 jar to your class path and start 'java org.easymock.tests.AllTests'
.
The source code of EasyMock is stored in the zip file src.zip
.
Usage
Most parts of a software system do not work in isolation, but collaborate with other parts to get their job done. In a lot of cases, we do not care about using collaborators in unit testing, as we trust these collaborators. If we do care about it, Mock Objects help us to test the unit under test in isolation. Mock Objects replace collaborators of the unit under test.
The following examples use the interface Collaborator
:
package org.easymock.samples;
public interface Collaborator {
void documentAdded(String title);
void documentChanged(String title);
void documentRemoved(String title);
byte voteForRemoval(String title);
byte[] voteForRemovals(String[] title);
}
Implementors of this interface are collaborators (in this case listeners) of a class named ClassUnderTest
:
public class ClassUnderTest {
// ...
public void addListener(Collaborator listener) {
// ...
}
public void addDocument(String title, byte[] document) {
// ...
}
public boolean removeDocument(String title) {
// ...
}
public boolean removeDocuments(String[] titles) {
// ...
}
}
The code for both the class and the interface may be found in the package org.easymock.samples
in samples.zip
.
The following examples assume that you are familiar with the JUnit testing framework. Although the tests shown here use JUnit in version 3.8.1, you may as well use JUnit 4 or TestNG.
The first Mock Object
We will now build a test case and toy around with it to understand the functionality of the EasyMock package. samples.zip
contains a modified version of this test. Our first test should check whether the removal of a non-existing document does not lead to a notification of the collaborator. Here is the test without the definition of the Mock Object:
package org.easymock.samples;
import junit.framework.TestCase;
public class ExampleTest extends TestCase {
private ClassUnderTest classUnderTest;
private Collaborator mock;
protected void setUp() {
classUnderTest = new ClassUnderTest();
classUnderTest.addListener(mock);
}
public void testRemoveNonExistingDocument() {
// This call should not lead to any notification
// of the Mock Object:
classUnderTest.removeDocument("Does not exist");
}
}
For many tests using EasyMock 2, we only need a static import of methods of org.easymock.EasyMock
. This is the only non-internal, non-deprecated class of EasyMock 2.
import static org.easymock.EasyMock.*;
import junit.framework.TestCase;
public class ExampleTest extends TestCase {
private ClassUnderTest classUnderTest;
private Collaborator mock;
}
To get a Mock Object, we need to
- create a Mock Object for the interface we would like to simulate,
- record the expected behavior, and
- switch the Mock Object to replay state.
Here is a first example:
protected void setUp() {
mock = createMock(Collaborator.class); // 1
classUnderTest = new ClassUnderTest();
classUnderTest.addListener(mock);
}
public void testRemoveNonExistingDocument() {
// 2 (we do not expect anything)
replay(mock); // 3
classUnderTest.removeDocument("Does not exist");
}
After activation in step 3, mock
is a Mock Object for the Collaborator
interface that expects no calls. This means that if we change our ClassUnderTest
to call any of the interface's methods, the Mock Object will throw an AssertionError
:
java.lang.AssertionError:
Unexpected method call documentRemoved("Does not exist"):
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentRemoved(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentRemoved(ClassUnderTest.java:74)
at org.easymock.samples.ClassUnderTest.removeDocument(ClassUnderTest.java:33)
at org.easymock.samples.ExampleTest.testRemoveNonExistingDocument(ExampleTest.java:24)
...
Adding Behavior
Let us write a second test. If a document is added on the class under test, we expect a call to mock.documentAdded()
on the Mock Object with the title of the document as argument:
public void testAddDocument() {
mock.documentAdded("New Document"); // 2
replay(mock); // 3
classUnderTest.addDocument("New Document", new byte[0]);
}
So in the record state (before calling replay
), the Mock Object does not behave like a Mock Object, but it records method calls. After calling replay
, it behaves like a Mock Object, checking whether the expected method calls are really done.
If classUnderTest.addDocument("New Document", new byte[0])
calls the expected method with a wrong argument, the Mock Object will complain with an AssertionError
:
java.lang.AssertionError:
Unexpected method call documentAdded("Wrong title"):
documentAdded("New Document"): expected: 1, actual: 0
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentAdded(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentAdded(ClassUnderTest.java:61)
at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:28)
at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:30)
...
All missed expectations are shown, as well as all fulfilled expectations for the unexpected call (none in this case). If the method call is executed too often, the Mock Object complains, too:
java.lang.AssertionError:
Unexpected method call documentAdded("New Document"):
documentAdded("New Document"): expected: 1, actual: 1 (+1)
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentAdded(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentAdded(ClassUnderTest.java:62)
at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:29)
at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:30)
...
Verifying Behavior
There is one error that we have not handled so far: If we specify behavior, we would like to verify that it is actually used. The current test would pass if no method on the Mock Object is called. To verify that the specified behavior has been used, we have to call verify(mock)
:
public void testAddDocument() {
mock.documentAdded("New Document"); // 2
replay(mock); // 3
classUnderTest.addDocument("New Document", new byte[0]);
verify(mock);
}
If the method is not called on the Mock Object, we now get the following exception:
java.lang.AssertionError:
Expectation failure on verify:
documentAdded("New Document"): expected: 1, actual: 0
at org.easymock.internal.MocksControl.verify(MocksControl.java:70)
at org.easymock.EasyMock.verify(EasyMock.java:536)
at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:31)
...
The message of the exception lists all missed expectations.
Expecting an Explicit Number of Calls
Up to now, our test has only considered a single method call. The next test should check whether the addition of an already existing document leads to a call to mock.documentChanged()
with the appropriate argument. To be sure, we check this three times (hey, it is an example ;-)):
public void testAddAndChangeDocument() {
mock.documentAdded("Document");
mock.documentChanged("Document");
mock.documentChanged("Document");
mock.documentChanged("Document");
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
verify(mock);
}
To avoid the repetition of mock.documentChanged("Document")
, EasyMock provides a shortcut. We may specify the call count with the method times(int times)
on the object returned by expectLastCall()
. The code then looks like:
public void testAddAndChangeDocument() {
mock.documentAdded("Document");
mock.documentChanged("Document");
expectLastCall().times(3);
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
verify(mock);
}
If the method is called too often, we get an exception that tells us that the method has been called too many times. The failure occurs immediately at the first method call exceeding the limit:
java.lang.AssertionError:
Unexpected method call documentChanged("Document"):
documentChanged("Document"): expected: 3, actual: 3 (+1)
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentChanged(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentChanged(ClassUnderTest.java:67)
at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:26)
at org.easymock.samples.ExampleTest.testAddAndChangeDocument(ExampleTest.java:43)
...
If there are too few calls, verify(mock)
throws an AssertionError
:
java.lang.AssertionError:
Expectation failure on verify:
documentChanged("Document"): expected: 3, actual: 2
at org.easymock.internal.MocksControl.verify(MocksControl.java:70)
at org.easymock.EasyMock.verify(EasyMock.java:536)
at org.easymock.samples.ExampleTest.testAddAndChangeDocument(ExampleTest.java:43)
...
Specifying Return Values
For specifying return values, we wrap the expected call in expect(T value)
and specify the return value with the method andReturn(Object returnValue)
on the object returned by expect(T value)
.
As an example, we check the workflow for document removal. If ClassUnderTest
gets a call for document removal, it asks all collaborators for their vote for removal with calls to byte voteForRemoval(String title)
value. Positive return values are a vote for removal. If the sum of all values is positive, the document is removed and documentRemoved(String title)
is called on all collaborators:
public void testVoteForRemoval() {
mock.documentAdded("Document"); // expect document addition
// expect to be asked to vote for document removal, and vote for it
expect(mock.voteForRemoval("Document")).andReturn((byte) 42);
mock.documentRemoved("Document"); // expect document removal
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
assertTrue(classUnderTest.removeDocument("Document"));
verify(mock);
}
public void testVoteAgainstRemoval() {
mock.documentAdded("Document"); // expect document addition
// expect to be asked to vote for document removal, and vote against it
expect(mock.voteForRemoval("Document")).andReturn((byte) -42);
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
assertFalse(classUnderTest.removeDocument("Document"));
verify(mock);
}
The type of the returned value is checked at compile time. As an example, the following code will not compile, as the type of the provided return value does not match the method's return value:
expect(mock.voteForRemoval("Document")).andReturn("wrong type");
Instead of calling expect(T value)
to retrieve the object for setting the return value, we may also use the object returned by expectLastCall()
. Instead of
expect(mock.voteForRemoval("Document")).andReturn((byte) 42);
we may use
mock.voteForRemoval("Document");
expectLastCall().andReturn((byte) 42);
This type of specification should only be used if the line gets too long, as it does not support type checking at compile time.
Working with Exceptions
For specifying exceptions (more exactly: Throwables) to be thrown, the object returned by expectLastCall()
and expect(T value)
provides the method andThrow(Throwable throwable)
. The method has to be called in record state after the call to the Mock Object for which it specifies the Throwable
to be thrown.
Unchecked exceptions (that is, RuntimeException
, Error
and all their subclasses) can be thrown from every method. Checked exceptions can only be thrown from the methods that do actually throw them.
Creating Return Values or Exceptions
Sometimes we would like our mock object to return a value or throw an exception that is created at the time of the actual call. Since EasyMock 2.2, the object returned by expectLastCall()
and expect(T value)
provides the method andAnswer(IAnswer answer)
which allows to specify an implementation of the interface IAnswer
that is used to create the return value or exception.
Inside an IAnswer
callback, the arguments passed to the mock call are available via EasyMock.getCurrentArguments()
. If you use these, refactorings like reordering parameters may break your tests. You have been warned.
Changing Behavior for the Same Method Call
It is also possible to specify a changing behavior for a method. The methods times
, andReturn
, and andThrow
may be chained. As an example, we define voteForRemoval("Document")
to
- return 42 for the first three calls,
- throw a
RuntimeException
for the next four calls,
- return -42 once.
expect(mock.voteForRemoval("Document"))
.andReturn((byte) 42).times(3)
.andThrow(new RuntimeException(), 4)
.andReturn((byte) -42);
Relaxing Call Counts
To relax the expected call counts, there are additional methods that may be used instead of times(int count)
:
times(int min, int max)
- to expect between
min
and max
calls,
atLeastOnce()
- to expect at least one call, and
anyTimes()
- to expected an unrestricted number of calls.
If no call count is specified, one call is expected. If we would like to state this explicitely, once()
or times(1)
may be used.
Strict Mocks
On a Mock Object returned by a EasyMock.createMock()
, the order of method calls is not checked. If you would like a strict Mock Object that checks the order of method calls, use EasyMock.createStrictMock()
to create it.
If an unexpected method is called on a strict Mock Object, the message of the exception will show the method calls expected at this point followed by the first conflicting one. verify(mock)
shows all missing method calls.
Switching Order Checking On and Off
Sometimes, it is necessary to have a Mock Object that checks the order of only some calls. In record phase, you may switch order checking on by calling checkOrder(mock, true)
and switch it off by calling checkOrder(mock, false)
.
There are two differences between a strict Mock Object and a normal Mock Object:
- A strict Mock Object has order checking enabled after creation.
- A strict Mock Object has order checking enabled after reset (see Reusing a Mock Object).
Flexible Expectations with Argument Matchers
To match an actual method call on the Mock Object with an expectation, Object
arguments are by default compared with equals()
. This may lead to problems. As an example, we consider the following expectation:
String[] documents = new String[] { "Document 1", "Document 2" };
expect(mock.voteForRemovals(documents)).andReturn(42);
If the method is called with another array with the same contents, we get an exception, as equals()
compares object identity for arrays:
java.lang.AssertionError:
Unexpected method call voteForRemovals([Ljava.lang.String;@9a029e):
voteForRemovals([Ljava.lang.String;@2db19d): expected: 1, actual: 0
documentRemoved("Document 1"): expected: 1, actual: 0
documentRemoved("Document 2"): expected: 1, actual: 0
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.voteForRemovals(Unknown Source)
at org.easymock.samples.ClassUnderTest.listenersAllowRemovals(ClassUnderTest.java:88)
at org.easymock.samples.ClassUnderTest.removeDocuments(ClassUnderTest.java:48)
at org.easymock.samples.ExampleTest.testVoteForRemovals(ExampleTest.java:83)
...
To specify that only array equality is needed for this call, we may use the method aryEq
that is statically imported from the EasyMock
class:
String[] documents = new String[] { "Document 1", "Document 2" };
expect(mock.voteForRemovals(aryEq(documents))).andReturn(42);
If you would like to use matchers in a call, you have to specify matchers for all arguments of the method call.
There are a couple of predefined argument matchers available.
eq(X value)
- Matches if the actual value is equals the expected value. Available for all primitive types and for objects.
anyBoolean()
, anyByte()
, anyChar()
, anyDouble()
, anyFloat()
, anyInt()
, anyLong()
, anyObject()
, anyShort()
- Matches any value. Available for all primitive types and for objects.
eq(X value, X delta)
- Matches if the actual value is equal to the given value allowing the given delta. Available for
float
and double
.
aryEq(X value)
- Matches if the actual value is equal to the given value according to
Arrays.equals()
. Available for primitive and object arrays.
isNull()
- Matches if the actual value is null. Available for objects.
notNull()
- Matches if the actual value is not null. Available for objects.
same(X value)
- Matches if the actual value is the same as the given value. Available for objects.
isA(Class clazz)
- Matches if the actual value is an instance of the given class, or if it is in instance of a class that extends or implements the given class. Available for objects.
lt(X value)
, leq(X value)
, geq(X value)
, gt(X value)
- Matches if the actual value is less/less or equal/greater or equal/greater than the given value. Available for all numeric primitive types and
Comparable
.
startsWith(String prefix), contains(String substring), endsWith(String suffix)
- Matches if the actual value starts with/contains/ends with the given value. Available for
String
s.
matches(String regex), find(String regex)
- Matches if the actual value/a substring of the actual value matches the given regular expression. Available for
String
s.
and(X first, X second)
- Matches if the matchers used in
first
and second
both match. Available for all primitive types and for objects.
or(X first, X second)
- Matches if one of the matchers used in
first
and second
match. Available for all primitive types and for objects.
not(X value)
- Matches if the matcher used in
value
does not match.
cmpEq(X value)
- Matches if the actual value is equals according to
Comparable.compareTo(X o)
. Available for all numeric primitive types and Comparable
.
cmp(X value, Comparator comparator, LogicalOperator operator)
- Matches if
comparator.compare(actual, value) operator 0
where the operator is <,<=,>,>= or ==. Available for objects.
Defining your own Argument Matchers
Sometimes it is desirable to define own argument matchers. Let's say that an argument matcher is needed that matches an exception if the given exception has the same type and an equal message. It should be used this way:
IllegalStateException e = new IllegalStateException("Operation not allowed.")
expect(mock.logThrowable(eqException(e))).andReturn(true);
Two steps are necessary to achieve this: The new argument matcher has to be defined, and the static method eqException
has to be declared.
To define the new argument matcher, we implement the interface org.easymock.IArgumentMatcher
. This interface contains two methods: matches(Object actual)
checks whether the actual argument matches the given argument, and appendTo(StringBuffer buffer)
appends a string representation of the argument matcher to the given string buffer. The implementation is straightforward:
import org.easymock.IArgumentMatcher;
public class ThrowableEquals implements IArgumentMatcher {
private Throwable expected;
public ThrowableEquals(Throwable expected) {
this.expected = expected;
}
public boolean matches(Object actual) {
if (!(actual instanceof Throwable)) {
return false;
}
String actualMessage = ((Throwable) actual).getMessage();
return expected.getClass().equals(actual.getClass())
&& expected.getMessage().equals(actualMessage);
}
public void appendTo(StringBuffer buffer) {
buffer.append("eqException(");
buffer.append(expected.getClass().getName());
buffer.append(" with message \"");
buffer.append(expected.getMessage());
buffer.append("\"")");
}
}
The method eqException
must create the argument matcher with the given Throwable, report it to EasyMock via the static method reportMatcher(IArgumentMatcher matcher)
, and return a value so that it may be used inside the call (typically 0
, null
or false
). A first attempt may look like:
public static Throwable eqException(Throwable in) {
EasyMock.reportMatcher(new ThrowableEquals(in));
return null;
}
However, this only works if the method logThrowable
in the example usage accepts Throwable
s, and does not require something more specific like a RuntimeException
. In the latter case, our code sample would not compile:
IllegalStateException e = new IllegalStateException("Operation not allowed.")
expect(mock.logThrowable(eqException(e))).andReturn(true);
Java 5.0 to the rescue: Instead of defining eqException
with a Throwable
as parameter and return value, we use a generic type that extends Throwable
:
public static <T extends Throwable> T eqException(T in) {
reportMatcher(new ThrowableEquals(in));
return null;
}
Reusing a Mock Object
Mock Objects may be reset by reset(mock)
.
Using Stub Behavior for Methods
Sometimes, we would like our Mock Object to respond to some method calls, but we do not want to check how often they are called, when they are called, or even if they are called at all. This stub behavoir may be defined by using the methods andStubReturn(Object value)
, andStubThrow(Throwable throwable)
, andStubAnswer(IAnswer answer)
and asStub()
. The following code configures the MockObject to answer 42 to voteForRemoval("Document")
once and -1 for all other arguments:
expect(mock.voteForRemoval("Document")).andReturn(42);
expect(mock.voteForRemoval(not(eq("Document")))).andStubReturn(-1);
Nice Mocks
On a Mock Object returned by createMock()
the default behavior for all methods is to throw an AssertionError
for all unexpected method calls. If you would like a "nice" Mock Object that by default allows all method calls and returns appropriate empty values (0
, null
or false
), use createNiceMock()
instead.
Object Methods
The behavior for the three object methods equals()
, hashCode()
and toString()
cannot be changed for Mock Objects created with EasyMock, even if they are part of the interface for which the Mock Object is created.
Checking Method Call Order Between Mocks
Up to this point, we have seen a mock object as a single object that is configured by static methods on the class EasyMock
. But many of these static methods just identify the hidden control of the Mock Object and delegate to it. A Mock Control is an object implementing the IMocksControl
interface.
So instead of
IMyInterface mock = createStrictMock(IMyInterface.class);
replay(mock);
verify(mock);
reset(mock);
we may use the equivalent code:
IMocksControl ctrl = createStrictControl();
IMyInterface mock = ctrl.createMock(IMyInterface.class);
ctrl.replay();
ctrl.verify();
ctrl.reset();
The IMocksControl allows to create more than one Mock Object, and so it is possible to check the order of method calls between mocks. As an example, we set up two mock objects for the interface IMyInterface
, and we expect the calls mock1.a()
and mock2.a()
ordered, then an open number of calls to mock1.c()
and mock2.c()
, and finally mock2.b()
and mock1.b()
, in this order:
IMocksControl ctrl = createStrictControl();
IMyInterface mock1 = ctrl.createMock(IMyInterface.class);
IMyInterface mock2 = ctrl.createMock(IMyInterface.class);
mock1.a();
mock2.a();
ctrl.checkOrder(false);
mock1.c();
expectLastCall().anyTimes();
mock2.c();
expectLastCall().anyTimes();
ctrl.checkOrder(true);
mock2.b();
mock1.b();
ctrl.replay();
Naming Mock Objects
Mock Objects can be named at creation using createMock(String name, Class toMock)
, createStrictMock(String name, Class toMock)
or createNiceMock(String name, Class toMock)
. The names will be shown in exception failures.
Backward Compatibility
EasyMock 2 contains a compatibility layer so that tests using EasyMock 1.2 for Java 1.5 should work without any modification. The only known differences are visible when failures occur: there are small changes in the failure messages and stack traces, and failures are now reported using Java's AssertionError
instead of JUnit's AssertionFailedError
.
EasyMock 2.1 introduced a callback feature that has been removed in EasyMock 2.2, as it was too complex. Since EasyMock 2.2, the IAnswer
interface provides the functionality for callbacks.
EasyMock Development
EasyMock 1.0 has been developed by Tammo Freese at OFFIS. The development of EasyMock is now hosted on SourceForge to allow other developers and companies to contribute.
Thanks to the people who gave feedback or provided patches, including Nascif Abousalh-Neto, Dave Astels, Francois Beausoleil, George Dinwiddie, Shane Duan, Wolfgang Frech, Steve Freeman, Oren Gross, John D. Heintz, Dale King, Brian Knorr, Dierk Koenig, Chris Kreussling, Robert Leftwich, Patrick Lightbody, Johannes Link, Rex Madden, David McIntosh, Karsten Menne, Bill Michell, Stephan Mikaty, Ivan Moore, Ilja Preuss, Justin Sampson, Markus Schmidlin, Richard Scott, Joel Shellman, Jiří Mareš, Alexandre de Pellegrin Shaun Smith, Marco Struck, Ralf Stuckert, Victor Szathmary, Henri Tremblay, Bill Uetrecht, Frank Westphal, Chad Woolley, Bernd Worsch, and numerous others.
Please check the EasyMock home page for new versions, and send bug reports and suggestions to the EasyMock Yahoo!Group. If you would like to subscribe to the EasyMock Yahoo!Group, send a message to easymock-subscribe@yahoogroups.com.
EasyMock Version 2.3 (July 9 2007)
Changes since 2.2:
- French documentation
- Matchers for Comparable parameters
- Decimal comparison fix
- Mock Objects can now be named
- Include Bill Michell's ThreadLocal fix
- Converted EasyMock's unit tests to JUnit 4
Changes since 2.1:
- answers for expected calls can now be created at call time via
andAnswer(IAnswer answer)
and andStubAnswer(IAnswer answer)
callback(Runnable runnable)
has been removed, for callbacks, please switch to andAnswer(IAnswer answer)
and andStubAnswer(IAnswer answer)
replay()
, verify()
and reset()
now accept multiple mock objects as arguments
Changes since 2.0:
- arguments passed to the mock object are now available in callbacks via
EasyMock.getCurrentArguments()
- fixed bug reported in http://groups.yahoo.com/group/easymock/message/558
- earlier failing if unused matchers were specified
Changes since 1.2:
- support for flexible, refactoring-safe argument matchers
- no mock control is needed for single Mock Objects
- stub behavior replaces default behavior
- support for call order checking for more than one mock, and to switch order checking on and off
- support for callbacks
- EasyMock now throws
java.lang.AssertionError
instead of junit.framework.AssertionFailedError
so that it is now independent from the testing framework, you may use it with JUnit 3.8.x, JUnit 4 and TestNG
- deprecated old API
关于单元测试,模拟对象一直是不可缺少的,尤其对于复杂的应用来说。
这么多的模拟对象框架中,个人觉得比较好用的当属EasyMock了。当然JMock也不错。
下面简单介绍一下EasyMock 。(基本翻译EasyMock的文档,可能有些地方不是很恰当)
EasyMock 2 主要用于给指定的接口提供模拟对象。
模拟对象只是模拟领域代码直接的部分行为,能检测是否他们如定义中的被使用。使用 Mock 对象,来模拟合作接口,有助于隔离测试相应的领域类。
创建和维持 Mock 对象经常是繁琐的任务,并且可能会引入错误。 EasyMock 2 动态产生 Mock 对象,不需要创建,并且不会产生代码。
有利的方面:
不需要手工写类来处理 mock 对象。
支持安全的重构 Mock 对象:测试代码不会在运行期打断当重新命名方法或者更改方法参数。
支持返回值和例外。
支持检察方法调用次序,对于一个或者多个 Mock 对象。
不利的方面: 2.0 仅使用于 java 2 版本 5.0 或者以上
以一个例子来说明如何使用EasyMock:
假设有一个合作接口Collaborator:
package org.easymock.samples;
public interface Collaborator {
void documentAdded(String title);
void documentChanged(String title);
void documentRemoved(String title);
byte voteForRemoval(String title);
byte[] voteForRemovals(String[] title);
}
|
我们主要的测试类为:
package org.easymock.samples;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class ClassUnderTest {
private Set<Collaborator> listeners = new HashSet<Collaborator>();
private Map<String, byte[]> documents = new HashMap<String, byte[]>();
public void addListener(Collaborator listener) {
listeners.add(listener);
}
public void addDocument(String title, byte[] document) {
boolean documentChange = documents.containsKey(title);
documents.put(title, document);
if (documentChange) {
notifyListenersDocumentChanged(title);
} else {
notifyListenersDocumentAdded(title);
}
}
public boolean removeDocument(String title) {
if (!documents.containsKey(title)) {
return true;
}
if (!listenersAllowRemoval(title)) {
return false;
}
documents.remove(title);
notifyListenersDocumentRemoved(title);
return true;
}
public boolean removeDocuments(String[] titles) {
if (!listenersAllowRemovals(titles)) {
return false;
}
for (String title : titles) {
documents.remove(title);
notifyListenersDocumentRemoved(title);
}
return true;
}
private void notifyListenersDocumentAdded(String title) {
for (Collaborator listener : listeners) {
listener.documentAdded(title);
}
}
private void notifyListenersDocumentChanged(String title) {
for (Collaborator listener : listeners) {
listener.documentChanged(title);
}
}
private void notifyListenersDocumentRemoved(String title) {
for (Collaborator listener : listeners) {
listener.documentRemoved(title);
}
}
private boolean listenersAllowRemoval(String title) {
int result = 0;
for (Collaborator listener : listeners) {
result += listener.voteForRemoval(title);
}
return result > 0;
}
private boolean listenersAllowRemovals(String[] titles) {
int result = 0;
for (Collaborator listener : listeners) {
result += listener.voteForRemovals(titles);
}
return result > 0;
}
}
|
第一个Mock 对象
我们将创建test case 并且围绕此理解相关的EasyMock 包的功能。第一个测试方法,用于检测是否删除一个不存在的文档,不会发通知给合作类。
package org.easymock.samples;
import junit.framework.TestCase;
public class ExampleTest extends TestCase {
private ClassUnderTest classUnderTest;
private Collaborator mock;
protected void setUp() {
classUnderTest = new ClassUnderTest();
classUnderTest.addListener(mock);
}
public void testRemoveNonExistingDocument() {
// This call should not lead to any notification
// of the Mock Object:
classUnderTest.removeDocument("Does not exist");
}
}
|
对于多数测试类,使用EasyMock 2,我们只需要静态引入org.easymock.EasyMock
的方法。
import static org.easymock.EasyMock.*;
import junit.framework.TestCase;
public class ExampleTest extends TestCase {
private ClassUnderTest classUnderTest;
private Collaborator mock;
}
|
为了取得Mock 对象,需要:
l 创建Mock 对象从需要模拟的接口
l 记录期待的行为
l 转换到Mock对象,replay状态。
例如:
protected void setUp() {
mock = createMock(Collaborator.class); // 1
classUnderTest = new ClassUnderTest();
classUnderTest.addListener(mock);
}
public void testRemoveNonExistingDocument() {
// 2 (we do not expect anything)
replay(mock); // 3
classUnderTest.removeDocument("Does not exist");
}
|
在执行第三步后,mock 为Collaborator
接口的Mock对象,并且期待没有什么调用。这就意味着,如果我们改变ClassUnderTest去调用此接口的任何方法,则Mock对象会抛出AssertionError:
java.lang.AssertionError:
Unexpected method call documentRemoved("Does not exist"):
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentRemoved(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentRemoved(ClassUnderTest.java:74)
at org.easymock.samples.ClassUnderTest.removeDocument(ClassUnderTest.java:33)
at org.easymock.samples.ExampleTest.testRemoveNonExistingDocument(ExampleTest.java:24)
...
|
增加行为
让我们开始第二个测试。如果document
被classUnderTest
增加,我们期待调用
mock.documentAdded()
在Mock对象使用document的标题作为参数:
public void testAddDocument() {
mock.documentAdded("New Document"); // 2
replay(mock); // 3
classUnderTest.addDocument("New Document", new byte[0]);
}
|
如果classUnderTest.addDocument("New Document", new byte[0])
调用期待的方法,使用错误的参数,Mock对象会抛出AssertionError:
java.lang.AssertionError:
Unexpected method call documentAdded("Wrong title"):
documentAdded("New Document"): expected: 1, actual: 0
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentAdded(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentAdded(ClassUnderTest.java:61)
at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:28)
at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:30)
...
|
同样,如果调用多次此方法,则也会抛出例外:
java.lang.AssertionError:
Unexpected method call documentAdded("New Document"):
documentAdded("New Document"): expected: 1, actual: 1 (+1)
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentAdded(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentAdded(ClassUnderTest.java:62)
at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:29)
at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:30)
...
|
验证行为
当我们指定行为后,我们将验证实际发生的。当前的测试将会判断是否Mock对象会真实调用。可以调用verify(mock)
来山正是否指定的行为被调用。
public void testAddDocument() {
mock.documentAdded("New Document"); // 2
replay(mock); // 3
classUnderTest.addDocument("New Document", new byte[0]);
verify(mock);
}
|
如果失败,则抛出AssertionError
期待明显数量的调用
到现在,我们的测试只是调用一个简单的方法。下一个测试将会检测是否已经存在document导致mock.documentChanged()
调用。为了确认,调用三次
public void testAddAndChangeDocument() {
mock.documentAdded("Document");
mock.documentChanged("Document");
mock.documentChanged("Document");
mock.documentChanged("Document");
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
verify(mock);
}
|
为了避免重复的mock.documentChanged("Document")
,EasyMock提供一个快捷方式。可以通过调用方法expectLastCall().times(int times)
来指定最后一次调用的次数。
public void testAddAndChangeDocument() {
mock.documentAdded("Document");
mock.documentChanged("Document");
expectLastCall().times(3);
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
verify(mock);
}
|
指定返回值
对于指定返回值,我们通过封装expect(T value)
返回的对象并且指定返回的值,使用方法andReturn(Object returnValue)于expect(T value)
.返回的对象。
例如:
public void testVoteForRemoval() {
mock.documentAdded("Document"); // expect document addition
// expect to be asked to vote for document removal, and vote for it
expect(mock.voteForRemoval("Document")).andReturn((byte) 42);
mock.documentRemoved("Document"); // expect document removal
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
assertTrue(classUnderTest.removeDocument("Document"));
verify(mock);
}
public void testVoteAgainstRemoval() {
mock.documentAdded("Document"); // expect document addition
// expect to be asked to vote for document removal, and vote against it
expect(mock.voteForRemoval("Document")).andReturn((byte) -42);
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
assertFalse(classUnderTest.removeDocument("Document"));
verify(mock);
}
|
取代expect(T value)
调用,可以通过expectLastCall()
.来代替
expect(mock.voteForRemoval("Document")).andReturn((byte) 42);
|
等同于
mock.voteForRemoval("Document");
expectLastCall().andReturn((byte) 42);
|
处理例外
对于指定的例外(更确切的:Throwables)被抛出,由expectLastCall()
和expect(T value)
返回的对象,提供了方法andThrow(Throwable throwable)
。方法不得不被调用记录状态,在调用Mock对象后,对于此指定了要抛出的Throwable。
基本的方法,已经说完了,当然这不能完全说明EasyMock的使用。更多的因素请参考EasyMock的文档
http://www.easymock.org/Documentation.html