摘要 年秋
在美國勒海大學亞科卡學院的一份研究報告《
世紀美國制造業的戰略
一個工業主導的觀點》中
首次提出了敏捷競爭的概念
而今天
我們似乎已經看到
敏捷已經在我們身邊
形影不離
之前Matrix一則討論激烈的新聞(l)
也表達了敏捷在今天的熱度
下面通過一個簡單的例子
告訴你如何起步敏捷開發
概述 如果你沒有采用敏捷的方式
那你就落後了
這是最近SD Best Practices
會議上的標語
敏捷的方法就象XP和Scrum正在世界的軟件發展中
變得越來越普遍了
敏捷是一個巨大的改變
讓軟件開發者把重心轉移到質量和速度上
這對已經被比喻成面向對象設計的軟件開發有很大的影響
但是
這種影響的一些方面已經放慢了
GUI的開發
自從大多數軟件包含了一些類型的GUI
而且比較多的軟件開發的百分比是去完成以GUI為中心的
應用敏捷的優勢去開發GUI就是關鍵問題了
什麼在阻止人們用敏捷的方法來開發GUI呢?不論他們的應用是基於web的或是桌面應用
大多數開發者不做用戶界面user interface的測試驅動開發test
driven development (TDD)
這都因為一個簡單的原因
單元測試GUI是很困難的
測試GUI是很乏味沉悶而且容易出錯的
包含了模擬用戶事件的復雜代碼
在事件傳播和控制重繪的時候等待
然後在他顯示給用戶之前
嘗試著檢測狀態
敏捷依賴於測試驅動開發
但是為GUI的特定行為來寫有效的測試代碼是困難的
在cube farm(辦公農莊
用隔斷間隔成很多小工作間的辦公室? 商務英語)的GUI方面上
質量和設計從敏捷方法中受益已經被完全認識到
敏捷實踐正在滲透進這個領域
單元測試GUI元素的工具激增
JFCUnit 框架測試GUI是用java Swing
基於Web的GUI能被HTMLUnit
HTTPUnit
jWebUnit和類似的工具測試
許多GUI構造器和工具包和單元測試工具有關系
比如VBUnit是為了Visual Basic
QtUnit是給Qt用的
工具已經存在了
但是處理仍是不確定的
在測試驅動開發(TDD)中
每個代碼的改變都在新行為的單元測試前
在開發GUI時
許多變化不過是視覺顯示上的調整
比如改變元素的位置
文本
或者顏色
你可以加一個按鈕
建一個菜單項
或者構造一個對話框
但是怎樣和為什麼你要測試這種變化呢?測試每個標簽或者顏色值是很愚蠢的
同樣的
對於標准的元素象按鈕和域
測試他們通常的行為是沒有意義的
象對鼠標移動的響應
鍵的按下
點擊
和諸如此類的
這些事情是不可能被中斷的
怎樣去測試他們的問題只是徒勞的增添了做GUI測試的難度
一個關鍵的問題
怎樣做測試先行的開發?答案就在於GUI的編碼是怎樣組織的
敏捷方式的領袖例如Kent Beck 和 David Astels建議在構造GUI的時候要保持視圖對象盡可能是輕量的
而且
在表面下
( below the surface
)測試視圖層
這個 敏捷對象/瘦視圖 模型和我們熟悉的 文檔
視圖 及 客戶端
服務器模式類似
但是被應用於個別的GUI元素
內容和表現的分離改善了代碼的設計
使他更模塊化和更利於測試
每個用戶界面的組件被實現為一個敏捷對象
包括將要被測試的應用的行為
但不包括GUI表現的代碼
每個敏捷對象有一個相應的瘦視圖類只包括普通的GUI行為
采用這種設計模式
GUI構造變得可以被應用於 測試驅動開發(TDD) 處理了
例子
構造一個登錄對話框
讓我們進入一個例子看看怎樣使用TDD和 敏捷對象/瘦視圖 代碼設計模式去開發一個GUI對話框
起初
讓我們考慮對話框的圖形
敏捷開發提倡預先最小化設計
讓軟件構架在多次循環開發中重構
但是這個方法對GUI設計不是很合適
設計一個用戶界面是一個創造的過程
應該規范地處理
畫草圖
做原型
和可用性測試
然後
盡管在GUI下的代碼可以用TDD迭代地設計
一個形象的設計草圖是明智的第一步
這個對話框的基本的設計在圖
中勾畫出來
Figure GUI design sketch for login dialog 這個對話框很簡單
包括用戶名和密碼域
相應的靜態文本框和標簽
登錄和取消按鈕
做為一個他行為的初始輪廓
我們決定登錄成功的話對話框關閉
登錄失敗的話對話框仍然開著
取消按鈕也關閉對話框
基本的 敏捷對象/瘦視圖 代碼類設計的對話框實現在圖
中表示
Figure The classes LoginDialog and LoginDialogView 敏捷對象類LoginDialog 將包含一個方法對應對話框的每個功能行為
瘦視圖類LoginDialogView 將只包含簡單的和顯示相關的代碼
還有get/set 方法去讀取和設置顯示的信息
在這個過程裡
只有LoginDialog裡復雜的功能需要被單元測試
我們可以十分自信在LoginDialogView 裡的簡單行為可以正常工作
第一個構造的組件是敏捷對象LoginDialog
他需要一個相應的測試類LoginDialogTest
第一個測試方法將要驗證登錄方法
如圖
所示
Figure The smart object LoginDialog and its test class LoginDialogTest
作為測試先行的開發方法規定
首先要寫單元測試
測試預期和定義了要被測試的功能設計
我們需要獲得一個用戶名和密碼
然後返回一個登錄成功或者登錄失敗
一個用來判斷的接口方法來做剛才所述的
boolean login(String username
String password);
測試類LoginDialogTest 將測試這個功能
例
展示了在LoginDialogTest
java
文件中他的初始實現
LoginDialogTest
java
import junit
framework
*;
public class LoginDialogTest extends TestCase {
public void testLogin() {
LoginDialog dialog = new LoginDialog();
assertTrue( dialog
login(
user
passwd
) );
}}
這個測試是基於JUnit基礎測試類TestCase的
測試方法testLogin()創建了一個LoginDialog 的實例
調用了他的login()方法
然後判定結果是真
這段代碼將不會編譯
因為LoginDialog 不存在
在TDD過程後
LoginDialog
將生成和保存
代碼編譯後
測試運行驗證將象預期的那樣失敗(因為方法沒有實現)
然後 LoginDialog 為了通過單元測試給出最小的實現
遵照敏捷的聖條 做 可能工作的最簡單的事情(the simplest thing that could possibly work)
例
展示了最初的LoginDialog 版本
用最少的代碼通過了單元測試
實現在LoginDialog
java
文件裡
LoginDialog
java
public class LoginDialog {
LoginDialog() {}
public boolean login(String username
String password) {
return true; } }
使用下面的命令來運行代碼
javac
classpath
;junit
jar
LoginDialogTest
java
javac
classpath
LoginDialog
java
classpath 必須包括junit
jar 來運行單元測試
因為他使用了JUnit
在Linux
Mac OSX
還有其他的UNIX系統上
classpath將包含一個冒號(:)而不是想下面那樣用一個分號
測試將如下運行
java
classpath
;junit
jar
junit
textui
TestRunner LoginDialogTest
單元測試通過了
真好!不幸的是
這個編碼只是模擬一下
Login()方法將總是批准登錄
毋庸置疑
客戶將不會欣賞這種水平的安全機制
顯然
要寫的下一個測試是驗證如果給的條件不正確的話將失敗
例
展示了LoginDialogTest 的第二個測試方法去實現這個目的
testLoginFail()
既然兩個測試都使用一個LoginDialog 的實例
測試類被重構為在他的setUp() 方法裡創建一個固定的測試用的LoginDialog
LoginDialogTest
java
import junit
framework
*;
public class LoginDialogTest extends TestCase {
private LoginDialog dialog;
public void setUp() {
dialog = new LoginDialog();
}
public void testLogin() {
assertTrue( dialog
login(
user
passwd
) );
}
public void testLoginFail() {
assertFalse( dialog
login(
) );
}}
LoginDialog 必須得通過新的測試
不能在第一次測試的時候有失敗
TDD過程引導我們構造我們需要的真正的功能
在用正確的用戶名和密碼登錄的時候
能成功登錄
如果不是
就失敗
例
展示了按此修改的LoginDialog
LoginDialog
java
public class LoginDialog {
private String user =
user
;
private String passwd =
passwd
;
LoginDialog() {}
public boolean login(String username
String password) {
if (user
equals(username) && passwd
equals(password))
return true;
else
return false;
} }
LoginDialog 現在能通過所有的測試
為此
他包括了符合成功登錄條件的用戶名和密碼域
顯然
這只是比第一個版本的安全性能稍微好一些
登錄代碼不應該包含認證的硬編碼!基於這點
我們應該引入一個單獨的類來包含LoginDialog 用的驗證用戶的登錄信息
然而
這個例子是關於GUI構造的
那讓我們暫停這個不安全的登錄代碼
繼續GUI方面
現在
我們已經建立了登錄功能
並用單元測試覆蓋了他
但沒有可視的GUI來顯示它
那下一步該做什麼呢?對於已經作的和測試的實際功能
在GUI方面做的是創建和顯示圖像元素
然後在適當的時候調用login()方法
這個功能是普通和容易建立的
所以他不包含能中斷和需要單元測試的復雜行為
因此
當建立GUI元素時
我們不需要去做測試先行的開發
例
展示了創建對話框窗口的Swing類LoginDialogView
他的實現在LoginDialogView
java
文件
LoginDialogView
java
import java
awt
*;
import java
awt
event
*;
import javax
swing
*;
public class LoginDialogView extends JFrame
implements ActionListener {
protected JTextField usernameField;
protected JTextField passwordField;
protected JButton loginButton;
protected JButton cancelButton;
private LoginDialog dialog;
LoginDialogView(LoginDialog dlg) {
super(
Login
);
setSize(
);
dialog = dlg;
addControls();
loginButton
addActionListener( this );
cancelButton
addActionListener( this );
}
public void actionPerformed(ActionEvent e) {
String cmd = e
getActionCommand();
if (cmd
equals(
Login
)
&& dialog
login(usernameField
getText()
passwordField
getText())) {
hide();
}
}
private void addControls() {
Container contentPane = this
getContentPane();
contentPane
setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
JLabel label
= new JLabel(
Username:
Label
RIGHT);
c
insets = new Insets(
);
c
gridx =
;
c
gridy =
;
contentPane
add(label
c);
usernameField = new JTextField(
);
usernameField
setMinimumSize(new Dimension(
));
c
gridx =
;
contentPane
add(usernameField
c);
JLabel label
= new JLabel(
Password:
Label
RIGHT);
c
gridx =
;
c
gridy =
;
contentPane
add(label
c);
passwordField = new JTextField(
);
passwordField
setMinimumSize(new Dimension(
));
c
gridx =
;
contentPane
add(passwordField
c);
loginButton = new JButton(
Login
);
c
gridx =
;
c
gridy =
;
contentPane
add(loginButton
c);
cancelButton = new JButton(
Cancel
);
c
gridx =
;
contentPane
add(cancelButton
c);
}}
LoginDialogView 包含了文本域
標簽
和按鈕元素
除了普通的GUI行為外
他只是有一個簡單的行為
被actionPerformed() 方法實現
這個行為就是當登錄按鈕被點擊後
login()方法被調用
如果登錄成功
對話框就被所調用的hide()方法所關閉
為了調用login()函數
在LoginDialogView 構造器裡需要接收一個LoginDialog實例
另外
他組裝了完整的GUI設置和事件處理代碼
大部分代碼在addControls() 裡
他簡單的創建和排版了窗體上的GUI元素
LoginDialogView 代碼示范了一個GUI瘦視圖元素怎樣被設計使它只包含普通的GUI代碼
而把重要的需要測試應用的行為放到一個單獨
可測試的敏捷對象中
LoginDialogView 只需要通過創建它來測試
察看他
從用戶的角度確認它看起來和運行起來象期望的那樣
例
展示了可執行的類APPMain
它創建了對話窗體來傳遞可用性測試(指的是傳遞loginDialog的實例)
AppMain
java
public class AppMain {
public static void main(String[] args) {
AppMain app = new AppMain();
} public AppMain() {
LoginDialog dialog = new LoginDialog();
LoginDialogView view = new LoginDialogView(dialog);
view
show();
while (view
isVisible()) {
try {
Thread
currentThread()
sleep(
);
} catch(Exception x) {}
}
System
exit(
);
}}
AppMain 類簡單的創建一個LoginDialog 和LoginDialogView
顯示視圖
休眠直到視圖關閉
然後退出
AppMain 象下面一樣運行
java –classpath
AppMain
運行它創建登錄對話框
如圖
所示
Figure The login dialog window 和登錄對話框交互驗證了用圖
所示的值登錄
會登錄成功然後窗體關閉
試著用其它的值登錄
窗體將保持打開
因為登錄失敗了
取消按鈕關閉窗體
就象窗體的關閉按鈕一樣
這個登錄對話框就如同設計的那樣運行
解決方案 我們已經根據TDD創建了登錄對話框和一個敏捷對象/瘦視圖設計模式
得到了一個有很好構架和功能的程序
有功能的應用行為被單元測試所覆蓋
普通的用來顯示的代碼不需要復雜的GUI測試
圖
展示了我們所開發的這個軟件的構架
Figure The classes LoginDialog LoginDialogView and LoginDialogTest
基於此
其他的特性可以被加入
登錄對話框可以有一個消息域去提醒用戶登錄失敗
其他的登陸參數域也可以被加入
一個單獨的驗證對象可以被創建
硬編碼的登錄值可以被刪掉
不管怎麼變化
TDD和敏捷對象/瘦視圖模式提供了一個設計和實現上的清晰的方向
重要的應用功能是在於可以測試的敏捷的對象
和在瘦視圖中普通的顯示用的代碼的
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27653.html