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

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

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 kojenchieh 的頭像
    kojenchieh

    David Ko的學習之旅

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