『Java同時プログラミング実戦』第12章同時プログラムのテスト読書ノート

9058 ワード

同時テストは、セキュリティテスト(エラーのない動作)とアクティブテスト(良好な動作が最終的に発生する)の2つに大きく分けられます.
セキュリティ・テスト-通常、クラスの動作が他の仕様と一致しているかどうかを判断するテスト不変性条件の形式が使用されます.
アクティブテスト-進捗テストと非進捗テストの2つの側面が含まれます.
パフォーマンステストは、スループット、応答性、伸縮性など、アクティブなテストに関連しています.

一、正確性テスト


検査が必要な不変条件と遅延条件を探し出す.
import java.util.concurrent.Semaphore;

public class BoundedBuffer<E> {

	private final Semaphore availableItems, availableSpaces;
	private final E[] items;
	private int putPosition = 0;
	private int takePosition = 0;

	@SuppressWarnings("unchecked")
	public BoundedBuffer(int capacity) {
		availableItems = new Semaphore(0);
		availableSpaces = new Semaphore(capacity);
		items = (E[]) new Object[capacity];
	}

	public boolean isEmpty() {
		return availableItems.availablePermits() == 0;
	}

	public boolean isFull() {
		return availableSpaces.availablePermits() == 0;
	}

	public void put(E x) throws InterruptedException {
		availableSpaces.acquire();
		doInsert(x);
		availableItems.release();
	}

	public E take() throws InterruptedException {
		availableItems.acquire();
		E item = doExtract();
		availableSpaces.release();
		return item;
	}

	private synchronized void doInsert(E x) {
		int i = putPosition;
		items[i] = x;
		putPosition = (++i == items.length)? 0 : i;
	}

	private synchronized E doExtract() {
		int i = takePosition;
		E x = items[i];
		items[i] = null;
		takePosition = (++i == items.length)? 0 : i;
		return x;
	}

}

1基本的なユニットテスト
import static org.junit.Assert.*;
import org.junit.Test;

public class BoundedBufferTests {

	@Test
	public void testIsEmptyWhenConstructed() {
		BoundedBuffer<Integer> bb = new BoundedBuffer<Integer>(10);
		assertTrue(bb.isEmpty());
		assertFalse(bb.isFull());
	}

	@Test
	public void testIsFullAfterPuts() throws InterruptedException {
		BoundedBuffer<Integer> bb = new BoundedBuffer<Integer>(10);
		for (int i = 0; i < 10; i++) {
			bb.put(i);
		}
		assertTrue(bb.isFull());
		assertTrue(bb.isEmpty());
	}
}

2ブロック操作のテスト
takeメソッドがブロックされているかどうか、処理が中断されています.
空のキャッシュから要素を取得します.
	@Test
	public void testTakeBlocksWhenEmpty(){
		final BoundedBuffer<Integer> bb = new BoundedBuffer<Integer>(10);
		Thread taker = new Thread(){
			@Override
			public void run() {
				try {
					int unused =  bb.take();
					fail(); // , 
				} catch (InterruptedException e) { }
			}
		};
		try {
			taker.start();
			Thread.sleep(LOCKUP_DETECT_TIMEOUT);
			taker.interrupt();
			taker.join(LOCKUP_DETECT_TIMEOUT);
			assertFalse(taker.isAlive());
		} catch (InterruptedException e) {
			fail();
		}
	}

空のキャッシュから要素を取得しようとする取得スレッドを作成します.
takeメソッドが成功した場合、テストに失敗したことを示します.
テストを実行したスレッドは、取得スレッドを開始し、しばらく待ってからスレッドを中断します.
「取得」スレッドがtakeメソッドで正しくブロックされている場合、InterruptedExceptionが放出され、この異常をキャプチャしたcatchブロックはこの異常をテストに成功したと見なし、スレッドを終了させます.
その後、メインテストスレッドは「取得」スレッドとマージしようとし、Threadを呼び出す.isAliveはjoinメソッドが正常に返されたかどうかを検証し、「取得」スレッドが割り込みに応答できる場合、joinはすぐに完了することができる.
使用するgetStateは、スレッドが条件待ちでブロックされるかどうかを検証するために使用されますが、この方法は信頼できません.ブロックされたスレッドはWAITINGまたはTIMED_に入る必要はありません.WAITINGなどの状態であるため、JVMはスピン待ちによるブロッキングを選択することができる.
3セキュリティテスト
コンカレント・クラスのセキュリティ・テストの構築において、チェックしやすいプロパティを見つけるには、エラーが発生した場合に失敗する可能性が高く、エラー・チェック・コードが人為的にコンカレント性を制限しないという重要な問題を解決する必要があります.理想的には、テストプロパティに同期メカニズムは必要ありません.
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import junit.framework.TestCase;

public class PutTakeTest extends TestCase {
	private static final ExecutorService pool = Executors.newCachedThreadPool();
	private final AtomicInteger putSum = new AtomicInteger(0);
	private final AtomicInteger takeSum = new AtomicInteger(0);
	private final CyclicBarrier barrier;
	private final BoundedBuffer<Integer> bb;
	private final int nTrials, nPairs;

	public static void main(String[] args) {
		new PutTakeTest(10, 10, 100000).test(); //  
		pool.shutdown();
	}

	static int xorShift(int y) {
		y ^= (y << 6);
		y ^= (y >>> 21);
		y ^= (y << 7);
		return y;
	}

	public PutTakeTest(int capacity, int nPairs, int nTrials) {
		this.bb = new BoundedBuffer<Integer>(capacity);
		this.nTrials = nTrials;
		this.nPairs = nPairs;
		this.barrier = new CyclicBarrier(nPairs * 2 + 1);
	}

	void test() {
		try {
			for (int i = 0; i < nPairs; i++) {
				pool.execute(new Producer());
				pool.execute(new Consumer());
			}
			barrier.await(); //  
			barrier.await(); //  
			assertEquals(putSum.get(), takeSum.get());
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	class Producer implements Runnable {
		@Override
		public void run() {
			try {
				int seed = (this.hashCode() ^ (int) System.nanoTime());
				int sum = 0;
				barrier.await();
				for (int i = nTrials; i > 0; --i) {
					bb.put(seed);
					sum += seed;
					seed = xorShift(seed);
				}
				putSum.getAndAdd(sum);
				barrier.await();
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
	}

	class Consumer implements Runnable {
		@Override
		public void run() {
			try {
				barrier.await();
				int sum = 0;
				for (int i = nTrials; i > 0; --i) {
					sum += bb.take();
				}
				takeSum.getAndAdd(sum);
				barrier.await();
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
	}
}

4リソース管理のテスト
他のオブジェクトを所有または管理するオブジェクトについては、これらのオブジェクトが必要ない場合に参照を破棄する必要があります.リソース漏洩のテスト例:
class Big {
	double[] data = new double[100000];
};

void testLeak() throws InterruptedException{
	BoundedBuffer<Big> bb = new BoundedBuffer<Big>(CAPACITY);
	int heapSize1 = /*   */;
	for (int i = 0; i < CAPACITY; i++){
		bb.put(new Big());
	}
	for (int i = 0; i < CAPACITY; i++){
		bb.take();
	}
	int heapSize2 = /*   */;
	assertTrue(Math.abs(heapSize1 - heapSize2) < THRESHOLD);
}

5コールバックの使用
6より多くの交互操作を生成

二、性能テスト


パフォーマンステストの目標-経験値に基づいて、さまざまな制限値を調整します.たとえば、スレッド数、キャッシュ容量などです.
1 PutTakeTestで計時機能を追加
フェンスベースタイマ
       this .timer = new BarrierTimer();
       this .barrier = new CyclicBarrier(nPairs * 2 + 1, timer);

       public class BarrierTimer implements Runnable{
             private boolean started ;
             private long startTime ;
             private long endTime ;
                  
             @Override
             public synchronized void run() {
                   long t = System.nanoTime();
                   if (!started ){
                         started = true ;
                         startTime = t;
                  } else {
                         endTime = t;
                  }
            }
            
             public synchronized void clear(){
                   started = false ;
            }
            
             public synchronized long getTime(){
                   return endTime - startTime;
            }
      }

修正後のtestメソッドではフェンスベースのタイマーが使用されています
       void test(){
             try {
                   timer.clear();
                   for (int i = 0; i < nPairs; i++){
                         pool .execute( new Producer());
                         pool .execute( new Consumer());
                  }
                   barrier .await();
                   barrier .await();
                   long nsPerItem = timer.getTime() / ( nPairs * (long )nTrials );
                  System. out .println("Throughput: " + nsPerItem + " ns/item");
                  assertEquals(putSum.get(), takeSum.get() )
            } catch (Exception e) {
                   throw new RuntimeException(e);
            }
      }

・生産者消費者モードの異なるパラメータの組み合わせにおけるスループット
.異なるスレッド数における境界キャッシュの伸縮性
.キャッシュのサイズの選択方法
       public static void main(String[] args) throws InterruptedException {
             int tpt = 100000; //  
             for (int cap = 1; cap <= tpt; cap *= 10){
                  System. out .println("Capacity: " + cap);
                   for (int pairs = 1; pairs <= 128; pairs *= 2){
                         TimedPutTakeTest t = new TimedPutTakeTest(cap, pairs, tpt);
                        System. out .println("Pairs: " + pairs + "\t");
                        t.test();
                        System. out .println("\t" );
                        Thread. sleep(1000);
                        t.test();
                        System. out .println();
                        Thread. sleep(1000);
                  }
            }
             pool .shutdown();
      }

スループット/スレッド数の関係の表示
2複数アルゴリズムの比較
3応答性測定

三、性能テストの落とし穴を避ける

1ごみ回収
2動的コンパイル
3コードパスの非現実的なサンプリング
4非現実的な競争力
5不要コードの消去

四、その他のテスト方法

コードレビュー
2静解析ツール
     FindBugs、Lint
3側面向けのテスト技術
4分析とモニタリングツール