如何模擬(線上或未完成)物件讓自動化測試可以繼續進行
這篇文章主要說明當有些物件在測試環境或是測試周期中尚未完成或是無法執行時,測試要如何繼續進行?
什麼情況下會這樣呢? 例如股市交易,我們測試時不可能等到上市交易的時間才進行測試,當然也不可能拿真實的股市交易測試。
再者,例如有些物件正在開發,當測試的時候,我們不希望等到所有物件開發結束才能進行測試。
這些狀況下,我們就會利用 Mock Object模擬這些物件讓測試可以繼續進行。
最後我們以 Java 的EasyMock,實作並說明如何模擬一個股市價格資訊的物件。
測試情境
我們主要用股市價格為例子說明。股市的即時價格只有在交易時間才有提供,因此測試時,我們必須模擬股市的狀態。
範例中有兩個物件,一個負責取得股價資訊(Class MockGetStockPrice)。另一個物件(class MockMarketValue)則是使用取得股價資訊,進一步計算投資的價值。
MockGetStockPrice
股市交易價格的資訊有可能從交易市場取得,有可能從政府或是證券商取得。
我們假設這個物件的股價資訊或是內容在測試環境中無法執行或是尚未完成。
程式範例如下:
[pastacode lang=”java” message=”” highlight=”” provider=”manual”]
public class MockGetStockPrice {
public float getPrice(int StockID) {
float price = 0;
//Here is the implementation to be done
return price;
}
}
[/pastacode]
MockMarketValue
這個物件主要是使用MockGetStockPrice所提供 getPrice來獲取股價資訊。
float marketValue = myStock.getPrice(stockID);
問題來了,MockGetStockPrice.getPrice 實作尚未完成,而且無法在測試環境下執行,那麼我們怎麼辦呢?
因此這時候我們要透過 Mock模擬的方式,讓MockGetStockPrice.getPrice也可以在測試環境下執行。
說穿了,Mock其實就是預先訂譯好MockGetStockPrice.getPrice會固定回什麼值。
[pastacode lang=”java” message=”” highlight=”” provider=”manual”]
public class MockMarketValue {
int stockID;
public void setStockID(int stockID){
this.stockID = stockID;
}
public float getMarketValue(MockGetStockPrice myStock) {
float marketValue = myStock.getPrice(stockID);
return marketValue;
}
}
[/pastacode]
EasyMock的使用
為了要模擬MockGetStockPrice物件,我們必須宣告
stockPriceMock = EasyMock.createMock(MockGetStockPrice.class);
接著,定義這個模擬物件的輸入與輸出。
輸入:EasyMock.anyInt(),表示可以參數可以給任何的數值 i.e. getPrice(1101)
回傳:andReturn((float) 30.1,任何輸入的參數該 getPrice()都會回傳 30.1
EasyMock.expect(stockPriceMock.getPrice(EasyMock.anyInt())).andReturn((float) 30.1);
簡言之,只要呼叫到 stockPriceMock.getPrice(1101) 或是 stockPriceMock.getPrice(1102) 全部都會回傳 30.1
定義完模擬物件的輸入與輸出之後,啟動該模擬物鍵
EasyMock.replay(stockPriceMock);
最後驗證測試結果,
Assert.assertEquals((float)30.1, myInvest.getMarketValue(stockPriceMock));
EasyMock.verify(stockPriceMock);
這樣做最大的目的是當MockGetStockPrice無法在測試環境下執行時,我們還是可以繼續驗證MockMarketValue
而 MockGetStockPrice的執行就透過事先定義預期的輸入與輸出Mock來執行。
EasyMock 環境下載
要執行 EasyMock必須下載一些相關的 JAR
- https://github.com/cglib/cglib/releases
- http://objenesis.org/download.html
- http://sourceforge.net/projects/easymock/files/EasyMock/3.2/easymock-3.2.zip/download
Java範例程式
MockGetStockPrice
[pastacode lang=”java” message=”” highlight=”” provider=”manual”]
public class MockGetStockPrice {
public float getPrice(int StockID) {
float price = 0;
//Here is the implementation to be done
return price;
}
}
[/pastacode]
MockMarketValue
[pastacode lang=”java” message=”” highlight=”” provider=”manual”]
public class MockMarketValue {
int stockID;
public void setStockID(int stockID){
this.stockID = stockID;
}
public float getMarketValue(MockGetStockPrice myStock) {
float marketValue = myStock.getPrice(stockID);
return marketValue;
}
}
[/pastacode]
MockMarketValue_test
[pastacode lang=”java” message=”” highlight=”” provider=”manual”]
import org.easymock.EasyMock;
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
public class MockMarketValue_test {
private MockGetStockPrice stockPriceMock;
@BeforeTest
public void setUp() throws Exception {
stockPriceMock = EasyMock.createMock(MockGetStockPrice.class);
}
@AfterTest
public void tearDown() throws Exception {
stockPriceMock = null;
}
@Test
public void testPrintHelloWorld() {
EasyMock.expect(stockPriceMock.getPrice(EasyMock.anyInt())).andReturn((float) 30.1);
EasyMock.replay(stockPriceMock);
MockMarketValue myInvest = new MockMarketValue();
myInvest.setStockID(1101);
Assert.assertEquals((float)30.1, myInvest.getMarketValue(stockPriceMock));
EasyMock.verify(stockPriceMock);
}
}
[/pastacode]