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; } } }
沒有留言:
張貼留言