利用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 < 8 ) {
      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 < 8 ) {
      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很難被再利用

這些問題要怎麼被處理呢? 下次我們再繼續吧!!

arrow
arrow
    全站熱搜

    kojenchieh 發表在 痞客邦 留言(0) 人氣()