自動化功能測試的邏輯

持續交付涉及到軟件開發從需求到上線、運維全生命週期的各個活動。其中很重要的一個活動就是測試。如果沒有動化測試,整個交付的節奏就會慢下來。接下來我們來聊一聊這背後的邏輯和如何才能把它做好。
        軟件開發中的自動化測試可以粗略的分爲自動化單元測試和自動化功能測試。二者有很多的相似之處,但同樣也有很多不同的關注點。本文主要關注的是自動化功能測試


爲什麼要做自動化測試
        如果你的軟件是Web形態的,則用戶可以通過瀏覽器來使用你的軟件;如果你的軟件是手機遊戲,則用戶需要有一臺手機才能使用你的軟件。而功能測試,顧名思義,測的就是這些用戶能夠看到並使用的功能。
        其實大多數軟件開發流程都有功能測試這個環節。比如在傳統的瀑布模式中,每次發佈之前都需要進行一輪或者多輪的迴歸測試,以保證軟件功能正確。在這種模式下,手動的功能測試就是全部的測試活動。這些測試起到了防護網的作用,保證在發佈前能夠找到大部分的bug,並修復之。聽起來不錯,那爲什麼我們還是需要自動化的功能測試


強哥的故事
        讓我們回到開發人員的視角來看看強哥的故事。強哥所在的團隊使用迭代 開發,每個功能開發完成之後,會讓測試人員及時進行測試,每個月會進行一次發佈。
        作爲一個有節操的開發人員,秉承爲自己的代碼負責的原則,強哥喜歡對自己所開發的功能進行細緻的分析、開發和測試。這不,手上接了一個活,經過一番分析,梳理出了8個case:
花了一個小時完成了case 1,進行了一番完備的測試。又過了一個小時,case 2也完成了。強哥把case 1和case 2都再測試一遍。如此反覆,等完成case 5的時候,自然需要從1到5都測試一遍。
        這 時候強哥心裏有點犯嘀咕了:“case 1測了好幾遍了,都的有點想吐了,而且case 5修改的代碼其實跟case 1不是特別相關,要不先不測了,等到最後功能完成的時候在一起測試一遍吧”。於是在沒有迴歸測試的前提下強哥完成了5、6、7、8這些case,而這時距 離剛開始做這個功能已經過去兩天了。
        “終於做完了”,強哥長吁了一口氣,“整體測一下吧”。不測不要緊,一測發現case 1、3,7都通不過了。
        “還好又進行了測試”,強哥得意地想着,然後着手開始修復這些問題。 經過一番艱苦卓絕的debug,終於定位了問題,並全部修復。這些工作又花掉了強哥3個小時。但強哥覺得這都是值得的,然後帶着程序員的驕傲把功能交付給 了測試人員。強哥考慮到的這些場景經過測試人員的測試,沒有任何問題,一次通過。只是還有兩個異常場景強哥沒有考慮到,所以又回過頭來修復了一下,這次倒 是很快,10分鐘就修好了。不過爲了保險起見,強哥還是把所有的場景又迴歸了一遍。幸好,沒什麼問題,然後又提交給了測試人員。這次再沒有什麼問題了,通 過!
就這樣,強哥帶着程序員的驕傲一次次高質量地完成了手裏的工作。然後來到了迭代末尾,距離強哥完成上面那個功能已經過去三週了。要進行發佈 了,迴歸測試肯定還是要做的。第一輪結束後,發現了幾個問題,其實一個竟然出現在強哥開發的功能中。“這個case我可是精心測過的,還測了好幾遍,怎麼 掛了?”,強哥很不高興。其他的開發同事跟強哥說:“別人做功能的時候,不可避免會影響已有的功能嘛,這都是正常現象,不然要回歸測試幹嘛呢?”
        他 說的好有道理,強哥一時竟無法反駁。在進行某一個小功能的開發時,可以在每次修改之後,都把這個功能所涵蓋的case都回歸一遍,但當這個功能提交給測試 以後呢?別人在同一個代碼庫中做了某些修改,是不會對你的功能再進行一遍測試的。且不說別人都不知道你做了什麼功能,就算知道,也不可能把所有的功能都再 測試一遍呀,那要花多少時間呢?
        修好這個問題之後,帶着無奈和不甘心,強哥查了一下是哪次提交破壞了他的功能。讓他驚訝的是,在他完成那個功能的兩天後,也就是將近三週前,這個功能就壞掉了。強哥心裏很不舒服。。。


自動化拯救世界
        強哥所開發的軟件是Web應用。後來強哥在一篇文章中知道了Selenium可以用來做瀏覽器的自動化測試。感覺豁然開朗:“如果我做的功能都能夠用這個自動化測試來覆蓋,那之前的那些問題大概都能解決了吧”。
        每次做完一個小功能時,補充一個自動化測試。後面每加一個功能只需要運行一遍這個不斷增加的測試集即可。
        這些測試不光可以在我開發一個功能的時候幫我去運行這些重複性的驗證,別人也可以在做完修改之後運行這些測試,以保證他沒有破壞已有的功能。
        有了這些測試的保證,發佈前的迴歸也會快的多。


爲什麼不做自動化測試?
        聽起來做自動化功能測試真的是一件很美好的事情,但實際上做的人並不多。那都是什麼原因呢?


初始投入
        做自動化測試的技術門檻不算高,但一開始還是需要一些投入。其中包括工具的學習和使用,基礎框架的搭建、相關CI任務的搭建等。於是很多人都是這種狀態:
346_1955944310590453_ac7396655be9605.png


持續的投入
        測 試本身也是以代碼的形式存在的,是代碼就會腐化。於是編寫測試也會變成一件成本越來越高的事情。另一方面,測試的有效性也很關鍵。比如如果一個用例聲稱會 覆蓋功能A,但是功能A出問題時,測試竟然沒報錯;或者當一個測試失敗10次,其中有5次是因爲測試環境出錯、測試代碼本身不穩定造成的,那估計團隊也會 越來越不想做寫自動化測試了。


運行時間太久
        功能測試一般都會模擬真正用戶的行爲,所以相對於單元測試來說,它們都很慢。如果對軟件的所有行爲編寫功能測試,隨着時間的推移,你會發現跑一邊測試就需要一個小時,那還會有誰在每次提交之前運行一遍測試?


如何有效的做自動化測試
        爲了有效地進行自動化測試,你需要考慮很多。
選擇正確的工具
        要知道如何選擇工具,首先要了解自動化測試的層次:
346_1955944310590453_d56e5f9318703df.png
        測 試框架本身非常關鍵,一個好的測試框架本身可以幫助你編寫出可維護的測試用例,並提高編寫效率。你可以使用junit、rspec之類的工具可以達到目 的。但Cucumber和Robotframe等工具可以提供更多更好的功能,詳情可以參看筆者的另一篇文章:Cucumber和 Robotframework對比。如果你在進行基於Rack的應用程序的測試,那Capybara絕對是不二的選擇。
        一個好的 測試驅動同樣可以大幅提升用例開發的效率。在對網絡通信的測試中,如果沒有Scapy這個庫的支持,恐怕編寫用例的成本要增加一個數量級。在Web測試的 上下文中,不光要考慮測試庫是否能夠滿足你的功能性需求,還要考慮運行速度。基於真實瀏覽器的測試是很慢的,所以出現了PhontamJS這樣的 headless driver。
        數據準備同樣是編寫測試中一個非常重要的步驟。你可以選擇自己往數據裏面插數據,並進行清理,但如果有一個工具可以幫助你做類似的事情,豈不是很好?Ruby世界的FactoryGirl,和DatabaseCleaner可以在這方面提供很多幫助,節省你大量的時間。
        能找到合適的工具是一件很幸運的事情。很多時候也可能找不到很好的工具。但這也沒關係,就像編寫產品代碼一樣,自己花時間寫一個相應的庫或者工具就好。


修復不穩定因素
        環境不穩定、測試代碼本身不穩定等因素都會導致大家越來越不想去運行測試。所以要在第一時間修復這些問題。舉兩個例子:
        我工作過的一個軟件系統會 依賴與一個搜索引擎。由於搜索引擎是收費軟件,爲了節省成本,持續集成所使用的搜索引擎和手工測試使用的搜索引擎是同一個實例。所以有時候手工測試在其中 注入數據時會導致自動化測試失敗。後來又花錢買了一份liscense部署了一臺新的實例,測試穩定性有了很大的提升。
        如果發現 了不穩定的測試,要第一時間修復。如果一時半會修復不了怎麼辦呢?我所在的團隊曾經採用過的一種方法是打一個@quarantine的標籤給這個測試,然 後修改運行腳本,跳過所有打了這個標籤的測試。然後再慢慢修復這個測試。這樣就可以保證整個測試集是健康的,測試結果一定能夠給你帶來有效的信息


方便測試的運行
        如果我告訴你爲了運行整個測試集,你需要手動重建數據庫,然後feed一些測試數據到數據庫中,然後啓動應用程序A、B和C,然後再配置一 系列的環境變量,然後再運行一串巨長的帶着各種參數的命令,你是否還願意跑這個測試?爲何不把這些繁瑣的工作寫到一個腳本中,讓別人簡單地運行一個短短的 命令就可以完成這個工作?這個腳本可以是一個shell腳本,但如果能夠集成到項目所使用的構建工具中就更好了。在Ruby項目中可以集成到Rake中, 在Node項目中可以集成到Gulp中,在Java項目中可以集成到Gradle中。
        好了,現在你可以方便地運行所有的測試了。但是別忘了開發人員大部分的時間是在編寫和調試單個用例,所以請保證你選用的測試框架或者IDE能夠支持單個用例的運行和調試。
結合持續集成和PrePush
        自動化功能測試的運行速度是比較慢的。即使你已經採用了類似PhantomJS這樣的驅動,所有測試的整體運行時間還是不可避免的會很長。在持續集成服務上 可以採用並行的方式讓多臺機器來同時運行測試。但在本地如何並行運行呢?答案是挑選其中一部分我們覺得非常重要的測試來運行,而不是全部。具體的做法就是 在我們認爲很重要的用例上打上@prePush之類的標籤,然後修改啓動測試的腳本來只運行打了這些標籤的測試。經驗值是運行prePush的時間不要超 過5分鐘。
有了這個prePush腳本,就可以把它嵌在git push的hook中(假設你使用的是git),以保證每個人在提交代碼的時候都能夠運行一遍這些測試,從而確保自己的修改沒有破壞別人的功能。


自動化測試不能解決什麼問題
        當然了,自動化測試也不是萬能的。舉個例子,強哥對自己能夠想到的功能進行了完備的測試,但是有些異常場景如果沒有考慮到,自然也就沒法編寫相應的測試。所以需求分析和用例設計其實是一個單獨的、但也同等重要的課題。
        上文提到了很多次功能測試運行時間太久的問題,這個問題除了使用prePush和並行運行之外還有什麼辦法可以解決嗎?答案就是自動化單元測試和測試金字塔!當然本文就不對這個話題展開討論。


不忘初心
自動化功能測試本身並不是目的,並不是你跟別人說:“看,我的測試覆蓋率超過90%”,你的項目就成功了。代碼沒bug、開發可以快速交付、市場認可纔是最終目的,而自動化功能測試只是實現這些的目標的一個手段而已。即不是唯一的手段,也不是必不可少的手段。 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章