前言
做性能的同學一定遇到過這樣的場景:應用級別的性能測試發現一個操作的響應時間很長,然後要花費很多時間去逐級排查,最後卻發現罪魁禍首是代碼中某個實現低效的底層算法。這種自上而下的逐級排查定位的方法,效率通常都很低,代價也很高。所以,我們就需要在項目早期,對一些關鍵算法進行代碼級別的性能測試,以防止此類在代碼層面就可以被發現的性能問題,遺留到最後的系統性能測試階段才被發現。但是,從實際執行的層面來講,代碼級性能測試並不存在嚴格意義上的測試工具,通常的做法是:改造現有的單元測試框架。
而最常使用的改造方法是:
- 將原本只會執行一次的單元測試用例連續執行 n 次,這個 n 的取值範圍通常是 2000~5000;
- 統計執行 n 次的平均時間。如果這個平均時間比較長(也就是單次函數調用時間比較長)的話,比如已經達到了秒級,那麼通常情況下這個被測函數的實現邏輯一定需要優化。
這裏之所以採用執行 n 次的方式,是因爲函數執行時間往往是毫秒級的,單次執行的誤差會比較大,所以採用多次執行取平均值的做法。
那麼有沒有現成的這樣的測試工具呢?當然也是有的,比如今天我們介紹的主角-- ContiPerf。
ContiPerf 簡介
ContiPerf 是一個輕量級的測試工具,基於JUnit 4 開發,可用於效率測試等。可以指定在線程數量和執行次數,通過限制最大時間和平均執行時間來進行性能測試。
官網地址:https://sourceforge.net/p/contiperf/wiki/Home/
ContiPerf 使用
接下來我們一起來實踐一個例子,
首先,加入 pom 依賴包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--引入 ContiPerf 測試工具-->
<dependency>
<groupId>org.databene</groupId>
<artifactId>contiperf</artifactId>
<version>2.3.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
這裏爲了演示,編寫了一個簡單的測試接口:
UnitTestService.java
/**
* 測試接口類
* @author zuozewei
*
*/
public interface UnitTestService {
public String process(String msg);
}
實現類:UnitTestServiceImpl.java
@Service
public class UnitTestServiceImpl implements UnitTestService {
/**
* 爲了測試,這裏直接返回傳入的值
*/
@Override
public String process(String msg) {
// TODO Auto-generated method stub
return msg;
}
}
編寫 UnitTestServiceTest 測試類,進入 ContiPerfRule。
/**
* 編寫接口性能測試類
* @author zuozewei
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest //SpringBootTest 是springboot 用於測試的註解,可指定啓動類或者測試環境等,這裏直接默認。
public class UnitTestServiceTest {
@Autowired
UnitTestService testService;
// 引入 ContiPerf 進行性能測試
@Rule
public ContiPerfRule contiPerfRule = new ContiPerfRule();
@Test
@PerfTest(invocations = 10000,threads = 100) //100個線程 執行10000次
public void test() {
String msg = "this is a test";
String result = testService.process(msg);
//斷言 是否和預期一致
Assert.assertEquals(msg,result);
}
}
注意:
@Rule 是J unit 提供的一個擴展接口註解,其接口類爲:org.junit.rules.MethodRule
,注意在 Junit5 中,已經被 TestRule 所替代了。
也可以通過對類指定 @PerfTest 和 @Required,表示類中方法的默認設置。
@PerfTest註解:
- invocations:執行次數n,與線程數量無關,默認值爲1
- threads:線程池數量n,併發執行n個線程
- duration:重複地執行時間n,測試至少執行n毫秒
@Required註解:
- @Required(throughput = 20):要求每秒至少執行20個測試;
- @Required(average = 50):要求平均執行時間不超過50ms;
- @Required(median = 45):要求所有執行的50%不超過45ms;
- @Required(max = 2000):要求沒有測試超過2s;
- @Required(totalTime = 5000):要求總的執行時間不超過5s;
- @Required(percentile90 = 3000):要求90%的測試不超過3s;
- @Required(percentile95 = 5000):要求95%的測試不超過5s;
- @Required(percentile99 = 10000):要求99%的測試不超過10s;
- @Required(percentiles = “66:200,96:500”):要求66%的測試不超過200ms,96%的測試不超過500ms。
運行測試,控制檯會生成結果:
com.zuozewei.springbootcontiperfdemo.service.UnitTestServiceTest.test
samples: 10000
max: 331
average: 33.3522
median: 30
同時訪問:target/contiperf-report/index.html,會生成圖表:
注意:圖表需要科學上網才能顯示
圖表中的指標:
- Execution time: 執行時間
- Throughput: TPS
- Min. latency: 最小響應時間
- Average latency: 平均響應時間
- Median: 響應時間中位數
- 90%: 90%響應時間範圍
- Max latency: 最大響應時間
小結
這裏主要是對 Junit 和 ContiPerf 的使用簡單的示例,在單元測試階段的時候考慮做這種代碼級性能測試,肯定會提高 ROI(投入產出比)的,而且代價非常小,希望本文對各位同學都能有所啓發。
示例代碼:
https://github.com/zuozewei/PerformanceTest-Examples/tree/master/contiperf/springboot-contiperf-demo
參考資料:
[1]:https://sourceforge.net/p/contiperf/wiki/Home
[2]:軟件測試52講 茹炳晟