在以前的博客中,測試腳本是使用線性模式來編寫的,以下:
注意:本博客全部代碼僅爲示例html
# -*- coding:utf-8 -*- # @author: 給你一頁白紙 import logging from appium import webdriver from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support.ui import WebDriverWait from appium.webdriver.common.mobileby import MobileBy as By logging.basicConfig(filename='./testLog.log', level=logging.INFO, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s') def android_driver(): desired_caps = { "platformName": "Android", "platformVersion": "10", "deviceName": "PCT_AL10", "appPackage": "com.ss.android.article.news", "appActivity": ".activity.MainActivity", "unicodeKeyboard": True, "resetKeyboard": True, "noReset": True, } logging.info("啓動今日頭條APP...") driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps) return driver def is_toast_exist(driver, text, timeout=20, poll_frequency=0.1): ''' 判斷toast是否存在,是則返回True,不然返回False ''' try: toast_loc = (By.XPATH, ".//*[contains(@text, %s)]" % text) WebDriverWait(driver, timeout, poll_frequency).until( ec.presence_of_element_located(toast_loc) ) return True except: return False def login_test(driver): '''登陸今日頭條操做''' logging.info("開始登錄今日頭條APP...") try: driver.find_element_by_id("com.ss.android.article.news:id/bu").send_keys("xxxxxxxx") # 輸入帳號 driver.find_element_by_id("com.ss.android.article.news:id/c5").send_keys("xxxxxxxx") # 輸入密碼 driver.find_element_by_id("com.ss.android.article.news:id/a2o").click() # 點擊登陸 except Exception as e: logging.error("登陸錯誤,緣由爲:{}".format(e)) # 斷言是否登陸成功 toast_el = is_toast_exist(driver, "登陸成功") assert toast_el, True logging.info("登錄成功...") if __name__ == '__main__': driver = android_driver() login_opera(driver)
可是,這種線性模式存在如下等缺點:python
元素定位屬性和代碼混雜在一塊兒,不方便後續維護android
公共模塊和業務模塊混合在一塊兒,顯得代碼冗餘web
適用測試場景太單一設計模式
在業務場景較爲簡單時這樣寫彷佛沒問題,但一旦遇到產品需求變動、業務邏輯比較複雜,須要維護的時就會很是麻煩。app
將公共方法(如:is_toast_exist(),日誌記錄器等)抽離出來,放入單獨模塊框架
將元素定位方法、元素屬性值、測試業務代碼分離單元測試
登陸操做單獨封裝成一個模塊測試
使用Unittest單元測試框架管理並執行測試用例優化
基於以上思路,咱們就須要引入Page Object測試設計模式。
Page Object模式是Selenium中的一種測試設計模式,是Selenium、appium自動化測試項目的最佳設計模式之一。Page Object的一般的作法是,將公共方法、邏輯操做(元素定位、操做步驟)、測試用例、測試數據和測試驅動相互分離,能夠理解爲將測試項目進行以下分層:
公共方法層
邏輯操做層(元素定位,測試步驟)
測試用例層(測試業務)
測試數據層
測試驅動層(執行測試用例)
公共方法層,包括公共方法或基礎方法。
邏輯操做層,主要是將每個頁面或該頁面須要測試的某個功能涉及到的元素設計爲一個class。
測試用例層,只需調用邏輯操做層中對應頁面的class便可。
測試數據層,即測試數據分離,包括配置數據和測試數據,如Capabilities、登陸帳號密碼。
測試驅動層,執行整個測試並生成測試報告。
使用Page Object模式,Unittest管理測試用例。unittest框架請參考博客Unittest單元測試框架
封裝App啓動的Capabilities配置信息,baseDriver.py
# -*- coding:utf-8 -*- # @author: 給你一頁白紙 import yaml from appium import webdriver from common.baseLog import logger def android_driver(): stream = open("../config/desired_caps", "r") data = yaml.load(stream, Loader=yaml.FullLoader) desired_caps = {} desired_caps["platformName"] = data["Android"], desired_caps["platformVersion"] = data["platformVersion"], desired_caps["deviceName"] = data["deviceName"], desired_caps["appPackage"] = data["appPackage"], desired_caps["appActivity"] = data["appActivity"], desired_caps["unicodeKeyboard"] = data["unicodeKeyboard"], desired_caps["resetKeyboard"] = data["resetKeyboard"], desired_caps["noReset"] = data["noReset"], desired_caps["automationName"] = data["automationName"] # 啓動app try: driver = webdriver.Remote('http://' + str(data['ip']) + ':' + str(data['port']) + '/wd/hub', desired_caps) logger.info("APP啓動成功...") driver.implicitly_wait(8) return driver except Exception as e: logger.error("APP啓動失敗,緣由是:{}".format(e)) if __name__ == '__main__': android_driver()
封裝基礎類,basePage.py
# -*- coding:utf-8 -*- # @author: 給你一頁白紙 from common.baseLog import logger from selenium.webdriver.support.ui import WebDriverWait from appium.webdriver.common.mobileby import MobileBy as By from selenium.webdriver.support import expected_conditions as EC class BasePage: def __init__(self, driver): self.driver = driver def get_visible_element(self, locator, timeout=20): '''獲取可視元素''' try: return WebDriverWait(self.driver, timeout).until( EC.visibility_of_element_located(locator) ) except Exception as e: logger.error("獲取元素失敗:{}".format(e)) def is_toast_exist(driver, text, timeout=20, poll_frequency=0.1): ''' 判斷toast是否存在,是則返回True,不然返回False ''' try: toast_loc = (By.XPATH, ".//*[contains(@text, %s)]" % text) WebDriverWait(driver, timeout, poll_frequency).until( EC.presence_of_element_located(toast_loc) ) return True except: return False
日誌模塊baseLog.py請參考博客Python日誌採集
封裝登陸,login_page.py
# -*- coding:utf-8 -*- # @author: 給你一頁白紙 from common.baseLog import logger from common.basePage import BasePage from appium.webdriver.common.mobileby import MobileBy as By class LoginPage(BasePage): username_inputBox = (By.ID, "com.ss.android.article.news:id/bu") # 登陸頁用戶名輸入框 password_inputBox = (By.ID, "com.ss.android.article.news:id/c5") # 登陸頁密碼輸入框 loginBtn = (By.ID, "com.ss.android.article.news:id/a2o") # 登陸頁登陸按鈕 def login_action(self, username, password): logger.info("開始登陸...") logger.info("輸入用戶名:{}".format(username)) self.get_visible_element(self.username_inputBox).send_keys(username) logger.info("輸入密碼:{}".format(password)) self.get_visible_element(self.password_inputBox).send_keys(password) self.get_visible_element(self.loginBtn).click()
封裝setUp、tearDown,baseTest.py
# -*- coding:utf-8 -*- # @author: 給你一頁白紙 import time import unittest from common.baseDriver import android_driver class StartEnd(unittest.TestCase): def setUp(self) -> None: self.driver = android_driver() def tearDown(self) -> None: time.sleep(2) self.driver.close_app()
封裝測試用例,test_login.py
# -*- coding:utf-8 -*- # @author: 給你一頁白紙 from common.baseLog import logger from common.baseTest import StartEnd from page.login_page import LoginPage class LoginTest(StartEnd): def test_login_right(self): logger.info("正確的帳號、密碼登陸") l = LoginPage(self.driver) l.login_action("13838380000", "123456") result = l.is_toast_exist("登陸成功") self.assertTrue(result) def test_login_error(self): logger.info("正確的帳號、錯誤的密碼登陸") l = LoginPage(self.driver) l.login_action("13838380000", "111111") result = l.is_toast_exist("密碼錯誤") self.assertTrue(result)
Capabilities配置數據,desired_caps.yml
appActivity: .activity.MainActivity appPackage: com.ss.android.article.news deviceName: newDeviceName platformName: Android platformVersion: newPlatformVersion automationName: UiAutomator2 unicodeKeyboard: true resetKeyboard: true noReset: true ip: 127.0.0.1 port: 4723
測試用例test_login.py中,正確的帳號、正確密碼、錯誤密碼也能夠配置在Yaml文件中,即數據分離,使用時讀取便可。Yaml文件的使用可參考博客Python讀寫Yaml文件。
執行測試模塊,run.py
# -*- coding:utf-8 -*- # @author: 給你一頁白紙 import time import unittest import HTMLTestRunner now = time.strftime("%Y-%m-%d_%H_%M_%S") report_dir = './report/' fp = open(report_dir + now + "_report.html", 'wb') runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title="App自動化測試報告", description="測試用例狀況") test_dir='./testcase' suite = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py') runner.run(suite) fp.close()
運行run.py模塊就能執行整個測試項目。