Android APP 自動化測試

自動化測試環境安裝

Android APP 自動化測試框架

Unittest框架

使用unittest編寫用例,必須遵守以下規則:

1、測試文件必須先import unittest;

2、測試類必須繼承unittest.TestCase;

3、測試方法必須以“test_”開頭;

4、測試類必須要有unittest.main()方法。

Test Fixture

A test fixture represents the preparation needed to perform one or more tests, and any assoicate cleanup actions.

  • setUp() : 進行測試前的初始化工作。
  • testCase() : 進行測試工作,可以有很多個
  • tearDown() : 執行測試后的清除工作。
Test Case & Test Suite &Test Runner

官網的含義介紹:

A test case is the smallest unit of testing. It checks for a specific response to a particular set of inputs. 測試用例是最小的測試單元。它檢查對特定輸入集的特定響應。

A test suite is a collection of test cases, test suites, or both. It is used to aggregate tests taht should be executed together. 測試套件是測試用例、測試套件或兩者的集合。它用于聚合應該一起執行的測試。

A test runner is a component thich orchestrates the execution of tests and provides the outcome to the user. 測試運行器是一個組件,它協調測試的執行并向用戶提供結果。

應用Test Suite 和Test Runner 運行自動化腳本
import unittestdemo
import unittest


# 以一個類的維度去執行
cases = unittest.TestLoader.loadTestsFromTestCase(unittestdemo.MyTestCase)
# 可以一次添加多個cases
my_suite = unittest.TestSuite([cases])

# 添加單個測試用例,在使用ddt后,將會不可用
# my_suite = unittest.TestSuite()
# my_suite.addTest(unittestdemo.MyTestCase("test_something"))

my_runner = unittest.TextTestRunner(verbosity=2)
my_runner.run(my_suite)
數據驅動DDT

官網

DDT(Data-Driven Tests) allows you to multiply one test case by runing it with different test data, and make it appear as multiple test cases.

DDT(數據驅動測試)允許您將一個測試用例與不同的測試數據一起運行,從而使它顯示為多個測試用例。

數據驅動DDT的使用
  • 準備第三方庫

    首先安裝ddt庫, 其次在腳本中引入ddt

    pip install ddt

  • 使用

    import unittest
    from ddt import ddt, data, unpack
    
    # 聲明該測試類采用ddt
    @ddt
    class MyTestCase(unittest.TestCase):
    
        # 使用元祖存放被測試的數據,一次只有一個參數的情況,每一組數據都會執行一次測試用例
        @data(1, 2, 3)
        def test_something(self, value):
            print value
            self.assertEqual(value, 2)
    
        # 使用元祖存放被測試的數據,一次有多個參數的情況,每一組數據都會執行一次測試用例
        @data((1, 2), (2, 3))
        @unpack
        def test_something(self, value1, value2):
            print value1, value2
            self.assertEqual(value2, value1 + 1)    
    
    if __name__ == '__main__':
        unittest.main()
        
    

    APP API自動化測試

    API

    API (Application Programming Interface, 簡稱API),又稱為應用編程接口,就是軟件系統不同組成部分銜接的約定。

    API種類:

    • 面向對象語言的API
    • 庫與框架的API
    • API與協議
    • API與設備接口
    • Web API - 就是APP API測試中要測試的HTTP API
    HTTP API

    HTTP中的8種不同方法:

    • GET:表示客戶端需要請求服務器的某個資源,對應DB中的Select操作,為http請求常用方法
    • POST: 一般用于向系統中更新數據,對應DB中的Update操作,為http請求常用方法;參數在Requet-Body中傳遞 ;相比較Get,較為安全
    • PUT:一般用于向系統中插入數據(當然,其功能Post也能實現,與Post有很多相似之處),對應DB中的Insert操作;傳輸內容放在Request-Body中;不安全,不帶驗證機制,故一般不使用該方法
    • DELETE:一般用于向系統中刪除數據,對應DB中的Delete操作;不帶驗證機制,故不安全
    • OPTIONS: 一般用來詢問URI支持的方法;查詢服務器的性能
    • HEAD:用法與Get一樣,只不過Head只返回Http-Responce頭部信息;由于Head只返回頭部信息(相對于Get,輕量級),故一般被用于確認URI的有效性,資源更新的日期時間等
    • TRACE:追蹤路徑,如追蹤一個資源請求中間所經過的代理;回顯服務器收到的請求,主要用于測試或診斷
    • CONNECT:隧道協議連接代理

抓包神奇Fiddler

環境準備
  • Fiddler安裝 - 官網下載地址

  • 基本設置

    打開Fiddler Tool->Fiddler Options->HTTPS 。 (配置完后記得要重啟Fiddler)

    設置https

    截獲HTTPS請求,第一次會彈出證書安裝提示,若沒有彈出提示,勾選Actions-> Trust Root Certificate,另外,如果你要監聽的程序訪問的 HTTPS 站點使用的是不可信的證書,則請接著把下面的 “Ignore servercertificate errors” 勾選上。

    trust_root_certificate

注意:Fiddler 是以代理web服務器的形式工作的,它使用代理地址:127.0.0.1,端口:8888。當Fiddler退出的時候它會自動注銷,這樣就不會影響別的 程序。不過如果Fiddler非正常退出,這時候因為Fiddler沒有自動注銷,會造成網頁無法訪問。解決的辦法是重新啟動下Fiddler。

基本界面
用戶界面
設置斷點修改Request/Response
  • Rules - Automatic BreakPoints - Before Requests/After Requests(但是這樣會攔截所有請求)

  • 通過工具欄設置斷點

    工具欄斷點

如圖,箭頭所指的位置時可以點擊的。共三種狀態:
空白:不設置斷點。
箭頭向上:表示斷點請求。此時客戶端的請求是無法直接到達目標服務器的,需要手動控制。
箭頭向下:表示斷點響應。此時目標服務器的響應是無法直接到達客戶端的,需要手動控制。

  • 通過命令設置斷點

    在上圖框起來的上方的黑色區域就是命令行,在命令行中輸入命令:

    bpu www.baidu.com  (斷點請求)
    bpu (清除攔截的請求斷點)
    bpuafter www.baidu.com(斷點響應)
    bpuafter (清除攔截的響應斷點)
    這種方法只會中斷指定的域名,如示例命令中的www.baidu.com
    
  • AutoResponder標簽 - Fiddler 的AutoResponder tab允許你從本地返回文件,而不用將http request 發送到服務器上。

構造HTTP請求

Composer允許自定義請求發送到服務器,可以手動創建一個新的請求,也可以在會話表中,拖拽一個現有的請求。

Parsed模式下你只需要提供簡單的URLS地址即可(如下圖,也可以在RequestBody定制一些屬性,如模擬瀏覽器User-Agent)。

構造get請求
抓取手機數據包
手機抓包設置

fiddler監聽端口默認是 8888,你可以把它設置成任何你想要的端口。勾選上 “Allow remote computersto connect” ,允許遠程設備連接。

為了減少干擾,可以去掉 “Act assystem proxy on startup” 。

手機端(客戶端)設置
  • 首先查看電腦的 IP 地址,確保手機和電腦在同一個局域網內。(cmd命令行內輸入ipconfig)

  • 將 Fiddler 代理服務器的證書導到手機上才能抓這些 APP 的包。導入的過程:打開瀏覽器,在地址欄中輸入代理服務器的 IP 和端口(即電腦的IP加fiddler的端口),會看到一個Fiddler 提供的頁面,然后確定安裝就好了。

下載證書

中,選擇“修改網絡”。在接下來彈出的對話框中,勾選“顯示高級選項”。在接下來顯示的頁面中,點擊“代理”,選擇“手動”。代理服務器主機名設為 PC 的 IP ,代理服務器端口設為 Fiddler 上配置的端口 8888,點”保存”。

數據驅動DDT實現API接口自動化測試

  • 安裝requests模塊
pip install requests
  • 使用requests模塊和ddt完成自動化api測試

    import unittest
    import requests
    from ddt import ddt, data, unpack
    import sys
    
    reload(sys)
    sys.setdefaultencoding('utf-8')
    
    @ddt
    class HttpTestCase(unittest.TestCase):
        def setUp(self):
            print "開始"
    
        def tearDown(self):
            print "結束"
    
        @data('101010100', '101250101', '101250301', '101250603', '101251003')
        def testGet(self, city_id):
            # headers = {
            #     "User-Agent": "test"
            # }
            # cookies = {}
    
            result = requests.get("http://www.weather.com.cn/data/sk/" + city_id + ".html")
            result.encoding = 'utf-8'
            print result.text
    
            self.assertTrue(city_id in result.text)
    
    
    if __name__ == '__main__':
        unittest.main()
    
    

    post和get類似,post請求會多一個params,也是一個字典,key和value的形式,調用requests.post(url, data=params, headers = headers, cookies = cookies)。

Android Native APP自動化(Python)

自動化測試工具Appium

Appium 是一個開源、跨平臺的自動化測試工具,用于測試Native(原生)Hybrid(混合)應用,支持iOS,Android和FirefoxOS平臺。

在Android平臺,是基于UIAutomator 框架。

Appium的理念
  • 無需重新編譯應用
  • 不局限于語言和框架
  • 無需重復造輪子,接口統一
  • 無論精神上,還是名義上,必須開源(免費)
Appium的特點
  • 跨架構,Native、Hybrid、Webview
  • 跨設備,Android、iOS、Firefox OS
  • 跨語言,Java、Python、Ruby、PHP、JavaScript
  • 跨進程,不依賴源碼(基于UIAutomator)
appium.jpg
腳本設計原則

LOVE原則

  • L:Locate 定位元素
  • O:Operate 操作元素
  • V:Verify 驗證結果
  • E:Exception 處理異常
自動化測試腳本demo編寫

下面的demo是對系統自帶的計算器APP進行測試的,界面元素的id和包名都是通過UIAutomator viewer獲取的。

import unittest
from appium import webdriver


class MyTestCase(unittest.TestCase):

    def setUp(self):
        desired_caps = {}
        desired_caps['platformName'] = 'Android'
        desired_caps['platformVersion'] = '9.0'
        desired_caps['deviceName'] = '192.168.115.101:5555'
        desired_caps['appPackage'] = 'com.android.calculator2'
        desired_caps['appActivity'] = '.Calculator'
        desired_caps["unicodeKeyboard"] = "True"
        desired_caps["resetKeyboard"] = "True"
        self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)

    def test_something(self):
        self.driver.find_element_by_id("digit_1").click()
        self.driver.find_element_by_id("digit_3").click()
        self.driver.find_element_by_id("digit_5").click()
        self.driver.find_element_by_id("op_add").click()
        self.driver.find_element_by_id("digit_8").click()
        self.driver.find_element_by_id("digit_9").click()
        self.driver.find_element_by_id("op_mul").click()
        self.driver.find_element_by_id("digit_3").click()
        self.driver.find_element_by_id("digit_9").click()
        self.driver.find_element_by_id("digit_2").click()
        self.driver.find_element_by_id("eq").click()

        result = self.driver.find_element_by_id("result").text
        self.assertEqual(result, "35,023")

    def tearDown(self):
        # 一定記得退出driver,不然下次運行會直接報錯,除非在Appium中手動停止連接
        self.driver.quit()


if __name__ == '__main__':
    unittest.main()

先運行Appium,點擊android圖標進行設置,主要設置platformVersion,我是用的Appium版本為appium1.4.16,由于appium1.4.16版本最高只支持安卓6.0版本,所以可以參考這篇文章進行一些修改設置,然后運行Appium啟動連接,再運行上面的demo,可以得到下面的輸出結果:

Testing started at 11:56 ...
E:\Python27\python.exe "E:\Program Files\JetBrains\PyCharm Community Edition 2019.1.3\helpers\pycharm\_jb_unittest_runner.py" --target unittestdemo.MyTestCase
Launching unittests with arguments python -m unittest unittestdemo.MyTestCase in F:\code\AutomatedTestingDemo


Ran 1 test in 16.699s

OK

Process finished with exit code 0
Appium常用的相關API的介紹
  • 控件定位

    方法 說明 備注
    find_element_by_id 通過元素的id定位,返回含有該屬性的元素,在UIAutomator viewer上即resource id 找不到元素時會拋出異常,以下同理
    find_elements_by_id 通過元素的id定位,返回含有該屬性的所有元素 找不到元素時不會拋出異常,以下同理
    find_element_by_name 通過元素Name定位,返回含有該屬性的元素,對于android,即text屬性
    find_elements_by_name 通過元素Name定位(元素的名稱屬性text),含有該屬性的所有元素
    find_element_by_xpath 通過Xpath定位,返回含有該屬性的元素
    find_elements_by_xpath 通過Xpath定位,返回含有該屬性的所有元素
    find_element_by_class_name 通過元素class name屬性定位,返回包含該屬性的元素
    find_elements_by_class_name 通過元素class name屬性定位,返回包含該屬性的所有元素 該字段存在的意義主要是為了一些有殘障的人士準備的,方便他們使用程序
    find_element_by_accessibility_id 通過accessibility id定位,在android app上相當于content-description字段,而在ios app 上accessibility identifier字段 同上
    find_elements_by_accessibility_id 同上
    find_element_by_android_uiautomator 根據UIautomator定位元素 僅android
    find_elements_by_android_uiautomator 同上 僅android
    find_element_by_ios_uiautomation 在iOS中通過uiautomation找到一個元素 僅iOS
    find_elements_by_ios_uiautomation 同上 僅iOS
  • 動作操作(手勢操作)

    方法 說明 備注
    click 點擊 element.click()
    send_keys 在元素中模擬輸入 開啟appium自帶的輸入法并配置了appium輸入法后,即unicodeKeyboard、resetKeyboard,可以輸入中英文
    clear 清除輸入的內容 element.clear()
    swipe(self, start_x, start_y, end_x, end_y, duration=None) 滑動,主要用于緩慢滑動,從(start_x, start_y)點滑動到(end_x, end_y)點,可以自定義duration【毫秒】滑動時間 driver.swipe(100,100,100,400)
    flick(self, start_x, start_y, end_x, end_y) 快速滑動 主要用于快速滑動,無duration,如View切換,按住(start_x, start_y)點后快速滑動至(end_x, end_y)點
    shake 搖一搖
    lock 鎖屏 iOS專有
    scroll(self, origin_el, destination_el) 滾動 從元素origin_el滾動至元素destination_el,只有iOS可以使用
    drag_and_drop(self, origin_el, destination_el) 拖放 將元素origin_el拖到目標元素destination_el
    zoom(self, element=None, percent=200, steps=50) 在元素上執行放大 driver.zoom(element)#默認分成50步完成,放大量為200%
    pinch(self, element=None, percent=200, steps=50) 在元素上執行縮小 driver.pinch (element)
    tap(self, positions, duration=None) 模擬手指點擊(最多五個手指),可設置按住時間長度(單位毫秒),positions參數為單位為元組的列表,如[(x1,y1),(x2,y2)] driver.tap([(300,500)],10)
    keyevent 發送按鍵碼(安卓僅有) KEYCODE_HOME (按鍵Home) : 3 ;KEYCODE_MENU (菜單鍵) : 82 ;KEYCODE_BACK (返回鍵) : 4`
  • 獲取相關元素及設備相關信息

    方法 說明 備注
    get_window_size 獲取當前設備的寬、高 width=driver.get_window_size()['width']
    location 獲取元素的左上角坐標 x=element.location['x']
    size 獲取元素的寬、高 width=element.size['width']
    current_activity 獲取設備當前的activity driver.current_activity
    context 獲取當前會話的當前上下文 driver.context
    app_strings 對于Android,獲取app的strings.xml文件內容 ios待試
  • 其他操作(系統操作如網絡等、截圖)

    方法 說明 備注
    wait_activity(self, activity, timeout, interval=1) 等待指定activity的出現,返回true or false 默認是輪詢間隔是1s,需要值得注意的是這里的參數是current_activity打印出來的值,即不包含包名
    quit 退出 退出腳本運行,并關閉每個相關的窗口連接
    background_app 后臺運行 把app放置于后臺運行,設置時間seconds,單位s,相當于一段時間后重啟app,而不是home將app放置后臺
    save_screenshot(self, filename) 截圖 filename參數為路徑,包含文件名稱
    is_app_installed 指定App是否安裝 參數為包名,已安裝返回True,否則返回False
    remove_app 卸載指定app 參數為包名
    install_appt(self, filename) 安裝指定app filename參數為路徑,包含apk名稱
    launch_app 啟動app 啟動的app為desired_caps里設置的app
    close_app 關閉app 關閉的app為desired_caps里設置的app
    start_activity 啟動指定activity 參數為packageName和activityName

Android Native APP自動化(Python)

針對于Hybrid的App,Appium是基于Selendroid框架實現,而Selendroid框架又是基于Instrumentation框架實現的。

可見,Appium本身是借助于其他框架控制APP。

Selendroid的架構
selendroid原理
定位頁面元素
  • 原生頁面元素:使用UIAutomator viewer定位

  • hybrid頁面原色: 使用Chrome瀏覽器的Inspector工具(打開瀏覽器,按F12進入開發者模式,先切換成手機顯示模式,然后在定位到元素后,在其對應的代碼里右鍵-copy XPath,其他瀏覽器也可以)。

copy_xpath

上下文

  • O:Operate 操作元素
  • V:Verify 驗證結果
  • E:Exception 處理異常
基于Selendroid的自動化腳本實現

在頁面里搜索一個關鍵詞,并驗證和預期一致

  • Appium的配置、啟動

    [圖片上傳失敗...(image-85a3f2-1586312847117)]

  • 腳本的初始化

    [圖片上傳失敗...(image-2236ef-1586312847117)]

  • 腳本的實現 - S-LOVE原則

    import unittest
    import time
    from appium import webdriver
    
    
    class MyTestCase(unittest.TestCase):
    
        def setUp(self):
            # 定義初始化的屬性信息
            self.desired_caps = {}
            self.desired_caps['platformName'] = 'Android'
            self.desired_caps['platformVersion'] = '6.0'
            self.desired_caps['deviceName'] = '192.168.115.101:5555'
            self.desired_caps['appPackage'] = 'com.hyd.miniwebbrowser'
            self.desired_caps['appActivity'] = '.MainActivity'
            self.desired_caps["unicodeKeyboard"] = "True"
            self.desired_caps["resetKeyboard"] = "True"
            self.desired_caps["automationName"] = "Selendroid"
            self.driver = webdriver.Remote('http://localhost:4723/wd/hub', self.desired_caps)
    
        def testSearch(self):
            # Locate 定位輸入框
            input_url = self.driver.find_element_by_id("et_url")
            # Operate 操作
            input_url.send_keys("http://wap.sogou.com")
    
            btn_search = self.driver.find_element_by_id("btn_search")
            btn_search.click()
    
            time.sleep(5)
    
           # Switch 切換當前的上下文
            print self.driver.contexts
            self.driver.switch_to.context('WEBVIEW_0')
            print self.driver.current_context
            time.sleep(5)
    
            # 定位web輸入框
            web_input = self.driver.find_element_by_xpath('//*[@id="keyword"]')
            web_input.click()
            web_input.send_keys("2020")
            web_search_button = self.driver.find_element_by_xpath('//*[@id="searchform"]/div/div/div[1]/div[2]/input')
            web_search_button.click()
            time.sleep(5)
    
            # 檢驗查詢結果
            first_result = self.driver.find_element_by_xpath('//*[@id="sogou_vr_30010212_1"]/div/div[1]/a[1]')
            self.assertTrue("2020" in first_result.text)
    
        def tearDown(self):
            self.driver.quit()
    
    
    if __name__ == '__main__':
        unittest.main()
    
  • 腳本的運行

    Testing started at 18:47 ...
    E:\Python27\python.exe "E:\Program Files\JetBrains\PyCharm Community Edition 2019.1.3\helpers\pycharm\_jb_unittest_runner.py" --target HybridScript.MyTestCase.testSearch
    Launching unittests with arguments python -m unittest HybridScript.MyTestCase.testSearch in F:\code\AutomatedTestingDemo\hybrid_app
    
    
    Ran 1 test in 39.463s
    
    OK
    
    Process finished with exit code 0
    [u'WEBVIEW_0', u'NATIVE_APP']
    WEBVIEW_0
    

結語

本文主要是我自己在學習用Python對Android APP進行自動化測試時踩過的一些坑和寫的Demo,離真正的自動化測試還有一定的距離,所以大家可以多寫寫自動化測試腳本,業精于勤!有不對的地方也請在評論里指出,謝謝!
文中代碼已上傳至GitHub,傳送門

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內容