在本系列的上一篇文章中,我通過系統登入這一典型功能點,演示了編寫自動化測試指令碼的整個流程,並對測試指令碼進行了初步優化。
在本文中,我將重點介紹如何對自動化測試指令碼實現⎡工程化⎦的組織和管理。
測試指令碼⎡工程化⎦
首先說下什麼是測試指令碼的工程化。
通過之前的工作,我們已經可以讓單個自動化測試用例正常執行起來了。然而,這還只算是一個demo
,一切才剛剛開始。
試想,一個專案的自動化測試用例少則數百,多則成千上萬。如何將這些自動化測試用例組織起來?如何實現更好的可重用機制?如何實現更好的可擴充機制?這些都還是我們當前的demo所不具備的,也是我們需要通過“工程化”手段進行改造的原因。
引入Minitest/RSpec
在Ruby中,說到測試首先就會想到Minitest或RSpec,這是Ruby中用的最多的兩個測試框架。通過這些框架,我們可以很好地實現對Ruby測試用例的管理。
同樣地,由於我們的自動化測試指令碼是採用Ruby編寫的,因此我們也可以使用Minitest/RSpec來管理我們的自動化測試用例。
基於該想法,我們採用RSpec對之前的系統登入測試用例進行工程結構初始化。對於熟悉Ruby程式設計,或者有一定程式碼基礎的同學而言,很自然地,可以將測試用例框架初始化為如下結構。
1 2 3 4 5 6 7 8 9 10 11 |
├── Gemfile ├── android │ └── appium.txt ├── common │ ├── requires.rb │ └── spec_helper.rb └── ios ├── appium.txt └── spec └── login_spec.rb |
在Gemfile
中,指定了專案依賴的庫。
1 2 3 4 5 6 |
# filename: Gemfile source 'https://gems.ruby-china.org' gem 'rspec' gem 'appium_lib' gem 'appium_console' |
在common/spec_helper.rb
中,定義了模擬器和RSpec初始化相關的程式碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# filename: common/spec_helper.rb def setup_driver return if $driver appium_txt = File.join(Dir.pwd, 'ios', 'appium.txt') caps = Appium.load_appium_txt file: appium_txt Appium::Driver.new caps end def promote_methods Appium.promote_appium_methods RSpec::Core::ExampleGroup end setup_driver promote_methods RSpec.configure do |config| config.before(:each) do $driver.start_driver wait { alert_accept } end config.after(:each) do driver_quit end end |
在common/requires.rb
中,實現了對相關庫檔案的引用。
1 2 3 4 5 6 7 8 |
# filename: common/requires.rb # load lib require 'rspec' require 'appium_lib' # setup rspec require_relative 'spec_helper' |
在ios/appium.txt
中,對iOS模擬器資訊和測試包路徑進行了配置。
1 2 3 4 5 |
[caps] platformName = "ios" deviceName = "iPhone 6s" platformVersion = "9.3" app = "/Users/Leo/MyProjects/AppiumBooster/ios/app/test.app" |
在ios/spec/
目錄中,則是測試用例的內容。例如,ios/spec/login_spec.rb
對應的就是系統登入的測試用例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# filename: ios/spec/login_spec.rb require_relative '../../common/requires' describe 'Login' do it 'with valid account' do wait { id('btnMenuMyAccount').click } wait { id 'uiviewMyAccount' } wait { id('tablecellMyAccountLogin').click } wait { id 'uiviewLogIn' } wait { id('txtfieldEmailAddress').type 'leo.lee@dji.com' } wait { id('sectxtfieldPassword').type '123321' } wait { id('btnLogin').click } wait { id 'tablecellMyMessage' } end end |
通過以上程式碼結構初始化,我們的測試用例框架的雛形就形成了。接下來,在Terminal中切換到專案根目錄,然後通過rspec ios
命令就可以執行ios目錄中的測試用例了。
1 2 3 4 5 6 |
➜ rspec ios . Finished in 2 minutes 7.2 seconds (files took 1.76 seconds to load) 1 example, 0 failures |
完整的程式碼請參考debugtalk/AppiumBooster
的1.FirstTest
分支。
新增第二條測試用例
現在,我們嘗試往當前的測試框架中新增第二條測試用例。
例如,第二條測試用例要實現啟動後從當前地區切換至中國。那麼,就可以新增ios/spec/change_country_spec.rb
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# filename: ios/spec/change_country_spec.rb require_relative '../../common/requires' describe 'Change country' do it 'from Hong Kong to China' do wait { id('btnMenuMyAccount').click } wait { id 'uiviewMyAccount' } wait { id('tablecellMyAccountSystemSettings').click } wait { id 'txtCountryDistrict' } wait { id('txtCountryDistrict').click } wait { id 'uiviewSelectCountry' } wait { id('tablecellSelectCN').click } wait { id('btnArrowLeft').click } wait { id 'uiviewMyAccount' } end end |
完整的程式碼請參考debugtalk/AppiumBooster
的2.SecondTest
分支。
現在我們凝視已經新增的兩個測試用例,有發現什麼問題麼?
是的,重複程式碼太多。在每一步操作中,都要用id
來定位控制元件,還要用wait
來實現等待機制。
除此之外,當前程式碼最大的問題就是測試用例與控制元件對映雜糅在一起。造成的後果就是,不管是控制元件對映發生變動,還是測試用例需要修改,都要來修改這一份程式碼,維護難度較大。
重構:測試用例與控制元件對映分離
基於以上問題,我們首要的改造任務就是將測試用例與控制元件對映進行分離。
考慮到常用的控制元件操作方法就只有幾個(click
,type
),因此我們可以將控制元件操作方法單獨封裝為一個模組,作為公共模組。
1 2 3 4 5 6 7 8 9 10 11 |
module Actions def click wait { @found_cell.click } end def type(text) wait { @found_cell.type text } end end |
然後,將APP中每一個頁面封裝為一個模組(module
),將頁面中的控制元件對映為模組的靜態方法(method
),並通過include
機制引入方法模組。
例如,登入頁面就可以封裝為如下程式碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
module Pages module Login class << self include Actions def field_Email_Address @found_cell = wait { id 'txtfieldEmailAddress' } self end def field_Password @found_cell = wait { id 'sectxtfieldPassword' } self end def button_Login @found_cell = wait { id 'btnLogin' } self end end end end module Kernel def login Pages::Login end end |
這裡還用到了一點Ruby超程式設計技巧,就是將頁面模組封裝為一個方法,並加入到Kernel
模組下。這樣做的好處就是,我們可以在專案的任意地方直接通過login.button_Login.click
這樣的形式來對控制元件進行操作了。
完成以上改造後,系統登入測試用例就可以採用如下形式進行編寫了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
describe 'Login' do it 'with valid account' do # switch to My Account page my_account.button_My_Account.click inner_screen.has_control 'uiviewMyAccount' # enter login page my_account.button_Login.click inner_screen.has_control 'uiviewLogIn' # login login.field_Email_Address.type 'leo.lee@dji.com' login.field_Password.type '123321' login.button_Login.click inner_screen.has_control 'tablecellMyMessage' end end |
完整的程式碼請參考debugtalk/AppiumBooster
的3.RefactorV1
分支。
To be continued …
經過這一輪重構,我們的測試用例與控制元件對映已經實現了分離,測試用例的可重用性與可擴充套件性也得到了極大的提升。
然而,在當前模式下,所有的測試用例仍然是以程式碼形式存在的,新增和修改測試用例時都需要到工程目錄下編輯Ruby檔案。
那有沒有一種可能,我們只需要在表格中維護自動化測試用例(如下圖),然後由程式碼來讀取表格內容就可以自動執行測試呢?
是的,這就是我們對測試框架進行⎡工程化⎦改造的下一個形態,也就是AppiumBooster
現在的樣子。
在下一篇文章中,我們再進行詳細探討。