标签 Mock 下的文章

再谈Mock Object

发现静寂的夜能让我的思维加快。

Mock Object进行Unit Test已经一周多了,发现以前对Mock Object还是很肤浅,即使是现在我也不敢说我对Mock Object的理解就一定正确。

这篇blog假设你已经熟悉JUnit、了解Mock和TDD
如果你是直接开始使用JMock 、Easy Mock或者是MockMaker等Mock Object框架的,我建议你简单了解一下Mock Object的演化历史,这样你在使用Mock Object时才会更有的放矢。

Mock object有关键的两个概念:
* 建立起环境的概念。在www.mockobjects.com的faq中有一句是这样叙述的“I think the fundamental thing to remember about Mock objects is that they are just that – simple shells or placeholders.” Mock object只是替代了被测Object环境的代码。
* test assertions被隐藏在Mock object内部实现中了,在你的test case中用来verify被测代码与Mock object的交互。

在单元测试中,人们发现有一些问题(这些问题在我的“认识Mock Object”中已列出)常见单元测试工具(如JUnit)并不能很好、很便捷的解决,这样Mock object被引入来解决这些问题。

最初人们手工编写Mock Object。随着测试问题越来越复杂,人们自己手工编写的Mock object越来越多,人们开始将编写Mock Object过程中一些通用的东西抽象出来,形成了一些Mock Object Lib,以帮助开发人员快速得到自己需要的Mock Objects。

当前的Mock Object Lib有多种,大致可分为两类:
* Static Mock Objects Lib
* Dynamic Mock Objects Lib

这里简单举例说明一下不同类型Mock object lib的使用方法,并与手工编写进行对比。

问题:我们在测试某个class时,我们需要与MyInterface这个接口进行交互,而该接口尚未实现,这时我们使用Mock object来替代。

接口MyInterface:
public interface MyInterface {
    public SomeClass getSomething();
    public void setSomething(SomeClass aSomething)
        // Other methods omitted…
}

* 手工编写mock object:
public class HandcraftMockMyInterface implements MyInterface {
    public SomeClass getSomething() {
        return something;
    }
    public void setSomething(SomeClass aSomething){
        this.someting = aSomething;
    }
    private SomeClass something;
    //others
}

* 使用Static Mock Object Lib编写Mock Interface:
public class StaticMockMyInterface extends MockObject implements MyInterface{
    private final ExpectationValue something = new ExpectationValue("something");  
    public void setSomething(SomeClass something){
        this.something.setActual(something);
    }
    public void setExpectedSomething(SomeClass something){
        this.something.setExpected(something);
    }
    public void SomeClass getSomething(){
        return this. something;
    }  
}

* 使用Dynamic Mock Object Lib编写Mock Interface:(以JMock为例)
Mock dynamicMockMyInterface = new Mock(MyInterface.class);
MyInterface mi = (MyInterface) dynamicMockMyInterface.proxy();

SomeClass someThing = new SomeClass();
dynamicMockMyInterface.expects(once()).method(“setSomething”).with(eq(someThing));
dynamicMockMyInterface.expects(once()).method(“getSomething”).will(returnValue(someThing));

//执行你的测试代码,比如你的tested object与MyInterface mi交互的代码。

dynamicMockMyInterface.verify();

Mock Object的使用流程
- Setup any state — setup the fixture for your test
- Set expectations for the test
- Run the target code
- Verify that your expectations have been met

Mock Object Practical Experience
- 好的设计是容易测试的设计
- 尽量让测试在内存中完成,不要在硬盘上留下“垃圾”
- 面向接口使用Mock Object。better to implement interface , not inherit class

认识Mock Object

上周六我们Dominoo group讨论(以下称讨论)TDDJUnit的时候,提到过Mock Object,那次可能是我第一次听到Mock Object这个概念,程序员对新鲜的的东西都是敏感的,所以今天晚上花了一些时间了解了一下Mock Object的概念,做了一些简单实践。

术语
Tested Object – 被测对象
Mock – 假的 or 仿制的对象

* What is Mock Object?
在讨论中我大致了解到Mock Object一般是用来做辅助单元测试,它负责隔离Tested Object与真实环境中模块或实体(Real world object)的交互,并“替代”or “冒充”这些真实模块或实体与Tested Object进行交互。

在“JUnit in action”这本书中关于Mock Object的描述如下:
A mock object (or mock for short) is an object created to stand in for an object that your code will be collaborating with. Your code can call methods on the mock object, which will deliver results as set up by your tests.

* Mock Object给我带来什么好处?

看看下面的图:
|—————————————————————–|
|                                            |            
|    |———————|                            |        |—————————–|
|    |   Tested       |                                          | External Mock Object |
|    |    Object       |                            |        |—————————-|
|    |———————|                            |
|           /|\          |——————–|        |
|            |———–〉|    Internal Mock    |        |
|                      |      Object        |        |
|                      |——————–|        |
|    [Your system scope]                        |
|—————————————————————–    |

在测试你的Tested Object时,你可能会与你系统内的某个模块或系统外某个实体交互,而这些模块或实体在你做单元测试的时候可能并不存在,这时:

- Internal Mock Object可能是一个你的系统尚未完成的模块的“替身”(replacement);
- External Mock Object可能是测试你的Tested Object时需要的外部的环境实体的“替身”(replacement)。
不知道这样给Mock Object分类是否正确。

我们来看看与Real world object交互有什么不足之处:
- Real world object的行为具有不确定性,我们难于控制它们的输出or返回结果。
- Real world object有些时候是难于被建立的或者说是无法获得的。
- Real world object的有些行为难于被触发,如磁盘已满,网络error等。
- Real world object可能不存在,比如你的Tested Object需要与你的系统的另一个module交互,而另一个module尚未开发完毕。

当然还不止这些,我们仅仅是列出一部分。

使用Mock Object替代Real world object后我们就会解决上述问题,换句话说当上面的情况出现后,我们就可以使用Mock Object。这也是什么时候该使用Mock Object的answer。

Mock Object是我们自己编写的,我们拥有控制它的绝对的权力,我们可以定制它的行为和输出。

* Use Mock Object
使用Mock Object解决上述问题可分三步走:
1. Use an interface to describe the object
2. Implement the interface for production code
3. Implement the interface in a mock object for testing [3]
还有一点就是对于Internal Mock Object早晚你要实现出其Real world object的,因为那是你系统的一部分。

一个改自资料[3]的例子
public interface Environmental {
    public long getTime();
    // Other methods omitted…
}

对于这样一个接口,我们提供两种实现,
//real world object
public class SystemEnvironment implements Environmental {
    public long getTime() {
        return System.currentTimeMillis();
    }
    // other methods …
}

//mock object
public class MockSystemEnvironment implements Environmental {
    public long getTime() {
        return currentTime;
    }
    public void setTime(Time aTime){
        this.currentTime = aTime;
    }
    private Time currentTime;
    //others
}

我们可以看到在MockSystemEnvironment中我们提供“setTime”函数是为了提供控制Mock Object的接口。

我们要测试的类
//TestedObject
public class TestedObject{
    private Environmental env;
    TestedObject(Environmental aEnv){
        this.env = aEnv;
}
public boolean isAm(){
    Calendar cal = Calendar.getInstance();
    cal.setTimeInMillis(env.getTime());
    int hour = cal.get(Calendar.HOUR_OF_DAY);
        if (hour <=12) return true;
        return false;
    }
}

将要测试的类放入单元测试框架
public class TestTestedObject extends TestCase {
    public void testIsAm(){
        MockSystemEnvironment env = new MockSystemEnvironment();
        // Set up a target test time
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, 2004);
        cal.set(Calendar.MONTH, 10);
        cal.set(Calendar.DAY_OF_MONTH, 1);
        cal.set(Calendar.HOUR_OF_DAY, 16);
        cal.set(Calendar.MINUTE, 55);
        long t1 = cal.getTimeInMillis();
        env.setTime(t1);

        TestedObject to = new TestedObject(env);
        assertFalse(to.isAm());
    }
}

在该单元测试中我们使用了Mock Object,并且在使用前我们利用setTime接口,输入了我们需要的值。结果我们会通过测试。如果我们使用Real Object,我们得到的测试结果将是不固定的,后者可不是所期望的。从这个例子中你也应该体会到Mock object的一些好处了。

如果我们总是手动写我们需要的Mock Object,那将是一个很大的工作量。现在业界有了Mock Objects、easy mock等开源框架的支持,是我们编写Mock object变得越来越容易。

参考资料:
1、《Test-Driven Development – A practical guide》
2、《JUnit in action》
3、《Pragmatic Unit Testing》

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言进阶课 Go语言精进之路1 Go语言精进之路2 Go语言第一课 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats