由于腳本沒(méi)找到任何Bug,我們?cè)谀承┨囟A段的任務(wù)變成讓它查找潛在的系統(tǒng)問(wèn)題。
隨機(jī)化方法1 – 裸隨機(jī)
從業(yè)務(wù)角度來(lái)說(shuō),自動(dòng)化解決方案中任何步驟都是有效的。因此探索式測(cè)試使得我們可以自由地在任何時(shí)間點(diǎn)執(zhí)行任何步驟。這些步驟的混搭也很簡(jiǎn)單。我們需要在執(zhí)行過(guò)少數(shù)幾次測(cè)試后,遵循已實(shí)現(xiàn)步驟打造“隨機(jī)”測(cè)試用例。
輸入:解決方案中所有業(yè)務(wù)方法的數(shù)量,要生成的測(cè)試腳本數(shù)量,生成每個(gè)測(cè)試腳本所需步驟數(shù)量。
輸出:類(lèi)似于下列腳本:
myRandomCase_1(){
do_that();
do_bla();
verify_this();
}
很明顯,算某些測(cè)試用例可能(甚至已經(jīng))成功運(yùn)行,大部分依然會(huì)失敗,因?yàn)榇罅坑美龑?shí)際上是在試圖完成無(wú)效操作。如果還沒(méi)執(zhí)行過(guò)do_this(),那么verify_this()無(wú)疑會(huì)失敗。
隨機(jī)化方法2 – 有先決條件的隨機(jī)方法
這種方式的想法在于只有在工作流中已包含先覺(jué)步驟后,才向工作流中加入后續(xù)步驟,但這需要對(duì)代碼庫(kù)進(jìn)行必要的擴(kuò)充,確保測(cè)試案例生成器可以理解并保證準(zhǔn)確的序列。為此可在方法之上添加特性或注解:
@Reguires(do_this)
verify_this()
{…}
這樣我們得到了:
myRandomCase_2(){
do_bla();
do_this();
verify_this(); //can be added, because prerequisite step is already in test
}
這是一種更可預(yù)測(cè)的方法。但如果do_this()和verify_that()需要在同一個(gè)Page1上執(zhí)行,而do_bla()已經(jīng)到了Page2又該怎樣?
此時(shí)我們面臨一個(gè)新問(wèn)題:verify_that()會(huì)失敗,因?yàn)闊o(wú)法找到執(zhí)行所需的控制/上下文。
人工隨機(jī)化方法3 – 上下文感知
測(cè)試生成器必須了解執(zhí)行位置上下文(例如Web開(kāi)發(fā)中的“頁(yè)面”)。當(dāng)然,此時(shí)也可以通過(guò)特性/注解為生成器提供活躍上下文。
@ReguiresContext(pageThis)
verify_this()
{…}
@ReguiresContext(pageThis)
do_this()
{…}
@ReguiresContext(pageThis)
@MovesContextTo(pageThat)
do_bla()
{…}
本例中do_this()和verify_this()不會(huì)放在將上下文改為pageThat的方法,或上下文為pageThat的方法之后。
因此我們可以得到一個(gè)類(lèi)似下面這樣的測(cè)試腳本:
myRandomCase_3(){
do_this();
do_bla();
do_that();
}
或者也可以通過(guò)方法鏈實(shí)現(xiàn)。假設(shè)業(yè)務(wù)方法返回的對(duì)象為頁(yè)面,測(cè)試案例生成器會(huì)持續(xù)追蹤執(zhí)行“步驟”前后瀏覽器中顯示的頁(yè)面,因此可以確定需要調(diào)用驗(yàn)證或“步驟”方法的正確頁(yè)面。這種方法需要額外檢查以驗(yàn)證流程是否正確,但這個(gè)操作可以無(wú)須注解實(shí)現(xiàn)。
篩選恰當(dāng)?shù)挠美?br />
至此介紹的方法已經(jīng)可以生成相當(dāng)大量的測(cè)試用例。
主要問(wèn)題在于,驗(yàn)證過(guò)程本身,以及驗(yàn)證失敗的測(cè)試場(chǎng)景是否是應(yīng)用程序內(nèi)的Bug,而非自動(dòng)化測(cè)試腳本邏輯導(dǎo)致的,這些工作也需要耗費(fèi)大量時(shí)間。
因此可以實(shí)現(xiàn)一種“預(yù)言”類(lèi),借此預(yù)測(cè)所獲得的結(jié)果是否滿(mǎn)意,或是否代表任何錯(cuò)誤信息,并且必要時(shí)可進(jìn)行后續(xù)分析。然而本例我們選擇了一個(gè)略微不同的方法。
可以通過(guò)下列這一套規(guī)則代表應(yīng)用程序的失敗是Bug引起的:
1.500錯(cuò)誤或類(lèi)似頁(yè)面
2.JavaScript錯(cuò)誤
3.“未知錯(cuò)誤”或因?yàn)檎`用造成的類(lèi)似的錯(cuò)誤信息
4.應(yīng)用程序日志中有關(guān)異常和/或錯(cuò)誤情況的信息
5.發(fā)現(xiàn)與任何其他產(chǎn)品有關(guān)的錯(cuò)誤
本例中,可在每個(gè)步驟執(zhí)行完畢后驗(yàn)證應(yīng)用程序狀態(tài)。因此自動(dòng)生成的腳本看起來(lái)是這樣的:
myRandomCase_3(){
do_this();
validate_standard_rules();
do_bla();
validate_standard_rules();
do_that();
validate_standard_rules();
}
其中validate_standard_rules()方法可以搜索上文提到的各種問(wèn)題。
注意:通過(guò)與OOP結(jié)合,這種方法會(huì)顯得更為強(qiáng)大,可以檢測(cè)出實(shí)際的Bug。在Page Object超類(lèi)實(shí)現(xiàn)常規(guī)檢查需要查找“常規(guī)問(wèn)題”,例如JavaScript錯(cuò)誤、日志中的應(yīng)用程序錯(cuò)誤等。對(duì)于與特定頁(yè)面有關(guān)的合理檢查,可以繞過(guò)這種方法額外增加針對(duì)具體頁(yè)面的檢查。
實(shí)驗(yàn)
為了進(jìn)行實(shí)驗(yàn),我們決定使用公開(kāi)的郵件系統(tǒng)。考慮到Gmail和Yahoo的流行度,這些系統(tǒng)中所有存在的Bug都已被發(fā)現(xiàn)的可能性相當(dāng)高。因此我們選擇了ProtonMail。
Taking Over Random
假設(shè)自動(dòng)化解決方案已經(jīng)位,我們“采用”了Shiny系統(tǒng)的自動(dòng)化測(cè)試機(jī)制:首先建立一個(gè)通用的Java/Selenium測(cè)試項(xiàng)目,其中包含幾個(gè)使用Page Object模式實(shí)現(xiàn)的冒煙測(cè)試。隨后按照佳實(shí)踐,所有業(yè)務(wù)方法可以返回一個(gè)新的Page Object(針對(duì)業(yè)務(wù)方法結(jié)束時(shí)依然顯示在瀏覽器中的頁(yè)面)或當(dāng)前Page Object,除非頁(yè)面被更改。
為進(jìn)行自動(dòng)化探索式測(cè)試,我們?cè)黾恿税趀xplr.core包中的類(lèi),其中感興趣的當(dāng)屬TestCaseGenerator和TesCaseExecutor。
TestCaseGenerator
為了生成新的“隨機(jī)”測(cè)試用例,可以通過(guò)TestCaseGenerator類(lèi)調(diào)用兩個(gè)generateTestCase方法之一。這兩個(gè)方法都能以參數(shù)的方式接受代表所生成測(cè)試用例中“步驟驗(yàn)證對(duì)”數(shù)量的整數(shù)。第二個(gè)方法還可額外接受一個(gè)代表要使用的“驗(yàn)證策略”數(shù)量的參數(shù)(第一個(gè)方法使用默認(rèn)策略,本例為USE_PAGE_SANITY_VERIFICATIONS)。
驗(yàn)證策略代表在向測(cè)試用例添加“檢查”步驟時(shí)所用的方法。目前我們有兩個(gè)選項(xiàng):
1.USE_RANDOM_VERIFICATIONS:第一個(gè),同時(shí)也是明顯的策略。該策略的想法在于,使用來(lái)自頁(yè)對(duì)象的當(dāng)前驗(yàn)證方法。但不足之處在于嚴(yán)重依賴(lài)上下文。例如:我們隨機(jī)選擇了一個(gè)方法來(lái)驗(yàn)證特定主題的消息是否存在。首先,我們必須知道要查找哪個(gè)主題。為此我們引入了@Default注解和DefaultTestData類(lèi)。DefaultTestData包含的常規(guī)測(cè)試數(shù)據(jù)可用于隨機(jī)測(cè)試。@Default注解可用于將該數(shù)據(jù)綁定給特定的方法參數(shù)。隨后我們需要確保包含該主題的消息先于驗(yàn)證操作已存在(可在執(zhí)行該規(guī)范的過(guò)程中,或之前的任何測(cè)試過(guò)程中創(chuàng)建)。為此可通過(guò)@Depends注解告訴TestCaseGenerator檢查特定方法的調(diào)用,如果當(dāng)前步驟之前沒(méi)找到則直接添加。此外我們還需要確保消息沒(méi)有在驗(yàn)證之前刪除。我們發(fā)現(xiàn)對(duì)于生成的測(cè)試用例,依賴(lài)性問(wèn)題大幅降低了隨機(jī)化程度,并且這種方法的穩(wěn)定性也無(wú)法滿(mǎn)足要求。
2.USE_PAGE_SANITY_VERIFICATIONS:該策略可檢查顯而易見(jiàn)的應(yīng)用程序失敗,如顯示了錯(cuò)誤的頁(yè),錯(cuò)誤信息,JavaScript錯(cuò)誤,應(yīng)用程序日志中的錯(cuò)誤等。在依賴(lài)性方面這個(gè)策略更靈活,可在需要時(shí)實(shí)現(xiàn)針對(duì)具體頁(yè)的檢查,例如已經(jīng)足夠靈活到可以找出實(shí)際的Bug。目前我們將其用作默認(rèn)的驗(yàn)證策略。
TestCaseGenerator類(lèi)可按照類(lèi)名搜索Page對(duì)象:每個(gè)名稱(chēng)中包含“Page”字符串的類(lèi)都會(huì)被看作是頁(yè)對(duì)象。頁(yè)對(duì)象的所有公開(kāi)方法會(huì)被視作業(yè)務(wù)方法。名稱(chēng)包含“Verify”字符串的業(yè)務(wù)方法會(huì)被視作驗(yàn)證,所有其他方法會(huì)被視作測(cè)試步驟。@IgnoreInRandomTesting注解可用于從列表中排除某些工具方法或整個(gè)頁(yè)對(duì)象。
隨后可從兩個(gè)列表中隨機(jī)選擇方法生成測(cè)試用例:一個(gè)列表包含測(cè)試步驟,一個(gè)列表包含驗(yàn)證步驟(如果所選驗(yàn)證策略需要驗(yàn)證步驟的話(huà))。選擇第一個(gè)方法后,將檢查其返回值是否為另一個(gè)頁(yè)對(duì)象。如果返回值是另一個(gè)頁(yè)對(duì)象,那么將從其方法中選擇下一個(gè)步驟(參見(jiàn)上文備注)。為避免在兩個(gè)頁(yè)之間循環(huán)往復(fù),有一成的概率會(huì)跳轉(zhuǎn)至一個(gè)完全隨機(jī)的頁(yè)面。如果方法使用@Depends注解標(biāo)注了任何依賴(lài)項(xiàng),則會(huì)按需解決這些問(wèn)題并添加。
為避免出現(xiàn)從當(dāng)前所顯示頁(yè)之外其他對(duì)象調(diào)用測(cè)試方法的情況,生成的測(cè)試用例會(huì)傳遞一個(gè)額外的驗(yàn)證,借此添加缺少的導(dǎo)航調(diào)用。
TesCaseExecutor
生成之后,測(cè)試用例基本上是一種“類(lèi)-方法對(duì)”列表,可通過(guò)特定方式執(zhí)行或保存。盡管可在運(yùn)行時(shí)執(zhí)行,但從調(diào)試和后續(xù)分析的角度來(lái)看,保存為文件是一種更好的做法。
生成的測(cè)試用例可通過(guò)多種方式執(zhí)行,可以TesCaseExecutor作為其接口,以SaveToFileExecutor作為的實(shí)現(xiàn),借此可簡(jiǎn)單地創(chuàng)建一個(gè)代表所生成測(cè)試用例的.java文件。令人驚異的是,這種相當(dāng)簡(jiǎn)單的解決方案完全滿(mǎn)足了我們的需求:實(shí)現(xiàn)速度快,可對(duì)測(cè)試結(jié)果進(jìn)行深入分析,并能了解具體的生成方式。的不足在于,必須手工編譯并運(yùn)行生成的測(cè)試用例,不過(guò)對(duì)于實(shí)驗(yàn)來(lái)說(shuō),這也算不得什么大問(wèn)題。
SaveToFileExecutor生成的測(cè)試用例代碼可通過(guò)模板轉(zhuǎn)換為可編譯的文件。這樣生成的測(cè)試范例如下:
@Test(dataProvider = "WebDriverProvider")
public void test(WebDriver driver){
login(driver);
//****<Generated>****
ContactsPage contactspage = new ContactsPage(driver, true);
InboxMailPage inboxmailpage = contactspage.inbox();
inboxmailpage.sanityCheck();
ComposeMailPage composemailpage = inboxmailpage.compose();
composemailpage.sanityCheck();
composemailpage.setTo("
me@myself.com");
composemailpage.send();
inboxmailpage.sanityCheck();
List list = inboxmailpage.findBySubject("Seen that?");
inboxmailpage.sanityCheck();
inboxmailpage.inbox();
inboxmailpage.sanityCheck();
DraftsMailPage draftsmailpage = inboxmailpage.drafts();
draftsmailpage.sanityCheck();
inboxmailpage.inbox();
inboxmailpage.sanityCheck();
inboxmailpage.sendNewMessageToMe();
inboxmailpage.setMessagesStarred(true, "autotest", "Seen that?");
inboxmailpage.sanityCheck();
TrashMailPage trashmailpage = inboxmailpage.trash();
trashmailpage.sanityCheck();
//****</Generated>****
}
SaveToFileExecutor生成的代碼位于<Generated>備注之間,其余代碼由模板添加。
從所執(zhí)行的操作方面來(lái)看,我們生成的用例多樣化程度一般,但只要添加包含更多測(cè)試步驟的更多頁(yè)對(duì)象即可輕松解決。
在進(jìn)行過(guò)上千個(gè)“隨機(jī)”測(cè)試后,我們發(fā)現(xiàn)Protonmail沒(méi)什么大問(wèn)題(例如錯(cuò)誤頁(yè)),但瀏覽器匯報(bào)了一些JavaScript錯(cuò)誤,對(duì)于依賴(lài)JavaScript進(jìn)行郵件編解碼工作的系統(tǒng),這些問(wèn)題非常重要。很明顯,整個(gè)實(shí)驗(yàn)中我們并不能訪問(wèn)服務(wù)器日志,但實(shí)驗(yàn)的角度來(lái)說(shuō),已經(jīng)足夠展示出這樣的方法對(duì)被測(cè)試系統(tǒng)質(zhì)量的促進(jìn)能起到多大的作用。
當(dāng)然,隨機(jī)測(cè)試無(wú)法取代主觀或傳統(tǒng)測(cè)試技術(shù),但可在回歸測(cè)試過(guò)程中讓我們對(duì)應(yīng)用程序質(zhì)量更為自信。