代码改变世界

单元测试之Stub和Mock

2012-08-28 16:53  focusdada  阅读(222)  评论(0)    收藏  举报

在做单元测试的时候,我们会发现我们要测试的方法会引用很多外部依赖的对象,比如:(发送邮件,网络通讯,记录Log, 文件系统 之类的)。 而我们没法控制这些外部依赖的对象。  为了解决这个问题,我们需要用到Stub和Mock来模拟这些外部依赖的对象,从而控制它们

 

阅读目录

  1. 实例
  2. 设计测试用例
  3. 什么是外部依赖
  4. Stub和Mock的相同处
  5. Stub和Mock的区别
  6. 良好的产品代码才能单元测试
  7. Mock框架
  8. 源代码下载

 

实例

Analyze类会检查filename的长度,如果小于8,我们就会使用一个实现IWebService的类来记录错误.

我们需要给Analyze方法写单元测试。

复制代码
public class LogAnalyzer {     private IWebService service;     private IEmailService email;
public IWebService Service { get { return service; } set { service = value; } }
public IEmailService Email { get { return email; } set { email = value; } }
public void Analyze(string fileName) { if (fileName.Length < 8) { try { service.LogError("the file name is to short" + fileName); } catch (Exception e) { email.SendEmail("From@test.com", "To@test.com", "IWebServiceFailed", e.Message); } } } }
复制代码

设计测试用例

测试用例一:

fileName= "c:\test\test.txt" (长度大于8),     

期待测试结果: 不会发邮件

测试用例二:

fileName="c:\",(长度小于8),  并且记log失败 。 

期待测试结果: 发邮件

 

如果给Analyze方法写单元测试,为了实现测试用例二。  这时候我们就会碰到两个问题。

第一: 我们无法控制让Service对象记log时抛出异常. 因为Serveice对象我们无法控制

第二: 我们无法判断,Email对象是否发送了Email, (我们不能去Outlook查看收到邮件没有,这样就不是自动化了)

 

外部依赖对象

对于LogAnalyzer对象来说, Service和Email就是两个外部依赖对象. 我们需要自己写Stub和Mock来模拟这两个外部依赖对象。这样我们才能控制他们。

 

我们在测试的代码中新建StubWebService和MockEmailService.这两个class分别实现了IWebService和IEmailService.

 

复制代码
public class StubWebService : IWebService {     public void LogError(string message)     {         throw new Exception("StubWebService throw exception");     } }
public class MockEmailService : IEmailService { public string To; public string From; public string Subject; public string Message;
public void SendEmail(string to, string from, string subject, string message) { To = to; From = from; Subject = subject; Message = message; } }
复制代码

 

工作流程图如下

 

最后我们来看看我们的测试代码,

我们把StubWebService和MockEmailService两个类的实例注入到产品代码中。(因为多态特性嘛)。

通过控制StubWebService中的LogError方法,抛出一个异常。

然后判断MockEmailService中的SendEmail方法有没有被调用. 被调用了说明发送了Email(我们不需要真的收到一封邮件,因为SendEmail功能是IEmailService实现的,)

 

复制代码
[TestMethod] public void TestMethod1() {     StubWebService stubWebService = new StubWebService();     MockEmailService mockEmailSender = new MockEmailService();
LogAnalyzer log = new LogAnalyzer(); log.Emailservice = mockEmailSender; log.WebService = stubWebService;
// Act string tooShortFileName = "1.txt"; log.Analyze(tooShortFileName);
// Assert Assert.AreEqual("to@test.com", mockEmailSender.To); Assert.AreEqual("from@test.com", mockEmailSender.From); Assert.AreEqual("WebSerive log error", mockEmailSender.Subject); }
复制代码

 

Stub和Mock的相同处

从上面的例子我们可以看出, Stub和Mock都是模拟外部依赖,以便我们能控制。

 

Stub 和Mock 的区别

Stub是完全模拟一个外部依赖, 而Mock用来判断测试通过还是失败

 

 

良好的产品代码才能单元测试

如果产品代码是下面那样,你就没办法测试了。 因为WebService和EmailService两个类没有继承接口。我们无法把StubWebService和MockEmailService两个类注入到产品代码。

 

复制代码
    public class LogAnalyzer     {         private WebService webService;         private EmailService emailService;
public WebService WebService { get { return webService; } set { webService = value; } }
public EmailService Emailservice { get { return emailService; } set { emailService = value; } }
public void Analyze(string fileName) { if (fileName.Length < 8) { try { WebService.LogError("Filename too short:" + fileName); } catch (Exception e) { Emailservice.SendEmail("to@test.com", "from@test.com", "WebSerive log error", e.Message); } } } }
复制代码

 

Mock框架

其实我们没有必要自己写MockEmailService方法。  已经有现成的Mock框架可以用了, .NET中有Rhino Mock 和 Moq,  这两个框架比较好用

 

源代码下载

 

点击此处下载完整的源代码, 请用VS2008打开

 

[参考] The Art of Unit Testing