一個完整的 GUI 自動化測試程序

在介紹了 GUI 測試、輔助特性、報表生成等技術點之后,現(xiàn)在可以將它們串聯(lián)起來,實現(xiàn)一個較為完整的自動化測試程序。
以常見的用戶登錄為例,要進行自動化測試,需要考慮以下場景:
模擬鍵盤輸入密碼(空密碼、錯誤密碼、正確密碼);
模擬鼠標點擊登錄按鈕;
校驗錯誤提示信息,判斷是否與預期相符;
......
自動生成測試報告。
先來看一下我們的程序,以及自動化腳本執(zhí)行效果:

當腳本跑完之后,會生成一個自動化測試報告:

里面包含了所有的測試結果,分析起來特別方便!
1
登錄界面
來看具體的實現(xiàn),登錄界面包含了密碼框、登錄按鈕、提示標簽:
#ifndef?WIDGET_H
#define?WIDGET_H
#include?
#include?
class?QLabel;
class?QLineEdit;
class?QPushButton;
class?Widget?:?public?QWidget
{
????Q_OBJECT
public:
????//?錯誤狀態(tài)
????typedef?enum?ErrorStatus?{
????????NoError?=?0,
????????EmptyPassword,
????????WrongPassword
????}?ErrorStatus;
????explicit?Widget(QWidget?*parent?=?Q_NULLPTR);
????~Widget()?Q_DECL_OVERRIDE;
private?Q_SLOTS:
????void?login();
private:
????void?initUi();
????void?retranslateUi();
????void?initConnections();
????void?initAccessible();
private:
????QLineEdit?*m_lineEdit;
????QPushButton?*m_button;
????QLabel?*m_tipLabel;
????QMap?m_errorMap;
};
#endif?//?WIDGET_H
當點擊登錄按鈕后,會觸發(fā)槽函數(shù) login(),此時會校驗輸入的密碼,并進行錯誤提示:
#include?"widget.h"
#include?
#include?
#include?
#include?
Widget::Widget(QWidget?*parent)
????:?QWidget(parent)
{
????initUi();
????retranslateUi();
????initConnections();
????initAccessible();
}
Widget::~Widget()
{
}
void?Widget::initUi()
{
????m_lineEdit?=?new?QLineEdit(this);
????m_button?=?new?QPushButton(this);
????m_tipLabel?=?new?QLabel(this);
????QVBoxLayout?*layout?=?new?QVBoxLayout();
????layout->addStretch();
????layout->addWidget(m_lineEdit);
????layout->addWidget(m_button);
????layout->addWidget(m_tipLabel);
????layout->addStretch();
????layout->setSpacing(15);
????layout->setContentsMargins(10,?10,?10,?10);
????setLayout(layout);
}
void?Widget::retranslateUi()
{
????m_button->setText("Login");
????if?(m_errorMap.isEmpty())?{
????????m_errorMap.insert(NoError,?"Login?successful");
????????m_errorMap.insert(EmptyPassword,?"The?password?should?not?be?empty");
????????m_errorMap.insert(WrongPassword,?"Wrong?password");
????}
}
void?Widget::initConnections()
{
????connect(m_button,?&QPushButton::clicked,?this,?&Widget::login);
}
void?Widget::initAccessible()
{
????//?將被輔助技術識別
????m_lineEdit->setAccessibleName("passwordEdit");
????m_button->setAccessibleName("loginButton");
????m_tipLabel->setAccessibleName("tipLabel");
????m_lineEdit->setAccessibleDescription("this?is?a?password?line?edit");
????m_button->setAccessibleDescription("this?is?a?login?button");
????m_tipLabel->setAccessibleDescription("this?is?a?tip?label");
}
void?Widget::login()
{
????QString?password?=?m_lineEdit->text();
????if?(password.isEmpty())?{
????????m_tipLabel->setText(m_errorMap.value(EmptyPassword));
????}?else?if?(QString::compare(password,?"123456")?!=?0)?{
????????m_tipLabel->setText(m_errorMap.value(WrongPassword));
????}?else?{
????????m_tipLabel->setText(m_errorMap.value(NoError));
????}
}
注意:有一點很關鍵,需要調(diào)用 setAccessibleName() 為控件設置可訪問的名稱,這樣才能被輔助技術所識別!
由于在自動化腳本中需要進行文本校驗,所以應為 QPushButton、QLabel 等 UI 元素實現(xiàn) QAccessibleInterface 接口,然后定義接口工廠并進行安裝:
//?接口工廠
QAccessibleInterface?*accessibleFactory(const?QString?&classname,?QObject?*object)
{
????QAccessibleInterface?*interface?=?nullptr;
????if?(object?&&?object->isWidgetType())?{
????????if?(classname?==?"QLabel")
????????????interface?=?new?AccessibleLabel(qobject_cast(object));
????????if?(classname?==?"QPushButton")
????????????interface?=?new?AccessibleButton(qobject_cast(object));
????}
????return?interface;
}
至于細節(jié)內(nèi)容,可參考《讓 dogtail 識別 UI 中的元素》。
2
自動化測試腳本
當一切準備就緒,就可以編寫自動化測試腳本了,來看看 autotest.py 都有些什么:
通過 root.application() 找到應用程序;
定義枚舉 ErrorStatus,以表示登錄時的錯誤狀態(tài);
定義一個通用函數(shù) set_text(),用于設置輸入框的文本;
注意:直接調(diào)用 typeText() 只會追加,所以要輸入新內(nèi)容,必須先清空原有內(nèi)容。
繼承 TestCase 類,實現(xiàn)具體的測試用例;
構造測試集,并生成測試報告。
#!/usr/bin/env?python3
#?-*-?coding:?utf-8?-*-
import?unittest
import?HTMLTestRunner
from?dogtail.tree?import?*
import?time
from?enum?import?Enum,?unique
app?=?root.application(appName="Sample03",?description="/home/waleon/workspace/demos/build-Samples-unknown-Debug/Sample03/Sample03")
#?錯誤狀態(tài)
@unique
class?ErrorStatus(Enum):
????NoError?=?0
????EmptyPassword?=?1
????WrongPassword?=?2
#?設置文本
def?set_text(line_edit,?text):
????line_edit.click()
????line_edit.keyCombo('a' )
????line_edit.keyCombo('del')
????line_edit.typeText(text)
class?LoginTestCase(unittest.TestCase):
????"""登錄認證"""
????def?setUp(self):
????????print('==========?begin?==========')
????????self.tips?=?{
????????????????????????ErrorStatus.EmptyPassword:?'The?password?should?not?be?empty',
????????????????????????ErrorStatus.WrongPassword:?'Wrong?password',
????????????????????????ErrorStatus.NoError:?'Login?successful'
????????????????????}
????????self.password_edit?=?app.child('passwordEdit')
????????self.login_btn?=?app.child('loginButton')
????????self.tip_label?=?app.child('tipLabel')
????#?登錄文本內(nèi)容
????def?testLoginText(self):
????????self.assertEqual(self.login_btn.text,?'Login')
????#?密碼為空
????def?testEmptyPassword(self):
????????set_text(self.password_edit,?'')
????????self.login_btn.click()
????????self.assertEqual(self.tip_label.text,?self.tips[ErrorStatus.EmptyPassword])
????#?密碼不合法(特殊字符)
????def?testWrongPassword(self):
????????set_text(self.password_edit,?'~!@#$%')
????????self.login_btn.click()
????????self.assertEqual(self.tip_label.text,?self.tips[ErrorStatus.WrongPassword])
????#?密碼正確
????def?testCorrectPassword(self):
????????set_text(self.password_edit,?'123456')
????????self.login_btn.click()
????????self.assertEqual(self.tip_label.text,?self.tips[ErrorStatus.NoError])
????def?tearDown(self):
????????print('==========?end?==========')
if?__name__?==?'__main__':
????#?構造測試集
????suite?=?unittest.TestSuite()
????#?添加測試用例
????suite.addTest(LoginTestCase("testLoginText"))
????suite.addTest(LoginTestCase("testEmptyPassword"))
????suite.addTest(LoginTestCase("testWrongPassword"))
????suite.addTest(LoginTestCase("testCorrectPassword"))
????#?報告路徑
????date_time?=?time.strftime('%Y%m%d%H%M%S',?time.localtime(time.time()))
????report_path?=?'report_'?+?date_time?+?'.html'
????#?執(zhí)行測試
????with?open(report_path,?'wb')?as?f:
????????runner?=?HTMLTestRunner.HTMLTestRunner(
????????????stream=f,
????????????title='Sample03?unit?test',
????????????description='Sample03?report?output?by?HTMLTestRunner.'
????????)
????????runner.run(suite)
看到這里,是不是覺得自動化測試其實也蠻簡單的,并不像很多人說的那么難!
最后,說一下 dogtail 這個東西,雖然文檔資料比較少,但用起來很不錯。綜合來說,值得推薦!
·END·

