背景
最近在幫別的項目組執行性能測試,使用的工具是Jmeter。接口錄制和參數化前一個人已經做好了,我主要的工作就是執行腳本,撰寫測試報告。事情並不復雜,可做起來卻極為耗時。
首先,由於有6組賬號,分別對應6個不同的BU,而每個BU又需要執行1、10、20、30四種壓力模式。如果使用GUI模式跑,就需要執行24次,還需要每次自己改參數,實在是費心費力。
其次,使用Jmeter插件生成聚合結果后,要根據結果出一份報告,。在我之前做的同事,由於是第一輪測試,也就無從比較,只是從接口、頁面、錯誤率三個維度寫了一份報告。而我則需要根據本次和上次的結果,生成圖表,以便直觀地展示結果。剛開始做這件事時,我是根據需求,找到對應的接口和頁面,分別挪到Excel里,利用Excel生成圖表。可是既容易錯,還容易瞎,實在是折磨人。
解決方案
1
為了解決第一個問題,我的思路是找到Jmeter測試腳本的配置文件,復制多份,批量改成不同的配置,再利用bat腳本每次執行多個。
先將原來的測試腳本Jmx文件復制多份,按環境分成不同的文件夾,再按線程數整理進去,如下圖:
因為Jmx文件其實都是xml格式,里面存儲了腳本的配置,於是就使用VS Code打開文件夾,進行批量替換,這樣很快就能完成配置工作。
之后再寫bat腳本,以命令行模式執行jmx腳本,並生成測試報告。
注意在批處理文件中執行多條命令時,如果期望上一條執行完畢再繼續執行下一條(也就是順序調用),需要使用call方法。大致如下:
call %userprofile%\Desktop\apache-jmeter-3.3\bin\jmeter.bat -n -t 'CP BU.jmx' -l test_report.csv -e -o cp_test_report
call %userprofile%\Desktop\apache-jmeter-3.3\bin\jmeter.bat -n -t 'FA BU.jmx' -l test_report.csv -e -o fa_test_report
%userprofile%是系統變量,代表用戶目錄,形如C:\Users\xxx。這樣調用可以提高復用性,之后用到的電腦只需要將jmeter放在桌面,即可運行。
-n non-gui 以非GUI模式運行
-t test-file 要執行的腳本文件
-l logfile 記錄結果的文件,之后可以用來生成聚合報告
-o output html報告保存的路徑
這樣每次執行執行每種線程數的批處理文件,就可以自動執行並生成報表了。
其實還可以對生成報告的路徑,再做參數化配置,按照一定規則整合在一起。而且還可以將多線程的bat文件,再一起執行,這樣就更省事。這些都是可優化的地方
2
對比報告這部分就要麻煩些了。初步的思路是從新舊報告宏讀取數據,再使用生成圖表的庫生成對比圖。有了對比圖,要說的話就會少很多了,畢竟一圖勝千言嘛。
之前同事其實是直接從Jmeter中粘貼出來的報告,與腳本生成的html報告還有不一樣的地方。況且,html文件不好讀取數據,很難與excel中的數據對比。
最省腦子的方法自然是我按照原來的方式重新跑一遍,一一把數據粘貼出來,這樣結果的格式就一樣了,可是復用性太差,就不考慮了。之后想用笨方法,利用插件從打開的html報告中讀出表格。試了一圈,Chrome的插件要么只能用於http開頭的網頁,要么就是不起作用。又試了下UI Path(RPA工具,最近跟過一些教程),提取是成功了,可是好像和頁面上的行數不一致,大概是某些元素的沒有被識別為表格元素。無奈之下,只能想想能不能利用現有的報告生成類似之前版本的報告。有查到用jemter命令將jtl(jmeter的測試結果)轉換為聚合報告,可是腳本無法執行,說是缺插件,奈何jmeter上裝插件總是失敗,也沒辦法。只得自己在jemter界面上試試,在聚合報告插件中導入之前生成的csv文件。這下成功了,也稍微理解了這些報告的作用。這些插件其實就是將日志文件重新計算整理,展示出最關注的幾點。
這樣就保證數據格式一致了。接下來就是考慮讀取數據了。我最先想到的是用pandas中的Dataframe格式存儲數據。
寫個讀取的方法:
def read_reports_csv(folder): cp_df = pd.read_csv(os.path.join(folder, 'cp.csv')) mc_df = pd.read_csv(os.path.join(folder, 'mc.csv')) mcp113_df = pd.read_csv(os.path.join(folder, 'mcp113.csv')) mcproject_df = pd.read_csv(os.path.join(folder, 'mcproject.csv')) pa_df = pd.read_csv(os.path.join(folder, 'pa.csv')) fa_df = pd.read_csv(os.path.join(folder, 'fa.csv')) return cp_df, mc_df, mcp113_df, mcproject_df, pa_df, fa_df
至於生成圖表,我想到的是pyecharts。
繪制圖表的示例如下:
from pyecharts.charts import Bar from pyecharts import options as opts bar = ( Bar({"width": "800px", "height": "750px", }) # 初始化圖表寬和高 .add_xaxis(list(last_label)) # x軸的數 .add_yaxis('Response Time Average This Time',list(this_average)) # 增加y軸,本次結果 .add_yaxis('Response Time Average Last TIme',list(last_average)) # 增加y軸,上次結果 .set_global_opts( # 設置全局設置 title_opts=opts.TitleOpts(title='接口平均相應時間對比圖'), # 設置標題 xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=15)), # 設置x周,這里是將文字傾斜,便於顯示較長的文字 legend_opts=opts.LegendOpts(is_show=True, pos_right=10) # 設置圖例 ) )
生成圖表這塊,在網上查了很久,主要是參數配置不太理解。最后都是在官方文檔中找到了最后答案。不得不說,還是官方文檔靠譜,百度里搜索出來的結果有時能解燃眉之急,有時卻是東拼西湊,質量不高。還是多用Google吧。不過pyechart本身是源自百度的庫,估計國外用的人不多,資料大概也不多。
接下來是就往圖表里塞數據了,這里也是問題最多的地方,主要有以下幾點:
1.前后執行的接口並不是完全一致,沒辦法直接通過Label標簽排序,將要比較的數據對齊。
2.有些接口請求的參數是動態變化的,沒辦法將接口名寫死。
可見,數據清理將是重頭,會決定之后生成的圖表是否正確。我的解決思路是,將要比較的接口和頁面分別參數化,再拿着要比較的數據,取其Label字段,分別在新舊數據中搜索,生成新舊待比較的數據集,再將數據排列對齊,保證順序一致。Dataframe支持的操作其實已經很多了,就我目前了解的,沒有找到現成方案,就自己寫了些小方法,實現這些目標。具體代碼如下:
def df_contains(df, partial_labels): ''' 這一步是為了找出要對比的數據 遍歷列表,在Dataframe中匹配,凡是包含當前字符串的,都拿出來 ''' result_df = None for label in partial_labels: x = df[df['Label'].str.contains(label)] if result_df is None: result_df = x else: if not x.empty: result_df = result_df.append(x, ignore_index=True) return result_df.drop_duplicates(subset=['Label', 'Average','Median'], keep='first')
def replace_digits_in_df(df, label): ''' 這一步是為了取出label中的數字 Jmeter錄制的腳本中,每次請求前面都會加上序號,影響排序,需要統一去掉 當然也許Jmeter中本身就可以設置,只是我不知道 ''' for row in df.iterrows(): _ = row[1].Label df.loc[row[0], label] = re.sub('\d+', '', _) return df
def draw_api(last_df, this_df, column): ''' last_df: 上一次結果,pd.Dataframe this_df: 本次結果,pd.Dataframe return: 柱狀對比圖,可在notebook中繪制,也可直接導出html ''' last_temp = replace_digits_in_df(last_df,'Label') last = df_contains(last_temp, api_labels).sort_values(by=['Label']) this_temp = replace_digits_in_df(this_df,'Label') this = df_contains(this_df,api_labels).sort_values(by=['Label']) print(this.Label) print('--------') print(last.Label) # 下面都是為了取出新舊待比較數據集中的交集,避免數據錯位 this_del_index = this.append(last, sort=False).drop_duplicates(subset=['Label'], keep=False).index this = this.drop(this_del_index) last_del_index = last.append(this, sort=False).drop_duplicates(subset=['Label'], keep=False).index last = last.drop(last_del_index) this_average = this[column] this_label = this.Label last_average = last[column] last_label = last.Label bar = ( Bar({"width": "800px", "height": "750px", }) .add_xaxis(list(last_label)) .add_yaxis('Response Time Average This Time',list(this_average)) .add_yaxis('Response Time Average Last TIme',list(last_average)) .set_global_opts( title_opts=opts.TitleOpts(title='接口平均相應時間對比圖'), xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=15)), legend_opts=opts.LegendOpts(is_show=True, pos_right=10) ) ) return bar
當然,這些方法也是前前后后嘗試了很多次,慢慢寫出來的規則。經過對比,執行的兩輪中,沒有數據錯位的情況。其實這部分也是最費時間的。
另外,在jupyter notebook中可以實時查看生成的圖表,很是方便,推薦使用。只需要對最后生成的圖表對象,調用render_notebook()方法即可。最后生成的對比圖如下:
代碼地址:
https://github.com/MRFF/Learning-Python/blob/master/compare_reports.py