一、前情概要
但凡接觸過(guò)性能測(cè)試的都對(duì)以下情景深有體會(huì):
?1.測(cè)試前置工作量大繁瑣,手工操作費(fèi)時(shí)費(fèi)力;
?2.測(cè)試結(jié)果有出入,自己想再確認(rèn)一下,測(cè)試過(guò)程再來(lái)一遍吧;好不容易整出了一份報(bào)告,RD一看對(duì)結(jié)果不滿意存在質(zhì)疑,來(lái)回修改幾遍整個(gè)測(cè)試過(guò)程就需要再來(lái)幾遍,整個(gè)過(guò)程下來(lái)精疲力竭;
?3.性能數(shù)據(jù)整理細(xì)致入微,測(cè)試5分鐘,數(shù)據(jù)整理2小時(shí),一頓操作下來(lái)眼睛要瞎;
二、項(xiàng)目背景
為了減少以上痛苦對(duì)身體帶來(lái)的一萬(wàn)點(diǎn)暴擊傷害,以及從整個(gè)測(cè)試模式、方法來(lái)看,急需要一套自動(dòng)化性能測(cè)試方案來(lái)提高整個(gè)性能測(cè)試的效率;同時(shí)隨著DevOps(DevOps 強(qiáng)調(diào)的是高效組織團(tuán)隊(duì)之間如何通過(guò)自動(dòng)化的工具協(xié)作和溝通來(lái)完成軟件的生命周期管理,從而更快、更頻繁地交付更穩(wěn)定的軟件)在各個(gè)一二線大廠的推行,性能測(cè)試任務(wù)接入流水線用于周期性觸發(fā)執(zhí)行也是大勢(shì)所趨,因此一套全自動(dòng)化的性能測(cè)試方案亟待推出用于解決工作的難點(diǎn)問(wèn)題,提高整個(gè)的工作效率。
三、解決的主要問(wèn)題
?1.測(cè)試過(guò)程繁瑣,人工介入成本高;
?2.測(cè)試結(jié)果有出入,自己想再確認(rèn)一下,測(cè)試過(guò)程再來(lái)一遍吧;好不容易整出了一份報(bào)告,RD一看對(duì)結(jié)果不滿意存在質(zhì)疑,來(lái)回修改幾遍整個(gè)測(cè)試過(guò)程就需要再來(lái)幾遍,整個(gè)過(guò)程下來(lái)精疲力竭;
?3.性能數(shù)據(jù)整理細(xì)致入微,測(cè)試5分鐘,數(shù)據(jù)整理2小時(shí),一頓操作下來(lái)眼睛要瞎;
四、實(shí)現(xiàn)方案
整體框架:
Python + Appium + Emmagee
實(shí)現(xiàn)原理:
Python腳本實(shí)現(xiàn)Appium對(duì)Emmagee的驅(qū)動(dòng)-》進(jìn)而調(diào)起測(cè)試APP中的測(cè)試場(chǎng)景和case-》運(yùn)行-》性能測(cè)試數(shù)據(jù)自動(dòng)讀取-》數(shù)據(jù)統(tǒng)計(jì)、分析-》輸出測(cè)試報(bào)告
實(shí)現(xiàn)場(chǎng)景:
目前已實(shí)現(xiàn)支持基礎(chǔ)地圖、個(gè)性化地圖、熱力圖、海量Polyline地圖場(chǎng)景下放大、縮小、拖拽等多種操作下的性能數(shù)據(jù)的全自動(dòng)輸出
Demo示例:
第一步:材料準(zhǔn)備
1.待測(cè)APP/待測(cè)場(chǎng)景

2.Emmage性能測(cè)試工具
3.Appium工具
4.Python 3.7環(huán)境
第二步:按步驟來(lái)實(shí)現(xiàn)每個(gè)階段的自動(dòng)化
明確要實(shí)現(xiàn)自動(dòng)化的操作步驟,如上面實(shí)現(xiàn)原理所描述的"當(dāng)準(zhǔn)備好待測(cè)APP/待測(cè)場(chǎng)景后,實(shí)現(xiàn)操作-》運(yùn)行-》數(shù)據(jù)收集-》統(tǒng)計(jì)分析-》輸出報(bào)告"的全自動(dòng)化,因此按步驟來(lái)實(shí)現(xiàn)每個(gè)階段的自動(dòng)化。
1.Python腳本實(shí)現(xiàn)Appium對(duì)Emmagee的驅(qū)動(dòng)
# performance_test_tool.py
import os
import shutil
import time
from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction
from auto_analysis_tool import analysis_output_test_report
"""
入?yún)⒄f(shuō)明:
open_dir_path:Emmagee 輸出的原始性能數(shù)據(jù)文件所在目錄(文件按修改時(shí)間降序排列)
output_file_name:最終輸出的性能數(shù)據(jù)報(bào)告文件名名稱和所在路徑
start_line:截取原始性能數(shù)據(jù)文件開始行
end_line:截取原始性能數(shù)據(jù)文件結(jié)束行
"""
def start_testing(open_dir_path, output_file_name, start_line, end_line):
# appium服務(wù)監(jiān)聽地址
server = 'http://localhost:4723/wd/hub'
# app啟動(dòng)參數(shù)
desired_caps = {
# "platformName": "Android",
# "platformVersion": "6.0",
# "deviceName": "T7G0215A31011599",
# "appPackage": "com.netease.qa.emmagee",
# "appActivity": ".activity.MainPageActivity",
# # "appWaitActivity": ".activity.MainPageActivity",
# # "appWaitPackage": "com.netease.qa.emmagee",
# "appiumVersion": "2.5.1"
"platformName": "Android",
"platformVersion": "5.1.1",
"deviceName": "T3Q4C16A17003811",
"appPackage": "com.netease.qa.emmagee",
"appActivity": ".activity.MainPageActivity"
}
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 驅(qū)動(dòng)
driver = webdriver.Remote(server, desired_caps)
# 點(diǎn)擊設(shè)置按鈕
driver.find_element_by_id("com.netease.qa.emmagee:id/btn_set").click()
time.sleep(2)
# 設(shè)置采集頻率為1s
TouchAction(driver).tap(x=97, y=449).perform()
# 點(diǎn)擊返回按鈕
driver.find_element_by_id("com.netease.qa.emmagee:id/go_back").click()
time.sleep(5)
# 獲取測(cè)試APP RadioButton
radio_button = driver.find_element_by_xpath(
"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.ListView/android.widget.LinearLayout[5]/android.widget.RadioButton")
radio_button = driver.find_element_by_xpath("/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.ListView/android.widget.LinearLayout[8]/android.widget.RadioButton")
# 選中測(cè)試APP RadioButton
radio_button.click()
# 點(diǎn)擊開始測(cè)試按鈕
driver.find_element_by_id("com.netease.qa.emmagee:id/test").click()
# 運(yùn)行測(cè)試場(chǎng)景,如1分鐘
time.sleep(60)
# 點(diǎn)擊停止測(cè)試按鈕
TouchAction(driver).tap(x=453, y=286).perform()
# kill測(cè)試Demo
os.system("adb shell am force-stop baidumapsdk.demo")
time.sleep(5)
# 清除目標(biāo)目錄下的文件
del_file("./Emmagee/")
# adb pull 生成的測(cè)試報(bào)告到目標(biāo)文件夾下
os.system("adb pull /sdcard/Emmagee/ ./")
# 解析測(cè)試報(bào)告內(nèi)容:內(nèi)存、CPU信息,并輸出分析后的測(cè)試報(bào)告
analysis_output_test_report(open_dir_path, output_file_name, start_line, end_line)
# 關(guān)閉會(huì)話
driver.quit()
"""清空文件夾內(nèi)容"""
def del_file(filepath):
"""
刪除某一目錄下的所有文件或文件夾
:param filepath: 路徑
:return:
"""
if os.path.isdir(filepath):
del_list = os.listdir(filepath)
for f in del_list:
file_path = os.path.join(filepath, f)
if os.path.isfile(file_path):
os.remove(file_path)
elif os.path.isdir(file_path):
shutil.rmtree(file_path)
2.性能數(shù)據(jù)收集、解析、輸出報(bào)告
'''
# auto_analysis_tool.py
自動(dòng)化工具:自動(dòng)解析原始性能報(bào)告并輸出分析后的測(cè)試報(bào)告
'''
import os
import csv
import numpy
"""
對(duì)外暴露接口:解析測(cè)試報(bào)告內(nèi)容:內(nèi)存、CPU信息,并輸出分析后的測(cè)試報(bào)告
"""
def analysis_output_test_report(open_dir_path, output_file_name, start_line, end_line):
# 獲取原始測(cè)試報(bào)告文件路徑
origin_file_path = get_report_path(open_dir_path)
# 打開原始測(cè)試報(bào)告文件
file = open(origin_file_path, encoding="gbk")
# 讀取內(nèi)容并解析cpu/memory信息
data = csv.reader(file)
cpu = []
memory = []
for row in data:
if data.line_num < start_line:
continue
# 只獲取指定count_number行
if data.line_num > end_line:
break
cpu.append(float(row[5])) # 將字符串?dāng)?shù)據(jù)轉(zhuǎn)化為浮點(diǎn)型加入到數(shù)組之中
memory.append(float(row[2]))
print(data.line_num, row)
cpu_mean = numpy.mean(cpu) # 輸出cpu均值
cpu_max = max(cpu) # 輸出cpu最大值
memory_mean = numpy.mean(memory) # 輸出memory均值
memory_max = max(memory) # 輸出memory最大值
print(cpu_mean)
print(cpu_max)
print(memory_mean)
print(memory_max)
# 以約定格式輸出性能數(shù)據(jù)
write_analysis_report(output_file_name, cpu_mean, cpu_max, memory_mean, memory_max)
file.close()
"""
獲取測(cè)試報(bào)告文件路徑
"""
def get_report_path(open_file_path):
dir_list = os.listdir(open_file_path) # 列出文件夾下所有的目錄與文件
if not dir_list:
return
else:
# 注意,這里使用lambda表達(dá)式,將文件按照最后修改時(shí)間順序升序排列
# os.path.getmtime() 函數(shù)是獲取文件最后修改時(shí)間
# os.path.getctime() 函數(shù)是獲取文件最后創(chuàng)建時(shí)間
dir_list = sorted(dir_list, key=lambda x: os.path.getmtime(os.path.join(open_file_path, x)), reverse=True)
test_report_path = os.path.join(open_file_path, dir_list[0]) # 獲取Emmagee最新生成的測(cè)試報(bào)告文件
print(test_report_path)
return test_report_path
'''輸出分析后的測(cè)試報(bào)告'''
def write_analysis_report(output_file_name, cpu_mean, cpu_max, memory_mean, memory_max):
with open(output_file_name, 'w', encoding="gbk") as file_object:
file_object.write(
"cpu_mean:" + str(cpu_mean) + "\t" + "cpu_max:" + str(cpu_max) + "\t" + "memory_mean:" + str(
memory_mean) + "\t" + "memory_max:" + str(memory_max))
3.單個(gè)測(cè)試case運(yùn)行
# test_case1.py
from performance_test_tool import start_testing
start_testing("/Users/username/Documents/workSpace/PerformAutoTesting/Emmagee/",
"/Users/username/Documents/workSpace/PerformAutoTesting/outputReport/case1.txt", 11,
12)
4.性能報(bào)告文件輸出
# case1.txt
cpu_mean:17.495 cpu_max:27.59 memory_mean:89.375 memory_max:98.31
5.附件(Emmagee原始性能數(shù)據(jù)文件示例.csv)
