2009年4月20日
找到對(duì)象之后,就需要對(duì)其進(jìn)行操作了。在對(duì)對(duì)象進(jìn)行操作之前,需要了解RFT處理對(duì)象的方式。根據(jù)RFT的文檔,Java對(duì)象的關(guān)系如下圖:

由此可見,絕大多數(shù)的對(duì)象都是繼承于GuiTestObject和GuiSubitemTestObject。類似Button和CheckBox這樣的簡(jiǎn)單對(duì)象,自然是繼承于GuiTestObject,而像List和Table這樣的有內(nèi)部子對(duì)象的復(fù)雜對(duì)象,一定是繼承于GuiSubitemTestObject。根據(jù)這一規(guī)律,就可以分別建立你所需要的類了。
負(fù)責(zé)Button的類如下:
package framework.widgets;
import java.awt.Point;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.rational.test.ft.object.interfaces.GuiTestObject;
import com.rational.test.ft.object.interfaces.TestObject;
public class WButton extends GuiTestObject {
public WButton(TestObject button) {
super(button);
}
public void click() {
super.click();
}
public void click(int x, int y) {
super.click(new Point(x, y));
}
public void doubleClick() {
super.doubleClick();
}
public boolean isEnabled() {
return super.isEnabled();
}
}
其他簡(jiǎn)單對(duì)象也可以繼承ToggleGUITestObject或TextScrollTestObject等其他衍生于GuiTestObject類,這些類封裝了很多實(shí)用的方法可以直接使用,具體請(qǐng)參考RFT文檔中的API。
負(fù)責(zé)TabPane的類如下:
package framework.widgets;
import java.util.Vector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.rational.test.ft.object.interfaces.GuiSubitemTestObject;
import com.rational.test.ft.object.interfaces.TestObject;
import com.rational.test.ft.script.Index;
import com.rational.test.ft.script.Text;
import com.rational.test.ft.vp.ITestData;
import com.rational.test.ft.vp.ITestDataElementList;
import com.rational.test.ft.vp.ITestDataList;
public class WTabbedPane extends GuiSubitemTestObject {
static final Logger logger = LoggerFactory.getLogger("WTabbedPane.class");
public WTabbedPane(TestObject tabbedPane) {
super(tabbedPane);
}
public void clickTab(String tabName) {
this.click(new Text(tabName));
}
public void clickTab(int index) {
this.click(new Index(index));
}
public int getTabCount() {
logger.info("Get tab count");
ITestData data = (ITestData) super.getTestData("list");
ITestDataList list = (ITestDataList) data;
return list.getElementCount();
}
public int findTab(String text) {
logger.info("Find the index of tab {}", text);
Vector contents = this.getAllTabs();
for (int i = 0; i < contents.size(); i++) {
if (contents.get(i).toString().equals(text)) {
return i;
}
}
return -1;
}
public String getTabText(int index) {
logger.info("Get tab text with index {}", index);
Vector contents = this.getAllTabs();
if (index < 0 || index >= contents.size()) {
return null;
}
return (String) contents.get(index);
}
public Vector getAllTabs() {
logger.info("Get all tabs");
ITestDataList dataList = (ITestDataList) super.getTestData("list");
ITestDataElementList elementList = (ITestDataElementList) dataList
.getElements();
return elementList.getElements();
}
}
由上面的例子可以看出來,對(duì)對(duì)象的操作可以分為兩類:一類是施加行為,一類是讀取數(shù)據(jù)。對(duì)此,RFT的API給出了詳細(xì)的文檔,例如對(duì)于List對(duì)象,文檔如下:

由此可看出,對(duì)于List對(duì)象,可以通過.class, .classIndex, .itemCount, .itemText, .priorLabel,accessibleContext.accessibleName,name和toolTipText這些屬性進(jìn)行識(shí)別。獲取List對(duì)象后,它支持Text和Index這兩類子對(duì)象,可通過ITestDataList接口獲得全部列表元素和已選列表元素。上面getAllTabs()方法就是用來獲得所有Tab選項(xiàng)的,可以作為參考。
還有一些更為復(fù)雜的簡(jiǎn)單對(duì)象,如:TextField和Frame,以及復(fù)雜對(duì)象,如:Table和Tree。如果你能夠理解上述處理對(duì)象的方法,那么完全可以編寫符合自己項(xiàng)目需要的方法,對(duì)這些對(duì)象進(jìn)行各種各樣的操作。
2009年3月31日
2009年3月21日
窗體是程序的基礎(chǔ)。無論是主窗體,還是彈出窗體,他們往往都是需要首先定位的對(duì)象。窗體,作為一類特殊對(duì)象,他們都是根對(duì)象的直接子對(duì)象,針對(duì)這一特點(diǎn),對(duì)他們定位就非常簡(jiǎn)單了。通常,通過窗體標(biāo)題,就能很好的找到所需的窗體。具體方法如下。
private double waitCheckInterval = ((Double) getOption(IOptionName.WAIT_FOR_EXISTENCE_DELAY_BETWEEN_RETRIES))
.doubleValue();
private double waitMaxTime = ((Double) getOption(IOptionName.MAXIMUM_WAIT_FOR_EXISTENCE))
.doubleValue();
private TestObject rootTO = null;

public boolean getRootWithCaption(String captionExpr)
{
double timeNow = System.currentTimeMillis() / 1000;
double endTime = timeNow + waitMaxTime;
rootTO = null;

while (rootTO == null && timeNow < endTime)
{
RootTestObject root = RootTestObject.getRootTestObject();
TestObject[] ftWinObjects = null;
RegularExpression exp = new RegularExpression(captionExpr, false);
ArrayList<Property> v = new ArrayList<Property>();
v.add(new Property(".captionText", exp));
v.add(new Property(".domain", "Java"));
v.add(new Property("showing", "true"));
ftWinObjects = root.find(atChild((Property[]) v.toArray(new Property[0])));

if (ftWinObjects != null)
{

if (ftWinObjects.length > 1)
{
throw new AmbiguousRecognitionException("Find more windows with capture: " + captionExpr);
}

if (ftWinObjects.length == 1)
{
rootTO = ftWinObjects[0];
return true;
}

} else
{
sleep(waitCheckInterval);
timeNow = System.currentTimeMillis() / 1000;
}
}
return false;
}

上面的方法首先取得對(duì)象查找的間隔時(shí)間、最大等待時(shí)間,并聲明了空的窗體對(duì)象。接下來進(jìn)入方法,根據(jù)查找結(jié)果和最大等待時(shí)間來循環(huán)查找窗體。先獲得根測(cè)試對(duì)象,然后查找其直接子對(duì)象,查找條件為:窗體標(biāo)題符合正則表達(dá)式captionExpr的定義,屬于Java域,并且當(dāng)前為顯示狀態(tài)。最后處理查找結(jié)果,如果結(jié)果大于1個(gè),則拋出異常;如果結(jié)果等于1,則返回true;如果結(jié)果為空,則等待并重新計(jì)算時(shí)間,并繼續(xù)循環(huán)查找;如果最后仍未空并退出循環(huán),則返回false。
有的時(shí)候,窗口的出現(xiàn)并不是一定的,例如很多彈出窗口。這時(shí)候,對(duì)象查找并不需要循環(huán)等待,相應(yīng)的方法應(yīng)為:

public boolean getRootWithCaptionWithoutWait(String captionExpr)
{
rootTO = null;
sleep(waitCheckInterval);
RootTestObject root = RootTestObject.getRootTestObject();
TestObject[] ftWinObjects = null;
RegularExpression exp = new RegularExpression(captionExpr + "$", false);
ArrayList<Property> v = new ArrayList<Property>();
v.add(new Property(".captionText", exp));
v.add(new Property(".domain", "Java"));
v.add(new Property("showing", "true"));
ftWinObjects = root.find(atChild((Property[]) v.toArray(new Property[0])));

if (ftWinObjects != null)
{

if (ftWinObjects.length > 1)
{
throw new AmbiguousRecognitionException("Find more windows with capture: " + captionExpr);
}

if (ftWinObjects.length == 1)
{
rootTO = ftWinObjects[0];
return true;
}
}
return false;
}

這樣一來,就可以用統(tǒng)一的方法來進(jìn)行窗體的查找。具體代碼如下:

protected boolean ExistWin(String winName, boolean wait)
{
release();
if (wait)
return og.getRootWithCaption(winName);
else
return og.getRootWithCaptionWithoutWait(winName);
}

public boolean isDialog(String caption, boolean wait)
{
return super.ExistWin(caption, wait);
}
前一個(gè)方法利用傳入的窗體標(biāo)題正則表達(dá)式,和窗體查找邏輯,進(jìn)行窗體查找。后一個(gè)方法對(duì)其進(jìn)行調(diào)用,返回是否查找成功的結(jié)果。事實(shí)上,這兩個(gè)方法完全可以寫成一個(gè),但是在設(shè)計(jì)框架時(shí),應(yīng)考慮到自動(dòng)化操作的各個(gè)環(huán)節(jié),應(yīng)把每一個(gè)環(huán)節(jié)獨(dú)立開來,才能具有最大的靈活性。根據(jù)我的經(jīng)驗(yàn),對(duì)對(duì)象的查找和查找后的操作應(yīng)獨(dú)立成兩個(gè)不同類來完成,其中對(duì)對(duì)象的查找應(yīng)為一個(gè)RFT的super class。其實(shí)也就是繼承了RationalTestScript類的抽象類,對(duì)對(duì)象的操作應(yīng)為一個(gè)Script,這個(gè)Script的super class應(yīng)為自己之前定義的對(duì)象查找類,而不是默認(rèn)的RationalTestScript。這一部分后面還會(huì)講到。
當(dāng)需要關(guān)閉可能出現(xiàn)的錯(cuò)誤窗口時(shí),可以這樣使用:

if (aDialog.isDialog(".*Error", false))
{
aDialog.close();
}
當(dāng)需要操作一定出現(xiàn)的窗口時(shí),可以這樣使用:

if (aDialog.isDialog("Warning", true))
{
aDialog.clickButton("Yes");
}
至此,所有針對(duì)窗體的處理邏輯就基本完整了。當(dāng)然,可能有些被測(cè)程序可以同時(shí)打開多個(gè)實(shí)例,這就需要支持同時(shí)獲取并操作多個(gè)具有相同標(biāo)題的窗體。這樣的問題大家可以一同探討如何處理。
經(jīng)過這些年的使用,我認(rèn)為RFT是一個(gè)十分優(yōu)秀的自動(dòng)化測(cè)試工具。尤其是目前的7.0.1.2版本,已經(jīng)非常穩(wěn)定,非常好用了。
與大多數(shù)其它自動(dòng)化測(cè)試工具一樣,RFT提供了很多基礎(chǔ)設(shè)施,例如:對(duì)象的映射、動(dòng)作的錄制/回放、檢查點(diǎn)、數(shù)據(jù)池、Html或TxT格式的報(bào)告等等。這些基礎(chǔ)設(shè)施確實(shí)能夠大幅度提升這一工具的上手速度,但是我以為,正是這些基礎(chǔ)設(shè)施使得RFT的使用者在使用過程中忽略了對(duì)這些基礎(chǔ)設(shè)施背后內(nèi)容的了解,以至于出現(xiàn)了問題束手無策。其實(shí),RFT是一個(gè)開放的自動(dòng)化測(cè)試開發(fā)平臺(tái),幾乎所有他所提供的基礎(chǔ)設(shè)施都有相應(yīng)的API暴露給使用者,只要用好這些API,就能夠非常輕松的定制出適合自己的自動(dòng)化測(cè)試框架,運(yùn)行中出現(xiàn)問題也非常容易定位分析。
使用RFT有一段時(shí)間的人,應(yīng)該知道IBM有一個(gè)ITCL庫,是專門針對(duì)RFT的,他封裝了很多有用的方法,形成了一套開發(fā)框架,能夠明顯提高開發(fā)效率。不過,遺憾的是,這個(gè)庫非常的老(大部分類生成日期都是05年),如果你使用最新的RFT7.0,由于很多底層API都變化了,這個(gè)庫有不少部分都不能用了。
如果你仔細(xì)閱讀過這個(gè)庫,你就會(huì)發(fā)現(xiàn)RFT是一個(gè)多么靈活的工具,也正因?yàn)檫@一點(diǎn),很多人都不明白如何能夠正確的使用RFT。在我看來,RFT使用的最佳實(shí)踐就是開發(fā)屬于自己的測(cè)試框架。只有這樣才能發(fā)揮RFT的真正威力。
利用這個(gè)系列,我結(jié)合手頭的一個(gè)Swing界面的項(xiàng)目,介紹一下如何開發(fā)自己的測(cè)試框架。(Swing項(xiàng)目,運(yùn)行之間不要忘記Enable Jvm)。
這里,作為測(cè)試框架,不應(yīng)使用對(duì)象映射,而應(yīng)該完全依賴于動(dòng)態(tài)查找。這是因?yàn)樗鼑?yán)重依賴RFT提供的基礎(chǔ)設(shè)施,靈活性很差。具體內(nèi)容隨后將逐一介紹。