appium移動自動化測試框架搭建實戰,附源碼(一)


    最近搭建了一個安卓端的APP自動化測試框架,下面就總結一些搭建的過程和思路,有不足之處還請指出

    1、首先說明一下環境:

     編輯器:pycharm2018.3.2

     python環境:python3.6

     appium環境:appium V1.15.1

     另外還有生成報告用到的allure

 2、再給大家看一下框架結構:

    

 

    base里面放的公用的方法,比如find_element,click,sendKeys等;

    data里面放的是我的測試用例所用到的一些參數,yml文件

    files里面就是待測試的apk,測試用例,測試計划等,我這里還放了我測試過程需要上傳的圖片

    page和scripts是PO模式,page放的某頁面中的方法,scripts放的測試用例

    result放的測試結果的log和報告

    screen是放我測試過程中的一些截圖的  

    util和base功能一樣,天知道我為什么弄兩個。。其實這兩個文件夾合並也是可以的。

    下面就是挨個文件夾介紹了。

  3、因為很多地方用到了util和base里面的東西,所以我們先說這兩個

    util:

      

    先說一下log.py,是用來記錄log的,下面貼一下記錄log的代碼,這段代碼嚴格來說不是我寫的,之前看到一個公眾號,感覺還不錯,就拿過來改吧改吧用了,

import logging
from
datetime import datetime import os import threading class Log: def __init__(self): self.pro_dir = os.path.dirname(os.path.abspath(__file__)) self.pro_dir = os.path.split(self.pro_dir)[0]
# 下面是記錄log的文件創建的過程 self.result_path
= os.path.join(self.pro_dir, "result") if not os.path.exists(self.result_path): os.mkdir(self.result_path) self.log_path = os.path.join(self.result_path, str(datetime.now().strftime("%Y%m%d%H%M%S"))) if not os.path.exists(self.log_path): os.mkdir(self.log_path) self.logger = logging.getLogger() self.logger.setLevel(logging.INFO) # 創建處理器對象 handler = logging.FileHandler(os.path.join(self.log_path, "output.log")) formatter = logging.Formatter('%(levelname)s %(name)s:%(filename)s:%(lineno)s>> %(message)s') # 為處理器添加設置格式器對象,添加過濾器對象的方法為:handler.setFilter(filter) handler.setFormatter(formatter) self.logger.addHandler(handler) def get_logger(self): return self.logger class MyLog: """ 將上面的記錄log的方法放到一個線程內,讓它單獨啟用一個線程,是為了更好的寫log """ log = None mutex = threading.Lock() def __init__(self): pass @staticmethod def get_log(): if MyLog.log is None: MyLog.mutex.acquire() MyLog.log = Log() MyLog.mutex.release() return MyLog.log

check_devices是用來判斷手機有沒有連接上,以及有沒有安裝需要測試的APP

 創建driver的時候,首先判斷了手機有沒有連接上,接着判斷APP有沒有安裝,如果沒有安裝,再確認一下apk有沒有,有的話就自動安裝,安裝完再測試。所以用到了下面這堆

import glob import os from base.base_action import BaseAction from util.log import MyLog # 定義全局變量
devices_list_finally = [] chose_file_num = [] log = MyLog().get_log() logger = log.get_logger() def is_devices_link(): """ 檢查是否有設備連接PC,有則返回True :return: """ devices_list_start = [] devices_cmd = os.popen('adb devices').readlines() devices_list_start_count = len(devices_cmd) devices_list_start_count = devices_list_start_count - 2
    if devices_list_start_count >= 1: print('find devices linked') for devices_num in range(devices_list_start_count): devices_list_start.append(devices_cmd[devices_num + 1]) device_list_pers = devices_list_start[devices_num].index('\t') devices_list_finally.append(devices_list_start[devices_num][:device_list_pers]) print('devices list :' + '%d ' % (devices_num + 1) + '%s' % devices_list_finally[devices_num]) return True else: print('Can not find devices link...pls check device link...') logger.error("無法連接到手機,試試重新插拔手機") return False def is_apk_installed(apk_path): """ 判斷手機是否安裝了待測試APP,安裝則返回True :return: """ app_package = BaseAction.get_app_package(apk_path) app_package = 'package:' + app_package + '\n' all_packages = list(os.popen("adb shell pm list package")) if app_package in all_packages: return True else: return False # 檢查本地文件是否存在,這個文件放到了files文件夾下的apk文件夾里面
def check_local_file(apk_path): file_list = glob.glob(apk_path) file_index = len(file_list) if file_index != 0: if file_index == 1: return True else: logger.error("無法安裝APP,請檢查apk文件路徑是否正確") exit() # 安裝應用
def install_apk(apk_path): for install_apk_to_devices_index in range(len(devices_list_finally)): os.system('adb -s' + ' ' + devices_list_finally[install_apk_to_devices_index] + ' ' + 'install' + ' ' + apk_path)

GlobalVar.py文件,寫來是因為有的case需要跨文件設置全局變量,所以有了這個文件:

""" 定義全局變量,並且全局變量需要跨文件使用時,可以用該類。 比如定義全局變量的時候可以這樣: global_var = GlobalVar() global_var.set_value("name", "value") 使用該全局變量的時候這樣: global_var.get_value("name") """

class GlobalVar: def __init__(self): global _global_dict _global_dict = {} @staticmethod def set_value(name, value): _global_dict[name] = value @staticmethod def get_value(name, def_value=None): try: return _global_dict[name] except KeyError: return def_value

readConfig就是讀取配置文件的方法:

""" 讀取配置文件的各種方法 """
import codecs import configparser import os from selenium.webdriver.common.by import By from util.log import MyLog log = MyLog().get_log() logger = log.get_logger() def dir_log(test): """ 捕獲異常的裝飾器方法 :param test: :return: """
    def log(*args, **kwargs): try: res = test(*args, **kwargs) return res except Exception: raise
    return log class ReadConfig: project_dir = os.path.dirname(os.path.abspath(__file__)) project_dir = os.path.split(project_dir)[0] def __init__(self, config_path="config.ini"): # 需要讀取的配置文件路徑
        self.config_path = os.path.join(self.project_dir, config_path) try: with open(self.config_path, encoding="UTF-8") as fd: data = fd.read() # 判斷data是否帶BOM,如果帶就刪除
                if data[:3] == codecs.BOM_UTF8: data = data[3:] # 使用codecs.open打開文件,寫入的時候更不容易出現編碼問題,open方法只能寫入str
                    with codecs.open(self.config_path, "w", encoding="UTF-8") as file: file.write(data) except FileNotFoundError as e: # logging.error(str(e))
            print(e) # 將配置文件分割成一塊一塊的字典形式
        self.cfp = configparser.ConfigParser() self.cfp.read(self.config_path, encoding="UTF-8") @dir_log def get_db(self, name): value = self.cfp.get("DATABASE", name) return value @dir_log def get_test(self, name): value = self.cfp.get("TEST", name) return value

接下來是讀取數據庫的方法:

 
         
#encoding=utf-8
""" 讀取數據庫的方法 """

import pymysql from util.read_config import ReadConfig from util.log import MyLog class MyDB(object): def __init__(self): self.log = MyLog.get_log() self.logger = self.log.get_logger() local_read_config = ReadConfig() host = local_read_config.get_db("host") username = local_read_config.get_db("username") password = local_read_config.get_db("password") port = local_read_config.get_db("port") database = local_read_config.get_db("database") self.config = { 'host': str(host), 'user': username, 'password': password, 'port': int(port), 'db': database } self.db = None self.cursor = None @classmethod def __new__(cls, *args, **kwargs): """每一次實例化的時候,都返回同一個instance對象"""
        if not hasattr(cls, "_instance"): cls._instance = super(MyDB, cls).__new__(cls) return cls._instance def connect_db(self): try: self.db = pymysql.connect(**self.config) self.cursor = self.db.cursor() self.logger.info("連接數據庫成功") except ConnectionError as ex: self.logger.error(str(ex)) def execute_sql(self, sql, params=None): self.connect_db() self.cursor.execute(sql, params) self.db.commit() return self.cursor def get_all(self, cur): value = cur.fetchall() return value def close_db(self): self.db.close() self.logger.info("關閉數據庫")

上面這些涉及到了讀取配置文件的東西,所以把 config.ini文件貼一下:

 

上面的TEL里面的內容是連接手機用到的

 [DATABASE]下面是連接數據庫相關的信息

其余的內容有時間再更新~~~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM