並發程序中潛在錯誤的發生並不具有確定性,而是隨機的。
安全性測試:通常會采用測試不變性條件的形式,即判斷某個類的行為是否與其規范保持一致
活躍性測試:進展測試和無進展測試兩方面,這些都是很難量化的(性能:即吞吐量,響應性,可伸縮性測試)
一、正確性測試
重點:找出需要檢查的不變性條件和后驗條件
1、對基本單元的測試——串行的執行
1 public class BoundedBufferTests { 2 3 @Test 4 public void testIsEmptyWhenConstructed(){ 5 BoundedBuffer<String> bf = new BoundedBuffer<String>(10); 6 assertTrue(bf.isEmpty()); 7 } 8 9 @Test 10 public void testIsFullAfterPuts() throws InterruptedException{ 11 BoundedBuffer<String> bf = new BoundedBuffer<String>(10); 12 for (int i=0; i<10; i++){ 13 bf.put("" + i); 14 } 15 assertTrue(bf.isFull()); 16 assertTrue(bf.isEmpty()); 17 } 18 }
2、對阻塞操作的測試
每個測試必須等他創建的所有線程結束后才可以結束(join)
要測試一個方法的阻塞行為,類似於測試一個拋出異常的方法:如果這個方法可以正常返回,那么就意味着測試失敗。
在測試方法的阻塞行為時,將引入額外的復雜性:當方法被成功地阻塞后,還必須使方法解除阻塞。(中斷)
1 public void testTaskBlocksWhenEmpty(){ 2 final BoundedBuffer<Integer> bb = new BoundedBuffer<>(10); 3 Thread taker = new Thread(){ 4 @Override 5 public void run() { 6 try { 7 int unused = bb.take(); 8 fail(); //不應執行到這里 9 } catch (InterruptedException e) { 10 } 11 } 12 }; 13 try { 14 taker.start(); 15 Thread.sleep(1000); 16 taker.interrupt(); 17 taker.join(2000); //保證即使taker永久阻塞也能返回 18 assertFalse(taker.isAlive()); 19 } catch (InterruptedException e) { 20 fail(); 21 } 22 }
3、安全性測試
構建對並發類的安全性測試中,需要解決的關鍵問題在於,要找出那些容易檢查的屬性,這些屬性在發生錯誤的情況下極有可能失敗,同時又不會使得錯誤檢查代碼人為地限制並發性。理想的情況是,在測試屬性中不需要任何同步機制
例:通過計算入列和出列的校驗和進行檢驗(使用柵欄保證線程均運行到可檢驗處再檢驗)
4、資源管理測試
對於任何持有或管理其他對象的對象,都應該在不需要這些對象時銷毀對它們的引用
例:使用堆檢驗工具對內存資源使用進行檢驗
5、使用回調
可以通過自定義擴展類來進行相關測試
1 public class TestingThreadFactory implements ThreadFactory { 2 public final AtomicInteger numCreated = 3 new AtomicInteger(); //記錄創建的線程數 4 private final ThreadFactory factory = 5 Executors.defaultThreadFactory(); 6 7 @Override 8 public Thread newThread(Runnable r) { 9 numCreated.incrementAndGet(); 10 return factory.newThread(r); 11 } 12 }
6、產生更多的交替操作
使用yield、sleep命令更容易使錯誤出現
二、性能測試
性能測試的目標:
- 衡量典型測試用例中的端到端性能,獲得合理的使用場景
- 根據經驗值來調整各種不同的限值,如線程數量,緩存容量等
1、計時器
通過增加計時器,並改變各個參數、線程池大小、緩存大小,計算出運行時間
例:
2、多種算法的比較
使用不同的內部實現算法,找出具有更高的可伸縮性的算法
例:
3、響應性衡量
某個動作經過多長時間才能執行完成,這時就要測量服務時間的變化情況
除非線程由於密集的同步需求而被持續的阻塞,否則非公平的信號量通常能實現更好的吞吐量,而公平的信號量則實現更低的變動性(公平性開銷主要由於線程阻塞所引起)
三、避免性能測試的陷阱
1、垃圾回收
- 保證垃圾回收在執行測試程序期間不被執行,可通過-verbose:gc查看垃圾回收信息。
- 保證垃圾回收在執行測試程序期間執行多次,可以充分反映出運行期間的內存分配和垃圾回收等開銷。
2、動態編譯
- 可以讓測試程序運行足夠長時間,防止動態編譯對測試結果產生的偏差。
- 在HotSpot中設置-xx:+PrintCompilation,在動態編譯時輸出一條信息
3、對代碼路徑不真實采樣
- 動態編譯可能會讓不同地方調用的同一方法生成的代碼不同
- 測試程序不僅要大致判斷某個典型應用程序的使用模式,還要盡量覆蓋在該應用程序中將執行的代碼路徑集合
4、不真實的競爭程度
- 不同的共享數據和執行本地計算的比例,將表現出不同的競爭程度,也就有不同的性能和可伸縮性
5、無用代碼的消除
- 編譯器可能會刪除那些沒有意義或不會產生結果或可預測結果的代碼
- 使結果盡量是不可預測的
四、其他測試方法
代碼審查(人工檢查代碼),競態分析工具(FindBugs,CheckStyle),面向方面的測試技術,分析與檢測工具(jvisualvm)