Lego-美團接口自動化測試實踐
智能支付穩定性測試實戰
大衆點評App的短視頻耗電量優化實戰
「小衆」之美——Ruby在QA自動化中的應用
美團點評雲真機平臺實踐
質量運營在智能支付業務測試中的初步實踐html
衆所周知,接口自動化測試有着以下特色:前端
如何作好一個接口自動化測試項目呢?java
我認爲,一個「好的」自動化測試項目,須要從「時間」、「人力」、「收益」這三個方面出發,作好「取捨」。node
不能因爲被測系統發生一些變動,就致使花費了幾個小時的自動化腳本沒法執行。同時,咱們須要看到「收益」,不能爲了總想看到100%的成功,而少作或者不作校驗,可是校驗多了維護成本必定會增多,可能天天都須要進行大量的維護。mysql
因此作好這三個方面的平衡並不容易,常常能看到作自動化的同窗,作到最後就本末倒置了。jquery
想要提升ROI(Return On Investment,投資回報率),咱們必須從兩方面入手:android
咱們須要作到:ios
咱們須要作到:git
因此,我這邊開發了Lego接口測試平臺,來實現我對自動測試想法的一些實踐。先簡單瀏覽一下網站,瞭解一下大概是個什麼樣的工具。程序員
Lego接口測試解決方案是由兩部分組成的,一個就是剛剛看到的「網站」,另外一個部分就是「腳本」。
下面就開始進行「腳本設計」部分的介紹。
Lego接口自動化測試腳本部分,使用很常見的Jenkins+TestNG的結構。
相信看到這樣的模型並不陌生,由於不少的測試都是這樣的組成方式。
將自動化測試用例存儲至MySQL數據庫中,作成比較常見的「數據驅動」作法。
不少團隊也是使用這樣的結構來進行接口自動化,沿用的話,那在之後的「推廣」中,學習和遷移成本低都會比較低。
首先來簡單看一下目前的腳本代碼:
public class TestPigeon { String sql; int team_id = -1; @Parameters({"sql", "team_id"}) @BeforeClass() public void beforeClass(String sql, int team_id) { this.sql = sql; this.team_id = team_id; ResultRecorder.cleanInfo(); } /** * XML中的SQL決定了執行什麼用例, 執行多少條用例, SQL的搜索結果爲須要測試的測試用例 */ @DataProvider(name = "testData") private Iterator<Object[]> getData() throws SQLException, ClassNotFoundException { return new DataProvider_forDB(TestConfig.DB_IP, TestConfig.DB_PORT, TestConfig.DB_BASE_NAME,TestConfig.DB_USERNAME, TestConfig.DB_PASSWORD, sql); } @Test(dataProvider = "testData") public void test(Map<String, String> data) { new ExecPigeonTest().execTestCase(data, false); } @AfterMethod public void afterMethod(ITestResult result, Object[] objs) {...} @AfterClass public void consoleLog() {...} }
有一種作法我一直不提倡,就是把測試用例直接寫在Java文件中。這樣作會帶來不少問題:修改測試用例須要改動大量的代碼;代碼也不便於交接給其餘同窗,由於每一個人都有本身的編碼風格和用例設計風格,這樣交接,最後都會變成由下一個同窗所有推翻重寫一遍;若是測試平臺更換,沒法作用例數據的遷移,只能手動的一條條從新輸入。
因此「測試數據」與「腳本」分離是很是有必要的。
網上不少的範例是使用的Excel進行的數據驅動,我這裏爲何改用MySQL而不使用Excel了呢?
在公司,咱們的腳本和代碼都是提交至公司的Git代碼倉庫,若是使用Excel……很顯然不方便平常常常修改測試用例的狀況。使用MySQL數據庫就沒有這樣的煩惱了,因爲數據與腳本的分離,只需對數據進行修改便可,腳本每次會在數據庫中讀取最新的用例數據進行測試。同時,還能夠防止一些操做代碼時的誤操做。
這裏再附上一段我本身寫的DataProvider_forDB
方法,方便其餘同窗使用在本身的腳本上:
import java.sql.*; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * 數據源 數據庫 * * @author yongda.chen */ public class DataProvider_forDB implements Iterator<Object[]> { ResultSet rs; ResultSetMetaData rd; public DataProvider_forDB(String ip, String port, String baseName, String userName, String password, String sql) throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); String url = String.format("jdbc:mysql://%s:%s/%s", ip, port, baseName); Connection conn = DriverManager.getConnection(url, userName, password); Statement createStatement = conn.createStatement(); rs = createStatement.executeQuery(sql); rd = rs.getMetaData(); } @Override public boolean hasNext() { boolean flag = false; try { flag = rs.next(); } catch (SQLException e) { e.printStackTrace(); } return flag; } @Override public Object[] next() { Map<String, String> data = new HashMap<String, String>(); try { for (int i = 1; i <= rd.getColumnCount(); i++) { data.put(rd.getColumnName(i), rs.getString(i)); } } catch (SQLException e) { e.printStackTrace(); } Object r[] = new Object[1]; r[0] = data; return r; } @Override public void remove() { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } }
上面圖中提到了「配置文件」,下面就來簡單看一下這個XML配置文件的腳本:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"> <suite name="Pigeon Api測試" parallel="false"> <test name="xxx-xxx-service"> <parameter name="sql" value="SELECT * FROM API_PigeonCases WHERE team_id=2 AND isRun=1 AND service='xxx-xxx-service' AND env='beta';"/> <classes> <class name="com.dp.lego.test.TestPigeon"/> </classes> </test> <listeners> <listener class-name="org.uncommons.reportng.HTMLReporter"/> <listener class-name="org.uncommons.reportng.JUnitXMLReporter"/> </listeners> </suite>
對照上圖來解釋一下配置文件:
這樣作有什麼好處呢?
如上面的這個例子,在數據庫中會查詢出下面這56條測試用例,那麼這個標籤就會對這56條用例進行逐一測試。
<test>
標籤時,能夠分組展現使用多個<test>
標籤來區分用例,最大的好處就是也能在最後的報告上,達到一個分組展現的效果。
因爲使用了ReportNG
進行報告的打印,因此報告的展現要比TestNG自帶的報告要更加美觀、而且能自定義展現樣式,點開能看到詳細的執行過程。
若是有執行失敗的用例,一般報錯的用例會在最上方優先展現。
當兩個團隊開始使用時,爲了方便維護,將基礎部分抽出,各個團隊的腳本都依賴這個Base包,而且將Base包版本置爲「SNAPSHOT版本」。使用「SNAPSHOT版本」的好處是,以後我對Lego更新,各個業務組並不須要對腳本作任何改動就能及時更新。
當更多的團隊開始使用後,比較直觀的看的話是這個樣子的:
每一個團隊的腳本都依賴於個人這個Base包,因此最後,各個業務團隊的腳本就變成了下面的這個樣子:
能夠看到,使用了Lego以後:
mvn clean test -U -Dxml=xmlFileName
。因爲,全部測試用例都在數據庫因此這段腳本基本不須要改動了,減小了大量的腳本代碼量。
有些同窗要問,有時候編寫一條接口測試用例不僅是請求一下接口就行,可能還須要寫一些數據庫操做啊,一些參數可能還得本身寫一些方法才能獲取到啊之類的,那不code怎麼處理呢?
下面就進入「用例設計」,我將介紹我如何經過統一的用例模板來解決這些問題。
我在作接口自動化設計的時候,會思考通用、校驗、健壯、易用這幾點。
在寫自動化腳本的時候,都會想「細緻」,而後「寫不少」的檢查點;但當「校驗點」多的時候,又會由於不少緣由形成執行失敗。因此咱們的設計,須要在保證充足的檢查點的狀況下,還要儘量減小誤報。
執行測試的過程當中,不免會報失敗,執行失敗可能的緣由有不少,簡單分爲4類:
那針對上面的狀況:
經過這些手段,提升測試用例的健壯性,讓每一條自動化測試用例都能很好的完成測試任務,真正發揮出一條測試用例的價值。
說了這麼多,那咱們來看一下一條Lego接口測試用例的樣子。
一條Lego自動用例執行順序大概是以下圖這樣:
簡單區分一下各個部分,能夠看到:
那上面圖中提到了兩個名詞:
下面會先對這兩個名詞作一個簡單的介紹。
好比一個請求須要用到的參數。
{
"sync": false, "cityId": 1, "source": 0, "userId": 1234, "productId": 00004321 }
這個例子中有個參數"productId": 00004321
,而因爲測試的環境中,表單00004321極可能一些狀態已經發生了改變,甚至表單已經刪除,致使接口請求的失敗,那麼這時候,就很適合對"productId": 00004321
進行參數化,好比寫成這樣:
{
"sync": false, "cityId": 1, "source": 0, "userId": 1234, "productId": ${myProductId} }
因此對「參數化」簡單的理解就是:
經過一些操做,將一個「值」替換掉測試用例裏的一個「替代字符」
${myProductId}
的值能夠經過配置獲取到:
下面咱們來看一個「參數化」的實例:
(1) 首先咱們在參數化維護頁面中新建一個參數化,shopdealid
。
經過配置咱們能夠看到這個參數的值,是執行了一條SQL後,取用執行結果中DealID
字段的值。
(2) 在用例中,將須要這個表單號的地方用${shopdealid}替代。
那在編寫測試用例的時候,你們能夠看一下這個放大的圖片,在這裏的ProductID的值並非硬代碼一個固定的表單號,而是選擇了剛纔配置的參數化數據。
(3) 執行結果中,${shopdealid} 變爲實時查詢數據庫的來的一個真實的表單號。
從結果中能夠看到,咱們的這個參數被替換成了一個有效的值,而這個值就是咱們剛剛配置的那個SQL實時查詢而來的。
多個測試用例使用同一個參數進行測試
如50條測試用例都使用同一個id做爲參數進行測試,這時候咱們須要變動這個id。
無參數化時:
測試數據過時致使測試用例執行失敗
如一條用例參數須要傳入Token,可是Token會由於時間問題而致使過時,這時候用例就失敗了。
無參數化時:
有參數化時:
${測試用Token} = id:123
。數據庫獲取有效測試數據
參數中須要傳入DealId做爲參數,寫死參數的話,若是這個DealId被修改引發失效,那這條測試用例就會執行失敗。
不使用Lego時:
在Lego上的方案: - 使用參數化,實時獲取sql結果,查詢出一條符合條件的dealId來實現。 - 使用參數化,調用寫好的「生成訂單」接口用例實現,拿單號來實現。 - 先後置動做,插入一條知足條件的數據。
「先後置動做」的概念就比較好理解了:
在接口請求以前(或以後),執行一些操做
目前先後置動做支持6種類型:
這裏的SQL同時支持Select操做,這裏其實也是作了一些小的設計,會將查詢出來的所有的結果,放入到這個全局Map中。
好比查詢一條SQL獲得下表中的結果:
id | name | age | number :–: | :–: | :–: | :–: | :–: 0 | 張三 | 18 | 1122 1 | 李四 | 30 | 3344
那咱們能夠使用下面左邊的表達式,獲得對應的結果:
${pre.name}
—- 獲得 「張三」å${pre.age}
—- 獲得 18${pre.number}
—- 獲得 1122也能夠用:
${pre.name[0]}
—- 獲得 「張三」${pre.age[0]}
—- 獲得 18${pre.number[0]}
—- 獲得 1122${pre.name[1]}
—- 獲得 「李四」${pre.age[1]}
—- 獲得 30${pre.number[1]}
—- 獲得 3344這樣的設計,更加幫助在用例設計時,提供數據準備的操做。
(1) 首先咱們在先後置維護頁面中新建一個動做,獲取庫存上限未賣光團單
。
這個配置也是能夠支持在線調試的,在調試中,能夠看到能夠使用的參數化:
(2) 在測試用例中的前置動做,添加獲取庫存上限未賣光團單
。
這樣就能夠在整個測試用例中,使用${pre.ProductID}
,來替換掉原有的數據信息。
(3) 最後請求接口,返回了執行成功 。
Q:那若是一樣是獲取三個參數,使用3個「參數化的Select操做」和使用1個「前置動做的Select操做」又有什麼不一樣呢?
A: 不一樣在於執行時間上。 好比,咱們查詢最新的有效團單的「單號」「下單人」和「手機號」三個字段。 使用3個「參數化的Select操做」:可能當執行${單號}的時候獲得的訂單號是「10001」,可是當執行到${下單人}的時候,可能有誰又下了一單,可能取到的下單人變成了「10002」的「李四」而不是「10001」的「張三」了,最後可能「單號」「下單人」和「手機號」三個字段去的數據並不是同一行的數據。 而使用「前置動做的Select操做」:就能夠避免上面的問題,由於全部字段的數據是一次性查詢出來的,就不會出現錯位的狀況。
Q : 那「參數化的Select操做」和「前置動做的Select操做」這樣不一樣的取值時機又有什麼好用之處呢?
A : 因爲「前置動做」必定是接口請求前執行,「參數化」必定是用到的時候才執行這樣的特性。 因此在檢查點中,若是要驗證一個數據庫字段在通過接口調用後發生了變動,那使用「前置動做」和「參數化」同時去查詢這個字段,而後進行比較,不一致就說明發生了變化。 因此根據使用場景,選擇合適的參數化方式,很重要,選擇對了,能大大提高測試用例的測試數據健壯性。
回到一開始的流程圖,能夠按照一類一類來看執行過程。
測試發起基本仍是使用的Jenkins,穩定、成熟、簡單、公司工具組支持,也支持從Lego的Web頁面進行執行操做。
使用 @DataProvider 的方式,從DB數據庫中讀取測試用例,逐一執行進行測試。
在正式執行測試用例以前,會先進行一波參數替換的動做,在調用接口以後,還會執行一次參數替換動做。
參數替換後會進行前置動做的執行,而後在調用接口以後還會執行測試後動做,最後執行後置動做。
接口請求這部分就沒什麼好說的了,就是經過接口請求的參數,請求對應的接口,拿到返回結果。
這裏的話是爲了方便通用,因此要求返回的結果都是使用的String類型。這樣作最大的好處就是。好比說我如今有一種新的接口類型須要接入。那隻須要寫一個方法可以請求到這個接口,而且拿到String類型的返回結果,就能夠很快將新的接口類型接入Lego測試平臺進行接口測試。
檢查點部分是一條自動化測試用例的精髓,一條自動化測試用例是否能真正的發揮它的測試功能,就是看QA對這條測試用例的檢查點編寫是否作了良好設計。在Lego平臺上,目前我擁有的檢查點有6種不一樣的類型。
- 異常檢查點
- 當返回結果爲異常時,則會報錯。
- 可是有時候爲了作異常測試,能夠將這個檢查點關掉。
- 不爲空檢查點
- 顧名思義,當出現」「、」[]「、」{}「、null 這樣的的結果,都會報錯。也能夠根據本身用例的實際狀況關閉。
- 包含檢查點
- 不包含檢查點
- 「包含」和「不包含」檢查點是將接口的返回結果做爲一個String類型來看,檢查全部返回內容中是否「包含」或「不包含」指定的內容。
- 數據庫參數檢查點
- 顧名思義,不作過多的解釋了。
- JsonPath檢查點
- 這是我在Lego上設計的最具備特點的一種檢查點類型。
JsonPath的基本寫法是:{JsonPath語法}==value
JsonPath的語法和XPath的語法差很少,都是根據路徑的方法找值。這裏也是主要是針對返回結果爲JSON數據的結果,進行檢查。
具體的JsonPath語法能夠參考:https://github.com/json-path/JsonPath
說完了「JsonPath的語法」,如今說一下「JsonPath檢查點的語法」,「JsonPath檢查點的語法」是我本身想的,主要針對如下幾種數據類型進行校驗:
(1) 字符串類型結果檢驗
==
!==
=
!=
例如:
{$.[1].name}==aa
:檢查返回的JSON中第2個JSON的name字段是否等於aa。{$..type}=='14'
:檢查返回的JSON中每個JSON的name字段是否等於aa。{$.[1].type}==14 && {$.[1].orderId}==106712
:一條用例中多個檢查用&&鏈接。{$..orderId}!==12
:檢查返回的JSON中每一個JSON的orderId字段是否不等於12。{$..type}=1
:檢查返回的JSON中每一個JSON的type字段是否包含1。{$.[1].type}!=chenyongda
:檢查返回的JSON中第2個JSON的type字段是否不包含chenyongda。(2) 數值校驗
=
>
>=
<
<=
例如:
(3) List結果檢驗
.length
.contains(param)
.get(index)
例如:
{$..value}.length=3
:檢查返回的JSON中每一個JSON的value字段的列表是否等於3。{$.[0].value}.length<5
:檢查返回的JSON中第1個JSON的value字段的列表是否小於3。{$.[1].value}.length>4
:檢查返回的JSON中第2個JSON的value字段的列表是否大於4。{$..value}.contains('222')
:檢查返回的JSON中每一個JSON的value字段的列表是否包含222字符串。{$.[0].value}.contains(1426867200000)
:檢查返回的JSON中第1個JSON的value字段的列表是否包含1426867200000。{$.[0].value}.get(0)=='222'
:檢查返回的JSON中第1個JSON的value字段的列表中第1個內容是否等於222。{$..value}.get(2)='22'
:檢查返回的JSON中每一個JSON的value字段的列表中第3個內容是否包含22。(4) 時間類型處理
時間戳轉日期時間字符串:.todate
例如:
{$..beginDate}.todate==2015-12-31 23:59:59
:檢查返回的JSON中beginDate這個時間戳轉換成日期後是否等於2015-12-31 23:59:59。檢查點 | 檢查點等號左邊 | 指望值 | 驗證效果 |
---|---|---|---|
{$.value}==「good」 | [‘good’, ‘good’, ‘bad’, ‘good’] | 「good」 | 做爲4個檢查點,會拿列表裏的每一個對象逐一和「指望值」進行檢驗,每一次對比都是一個獨立的檢查點。 |
{$.value}==[「good」] | [‘good’, ‘good’, ‘bad’, ‘good’] | [「good」] | 做爲1個檢查點,做爲一個總體作全量比對。 |
{$.value}==[‘a’, ‘b’] | [[‘a’, ‘b’],[‘a’, ‘b’],[‘a’, ‘b’, ‘c’]] | [‘a’, ‘b’] | 做爲3個檢查點,道理和1同樣,列表中的數據分別和指望值作比較。 |
JsonPath中的檢查支持「參數化」和「先後置動做」,因此會看到不少如:
{$.param}=‘${param}’ && {$.param}==${pre.param}
這樣的檢查點:
「參數化」和「先後置動做」也支持遞歸配置,這些都是爲了可以讓接口自動化測試用例寫的更加靈活好用。
使用ReportNG能夠打印出很漂亮的報告。
報告會自定義一些高亮等展現方式,只須要在ReportNG使用前加上下面的語句,就能夠支持「輸出逃逸」,可以使用HTML標籤自定義輸出樣式。
System.setProperty("org.uncommons.reportng.escape-output", "false");
當使用Jenkins執行後,經過Jenkins API 、和Base包中的一些方法,定時獲取測試結果,落數據庫,提供生成統計圖表用。
既然打算作工具平臺了,就得設計方方面面,惋惜人手和時間上的不足,只能我一人利用下班時間進行開發。也算是擔任了Lego平臺的產品、後端開發、前端開發、運維和測試等各類角色。
Jenkins+TestNG+ReportNG+我本身開發的基本接口自動化測試Base jar包,基本上沒什麼太大難度。可是站點這塊,在來美團以前,還真沒開發過這樣的工具平臺,這個算是個人第一個帶Web界面的工具。邊Google邊作,沒想到不久還真的架起來了一個簡易版本。
使用 Servlet + Jsp 進行開發,前端框架使用Bootstrap,前端數據使用jstl,數據庫使用MySQL,服務器使用的公司的一臺Beta環境Docker虛擬機,域名是申請的公司內網域名,並開通北京上海兩側內網訪問權限。
功能上基本都是要知足的,界面上,雖然作不到驚豔吧,可是絕對不能醜,功能知足,可是長得一副80年代的界面,我本身都會嫌棄去使用它,因此界面上我仍是花了一些時間去調整和設計。熟練之後就快多了。
目前Lego由五個不一樣的項目組成,分別是「測試腳本」、「Lego-web頁面項目」、「用於執行接口測試的base包」、「小工具集合Lego-kit」和「lego-job」,經過上圖能夠看出各項目間的依賴關係。
細化各個項目的功能,就是下圖:
簡單來講,網站部分和腳本是分離的,中間的紐帶是數據庫。因此,沒有網站,腳本執行一點問題也沒有;一樣的,網站的操做,和腳本也沒有關係。
天天上班來會收到這樣的測試郵件,經過郵件能知道昨晚執行的狀況。若是有報錯,能夠點擊「詳細報告連接」,跳轉到在線報告。
在現報告能夠直接看到執行報錯的信息,而後點擊「LEGO維護傳送門」,能夠跳轉到Lego站點上,進行用例維護。
跳轉到站點上之後,能夠直接展現出該條測試用例的全部信息。定位,維護、保存,維護用例,能夠點擊「執行」查看維護後的執行結果,維護好後「保存」便可。
僅僅3步,1~2分鐘便可完成對一條執行失敗的用例進行定位、調試和維護動做。
經過頁面,咱們就能夠對一條測試用例進行:
lego-web項目一樣的使用base進行的用例執行,因此執行結果和打印都與腳本執行的一致的。
爲了更方便的寫用例,針對部分接口開發了一鍵批量生成用例的小工具。
經過Jenkins接口、Base包中基礎Test方法,將結果收集到數據庫,便於各組對測試結果進行分析。
這是天天執行後成功率走勢圖:
也能夠按月進行統計,生成統計的圖表,幫助各個團隊進行月報數據收集和統計。
有了能直觀看到測試結果的圖表,就會想要跟蹤失敗緣由。
因此在成功率數據的右邊,會有這樣的跟蹤失敗緣由的入口,也能夠很直觀地看到哪一些失敗的緣由尚未被跟蹤。點開後能夠對失敗緣由進行記錄。
最後會有生成圖表,能夠很清晰地看到失敗緣由以及失敗類型的佔比。
結合Jacoco,咱們能夠對接口自動化的代碼覆蓋率進行分析。
在多臺Slave機器上配置Jacoco仍是比較複雜的,因此能夠開發覆蓋率配置輔助工具來幫助測試同窗,提升效率。
除了上面的圖表,還會給用例優化提供方向。
經過用例數量統計的圖表,咱們能夠知道哪些服務用例還比較少,哪些環境的用例還比較少,能夠比較有針對性的進行測試用例的補充。
經過失敗緣由的圖表,咱們能夠改善本身用例中的「參數化」和「先後置動做」的使用,增長測試用例的健壯性。
經過線上接口調用量排序的圖表。咱們能夠有效的知道優先維護哪些服務的測試用例,經過表格中,咱們能夠看到,哪些服務已經覆蓋了測試用例,哪些沒有被覆蓋, 給各組的QA制定用例開發計劃,提供參考。
同時在維護接口自動化測試的時候,都會看到用例評分的狀況,來協助QA提升用例編寫的質量。
還作了「需求白板」,用來收集使用者的需求和Bug。除此以外,Lego平臺已經不僅是一個接口測試的平臺,還可讓想學習開發的QA領任務,學習一些開發技巧,提升本身的代碼能力。
本文根據美團高級測試開發工程師勳偉在美團第43期技術沙龍「美團金融千萬級交易系統質量保障之路」的演講整理而成。主要介紹了美團智能支付業務在穩定性方向遇到的挑戰,並重點介紹QA在穩定性測試中的一些方法與實踐。
美團支付承載了美團所有的交易流量,按照使用場景能夠將其分爲線上支付和智能支付兩類業務。線上支付,支撐用戶線上消費場景,處理美團全部線上交易,爲團購、外賣、酒店旅遊等業務線提供支付能力;智能支付,支撐用戶到店消費場景,處理美團全部線下交易,經過智能POS、二維碼支付、盒子支付等方式,爲商家提供高效、智能化的收銀解決方案。其中,智能支付做爲新擴展的業務場景,去年也成爲了美團增速最快的業務之一。
而隨着業務的快速增加,看似簡單的支付動做,背後系統的複雜度卻在持續提高。體如今:上層業務入口、底層支付渠道的不斷豐富,微服務化背景下系統的縱向分層、服務的橫向拆分,還有對外部系統(營銷中心、會員中心、風控中心等)、內部基礎設施(隊列、緩存等)的依賴也愈來愈多,整條鏈路上的核心服務節點超過20個,業務複雜度可想而知。
此外,技術團隊在短期內就完成了從幾我的到近百人規模的擴張,這也是一個潛在的不穩定因素。曾經在一段時間內,整個系統處在「牽一髮而動全身」的狀態,即便自身系統不作任何發版升級,也會由於一些基礎設施、上下游服務的問題,業務會毫無徵兆地受到影響。
痛定思痛,咱們對發生過的線上問題進行復盤,分析影響服務穩定性的緣由。經過數據發現,72%的嚴重故障集中在第三方服務和基礎設施故障,對應的一些典型事故場景,好比:第三方支付通道不穩定、基礎設施(如消息隊列)不穩定,進而致使整個系統雪崩,當依賴方故障恢復後,咱們的業務卻很難當即恢復。
基於這些問題,咱們開展了穩定性建設專項,目的很明確:提高服務的可用性。目標是逐步將系統可用性從2個9提高到3個9,再向4個9去努力。這個過程當中最核心的兩個策略:柔性可用,意思是儘量保證核心功能可用,或在有損狀況下儘量保證核心用戶體驗,下降影響;另外一個是快速恢復,即用工具或機制保證故障的快速定位和解決,下降故障修復時間。
圍繞這兩個策略,在穩定性建設中的常見操做:限流、熔斷降級、擴容,用於打造系統的柔性可用;故障響應SOP、故障自動處理,用於故障處理時的快速恢復。而QA的工做更側重於對這些「常見操做」進行有效性驗證。基於經驗,重點介紹「三把利劍」:故障演練、線上壓測、持續運營體系。
舉個真實的案例,在一次處理某支付通道不穩定的線上問題時,開發同窗執行以前已經測試經過的預案(服務端關閉該通道,預期客戶端將該支付通道的開關置灰,並會提示用戶使用其餘支付方式),但執行中卻發現預案沒法生效(服務端操做後,客戶端該支付通道仍處於開啓狀態)。非故障場景下預案功能正常,故障場景下卻失效了。
這就是故障演練的由來,咱們須要儘量還原故障場景,才能真正驗證預案的有效性。
故障演練的總體方案,主要分爲三部分:
爲了更高效地開展故障演練,咱們的策略是分爲兩個階段進行。首先,針對單系統進行故障演練,從故障樣本庫出發,全面覆蓋該系統全部的保護預案;在此基礎上,進行全鏈路故障演練,聚焦核心服務故障,驗證上下游服務的容錯性。
事實證實,故障演練確實給咱們帶來了不少「驚喜」,暴露了不少隱患。這裏列舉三類問題:數據庫主從延遲影響交易;基礎設施故障時,業務未作降級;依賴服務超時設置不合理、限流策略考慮不足等。
面對業務的指數級增加,咱們必須對系統可承載的流量作到心中有數。對於QA來講,須要找到精準、高效的系統容量評估方法。咱們碰到的難點包括:鏈路長、環節多、服務錯綜複雜,線下環境與線上差別大等等,基於測試有效性和測試成本考慮,咱們決定要作線上壓測,並且要實現全鏈路的線上壓測。
全鏈路壓測的實現方案,與業界主流方案沒有太大區別。根據壓測流程,首先,場景建模,以便更真實的還原線上系統運行場景;其次,基礎數據構造,應知足數據類型以及量級的要求,避免數據熱點;以後,流量構建,讀寫流量構造或回放,同時對壓測流量進行標記和脫敏;再以後,壓測執行,過程當中收集鏈路各節點的業務運行狀態、資源使用狀況等;最後,生成壓測報告。
基於全鏈路線上壓測方案,能夠根據業務需求,靈活地進行單鏈路壓測、分層壓測等。更爲重要的是,基於壓測咱們能夠進行線上的故障演練,用於更加真實的驗證系統限流、熔斷等保護預案。
經過全鏈路線上壓測,一方面讓咱們對系統容量作到心中有數,另外一方面也讓咱們發現了線上系統運行過程當中的潛在問題,並且這些問題通常都是高風險的。一樣列舉三類問題:基礎設施優化,如機房負載不均衡、數據庫主從延遲嚴重等;系統服務優化,如線程池配置不合理、數據庫須要拆分等;故障預案優化,如限流閾值設置太低,有的甚至已經接近限流邊緣而渾然不知等等。
智能支付的穩定性建設是做爲一個專項在作,持續了近3個月的時間;在效果還不錯的狀況下,咱們從智能支付延伸到整個金融服務平臺,以虛擬項目組的方式再次運轉了3個月的時間。經過項目方式,確實能集中解決現存的大部分穩定性問題,但業務在發展、系統在迭代,穩定性建設必然是一項長期的工做。因而,QA牽頭SRE、DBA、RD,創建了初步的穩定性持續運營體系,並在持續完善。
下面介紹持續運營體系的三大策略:
流程規範工具化,儘量減小人爲意識因素,下降人力溝通和維護成本。
如:配置變動流程,將配置變動視同代碼上線,以PR方式提交評審;代碼規範檢查落地到工具,儘量將編碼最佳實踐抽取爲規則,將人工檢查演變爲工具檢查。
質量度量可視化,提取指標、經過數據驅動相關問題的PDCA閉環。
如:咱們與SRE、DBA進行合做,將線上系統運維中與穩定性相關的指標提取出來,相似數據庫慢查詢次數、核心服務接口響應時長等等,並對指標數據進行實時監控,進而推動相關問題的解決。
演練壓測常態化,下降演練和壓測成本,具有常態化執行的能力。
如:經過自動化的觸發演練報警,驗證應急SOP在各團隊實際執行中的效果。
基於以上三個策略,構建穩定性持續運營體系。強調閉環,從質量度量與評價、到問題分析與解決,最終完成方法與工具的沉澱;過程當中,經過平臺建設來落地運營數據、完善運營工具,提高運營效率。
簡單展現當前持續運營體系的運行效果,包含風險評估、質量大盤、問題跟進以及最佳實踐的沉澱等。
綜上即是智能支付QA在穩定性建設中的重點工做。對於將來工做的想法,主要有3個方向。第一,測試有效性提高,持續去擴展故障樣本庫、優化演練工具和壓測方案;第二,持續的平臺化建設,實現操做平臺化、數據平臺化;第三,智能化,逐步從人工運營、自動化運營到嘗試智能化運營。
美團測試團隊負責App的質量保證工做,平常除了App的功能測試之外,還會重點關注App的性能測試。如今你們對手機愈來愈依賴,而上面各App的耗電量,直接影響了手機的待機時間,是用戶很是關心的一點。本文主要經過一個典型案例,介紹App性能測試中的電量測試,並總結了咱們由此引起的一些思考。
短視頻做爲已被市場驗證的新內容傳播載體,能有效增長用戶停留時長。大衆點評App從9.3版本開始推出短視頻相關內容,在各頁面新增了短視頻模塊。在短視頻功能測試中,咱們發現若是在視頻列表頁中播放視頻,手機很快就會發燙。針對這種現象,咱們立刻拉取數據進行了分析,測試數據代表,視頻列表頁耗電量居然是詳情頁的11倍。這是怎麼回事兒呢?
目前行業內有不少電量測試的方法,咱們採用的是Battery Historian,這是Google推出的一款Android系統電量分析工具,支持5.0(API 21)及以上系統手機的電量分析。
短視頻主要包括三個核心頁面:視頻列表頁、視頻詳情頁、做者頁,本次的測試對象就是這三個頁面。
測試機型:華爲Mate 9 Android 7.0 電池容量:4000mAh
播放的視頻時長:1min15s 測試場景設計:WiFi環境下,打開App,播放視頻,經過點擊「從新播放」,連續播放10次 對比場景:停在App首頁20min,手機不滅屏 注意:測試過程不充電,每次測試環境一致
以下是Battery Historian測試結果部分截圖:
對測試結果數據進行彙總整理:
消耗電量:系統總電量的佔比
從測試結果能夠看到,短視頻列表頁耗電量特別高,是視頻詳情頁的11倍。
視頻列表頁消耗電量太高,從測試數據能夠很明顯的看出來,視頻列表頁CPU佔用時間高不少。從播放器佈局來看,列表頁和做者頁比視頻詳情頁只是多出了動畫音符。以下圖,紅框中圈出的視頻左下角的音符。
電量消耗差別這麼大,是否跟動畫音符有關呢。爲了排除這個問題,從新編譯了一個去掉動畫音符的APK進行測試。測試結果:
從測試結果來看,CPU和耗電量很明顯都降低了不少,所以肯定是動畫音符引發的。打開GPU視圖更新的開關,查看三個頁面的繪製狀況。打開視頻列表頁,能夠看到,動畫音符每波動一次,會致使整個頁面都在不停的繪製。以下是視頻列表頁繪製的狀況:
從動圖能夠很明顯看出該頁面繪製十分異常,動畫音符每波動一次,會致使整個頁面都從新繪製一遍。
因此,到這裏就明白了問題的緣由,由於頁面上動畫音符的實現方式有問題,動畫音符波動時,致使整個頁面會跟着一塊兒不停的從新繪製。而頁面的重複繪製,會使App CPU佔用比正常狀況下高出不少,進而致使耗電量高。
定位到緣由以後,開發針對性的進行了修復。動畫音符柱狀圖的實現,以前設計由多個可變化的單柱形View組成,單個柱形View重寫了onMeasure & OnDraw方法,從外部柱狀圖View中初始化單個柱子的高度,而後自動根據一個函數式來變化高度。由於每次都須要層層調用Measure和對應的Layout,因此形成外層控件的屢次layout,進而形成CPU佔用率增大。修復以後,使用另外一種方式實現,只重寫了View的OnDraw方法,每次使用Canvas畫出全部柱狀圖,使用ValueAnimator來計算變化柱狀圖高度,也再也不影響父控件的Layout。以下是修復先後的核心代碼:
修復以後動畫音符波動時的繪製區域:
修復以後,從新使用Battery Historian進行驗證,測試結果:
從上面的測試結果,能夠看到,視頻列表頁和做者頁,耗電狀況獲得明顯的優化。
總結一下,短視頻耗電量的問題,是因爲錯誤的繪製方法,致使CPU佔用太高,進而致使耗電量高。那麼由於動畫音符致使耗電量異常的問題到這裏就完美的解決了。CPU負載高,會致使耗電量高是顯而易見的。可是還想深刻探索一下,在手機系統各App耗電量排行榜中,耗電量是怎麼計算的?還有哪些因素會影響耗電量呢?帶着這些疑問,咱們來看看系統計算耗電量的原理。
根據物理學中的知識,功=電壓*電流*時間,可是一部手機中,電壓值U正常來講是不會變的,因此能夠忽略,只經過電流和時間就能夠表示電量。模塊電量(mAh)=模塊電流(mA)*模塊耗時(h)。模塊耗時比較容易理解,可是模塊電流怎樣獲取呢,不一樣廠商的手機,硬件不一樣,是否會影響模塊的電流呢。看一下系統提供的接口:./frameworks/base/core/java/com/Android/internal/os/PowerProfile.java
該類提供了public double getAveragePower(String type)接口,type可取PowerProfile中定義的常量值,包括POWER_CPU_IDLE(CPU空閒時),POWER_CPU_ACTIVE(CPU處於活動時),POWER_WIFI_ON(WiFi開啓時)等各類狀態。而且從接口能夠看出來,每一個模塊的電流值,是從power_profile.xml文件取的值。PowerProfile.java只是用於讀取power_profile.xml的接口而已,後者纔是存儲系統耗電信息的核心文件。power_profile.xml文件的存放路徑是/system/framework/framework-res.apk。
以Nexus 6P爲例,在該路徑獲取到framework-res.apk文件。使用apktool,對framework-res.apk進行反解析,獲取到手機裏面的power_profile.xml文件,內容以下所示:
<?xml version="1.0" encoding="utf-8"?> <device name="Android"> <item name="none">0</item> <item name="screen.on">169.4278765</item> <item name="screen.full">79.09344216</item> <item name="bluetooth.active">25.2</item> <item name="bluetooth.on">1.7</item> <item name="wifi.on">21.21733311</item> <item name="wifi.active">98.04989804</item> <item name="wifi.scan">129.8951166</item> <item name="dsp.audio">26.5</item> <item name="dsp.video">242.0</item> <item name="gps.on">5.661105191</item> <item name="radio.active">64.8918361