利用mock object來進行interaction testing
Source: The art of unit testing with examples in .NET, Roy Osherove
http://www.amazon.com/Art-Unit-Testing-Examples-Net/dp/1933988274/ref=sr_1_1?ie=UTF8&s=books&qid=1267086529&sr=1-1
Chapter 4 Interaction testing using mock objects
首先我們先對一個名詞先做個了解: Interaction testing:
- Interaction testing is testing how an object sends input to or receives input from other objects
- 也就是 object A 如何和其他object作互動
- 所以它又叫作action-driven testing
和之前所提到的state-based testing比較起來, state-based testing是屬於result-driven testing, 看的是執行的結果是否正確, 例如某些property是否被修改正確, 而不管和其他人互動的狀況.
為什麼要提這兩個名詞呢? 這是因為要來談mock object, 以及mock object和stub的比較.
什麼是mock object呢?
- A mock object is a fake object in the system that decides whether the unit test has passed or failed.
- 驗證受測程式是否照預期的方式來和fake object互動
- 通常在每次測試當中不會有超過一個以上的mock object
為什麼要區分mock, stub呢? 最主要現今有許多tool或framework有用到這些名詞來描述不同事情, 因此有需要加以釐清, 之後大家才會容易了解. 並且你在挑選tool和framework時才知道哪一個較好.
基本上, mock和stub的差別是, stub不會讓測試失敗, 但是mock會.
Stub是用來取代受測程式所用到相關的object, 好讓我們的測試可以繼續進行而沒有問題. 所以當我們進行測試時, 測試程式會呼叫受測程式, 受測程式會呼叫stub, 最後通常我們會用assert驗證受測程式某些狀態是否正確.
可是mock object不一樣, 它要驗證的是受測程式和其它相關的object, 之間的互動是否正確.
此外我們還會聽到一個名詞叫fake object. 也是跟mock object和stub有關. 什麼是fake object呢:
- a generic term that can be used to describe either a stub or a mock object, because look like the real object.
- 所有如果是被用來檢查interaction是否正確的叫mock object, 其它的叫stub
讓我們來看個mock object 範例:
// an interface
public intercae IWebService {
void LogError(string message);
}
// mock object
public class MockService: IWebService {
public string LastError;
// 儲存受測程式所傳進來的值, 以驗證是否互動正確
public void LogError(string message) {
LastError = message;
}
}
// 測試程式
[Test]
public void Analyze_TooShortFile() {
// 建立一個mock object
MockService mockService = new MockService();
// 建立受測程式, 並將mock object傳入
LogAnalyzer log = new LogAnalyzer(mockService);
string tooShortFileName = "abc.txt";
log.Analyze(tooShortFileName);
// 確認是否受測程式以預期的方式呼叫mock object
Assert.AreEqual("Filename too short: abc.txt", mockService.LastError);
}
// 受測程式
public class LogAnalyzer {
private IWebService service;
public LogAnalyzer(IWebService service) {
this.service = service;
}
public void Analyze(string filename) {
if (filename.Length
service.LogError("Filename too short:" + filename );
}
}
}
所以在這個例子中, 我們重點不在檢查LogAnalyzer最後的狀態是否正確, 而是確認是否LogAnalyzer有和WebService互動正確.
接著再來看mock object和stub合在一起用的狀況:
// an interface for stub
public intercae IWebService {
void LogError(string message);
}
// an interface for mock object
public interface IEmailService {
void SendEMail(string to, string subject, string body);
}
// 受測程式
public class LogAnalyzer {
private IWebService service;
private IEmailService email;
public IWebService Service {
get { return service; }
set { service = value; }
}
public IEmailService Email {
get { return service; }
set { service = value; }
}
public void Analyze(string filename) {
if (filename.Length
try {
srvice.LogError("FileName too short:" + fileName);
}
catch(Exception e) {
email.SendEmal("a", "subject", e.Message);
}
}
}
}
// 測試程式
[TestFixure]
public class LogAnalyzerTest {
[Test]
public void Analyzer_WebServiceThrow() {
// 設定stub相關資料
StubService stubService = new StubService();
stubService.ToThrow = new Exception("fake exception");
// 建立mock object
MockEmailService mockEmail = new MockEmailService();
LogAnalyzer log = new LogAnalyzer();
// 把stub和mock object和受測程式關係給建立起來
log.Service = stubService;
log.Email = mockEmail;
string tooShortFileName = "abc.ext";
logAnalyze(tooShortFileName);
// 檢查受測程式是否正確和mock互動
Assert.AreEqual( "a", mockEmail.To );
Assert.AreEqual( "fake exception", mockEmail.Body );
Assert.AreEqual( "subject", mockEmail.Subject );
}
}
// Stub
public class StubService: IWebService {
public Exception ToThrow;
public void LogError(string message) {
if (ToTHrow != null)
throw ToThrow;
}
}
// mock object
public class MockEMailService: IEmailService {
public string To;
public string Subject;
public string Body;
public void SendEmail (string to, string, subject, string body) {
To = to;
Subject = subject;
Body = body;
}
}
看到這裡大家應該對於mock object和stub的差別有基本的認識了. 可是看這裡, 大家應該會有一些問題想問, 像是:
1. 需要花不少時間寫mock object和stub
2. 如果interface很複雜, 有很多method和參數時, 花的時間會更久, 並且困難度會變高
3. 若是要檢查傳到mock object or stub的參數, 需要一個一個呼叫assert來檢查. 若有很多參數時會很麻煩
4. 如果要記錄每個intercation的狀況, 需要記錄很多資料.
5. mock object和stub很難被再利用
這些問題要怎麼被處理呢? 下次我們再繼續吧!!