面向開發的測試技術(二):效能測試

Emac發表於2017-05-09

引子:自上世紀末Kent Beck提出TDD(Test-Driven Development)開發理念以來,開發和測試的邊界變的越來越模糊,從原本上下游的依賴關係,逐步演變成你中有我、我中有你的互賴關係,甚至很多公司設立了新的QE(Quality Engineer)職位。和傳統的QA(Quality Assurance)不同,QE的主要職責是通過工程化的手段保證專案質量,這些手段包括但不僅限於編寫單元測試、整合測試,搭建自動化測試流程,設計效能測試等。可以說,QE身上兼具了QA的質量意識和開發的工程能力。我會從開發的角度分三期聊聊QE這個亦測試亦開發的角色所需的基本技能,這篇是第二篇。

前情概要:

1 什麼是效能測試?

先來看一下維基百科裡對效能測試的定義,

In software engineering, performance testing is in general, a testing practice performed to determine how a system performs in terms of responsiveness and stability under a particular workload. - Wikipedia

注意上述定義中有三個關鍵詞:

  • responsiveness,即響應時間,請求發出去之後,服務端需要多久才能返回結果,顯然響應時間越短,效能越好。
  • stability,即穩定性,同樣的請求,不同時刻發出去,響應時間差別越小,穩定性越好,效能也越好。
  • workload,即負載,同一時刻服務端收到的請求數量,其中單位時間內成功處理的請求數量即吞吐量,吞吐量越大,效能越好。

響應時間和吞吐量是衡量應用效能好壞最重要的兩個指標。對於絕大多數應用,剛開始的時候,響應時間最短;隨著負載的增大,吞吐量快速上升,響應時間也逐漸變長;當負載超過某一個值之後,響應時間會突然呈指數級放大,同時吞吐量也應聲下跌,應用效能急劇下降,整個過程如下:

面向開發的測試技術(二):效能測試

圖片出處:效能測試應該怎麼做?

2 效能測試的目的

瞭解了應用效能變化的普遍規律,效能測試的目的也就有了答案:針對某一應用,找出響應時間和吞吐量的量化關係,找到應用效能變化的臨界點。你可能會問,知道了這些有什麼用呢?在我看來,至少有3個層面的好處:

第一,有的放矢,提高資源利用率。效能測試的過程就是量化效能的過程,有了各種效能資料,你才能對應用效能進行定量分析,找到並解決潛在的效能問題,從而提高資源利用率。

第二,科學的進行容量規劃。找到了應用效能變化的臨界點,也就很容易找到單節點的效能極限,這是進行容量規劃的重要決策依據。比如某一應用在單節點下的極限吞吐量是2000 QPS,那麼面對10000 QPS的流量,至少需要部署5個節點。

第三,改善QoS(Quality of Service)。很多時候,資源是有限的,面對超出服務能力的流量,為了保證QoS,必須做出取捨(比如限流降級,開關預案等),應用效能資料是設計QoS方案的重要依據。

3 效能測試的三個常見誤區

誤區1:只看平均值,不懂TP95/TP99

用平均值來衡量響應時間是效能測試中最常見的誤區。從第1小節的插圖可以看出,隨著吞吐量的增大,響應時間會逐漸變長,當達到最大吞吐量之後,響應時間會開始加速上升,尤其是排在後面的請求。在這個時刻,如果只看平均值,你往往察覺不到問題,因為大部分請求的響應時間還是很短的,慢請求只佔一個很小的比例,所以平均值變化不大。但實際上,可能已經有超過1%,甚至5%的請求的響應時間已經超出設計的範圍了。

更科學、更合理的指標是看TP95或者TP99響應時間。TP是Top Percentile的縮寫,是一個統計學術語,用來描述一組數值的分佈特徵。以TP95為例,假設有100個數字,從小到大排序之後,第95個數字的值就是這組數字的TP95值,表示至少有95%的數字是小於或者等於這個值。

以一次具體的效能測試為例,

面向開發的測試技術(二):效能測試

面向開發的測試技術(二):效能測試

總共有1000次請求,平均響應時間是58.9ms,TP95是123.85ms(平均響應時間的2.1倍),TP99是997.99ms(平均響應時間的16.9倍)。假設應用設計的最大響應時間是100ms,單看平均時間是完全符合要求的,但實際上已經有超過50個請求失敗了。如果看TP95或者TP99,問題就很清楚了。

誤區2:只關注響應時間和吞吐量,忽視請求成功率

雖說衡量應用效能好壞最主要是看響應時間和吞吐量,但這裡有個大前提,所有請求(如果做不到所有,至少也要絕大多數請求,比如99.9%)都被成功處理了,而不是返回一堆錯誤碼。如果不能保證這一點,那麼再低的響應時間,再高的吞吐量都是沒有意義的。

誤區3:忘了測試端也存在效能瓶頸

效能測試的第三個誤區是隻關注服務端,而忽略了測試端本身可能也存在限制。比如測試用例設定了10000併發數,但實際執行用例的機器最大隻支援5000併發數,如果只看服務端的資料,你可能會誤以為服務端最大就只支援5000併發數。如果遇到這種情況,或者換用更高效能的測試機器,或者增加測試機器的數量。

4 如何進行效能測試?

介紹完效能測試相關的一些概念之後,再來看一下有哪些工具可以進行效能測試。

4.1 JMeter

JMeter可能是最常用的效能測試工具。它既支援圖形介面,也支援命令列,屬於黑盒測試的範疇,對非開發人員比較友好,上手也非常容易。圖形介面一般用於編寫、除錯測試用例,而實際的效能測試建議還是在命令列下執行。

面向開發的測試技術(二):效能測試

併發設定

面向開發的測試技術(二):效能測試

請求引數

面向開發的測試技術(二):效能測試

結果報表

命令列下的常用命令:

  • 設定JVM引數:JVM_ARGS="-Xms2g -Xmx2g"
  • 執行測試:jmeter -n -t <jmx_file>
  • 執行測試同時生成報表:jmeter -n -t <jmx_file> -l <log_file> -e -o <report_dir>

除了JMeter,其他常用的效能測試工具還有ab, http_load, wrk以及商用的LoaderRunner

4.2 JMH

如果測試用例比較複雜,或者負責效能測試的人員具有一定的開發能力,也可以考慮使用一些框架編寫單獨的效能測試程式。對於Java開發人員而言,JMH是一個推薦的選擇。類似於JUnit,JMH提供了一系列註解用於編寫測試用例,以及一個執行測試的引擎。事實上,即將釋出的JDK 9預設就會包含JMH。

下面是我GitHub上的示例工程裡的一個例子,

@BenchmarkMode(Mode.Throughput)
@Fork(1)
@Threads(Threads.MAX)
@State(Scope.Benchmark)
@Warmup(iterations = 1, time = 3)
@Measurement(iterations = 3, time = 3)
public class VacationClientBenchmark {

    private VacationClient vacationClient;

    @Setup
    public void setUp() {
        VacationClientConfig clientConfig = new VacationClientConfig("http://localhost:3000");
        vacationClient = new VacationClient(clientConfig);
    }

    @Benchmark
    public void benchmarkIsWeekend() {
        VacationRequest request = new VacationRequest();
        request.setType(PERSONAL);
        OffsetDateTime lastSunday = OffsetDateTime.now().with(TemporalAdjusters.previous(SUNDAY));
        request.setStart(lastSunday);
        request.setEnd(lastSunday.plusDays(1));

        Asserts.isTrue(vacationClient.isWeekend(request).isSuccess());
    }

    // 僅限於IDE中執行
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(VacationClientBenchmark.class.getSimpleName())
                .build();

        new Runner(opt).run();
    }
}複製程式碼

其中:

  • @BenchmarkMode: 效能測試模式,支援Throughput,AverageTime,SingleShotTime等多種模式。
  • @Fork: 設定執行效能測試的Fork程式數,預設是0,表示共用JMH主程式。
  • @Threads: 併發數,Threads.MAX表示同系統的CPU核數。
  • @Warmup和@Measurement: 分別設定預熱和實際效能測試的執行輪數,每輪持續的時間等
  • @Setup和@Benchmark: 等同於JUnit裡的@BeforeClass和@Test

在命令列下,使用JMH框架編寫的效能測試程式只能以Jar包的形式執行(Main函式固定為org.openjdk.jmh.Main),因此一般會針對每個JMH程式單獨維護一個專案。如果是Maven專案,可以使用官方提供的jmh-java-benchmark-archetype,如果是Gradle專案,可以使用jmh-gradle-plugin外掛。

4 小結

以上就是我對效能測試的一些見解,歡迎你到我的留言板分享,和大家一起過過招。下一篇我將聊一下Web的自動化測試,敬請期待。

5 參考

相關文章