綠燈 | 紅燈

妙思資訊

2012年1月9日 星期一

你開發的程式好測試嗎(六)?

第五種情況:
Method中有使用到Thread.

若程式有使用Thread進行非同步運算該如何測試呢?有時侯遇到Thread的狀況很複雜就非常不好測。以下就舉一個簡單的例子來做Thread的測試。

假設有一台樂透機,當主持人按下開始後,就交由另一支Thread去進行選球的動作,該Thread要在一定的時間內從50顆球中選出5顆球。

這台樂透機 (LottoMachine)有一個方法為actionRunner(),執行此方法就相當於按下開始的動作。actionRunner() 會產生一條Thread,該Thread 會去執行 LuckRunner 中的run(),在run()中會隨機挑選5顆球,程式碼如下:
public class LottoMachine {
 
        //選取到的幸運彩球
 private List<ball> winBalls;  
        //開始執行,並由另一支Thread去進行選球的動作      
 public void actionRunner() throws InterruptedException {
  winBalls = new ArrayList<ball>();
  Thread t1 = new Thread(new LuckyRunner(winBalls));
  t1.start();
 } 

 public List<ball> getWinBalls() {
  return winBalls;
 }

 }


public class LuckyRunner implements Runnable {

 private List<ball> winBalls;;
 private Ball ball;
 private List<integer> ballNum = new ArrayList<integer>();
 {
  for (int i = 1; i < 50; i++) {
   ballNum.add(i);
  }
 }

 public LuckyRunner(List<ball> winBalls) {
  this.winBalls = winBalls;
 }

         //選取5個不重複號的球.先暫停500ms再執行,摸擬洗球的程序。
 @Override
 public void run() {

  try {
   Thread.sleep(500);
   for (int i = 0; i < 5; i++) {
    int numberIndex = (int) (Math.random() * (49 - i));
    int selNumber = ballNum.remove(numberIndex);
    ball = new Ball();
    ball.setBallNuber(selNumber);
    winBalls.add(ball);
   }

  } catch (InterruptedException e) {
   e.printStackTrace();
  }

 }

}


接下來要來撰寫測試。我們希望樂透機都能順利選出5顆彩球,號碼介於1~50之間,測試碼如下:

@Test
 public void testActionRunner() throws InterruptedException {

  LottoMachine machine = new LottoMachine();
  machine.actionRunner();  
  assertTrue(machine.getWinBalls().size() == 5); //will fail here!
  for (Ball ball : machine.getWinBalls()) {
   assertTrue(ball.getBallNuber() > 0 && ball.getBallNuber() <= 50);
   System.out.println("ballNum=" + ball.getBallNuber());
  }

 }

測試結果是Fail,因為在測試時選球的動作還在進行中,所以在machine.getWinBalls().size()是0,不是我們預期的5。這時候我們可以先讓測試這條Thread先暫停1秒中再來測試結果,這樣測試就會通過。所以我們在上述的測試碼中加上
Thread.sleep(1000)。
@Test
 public void testActionRunner() throws InterruptedException {

  LottoMachine machine = new LottoMachine();
  machine.actionRunner();
  Thread.sleep(1000);
  assertTrue(machine.getWinBalls().size() == 5);
  for (Ball ball : machine.getWinBalls()) {
   assertTrue(ball.getBallNuber() > 0 && ball.getBallNuber() <= 50);
   System.out.println("ballNum=" + ball.getBallNuber());
  }

 }

如果不想用Thread.sleep(),可以改寫用Callable 和FutureTask,使用FutureTask 的isDone()來取代Thread.sleep(),也是不錯的作法。以下為改寫的程式碼:



public class LottoMachine {

 
 private List<Ball> winBalls; 

        //回傳FutueTask,之後可用來檢查Thread的動作是否完成
 public FutureTask<List<Ball>> actionCallabler() {

  winBalls = new ArrayList<Ball>();
  Callable<List<Ball>> callabler = new LuckyCallabler(winBalls);
  FutureTask<List<Ball>> future = new FutureTask<List<Ball>>(callabler);
  Thread t = new Thread(future);
  t.start();

  return future;
 }

 public List<Ball> getWinBalls() {
  return winBalls;
 }

}
public class LuckyCallabler implements Callable<List<Ball;>;> {

 private List<Ball> winBalls;
 private Ball ball;
 private List<Integer> ballNum = new ArrayList<Integer;>();
 {
  for (int i = 1; i < 50; i++) {
   ballNum.add(i);
  }
 }

 public LuckyCallabler(List<Ball;> winBalls) {

  this.winBalls = winBalls;
 }

 //選取5個不重複號的球.先暫停500ms再執行,摸擬洗球的程序。
 @Override
 public List<Ball> call() throws Exception {

  try {
   Thread.sleep(500);
   for (int i = 0; i < 5; i++) {
    int numberIndex = (int) (Math.random() * (49 - i));
    int selNumber = ballNum.remove(numberIndex);
    ball = new Ball();
    ball.setBallNuber(selNumber);
    winBalls.add(ball);
   }

  } catch (InterruptedException e) {
   e.printStackTrace();
  }
  return winBalls;
 }
}


接下來進行unit test,因為使用FutureTask,所以可以由外部介由FutureTask的isDone 來了解Thread是否已執行完成。若Thread己執行完成,再來進行測試,就可測試Thread的執行結果,測試碼如下:

@Test
 public void testActionCaller() {

  LottoMachine machine = new LottoMachine();
  FutureTask<List<Ball>> future = machine.actionCallabler();
                //使用While 來等待,當future.isDone為true,則進行驗證,驗證完則立刻break;否則會進入無窮迴圈
  while (true) {
   if (future.isDone()) {
    assertTrue(machine.getWinBalls().size() == 5);
    for (Ball ball : machine.getWinBalls()) {
     assertTrue(ball.getBallNuber() > 0
       && ball.getBallNuber() <= 50);
     System.out.println("ballNum=" + ball.getBallNuber());
    }
    break;
   }

  }
 }

沒有留言: