test private method mock object - ntut.edu.twjykuo/train/utmock_30.pdf ·...
TRANSCRIPT
-
國立臺北科技大學資訊工程系郭忠義
Test Private MethodMock Object
國立臺北科技大學資訊工程系
郭忠義
-
國立臺北科技大學資訊工程系郭忠義
測試 private method 原則 單元測試
測試客戶端如何使用物件,驗證物件行為是否符合預期。
Object-Oriented 封裝原則 封裝變化:定義物件邊界,及外部可視介面,封裝物件內部
變化以隔離物件內外。
封裝細節:客戶端,不需了解物件內部資訊。
不須特別針對private/protected method測試 Visual studio 2012刪除2010 accessor測試private method機制。 根據TDD精神,所有production code都是為通過測試案例,
所以不會有涵蓋不到的production code。
2
-
國立臺北科技大學資訊工程系郭忠義
測試 private - 充分測試 public 內聚力高原則
Private method使用 code review即可。 充分測試 public method,即可充分測試到private method。 若無法充分測試到 private method
over design• TDD中,private是使用refactoring的extract method產生,若測試有涵蓋不到private method的code,應刪除此code。
private method是複雜的• 意即複雜public method、複雜的 object責任• 根據Single Responsibility Principle,重構此object,設計其它
object負責此任務。
3
-
國立臺北科技大學資訊工程系郭忠義
測試 private method 客戶滿額打折,當日累積滿額。
4
public class Customer {public static final int UPPER_BOUND=500;private int order[]; //訂單簡化紀錄private int getAmountOfDay() {
int money=0;for (int i=0; i=UPPER_BOUND);
}}
Customer- order: int []
- getAmountOdDay+ exceedDayLimit(int)
-
國立臺北科技大學資訊工程系郭忠義
測試 private – getAmountOfDay()
5
import static org.junit.Assert.*;import org.junit.After;import org.junit.Before;import org.junit.Test;public class CustomerTest {
private Customer c; @Beforepublic void setUp() throws Exception {
int [] order = {10, 20, 30, 40,50};c = new Customer(order);
}@Afterpublic void tearDown() throws Exception {
c=null;}@Testpublic void testExceedDayLimit() {
assertEquals(false, c.exceedDayLimit(300));assertEquals(true, c.exceedDayLimit(1000));
}}
-
國立臺北科技大學資訊工程系郭忠義
Mock – Java Mockito 下載 mockito https://code.google.com/p/mockito/
解壓縮mockito-1.9.5.zip到C:\eclipse\mockito
6
-
國立臺北科技大學資訊工程系郭忠義
Mock – Java Mockito Mockito 程式直接呼叫static function
Foo foo = mock(Foo.class),非寫成 Foo foo = Mockito.mock(Foo.class)
• Window - Preferences - Java - Editor - Content Assist• Add import instead of qualified name 和 Use static imports 打勾。
7
-
國立臺北科技大學資訊工程系郭忠義
Mock – Java Mockito 開啟Eclipse,造出Project - TestMock 將mockito-all-1.9.jar加到the build path
點選TestMock Project下的JRE System 按右鍵Build Path的Configure Build Path.. Properties for TestMock的Libraries的Add External JAR.. 選擇Mockito-all1.9.jar開啟
8
-
國立臺北科技大學資訊工程系郭忠義
Mock – Java Mockito 開啟Eclipse,造出Project - TestMock
造出 ITranslator interface
造出Greeting class
9
public class Greeting {private ITranslator translator;public Greeting(ITranslator translator) {
this.translator = translator; }public String sayHello(String language, String name) {
return translator.translate("English", language, "Hello") + " " + name;}
}
public interface ITranslator {public abstract String translate(String fromLanguage,
String toLanguage, String word);}
-
國立臺北科技大學資訊工程系郭忠義
Mock – Java Mockito Mock 架構
10
Setup(creation)
Exercise(test)
Verify
待測程式
(Greeting.java)Mock Object
(Itranslator.java)
expectations
expectations
InvokeI/O
Greeting
sayHello()
ITranslator
translat()
-
國立臺北科技大學資訊工程系郭忠義
Mock – Java Mockito 造出 mock/stub物件
Itranslator mockTranslator = mock(Itranslator.class);
待測物件greeting的sayHello(),呼叫mockTranslator的translate(),會產生的結果。 when(mockTranslator.translate("English", "Italian",
"Hello")).thenReturn("Ciau");
translate()執行時,丟出exception doThrow(new RunTimeException()).when(mockTranslator). translate();
translate()執行時,不作任何動作 doNothing().when(mockTranslator).translate();
驗證待測物件greeting是否呼叫所依賴物件mockTranslator 2的translate() 。 verify(mockTranslator). translate()(…) 11
-
國立臺北科技大學資訊工程系郭忠義
Mock – Java Mockito 造出Unit Test: File-New-Junit Unit Test
12
import static org.junit.Assert.*;import org.junit.After;import org.junit.Before;import org.junit.Test;import static org.mockito.Mockito.*;public class TestMock {
@Beforepublic void setUp() throws Exception { }@Afterpublic void tearDown() throws Exception { }
-
國立臺北科技大學資訊工程系郭忠義
Mock – Java Mockito
Run As – Junit Test
13
@Testpublic void shouldTestGreetingInItalian(){
//setupITranslator mockTranslator = mock(ITranslator.class);Greeting greeting = new Greeting(mockTranslator);when(mockTranslator.translate(“English”, “Italian”, “Hello”)).thenReturn(“Ciau”); //executeassertEquals("Ciau Paulo", greeting.sayHello("Italian", "Paulo"));//verifyverify(mockTranslator).translate("English", "Italian", "Hello");
}}
-
國立臺北科技大學資訊工程系郭忠義
Command設計模式監控系統 造一個TestServer Project
待測類別為 Server類別,整個系統劇本• 利用Command Design Pattern設計監控系統。• 每一個門有一個跟Server註冊的ICommand物件監控。• 若門被打開則回傳IResult物件,訊息為CRITICAL,此時Server物件啟動IAlert物件警示通知。
14
Server+ monitor()+ addCommand(ICommand)+ removeCommand(Icommand)+ getCommandSize(): int IResult
+ OK = 1+ CRITICAL = 2+ getStatus(): int+ getMessage: String
ICommand+ execute(): IResult
DoorCommand+ execute(): IResult
IDoor+ getStatus(): String
IAlert+ sendAlert(String)
-
國立臺北科技大學資訊工程系郭忠義
Command設計模式監控系統 Server類別有四個method
• addCommand:傳入ICommand物件註冊,用此物件做監控。• removeCommand:移除ICommand物件。• getCommandSize:傳回註冊的ICommand物件個數。• monitor:逐一呼叫每個向它註冊的ICommand物件之execute方法以執行監控工作。
15
public class Server {private List commands;private IAlert alert;public Server(IAlert i) {
alert = i;commands = new ArrayList();
}
-
國立臺北科技大學資訊工程系郭忠義
Command設計模式監控系統 Server類別有四個method
16
public void monitor() {IResult r=null;for (int i=0; i
-
國立臺北科技大學資訊工程系郭忠義
Command設計模式監控系統 IAlert介面,宣告sendAlert()發送警告。
ICommand介面,宣告execute:監控某特定設備,監控結果以IResult物件表示。
17
public interface IAlert {public void sendAlert(String s);public boolean wasAlertSend();
}
public interface ICommand {public IResult execute();
}
-
國立臺北科技大學資訊工程系郭忠義
Command設計模式監控系統 IDoor介面,宣告getStatus()獲取門的狀態開啟或關閉。
IResult介面有兩個method• getStatus:傳回監控結果,1表示OK,2表示CRITICAL。• getMessage:傳回字串代表監控結果的敘述。
18
public interface IResult {public final static int OK=1;public final static int CRITICAL =2;
public String getMessage();public int getStatus();
}
public interface IDoor {public String getStatus();
}
-
國立臺北科技大學資訊工程系郭忠義
Command設計模式監控系統 DoorCommand類別,監控門是否被打開或關閉。
• 透過IDoor介面判斷門的開啟或關閉狀態。– 不同sensor廠商提供IDoor介面,安裝門上監控。
• execute呼叫_door.getStatus()檢查門的狀態– 回傳「open」代表門被打開,execute函數傳回CRITICAL的
Result物件,將Result物件敘述設為Door is open。
19
-
國立臺北科技大學資訊工程系郭忠義
五種層級Mock物件 Dummy
不做任何事。傳入參數但不會被用到。
Stub 根據輸入參數回傳結果。
Spy 記錄物件的那個成員函數被呼叫,以確認與待測物件互動。
Mock 使用Mock函式庫動態建立,設定資料提供回傳值,或預期要
呼叫的特定成員函式。
Fake 接近原物件但實作較簡單。
例如Alert要傳簡訊,但先實作成存檔。 20
-
國立臺北科技大學資訊工程系郭忠義
Exercise:未使用Java Mockito Server類別為實作之待測類別,其餘為 mock/stub 物件。
測試Server類別,以及與Server互動的各 stub 物件。 最小實作所有mock/stub物件
public class TestServer {private IAlert spyAlert;private Server server;@Beforepublic void setUp() throws Exception {//server = new Server(spy);}@Afterpublic void tearDown() throws Exception {
spyAlert=null;server=null;
}
-
國立臺北科技大學資訊工程系郭忠義
Exercise:未使用Java Mockito Dummy: 不做任何事。傳入參數但不會被用到。
TestServer.java
public class DummyCommand implements ICommand{public IResult execute(){
throw new RuntimeException("Unimplement");}
}
@Test// Using Dummy Object DummyCommandpublic void testCommandSize() {
server = new Server(null);assertEquals(0, server.getCommandSize());//server.addCommand(null);server.addCommand(new DummyCommand());server.addCommand(new DummyCommand());assertEquals(2, server.getCommandSize());
}
-
國立臺北科技大學資訊工程系郭忠義
Exercise:未使用Java Mockito Stub: 根據輸入參數回傳結果。
public class StubClosedDoor implements IDoor{public String getStatus() { return "CLOSE"; }
}public class StubOpenDoor implements IDoor{
public String getStatus() { return "OPEN"; }}
-
國立臺北科技大學資訊工程系郭忠義
Exercise:未使用Java Mockito Stub: 根據輸入參數回傳結果。
public class DoorCommand implements ICommand{private IDoor door;public DoorCommand(IDoor i) { door = i; }public IResult execute() {
String s = door.getStatus();IResult r = null;if (s.startsWith("CLOSE")) {
r = new Result(IResult.OK, "OK");System.out.println("OK~");
} else if (s.startsWith("OPEN")){
r = new Result(IResult.CRITICAL, "OPEN");if (r.getStatus()==IResult.CRITICAL) { System.out.println("HELP~"); }
}return r;
}}
-
國立臺北科技大學資訊工程系郭忠義
Exercise:未使用Java Mockito Stub: 根據輸入參數回傳結果。
TestServer.java
@Testpublic void testExecuteDoorCommandWhenDoorOpen() {
DoorCommand doorCmd = new DoorCommand(new StubOpenDoor());IResult result = doorCmd.execute();assertEquals(Result.CRITICAL, result.getStatus());assertTrue(result.getMessage().startsWith("OPEN"));
}@Testpublic void testExecuteDoorCommandWhenDoorClosed() {
DoorCommand doorCmd = new DoorCommand(new StubClosedDoor());IResult result = doorCmd.execute();assertEquals(Result.OK, result.getStatus());assertTrue(result.getMessage().startsWith("OK"));
}
-
國立臺北科技大學資訊工程系郭忠義
Exercise:未使用Java Mockito Spy: 記錄物件的那個method被呼叫,確認與待測物件互動
public class SpyAlert implements IAlert{private boolean sendAlert;public SpyAlert() {
sendAlert = false;}public void sendAlert(String s) {
//System.out.println(s);sendAlert = true;
}public boolean wasAlertSend() {
return sendAlert;}
}
-
國立臺北科技大學資訊工程系郭忠義
Exercise:未使用Java Mockito Spy: 記錄物件的那個method被呼叫,確認與待測物件互動
TestServer.java@Testpublic void testMonitorSendAlertWhenDoorOpen() {
spyAlert = new SpyAlert();server = new Server(spyAlert);server.addCommand(new DoorCommand(new StubOpenDoor()));server.monitor();assertTrue(spyAlert.wasAlertSend());
}@Testpublic void testMonitorSendAlertWhenDoorClose() {
spyAlert = new SpyAlert(); server = new Server(spyAlert);server.addCommand(new DoorCommand(new StubClosedDoor()));server.monitor();assertFalse(spyAlert.wasAlertSend());
}
-
國立臺北科技大學資訊工程系郭忠義
Exercise:未使用Java Mockito Fake: 接近原物件但實作較簡單,例如Alert要傳簡訊,但
先實作成存檔。
import java.io.*;public class FakeLogAlert implements IAlert {
private PrintWriter writer;private String fileName;private void close(Closeable c) {
try { c.close(); }catch (IOException e) {
e.printStackTrace(); }
}public FakeLogAlert(String filename)
throws IOException {fileName = filename;
}
public void sendAlert(String msg) {try {
writer = new PrintWriter(fileName, "UTF-8");writer.println(msg);
} catch (FileNotFoundException e) {e.printStackTrace();
}catch (UnsupportedEncodingException e) {
e.printStackTrace();}finally {
close(writer);}
}public boolean wasAlertSend() {
return true;}
}
-
國立臺北科技大學資訊工程系郭忠義
Exercise:未使用Java Mockito TestServer.java
@Testpublic void testMonitorSendAlertToFakeLogAlertWhenDoorOpen() throws Exception {
IAlert fakeAlert = new FakeLogAlert("OpenFakelog.txt");Server server = new Server(fakeAlert);server.addCommand(new DoorCommand(new StubOpenDoor()));server.monitor();// assert that the OpenFakelog.txt contains a message
}@Testpublic void testMonitorSendAlertToFakeLogAlertWhenDoorClosed() throws Exception {
IAlert fakeAlert = new FakeLogAlert("ClosedFakelog.txt");Server server = new Server(fakeAlert);server.addCommand(new DoorCommand(new StubClosedDoor()));server.monitor();// assert that there is no ClosedFakelog.txt
}}
-
國立臺北科技大學資訊工程系郭忠義
Exercise:使用 Java Mockito 產生假的ICommand, IAlert, IResult物件,測試
Server.minitor()與IAlert.sendAlert()的互動
30
public class TestServer {@Beforepublic void setUp() throws Exception { }@Afterpublic void tearDown() throws Exception { }@Testpublic void TestMockWhenWindowBroken() throws Exception {
ICommand mockCmd = mock(ICommand.class);IAlert mockAlert = mock(IAlert.class);IResult mockResult = mock(IResult.class);Server server = new Server(mockAlert);when(mockCmd.execute()).thenReturn(mockResult);when(mockResult.getStatus()).thenReturn(IResult.CRITICAL);when(mockResult.getMessage()).thenReturn("Broken");server.addCommand(mockCmd);server.monitor();verify(mockAlert, times(1)).sendAlert("Broken");
}
-
國立臺北科技大學資訊工程系郭忠義
Exercise:使用 Java Mockito 產生假的ICommand, IAlert, IResult物件,測試
Server.minitor()與IAlert.sendAlert()的互動
31
@Testpublic void TestMockWhenWindowClose() throws Exception {
ICommand mockCmd = mock(ICommand.class);IAlert mockAlert = mock(IAlert.class);IResult mockResult = mock(IResult.class);Server server = new Server(mockAlert);when(mockCmd.execute()).thenReturn(mockResult);when(mockResult.getStatus()).thenReturn(IResult.OK);server.addCommand(mockCmd);server.monitor();verify(mockAlert, times(0)).sendAlert("Close");
}}