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