#
1 table.setSelectionBackground(Color.black); table.setSelectionForeground(Color.white);
Swing頗受歡迎的JTable類為顯示大塊數(shù)據(jù)提供了一種簡單的機(jī)制。JTable有很多東西是用于數(shù)據(jù)的生成和編輯,其中的很多東西還可以自定義,從而更進(jìn)一步增強(qiáng)其功能。本文會(huì)引導(dǎo)你一步步地進(jìn)入JTable的世界。
Listing A包含了一個(gè)簡單示例的代碼,這個(gè)示例會(huì)說明常用JTable的行為。用戶能夠更改JTable的布局、拖放它的欄,或者通過拖動(dòng)標(biāo)題的分隔線來改變其大小。
這些列被保存在一個(gè)String數(shù)組里:
1 String[] columnNames = {"Product","Number of Boxes","Price"};
數(shù)據(jù)被初始化并保存在一個(gè)二維的對象數(shù)組里:
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構(gòu)成的:
JTable table = new JTable(data, columnNames);
查看JTable
JTable的高度和寬度按照下面的方法來設(shè)定:
table.setPreferredScrollableViewportSize(new Dimension(300, 80));
如果JTable的一個(gè)列或者JTable窗口自身的大小被重新確定,那么其他列會(huì)被相應(yīng)的縮小或者放大,以適應(yīng)新的窗口。使用setAutoResizeMode()方法就能夠控制這種行為:
1 table.setAutoResizeMode(int mode);
mode整數(shù)字段可能的值有:
1 AUTO_RESIZE_OFF
2 AUTO_RESIZE_NEXT_COLUMN
3 AUTO_RESIZE_SUBSEQUENT_COLUMNS
4 AUTO_RESIZE_LAST_COLUMN
5 AUTO_RESIZE_ALL_COLUMNS
表格的缺省值
單元格內(nèi)方格坐標(biāo)線的缺省顏色是Color.gray。要更改這些方格坐標(biāo)線的顏色,就要用到:
1 table.setGridColor(Color.black);
你可以用下面的方法來改變行的高度:
1 table.setRowHeight(intpixelHeight);
各個(gè)單元格的高度將等于行的高度減去行間的距離。
在缺省情況下,內(nèi)容的前景顏色和背景顏色的選擇都是由Swing的所見即所得的實(shí)現(xiàn)來確定的。你可以使用下面的方法來更改選擇的顏色:
1 table.setSelectionBackground(Color.black); table.setSelectionForeground(Color.white);
你也可以隱藏單元格的方格坐標(biāo)線,就像下面這樣:
1 table.setShowHorizontalLines(false );
2 table.setShowVerticalLines(false);
列的寬度
JTable組件有幾個(gè)控制表格特性的類和接口。TableColumn會(huì)不斷追蹤列的寬度,并負(fù)責(zé)列大小的調(diào)整,包括最大和最小寬度。
TableColumnModel管理著TableColumns的集合以及列的選擇。要設(shè)置某個(gè)列的寬度,就要為表格列的模型設(shè)置一個(gè)參照。然后,取得想要的TableColumn并調(diào)用其setPreferredWidth()方法:
1 TableColumncolumn = table.getColumnModel().getColumn(0 );
2 column.setPreferredWidth(100);
當(dāng)用戶拖放列的時(shí)候,列的索引并不會(huì)發(fā)生改變。getColumn(0)方法會(huì)一直返回正確的列,無論它出現(xiàn)在屏幕的哪個(gè)地方。
標(biāo)題
JtableHeader會(huì)處理JTable標(biāo)題的顯示。你可以細(xì)分JtableHeader以獲得自定義的布局。例如,如果你的應(yīng)用程序需要一個(gè)跨越多個(gè)列的標(biāo)題,那么只用簡單地細(xì)分JtableHeader并將它集成到你的JTable里就行了。
你可以通過為當(dāng)前JTable的JtableHeader設(shè)置一個(gè)參照或者調(diào)用其setReorderingAllowed()方法,來指定標(biāo)題的重新排序是否被允許:
1 table.getTableHeader().setReorderingAllowed(false);
類似地,你可以確信列不會(huì)因?yàn)樵诹袠?biāo)題之間拖動(dòng)而改變大小。要達(dá)到這個(gè)目的,你就要使用setResizingAllowed()方法:
1 table.getTableHeader().setResizingAllowed(false);
選擇模式
在缺省狀況下,當(dāng)用戶在JTable里選擇一個(gè)單元格的時(shí)候,整個(gè)行都被選中了。有多種方法能夠讓用戶自定義選擇的方式。利用ListSelectionModel接口,你可以允許用戶選擇單個(gè)或者多個(gè)行:
1 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
ListSelectionModel有下面這些字段:
* SINGLE_SELECTION允許一次選擇一行。
* SINGLE_INTERVAL_SELECTION允許選擇相鄰的一系列行。
* MULTIPLE_INTERVAL_SELECTION也允許選擇相鄰的列,但是帶有擴(kuò)展功能。它允許用戶使用[Ctrl]鍵進(jìn)行多個(gè)互不相鄰的選擇(即選擇不相鄰的行)。
setCellSelectionEnabled()方法讓用戶能夠同時(shí)選擇單個(gè)單元格或者整個(gè)行:
1 table.setCellSelectionEnabled(true);
編輯單元格
我們這個(gè)簡單的表格允許用戶編輯表格里的任何單元格。Listing B列出了一個(gè)表格,它允許由程序員來決定哪些單元格能夠被編輯。第一步是創(chuàng)建一個(gè)自定義的TableModel:
1 class SimpleTableModel extends AbstractTableModel {}
數(shù)據(jù)被封裝在TableModel里,當(dāng)JTable初始化的時(shí)候,自定義的TableModel就被作為一個(gè)參數(shù)傳遞給JTable的構(gòu)造函數(shù)而不是那個(gè)二維的對象數(shù)組:
1 SimpleTableModelmyModel = new SimpleTableModel();
2 JTable table = new JTable(myModel);
如果想讓第二列和第三列也變得可以編輯,并把第一列變成恒定的,那么你就要強(qiáng)制替代TableModel的isCellEditable()方法:
1 public booleanisCellEditable(int row, intcol){
2 if (col == 0) {return false ;}
3 else {return true ; }
4 }
簡單的表格驗(yàn)證
你需要確保用戶只輸入整數(shù)值,假如說,向第二列(“盒子的數(shù)量”這一列)輸入值來強(qiáng)制替代setValueAt()方法,并將驗(yàn)證邏輯包括進(jìn)這個(gè)新方法里。首先,你要檢查列是否是整數(shù),以及這個(gè)列是否只應(yīng)該包含整數(shù)值:
1 if (data[0][col] instanceof Integer && !(value instanceof Integer))
2 {… } else { data[row][col] = value;}
然后,檢查被插入的值是否是個(gè)整數(shù)。如果它不是的,那么這個(gè)字段就不應(yīng)該被更新,而且應(yīng)該要顯示一條錯(cuò)誤信息:
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加入顏色。你可以通過強(qiáng)制替代其prepareRenderer()方法來向JTable加入背景顏色:
1 JTable table = new JTable(data, columnNames){
2 public Component prepareRenderer(TableCellRenderer r, int row, intcol){}
3 };
然后,插入決定哪些列應(yīng)該有顏色以及應(yīng)該是什么顏色的邏輯:
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 }
要注意,當(dāng)你更改單元格背景顏色的時(shí)候,你還應(yīng)該更該單元格里所顯示的文本的顏色,讓其變得更加易讀。圖C顯示了一個(gè)第一列和第二列加上了顏色的JTable。
一切皆在掌握中
我們的例子只是JTable其他部分的基礎(chǔ)。通過使用這些工具,你能夠快速和輕易地掌控對Java應(yīng)用程序所生成的表格的格式化,這樣就能夠讓你的用戶在進(jìn)行正常使用的時(shí)候不碰到障礙。
摘自:http://www.7dspace.com/doc/21/0601/20061905111047137.htm
在JAVA中使用拖拽功能
sun在java2中引入了一些新的方法來幫助實(shí)現(xiàn)拖拽功能,這些新的類在java.awt.dnd包中
實(shí)現(xiàn)一個(gè)D&D操作一般包括三個(gè)步驟:
首先實(shí)現(xiàn)一個(gè)拖拽源,這個(gè)拖拽源和相應(yīng)的組件是關(guān)聯(lián)起來的
第二步實(shí)現(xiàn)一個(gè)拖拽目標(biāo),這個(gè)目標(biāo)用來實(shí)現(xiàn)拖拽物的接收
第三步實(shí)現(xiàn)一個(gè)數(shù)據(jù)傳輸對象,該對象封裝拖動(dòng)的數(shù)據(jù)
_____________________ _____________________
| | | |
| DragSource Component| |DropTarget Component|
|_____________________| |____________________|
| |
|____________Transferable Data_________________|
Transferable 接口實(shí)現(xiàn)出的對象能夠保證 DropTarget Component讀懂拖拽過來的對象中包含的信息
如果是在同一個(gè)虛擬機(jī)中實(shí)現(xiàn)拖拽的話,DragSource Component會(huì)傳遞一個(gè)引用給DropTarget Component
但是如果在不同的JVM中或者是在JVM和本地系統(tǒng)之間傳遞數(shù)據(jù)的話我們就必須實(shí)現(xiàn)一個(gè)Transferable對象來傳遞數(shù)據(jù)
Transferable中封裝的內(nèi)容存放到DataFlavors,用戶可以通過訪問DataFlavors來獲取數(shù)據(jù)
1。創(chuàng)建可拖拽對象
一個(gè)對象那個(gè)如果想作為拖拽源的話,必須和五個(gè)對象建立練習(xí),這五個(gè)對象分別是:
* java.awt.dnd.DragSource
獲取DragSource的方法很簡單,直接調(diào)用DragSource.getDefaultDragSource();就可以得到DragSource對象
* java.awt.dnd.DragGestureRecognizer
DragGestureRecognizer類中實(shí)現(xiàn)了一些與平臺無關(guān)的方法,我們?nèi)绻朐谧约旱慕M件上實(shí)現(xiàn)拖拽的話只要調(diào)用createDefaultDragGestureRecognizer()方法就可以了
該方法接收三個(gè)參數(shù),建立組件和拖拽動(dòng)作之間的關(guān)系
* java.awt.dnd.DragGestureListener
當(dāng)建立了組件和拖拽動(dòng)作之間的聯(lián)系后,如果用戶執(zhí)行了拖拽操作,組件將發(fā)送一個(gè)消息給DragGestureListener監(jiān)聽器
DragGestureListener監(jiān)聽器接下來會(huì)發(fā)送一個(gè)startDrag()消息給拖拽源對象,告訴組件應(yīng)該執(zhí)行拖拽的初始化操作了
拖拽源會(huì)產(chǎn)生一個(gè)DragSourceContext對象來監(jiān)聽動(dòng)作的狀態(tài),這個(gè)監(jiān)聽過程是通過監(jiān)聽本地方法DragSourceContextPeer來實(shí)現(xiàn)的
* java.awt.datatransfer.Transferable
* java.awt.dnd.DragSourceListener
DragSourceListener接口負(fù)責(zé)當(dāng)鼠標(biāo)拖拽對象經(jīng)過組件時(shí)的可視化處理, DragSourceListener接口的顯示結(jié)果只是暫時(shí)改變組件的外觀
同時(shí)他提供一個(gè)feedback,當(dāng)用戶的拖拽操作完成之后會(huì)收到一個(gè)dragDropEnd的消息,我們可以在這個(gè)函數(shù)中執(zhí)行相應(yīng)的操作
再來回顧一下拖拽源的建立過程
首先、 DragGestureRecognizer 確認(rèn)一個(gè)拖拽操作,同時(shí)告知 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。創(chuàng)建droppable Component
創(chuàng)建一個(gè) droppable Component必須和下面兩個(gè)對象發(fā)生關(guān)聯(lián)
* java.awt.dnd.DropTarget
DropTarget構(gòu)造函數(shù)使DropTarget 和 DropTargetListener objects發(fā)生關(guān)聯(lián)
Droptarget對象提供 setComponent 和addDropTargetListener 兩個(gè)方法
* 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.
下面的例子演示了一個(gè)從樹中拖拽一個(gè)節(jié)點(diǎn)到文本域中
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(); //創(chuàng)建拖拽源
dragSource.createDefaultDragGestureRecognizer(jtr,
DnDConstants.ACTION_COPY_OR_MOVE,
new DragAndDropDragGestureListener()); //建立拖拽源和事件的聯(lián)系
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) {
//將數(shù)據(jù)存儲到Transferable中,然后通知組件開始調(diào)用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()) {
//拖拽動(dòng)作結(jié)束的時(shí)候打印出移動(dòng)節(jié)點(diǎn)的字符串
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();//使用該函數(shù)從Transferable對象中獲取有用的數(shù)據(jù)
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");
}
}
}
摘要: 通過兩種方法實(shí)現(xiàn)Drag and Drop:
1.比較初級的D&D:只利用java.awt.datatransfer.*中的類實(shí)現(xiàn).
2.高級D&D: 利用javax.awt.dnd.*中的類實(shí)現(xiàn).
比較初級D&D:只利用java.awt.datatransfer.*中的類實(shí)現(xiàn).
這種方法只支持對JComponent的拖拽.
&...
閱讀全文
B.1 單元測試(Unit Test)
一個(gè)單元(Unit)是指一個(gè)可獨(dú)立進(jìn)行的工作,獨(dú)立進(jìn)行指的是這個(gè)工作不受前一次或接下來的工作的結(jié)果影響。簡單地說,就是不與程序運(yùn)行時(shí)的上下文(Context)發(fā)生關(guān)系。
如果是在Java程序中,具體來說一個(gè)單元可以是指一個(gè)方法(Method)。這個(gè)方法不依賴于前一次運(yùn)行的結(jié)果,也不牽涉到后一次的運(yùn)行結(jié)果。
舉例來說,下面這個(gè)程序的gcd()方法可視為一個(gè)單元:
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()方法不可視為一個(gè)單元,要完成gcd的計(jì)算,必須調(diào)用setNum1()、setNum2()與gcd() 3個(gè)方法。
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;
}
然而要完全使用一個(gè)方法來完成一個(gè)單元操作在實(shí)現(xiàn)上是有困難的,所以單元也可廣義解釋為數(shù)個(gè)方法的集合。這數(shù)個(gè)方法組合為一個(gè)單元操作,目的是完成一個(gè)任務(wù)。
不過設(shè)計(jì)時(shí)仍優(yōu)先考慮將一個(gè)公開的方法設(shè)計(jì)為單元,輔助的方法則使用設(shè)定為私用,盡量不用數(shù)個(gè)公開的方法來完成一件工作,以保持接口簡潔與單元邊界清晰。將工作以一個(gè)單元進(jìn)行設(shè)計(jì),這使得單元可以重用,并且也使得單元可以進(jìn)行測試,進(jìn)而增加類的可重用性。
單元測試指的是對每一個(gè)工作單元進(jìn)行測試,了解其運(yùn)行結(jié)果是否符合我們的要求。例如當(dāng)編寫完MathTool類之后,也許會(huì)這么寫一個(gè)小小的測試程序:
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!");
}
}
在文字模式下使用文字信息顯示測試結(jié)果,這個(gè)動(dòng)作是開發(fā)人員經(jīng)常作的事情,然而您必須一行一行看著測試程序的輸出結(jié)果,以了解測試是否成功。另一方面,測試程序本身也是一個(gè)程序,在更復(fù)雜的測試中,也許會(huì)遇到測試程序本身出錯(cuò),而導(dǎo)致無法驗(yàn)證結(jié)果的情況。
JUnit是一個(gè)測試框架,通過它所提供的工具,可以減少編寫錯(cuò)誤的測試程序的機(jī)會(huì)。另一方面,可以有更好的方法來檢驗(yàn)測試結(jié)果,而不是看著一長串輸出的文字來檢驗(yàn)測試是否成功。JUnit測試框架讓測試的進(jìn)行更有效率且更具可靠性。
B.2 JUnit設(shè)置
JUnit最初是由Erich Gamma與Kent Beck編寫,為單元測試的支持框架,用來編寫與執(zhí)行重復(fù)性的測試。它包括以下特性:
Ü 對預(yù)期結(jié)果作判斷
Ü 提供測試裝備的生成與銷毀
Ü 易于組織與執(zhí)行測試
Ü 圖形與文字接口的測試器
要設(shè)定JUnit,可先到 JUnit官方網(wǎng)站(http://junit.org/)下載JUnit的zip文件,下載后解開壓縮文件,其中會(huì)含有junit.jar文件,將這個(gè)文件復(fù)制到所要的數(shù)據(jù)夾中,然后設(shè)定Classpath指向junit.jar。例如:
set classpath=%classpath%;YOUR_JUNIT_DIR\junit.jar
如果是Windows 2000/XP,可以在環(huán)境變量中設(shè)定Classpath變量(可參考附錄A中的Classpath設(shè)置介紹)。
B.3 第一個(gè)JUnit測試
要對程序進(jìn)行測試,首先要設(shè)計(jì)測試案例(Test Case)。一個(gè)測試案例是對程序給予假定條件,然后運(yùn)行程序并看看在給定的條件下,程序的運(yùn)行結(jié)果是否符合要求。
在JUnit下,可以繼承TestCase來編寫測試案例,并定義測試方法,每一個(gè)測試方法是以testXXX()來命名。一個(gè)例子如下所示:
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()方法用來斷定您的預(yù)期值與單元方法實(shí)際的返回結(jié)果是否相同,如果預(yù)期值與返回的結(jié)果不同則丟出異常,TestRunner會(huì)捕捉異常,并提取其中的相關(guān)信息以報(bào)告測試結(jié)果。這里使用的是文字模式的TestRunner。
接下來根據(jù)測試案例編寫實(shí)際的程序,首先試著讓測試案例能通過編譯:
package onlyfun.caterpillar;
public class MathTool {
public static int gcd(int num1, int num2) {
return 0;
}
}
編譯完MathTool.java并用javac來編譯它。在編譯完成之后,接著運(yùn)行測試案例,會(huì)得到以下的結(jié)果:
.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中并沒有編寫什么實(shí)際的邏輯,所以測試失敗。在測試驅(qū)動(dòng)中,測試案例所報(bào)告的結(jié)果通常是以測試失敗作為開始,您的挑戰(zhàn)就是要一步步消除這些失敗的信息。接下來根據(jù)測試案例,完成所設(shè)計(jì)的程序:
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;
}
再次運(yùn)行測試案例,會(huì)得到以下的結(jié)果,通過最后的OK信息,知道測試已經(jīng)成功:
.Time: 0
OK (1 test)
不一定要在main()中指定TestRunner,而可以直接啟動(dòng)一個(gè)TestRunner,并指定測試案例類(繼承TestCase的類)。例如啟動(dòng)一個(gè)Swing窗口的測試結(jié)果畫面:
java junit.swingui.TestRunner test.onlyfun.caterpillar.MathToolUnitTest
執(zhí)行的結(jié)果畫面如圖B-1所示。
在Swing窗口的測試結(jié)果顯示中,如果中間的橫棒是顯示綠色,表示所有的測試都已經(jīng)成功,如果中間的橫棒顯示紅色,表示測試失敗。JUnit的名言是Keep the bar green to keep the code clean,意思是保持綠色橫棒以保證測試成功。
也可以指定文字模式的測試結(jié)果。例如:
java junit.textui.TestRunner test.onlyfun.caterpillar.MathToolUnitTest

圖B-1 JUnit的Swing窗口測試結(jié)果
B.4 自動(dòng)構(gòu)建與測試
Ant可以進(jìn)行自動(dòng)化構(gòu)建,而JUnit可以進(jìn)行自動(dòng)化測試,Ant可以與JUnit結(jié)合,使得自動(dòng)化的構(gòu)建與測試變得可行。
如果要讓Ant能支持JUnit,建議直接將JUnit的junit.jar放置在Ant的lib目錄,并記得改變Classpath中原先有關(guān)junit.jar的設(shè)定。例如將Classpath重新指向%ANT_HOME%\lib\junit.jar(假設(shè)已經(jīng)如附錄A中設(shè)置了ant_home的環(huán)境變量)。雖然也有其他的方式可以設(shè)定,但這是最快也是最簡單的方法。
Ant使用<junit>標(biāo)簽來設(shè)定JUnit測試,下面是一個(gè)簡單的例子:
<?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屬性會(huì)將測試的結(jié)果簡單地顯示出來,<test>的name屬性是設(shè)定所要進(jìn)行測試的測試案例類。Ant構(gòu)建與調(diào)用JUnit進(jìn)行測試的信息如下:
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 自動(dòng)生成測試報(bào)告
接上一個(gè)主題,可以將JUnit的測試過程在Ant構(gòu)建過程中顯示出來,只要加入< formatter>標(biāo)簽設(shè)定即可:
<?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構(gòu)建與調(diào)用JUnit進(jìn)行測試的信息如下:
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
當(dāng)usefile屬性設(shè)定為true時(shí),會(huì)自動(dòng)將產(chǎn)生的結(jié)果保存在文件中,默認(rèn)是TEST-*.txt。其中*是測試案例類名稱。就上例而言,所產(chǎn)生的報(bào)告文件內(nèi)容如下:
Testsuite: test.onlyfun.caterpillar.MathToolUnitTest
Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0 sec
Testcase: testGcd took 0 sec
<formatter>標(biāo)簽還可以設(shè)定將測試的結(jié)果,以XML文件保存下來。一個(gè)編寫的例子如下,它將測試的結(jié)果保存至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>
也可以將測試結(jié)果所產(chǎn)生的XML文件轉(zhuǎn)換為HTML文件,使用Ant可以直接完成這個(gè)工作。<junitreport>標(biāo)簽使用 XSLT將XML文件轉(zhuǎn)換為HTML文件。下面的例子將前面的說明作個(gè)總結(jié),以完整呈現(xiàn)編寫的實(shí)例:
<?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>設(shè)定搜尋TEST-*.xml文件,將之轉(zhuǎn)換為HTML文件,而最后的結(jié)果被設(shè)定保存至report/html/目錄下,在format屬性中設(shè)定了HTML文件具有邊框(Frame),如果不設(shè)定這個(gè)屬性,則HTML報(bào)告文件就不具有邊框。在運(yùn)行Ant之后所產(chǎn)生的 HTML測試結(jié)果報(bào)告文件如圖B-2所示。

圖B-2 Ant結(jié)合JUnit所自動(dòng)產(chǎn)生的測試報(bào)告
附錄B只是對JUnit的一些簡介,如果需要更多有關(guān)JUnit的資料,可以參考以下的網(wǎng)址:
http://caterpillar.onlyfun.net/Gossip/JUnit/JUnitGossip.htm
移位運(yùn)算符
包括:
“>> 右移”;“<< 左移”;“>>> 無符號右移”
例子:
-5>>3=-1
1111 1111 1111 1111 1111 1111 1111 1011
1111 1111 1111 1111 1111 1111 1111 1111
其結(jié)果與 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
其結(jié)果與 -5*2*2*2 完全相同。
5>>3=0
0000 0000 0000 0000 0000 0000 0000 0101
0000 0000 0000 0000 0000 0000 0000 0000
其結(jié)果與 5/(2*2*2) 完全相同。
5<<3=40
0000 0000 0000 0000 0000 0000 0000 0101
0000 0000 0000 0000 0000 0000 0010 1000
其結(jié)果與 5*2*2*2 完全相同。
-5>>>3=536870911
1111 1111 1111 1111 1111 1111 1111 1011
0001 1111 1111 1111 1111 1111 1111 1111
無論正數(shù)、負(fù)數(shù),它們的右移、左移、無符號右移 32 位都是其本身,比如 -5<<32=-5、-5>>32=-5、-5>>>32=-5。
一個(gè)有趣的現(xiàn)象是,把 1 左移 31 位再右移 31 位,其結(jié)果為 -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
位邏輯運(yùn)算符
包括:
& 與;| 或;~ 非(也叫做求反);^ 異或
“& 與”、“| 或”、“~ 非”是基本邏輯運(yùn)算,由此可以演變出“與非”、“或非”、“與或非”復(fù)合邏輯運(yùn)算。“^ 異或”是一種特殊的邏輯運(yùn)算,對它求反可以得到“同或”,所以“同或”邏輯也叫“異或非”邏輯。
例子:
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
請注意!引用、轉(zhuǎn)貼本文應(yīng)注明原作者:Rosen Jiang 以及出處:http://www.tkk7.com/rosen
要改變Swing默認(rèn)的LookAndFeel,網(wǎng)上都說用UIManager下的一個(gè)靜態(tài)方法setLookAndFeel即可,但是我用了這個(gè)方法有半年的時(shí)間也沒有看到真正的WindowsLookAndFeel。昨天網(wǎng)上無意中才看到正解,要設(shè)置LookAndFeel,不僅要調(diào)用上面提到的方法,還要調(diào)用一個(gè)SwingUtilities類中的靜態(tài)方法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();
}
后者在運(yùn)行時(shí)對整個(gè)ComponentTree進(jìn)行更新,應(yīng)用當(dāng)前的UI設(shè)置。
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時(shí)出錯(cuò)了:( Errormsg:" + ex,
"setLookAndFeel",
JOptionPane.INFORMATION_MESSAGE);
}
|
GridBagLayout 不是用于簡單的示例程序界面。使用GridBagLayout搭建界面就像是在起居室中搭腳手架清除畫鉤一樣。
對于簡單的程序使用Boborderlayout和Gridlayout就綽綽有余了, 但如果要把程序提到實(shí)際應(yīng)用上你就得考慮使用GridBagLayout。當(dāng)然, 做復(fù)雜的應(yīng)用程序時(shí),一開始就使用GridBagLayout就會(huì)更有效率。
一旦你決定使用GridBagLayout,接下來一步便是要找一些紙和鉛筆,只有你準(zhǔn)確知道你的界面看上去需要成什么樣子,你就可以敲鍵盤。這就是說,你應(yīng)該在編碼之前進(jìn)行妥善規(guī)劃。
下面將介紹一個(gè)很小的應(yīng)用程序來幫助我們學(xué)習(xí)GridBagLayout,這個(gè)例子是從一個(gè)Flickr RSS fead中顯示一系列照片, 最后的界面就像下面這樣:

下面的是這個(gè)界面的一個(gè)原始草圖:

正如你所看到的,最終的結(jié)果看上去和計(jì)劃的想法完全一樣。
你應(yīng)該能看到在草圖里有一些線,這些線是用來把總界面分成若干行和列的,這樣你就很清楚每一個(gè)組件放置的格子位置。這就是GridBagLayout里"格"的那一部分,而圖上的數(shù)字就是格的號碼。
在某種意義上說, 我們可以把GridBagLayout想象成為早些年的HTML3和4,它們都是基于表的布局,Grid的概念就類似rowspan和colspan的意思,只不過換了個(gè)名字罷了。
隨著我們的界面和表格的設(shè)置完成,是時(shí)候該進(jìn)行界面布局并開始寫代碼了。
工作過程
這一節(jié)我假定你已經(jīng)了解了基本的窗口和組件創(chuàng)建知識。
通過這篇文章我們最終能在一個(gè)frame中布局組件,我們將在以后的文章對界面進(jìn)行改進(jìn)使它更適用。因此,為了了解這整個(gè)工作的過程,我們列出了所有的目標(biāo)代碼。
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);
}
}
構(gòu)造方法前的代碼都不是很特殊,都是一些相當(dāng)標(biāo)準(zhǔn)的import和變量定義。但是進(jìn)入構(gòu)造方法后,事情就變得有趣了。
Container contentPane = getContentPane();
GridBagLayout gridbag = new GridBagLayout();
contentPane.setLayout(gridbag);
我們以GridBagWindow的內(nèi)容面板作為開始來創(chuàng)建一個(gè)GridBagLayout對象,準(zhǔn)確地說,這個(gè)方法與過去我們所創(chuàng)建GridLayout對象和BorderLayout對象的方法是一樣的。那么,現(xiàn)在我們就開始來設(shè)置GridBagLayout對象使它作為內(nèi)容面板的布局。
GridBagConstraints c = new GridBagConstraints();
然后我要提到這整個(gè)進(jìn)程中的一個(gè)獨(dú)特的對象,那就是GridBagConstraints。這個(gè)對象在GridBagLayout中控制所有被安置在其中組件的約束。為了把一個(gè)組件增加到你的GridBagLayout中去,你首先必須將它與一個(gè)GridBagConstraints對象建立連接。
GridBagConstraints可以從11個(gè)方面來進(jìn)行控制和操縱,也可以給你提供一些幫助。這些內(nèi)容是:
- Gridx——組件的橫向坐標(biāo)
- Girdy——組件的縱向坐標(biāo)
- Gridwidth——組件的橫向?qū)挾?,也就是指組件占用的列數(shù),這與HTML的colspan類似
- Gridheight——組件的縱向長度,也就是指組件占用的行數(shù),這與HTML的rowspan類似
- Weightx——指行的權(quán)重,告訴布局管理器如何分配額外的水平空間
- Weighty——指列的權(quán)重,告訴布局管理器如何分配額外的垂直空間
- Anchor——告訴布局管理器組件在表格空間中的位置
- Fill——如果顯示區(qū)域比組件的區(qū)域大的時(shí)候,可以用來控制組件的行為??刂平M件是垂直填充,還是水平填充,或者兩個(gè)方向一起填充
- Insets——指組件與表格空間四周邊緣的空白區(qū)域的大小
- Ipadx—— 組件間的橫向間距,組件的寬度就是這個(gè)組件的最小寬度加上ipadx值
- ipady—— 組件間的縱向間距,組件的高度就是這個(gè)組件的最小高度加上ipady值
可能對于一個(gè)組件的每一個(gè)實(shí)例你都需要為它建立一個(gè)單獨(dú)的GridBagConstraints;然而,這種方法我們并不推薦使用。最好的方法是,當(dāng)你調(diào)用它的時(shí)候把對象設(shè)置為默認(rèn)值,然后針對于每一個(gè)組件改變其相應(yīng)的域。
這個(gè)方法具有通用性,因?yàn)樵谝恍┯蛑?,比如insets、padx、pady和fill這些域,對于每一個(gè)組件來說一般都是相同的,因此這樣對一個(gè)域進(jìn)行設(shè)置就會(huì)更輕松了,也能更輕松的在另外的組件中改變某些域的值。
如果在改變了某些域值之后,你想回到原始的域值的話,你應(yīng)該在增加下一個(gè)組件之前進(jìn)行改變。這種方法使你更容易明白你正在修改的內(nèi)容,也能使你更容易明白在一連串對象中的這11個(gè)參數(shù)的作用。
也許你現(xiàn)在對這些內(nèi)容還是一知半解,不過事實(shí)上一旦你理解了GridBagConstraints,值得安慰的是你以后做再困難的工作都會(huì)游刃有余了。
所以,如果我們已經(jīng)明白了GridBagConstraints的詳細(xì)用法了,那么現(xiàn)在就讓我們來看看在實(shí)際應(yīng)用中應(yīng)該如何來實(shí)現(xiàn)它:
tagLbl = new JLabel("Tags");
c.gridx = 0; //x grid position
c.gridy = 0; //y grid position
gridbag.setConstraints(tagLbl, c); //設(shè)置標(biāo)簽的限制
contentPane.add(tagLbl); //增加到內(nèi)容面板
我們所做的是示例我們的標(biāo)簽、分配給它一個(gè)格位置,將它與一個(gè)約束對象聯(lián)系起來并把它增加到我們的內(nèi)容面板中。
tagModeLbl = new JLabel("Tag Mode");
c.gridx = 0;
c.gridy = 1;
gridbag.setConstraints(tagModeLbl, c);
contentPane.add(tagModeLbl);
請注意,雖然我們已經(jīng)在我們的約束對象中把gridx的值設(shè)置為0,但是在這里我們?nèi)匀灰獙λM(jìn)行重新設(shè)置——這樣做沒有其它原因,只是為了增加可讀性。
下面,我們增加一個(gè)文本域以便能存儲我們希望能搜索到的關(guān)鍵字,再增加一個(gè)組合框以便用來搜索多個(gè)關(guān)鍵字。除了我們希望的文本域有兩列之外,這個(gè)概念其他的方面都與上面所說的是相同的,所以,我們需要在增加組合框之前重新設(shè)置文本域的值。
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);
做了這些之后,我們再在內(nèi)容面板中增加一些其余的簡單組件,這時(shí)候我們就能夠?yàn)g覽它了;其余的代碼應(yīng)該不會(huì)出現(xiàn)任何問題了。
到這個(gè)階段,我們應(yīng)該已經(jīng)得到了一個(gè)類似于我們先前所設(shè)計(jì)的界面了。

進(jìn)一步學(xué)習(xí)
當(dāng)然,界面不是智能的。重新設(shè)置窗口的大小,看看將會(huì)發(fā)生些什么事情。為什么它會(huì)那樣呢?那是因?yàn)槲覀冊O(shè)置了約束對象的weightx、weighty和fill的值。
關(guān)于其他類似的一些內(nèi)容我們將在后面的章節(jié)中進(jìn)行介紹,但是如果你希望能自己試試的話,參考GridBigLayout和GridBagConstraintsAPI文檔會(huì)對擴(kuò)充你的知識提供很好的幫助。
請先看下面這段程序:
public class Hello{
public static void main(String[] args){ //(1)
System.out.println("Hello,world!"); //(2)
}
}
看過這段程序,對于大多數(shù)學(xué)過Java 的從來說,都不陌生。即使沒有學(xué)過Java,而學(xué)過其它的高
級語言,例如C,那你也應(yīng)該能看懂這段代碼的意思。它只是簡單的輸出“Hello,world”,一點(diǎn)
別的用處都沒有,然而,它卻展示了static關(guān)鍵字的主要用法。
在1處,我們定義了一個(gè)靜態(tài)的方法名為main,這就意味著告訴Java編譯器,我這個(gè)方法不需要?jiǎng)?chuàng)建一個(gè)此類的對象即可使用。你還得你是怎么運(yùn)行這個(gè)程序嗎?一般,我們都是在命令行下,打入如下的命令(加下劃線為手動(dòng)輸入):
javac Hello.java
java Hello
Hello,world!
這就是你運(yùn)行的過程,第一行用來編譯Hello.java這個(gè)文件,執(zhí)行完后,如果你查看當(dāng)前,會(huì)發(fā)現(xiàn)多了一個(gè)Hello.class文件,那就是第一行產(chǎn)生的Java二進(jìn)制字節(jié)碼。第二行就是執(zhí)行一個(gè)Java程序的最普遍做法。執(zhí)行結(jié)果如你所料。在2中,你可能會(huì)想,為什么要這樣才能輸出。好,我們來分解一下這條語句。(如果沒有安裝Java文檔,請到Sun的官方網(wǎng)站瀏覽J2SE API)首先,System是位于java.lang包中的一個(gè)核心類,如果你查看它的定義,你會(huì)發(fā)現(xiàn)有這樣一行:public static final PrintStream out;接著在進(jìn)一步,點(diǎn)擊PrintStream這個(gè)超鏈接,在METHOD頁面,你會(huì)看到大量定義的方法,查找println,會(huì)有這樣一行:
public void println(String x)。好了,現(xiàn)在你應(yīng)該明白為什么我們要那樣調(diào)用了,out是System的一個(gè)靜態(tài)變量,所以可以直接使用,而out所屬的類有一個(gè)println方法。
靜態(tài)方法
通常,在一個(gè)類中定義一個(gè)方法為static,那就是說,無需本類的對象即可調(diào)用此方法。如下所示:
class Simple{
static void go(){
System.out.println("Go...");
}
}
public class Cal{
public static void main(String[] args){
Simple.go();
}
}
調(diào)用一個(gè)靜態(tài)方法就是“類名.方法名”,靜態(tài)方法的使用很簡單如上所示。一般來說,靜態(tài)方法常常為應(yīng)用程序中的其它類提供一些實(shí)用工具所用,在Java的類庫中大量的靜態(tài)方法正是出于此目的而定義的。
靜態(tài)變量
靜態(tài)變量與靜態(tài)方法類似。所有此類實(shí)例共享此靜態(tài)變量,也就是說在類裝載時(shí),只分配一塊存儲空間,所有此類的對象都可以操控此塊存儲空間,當(dāng)然對于final則另當(dāng)別論了??聪旅孢@段代碼:
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);
}}
結(jié)果如下:
v1.c=0 v2.c=0
v1.c=1 v2.c=1
由此可以證明它們共享一塊存儲區(qū)。static變量有點(diǎn)類似于C中的全局變量的概念。值得探討的是靜態(tài)變量的初始化問題。我們修改上面的程序:
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);
}}
運(yùn)行結(jié)果如下:
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
這個(gè)程序展示了靜態(tài)初始化的各種特性。如果你初次接觸Java,結(jié)果可能令你吃驚??赡軙?huì)對static后加大括號感到困惑。首先要告訴你的是,static定義的變量會(huì)優(yōu)先于任何其它非static變量,不論其出現(xiàn)的順序如何。正如在程序中所表現(xiàn)的,雖然v出現(xiàn)在v1和v2的前面,但是結(jié)果卻是v1和v2的初始化在v的前面。在static{后面跟著一段代碼,這是用來進(jìn)行顯式的靜態(tài)變量初始化,這段代碼只會(huì)初始化一次,且在類被第一次裝載時(shí)。如果你能讀懂并理解這段代碼,會(huì)幫助你對static關(guān)鍵字的認(rèn)識。在涉及到繼承的時(shí)候,會(huì)先初始化父類的static變量,然后是子類的,依次類推。非靜態(tài)變量不是本文的主題,在此不做詳細(xì)討論,請參考Think in Java中的講解。
靜態(tài)類
通常一個(gè)普通類不允許聲明為靜態(tài)的,只有一個(gè)內(nèi)部類才可以。這時(shí)這個(gè)聲明為靜態(tài)的內(nèi)部類可以直接作為一個(gè)普通類來使用,而不需實(shí)例一個(gè)外部類。如下代碼所示:
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");
}
}}
輸出結(jié)果會(huì)如你所料:
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
關(guān)于單元測試,模擬對象一直是不可缺少的,尤其對于復(fù)雜的應(yīng)用來說。
這么多的模擬對象框架中,個(gè)人覺得比較好用的當(dāng)屬EasyMock了。當(dāng)然JMock也不錯(cuò)。
下面簡單介紹一下EasyMock 。(基本翻譯EasyMock的文檔,可能有些地方不是很恰當(dāng))
EasyMock 2 主要用于給指定的接口提供模擬對象。
模擬對象只是模擬領(lǐng)域代碼直接的部分行為,能檢測是否他們?nèi)缍x中的被使用。使用 Mock 對象,來模擬合作接口,有助于隔離測試相應(yīng)的領(lǐng)域類。
創(chuàng)建和維持 Mock 對象經(jīng)常是繁瑣的任務(wù),并且可能會(huì)引入錯(cuò)誤。 EasyMock 2 動(dòng)態(tài)產(chǎn)生 Mock 對象,不需要?jiǎng)?chuàng)建,并且不會(huì)產(chǎn)生代碼。
有利的方面:
不需要手工寫類來處理 mock 對象。
支持安全的重構(gòu) Mock 對象:測試代碼不會(huì)在運(yùn)行期打斷當(dāng)重新命名方法或者更改方法參數(shù)。
支持返回值和例外。
支持檢察方法調(diào)用次序,對于一個(gè)或者多個(gè) Mock 對象。
不利的方面: 2.0 僅使用于 java 2 版本 5.0 或者以上
以一個(gè)例子來說明如何使用EasyMock:
假設(shè)有一個(gè)合作接口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;
}
}
|
第一個(gè)Mock 對象
我們將創(chuàng)建test case 并且圍繞此理解相關(guān)的EasyMock 包的功能。第一個(gè)測試方法,用于檢測是否刪除一個(gè)不存在的文檔,不會(huì)發(fā)通知給合作類。
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");
}
}
|
對于多數(shù)測試類,使用EasyMock 2,我們只需要靜態(tài)引入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 創(chuàng)建Mock 對象從需要模擬的接口
l 記錄期待的行為
l 轉(zhuǎn)換到Mock對象,replay狀態(tài)。
例如:
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");
}
|
在執(zhí)行第三步后,mock 為Collaborator
接口的Mock對象,并且期待沒有什么調(diào)用。這就意味著,如果我們改變ClassUnderTest去調(diào)用此接口的任何方法,則Mock對象會(huì)拋出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)
...
|
增加行為
讓我們開始第二個(gè)測試。如果document
被classUnderTest
增加,我們期待調(diào)用
mock.documentAdded()
在Mock對象使用document的標(biāo)題作為參數(shù):
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])
調(diào)用期待的方法,使用錯(cuò)誤的參數(shù),Mock對象會(huì)拋出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)
...
|
同樣,如果調(diào)用多次此方法,則也會(huì)拋出例外:
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)
...
|
驗(yàn)證行為
當(dāng)我們指定行為后,我們將驗(yàn)證實(shí)際發(fā)生的。當(dāng)前的測試將會(huì)判斷是否Mock對象會(huì)真實(shí)調(diào)用。可以調(diào)用verify(mock)
來山正是否指定的行為被調(diào)用。
public void testAddDocument() {
mock.documentAdded("New Document"); // 2
replay(mock); // 3
classUnderTest.addDocument("New Document", new byte[0]);
verify(mock);
}
|
如果失敗,則拋出AssertionError
期待明顯數(shù)量的調(diào)用
到現(xiàn)在,我們的測試只是調(diào)用一個(gè)簡單的方法。下一個(gè)測試將會(huì)檢測是否已經(jīng)存在document導(dǎo)致mock.documentChanged()
調(diào)用。為了確認(rèn),調(diào)用三次
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);
}
|
為了避免重復(fù)的mock.documentChanged("Document")
,EasyMock提供一個(gè)快捷方式??梢酝ㄟ^調(diào)用方法expectLastCall().times(int times)
來指定最后一次調(diào)用的次數(shù)。
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)
調(diào)用,可以通過expectLastCall()
.來代替
expect(mock.voteForRemoval("Document")).andReturn((byte) 42);
|
等同于
mock.voteForRemoval("Document");
expectLastCall().andReturn((byte) 42);
|
處理例外
對于指定的例外(更確切的:Throwables)被拋出,由expectLastCall()
和expect(T value)
返回的對象,提供了方法andThrow(Throwable throwable)
。方法不得不被調(diào)用記錄狀態(tài),在調(diào)用Mock對象后,對于此指定了要拋出的Throwable。
基本的方法,已經(jīng)說完了,當(dāng)然這不能完全說明EasyMock的使用。更多的因素請參考EasyMock的文檔
http://www.easymock.org/Documentation.html