Stub, Mock and Proxy Testing

Mock testing means unit testing with mock objects as substitutes for real objects. By real objects I mean the objects the tested unit (class) will be using in the real application. If you have a class Calculator, that needs a dao (Data Access Object) object to load the data it needs from a database, then the dao object is a "real object". In order to test the Calculator class you will have to provide it with a dao object that has a valid connection to the database. In addition you have to insert the data needed for the test into the database.

Setting up the connection and inserting the data in the database can be a lot of work. Instead you can provide the Calculator instance with a fake dao class which just returns the data you need for the test. The fake dao class will not actually read the data from the database. The fake dao class is a mock object. A replacement for a real object which makes it easier to test the Calculator class. Purist mock testers would call such a fake dao for a stub. I will get to that distinction later.

mock测试意味着使用真实对象的替代品mock对象来进行单元测试。真实对象就是指的会在真实项目中使用的测试对象。如果你有一个calculator类,需要一个dao对象来从数据库加载数据。那么这个时候dao对象就是一个“真实的对象”。为了测试calculator类,你需要提供一个和数据库有有效联系的dao对象。而且你必须向数据库插入需要测试用的数据。

配置同数据库间的联系和插入数据可能需要大量的工作。那么你就可以为calculator实例提供一个假的dao类用来仅仅返回你需要的数据就可以了。这个假的dao类并不会真的去读取数据库的数据。这个假的dao类就是一个nock对象。用来替换真实的对象使得更容易测试calculator类。

个人理解: mock就是对象的复制品。stub可以负责指定需要测试的数据。

The situation of the Calculator using a dao object can be generalized to "a unit that uses a collaborator". The unit is the Calculator and the collaborator is the dao object. I'll express it like this:

   unit --> collaborator

The arrow means "uses". When the collaborator is exchanged with a mock (or stub), it would be expressed like this:

   unit --> mock

In a unit test situation it will look like this:

   unit test --> unit --> collaborator

... or...

   unit test --> unit --> mock

 

Stubs, Mocks, and Proxies

There are three types of fake objects you can use for testing: Stubs, Mocks and Proxies. Remember, a stub, mock, or proxy replaces a collaborator of the tested unit during unit test. The stubs and mocks follows Martin Fowlers definition of stubs and mocks.

A Stub is an object that implements an interface of a component, but instead of returning what the component would return when called, the stub can be configured to return a value that suits the test. Using stubs a unit test can test if a unit can handle various return values from its collaborator. Using a stub instead of a real collaborator in a unit test could be expressed like this:

stub是用来执行组件接口的对象,但是并不是调用组件应当返回的值,stub可以为了单元测试而被配置返回值。使用stubs单元测试就可以处理多种不同的返回值情况用来测试了。

  1. unit test --> stub
  2. unit test --> unit --> stub
  3. unit test asserts on results and state of unit

First the unit test creates the stub and configures its return values. Then the unit test creates the unit and sets the stub on it. Now the unit test calls the unit which in turn calls the stub. Finally the unit test makes assertions about the results of the method calls on the unit.

A Mock is like a stub, only it also has methods that make it possible determine what methods where called on the Mock. Using a mock it is thus possible to both test if the unit can handle various return values correctly, and also if the unit uses the collaborator correctly. For instance, you cannot see by the value returned from a dao object whether the data was read from the database using a Statement or a PreparedStatement. Nor can you see if the connection.close() method was called before returning the value. This is possible with mocks. In other words, mocks makes it possible to test a units complete interaction with a collaborator. Not just the collaborator methods that return values used by the unit. Using a mock in a unit test could be expressed like this:

首先单元测试创建stub并且配置他的返回值。然后单元测试创建单元并且设置stub。现在单元测试调用单元就是调用stub。最后单元测试使用assertions来处理单元的方法调用结果。

mock类似stub,只是他也有方法可以来确定mock调用什么方法和什么时候调用。如果unit可以正确处理多个返回值,那么使用mock就可以都测试到这些结果。可以用来测试交互行为。

  1. unit test --> mock
  2. unit test --> unit --> mock
  3. unit test asserts on result and state of unit
  4. unit test asserts on the methods called on mock

First the unit test creates the mock and configures its return values. Then the unit test creates the unit and sets the mock on it. Now the unit test calls the unit which in turn calls the mock. Finally the unit test makes assertions about the results of the method calls on the unit. The unit test also makes assertions on what mehods were called on the mock.

首先unit test创建mock并且配置他的返回值。然后单元测试创建单元并且为他们设置mock。现在单元测试调用unit,unit调用mock。最后使用assertion来判断结果。

Proxies in mock testing are mock objects that delegate the method calls to real collaborator objects, but still records internally what methods were called on the proxy. Thus proxies makes it possible to do mock testing with real collaborators. Using a proxy in a unit test could be expressed like this:

  1. unit test --> collaborator
  2. unit test --> proxy
  3. unit test --> unit --> proxy --> collaborator
  4. unit test asserts on result and state of unit
  5. unit test asserts on methods called on proxy

First the unit test creates the collaborator. Then the unit test creates the proxy for the collaborator. Third the unit test creates the unit and sets the proxy on it. Now the unit test calls the unit which in turn calls the proxy. Finally the unit test makes assertions about the results of the method calls on the unit. The unit test also makes assertions on what mehods were called on the proxy.

 

Stub, Mock, and Proxy Testing with Testimonial

There are several popular mock testing API's available. Among others JMock and EasyMock. As of writing these two API's do not support proxies as described above. Note: They *use* java.lang.reflect.Proxy instances to implement their dynamic mocks. But that is not the same as the test proxies described above. These API's can only be used with stubs and mocks, not proxy for real collaborators. I'm sure they'll add it in the future.

For the code examples in this text I will be using my own testing API, Butterfly Testing Tools . I developed Butterfly Testing Tools because I needed the proxy testing feature that neither JMock nor EasyMock has. In addition the API's could be designed a bit more straigtforward and flexible, in my opinion (that, I guess, is a matter of personal style).

First lets see how to create stub for an interface:

   Connection connection =
      (Connection) MockFactory.createProxy(Connection.class);

The connection variable is a stub of the Connection interface. I can now call methods on the connection instance just as if it was a real database connection. The methods will only return null though, since the stub is not configured to return any special value. In order to configure the stub to return the values appropriate for your test, you must obtain the Mock for the stub. Here is how that is done:

   IMock mock = MockFactory.getMock(connection);

Now you have the mock associated with the stub. One of the methods the IMock interface has is

   addReturnValue(Object returnValue);

Using the addReturnValue method you can add return values to the stub. You can add as many as you want. The return values will be returned from the stub in the same sequence they were added. Once a return value has been returned it is removed from the stub, just like in a queue. Note: The sequence of added return values must match the sequence of called methods on the stub! If you add a String "myReturnValue" as return value to the stub and then call connection.prepareStatement("select * from houses") which returns a PreparedStatement, you will get an exception. The String return value cannot be returned from the connection.prepareStatement("..."); You will have to make sure yourself that the return values and called methods on the stub match.

Once you have the IMock instance for a stub you can also make assumptions about what methods were called on the stub. The IMock interface has a series of assertCalled(MethodInvocation) methods for this purpose. Here is an example:

   mock.assertCalled(new MethodInvocation("close"));

If the connection.close() method has not been called a java.lang.AssertionError is thrown. If you are using JUnit for your unit test JUnit will catch the AssertionError and report that the test failed. There are other assertCalled() etc. methods on the IMock interface. See the Testimonial project for more info.

The last thing I will show is how to make a proxy for a real connection and get the mock associated with the proxy:

   //opens a real database connection.
Connection realConnection = getConnection(); Connection proxyConnection = MockFactory.createProxy(realConnection);
IMock mock = MockFactory.getMock(proxyConnection);

Simple, isn't it? It's just the same as when creating stubs for interfaces. You just provide the real collaborator to the MockFactory instead of an interface (class object). The proxyConnection will record all methods called on it before forwarding the method call to the realConnection instance. That way you can use the proxyConnection just as fine as a real connection, and at the same time make mock assertions about what methods were called on it.

You can even turn the proxyConnection into a stub temporarily by adding a return value to the proxy via the mock.addReturnValue(...). When the proxyConnection sees a return value it will return that instead of forwarding the call to the realConnection. Once all return values have been returned the proxyConnection will continue forwarding the method calls to the realConnection instance. That way you can switch between using the proxyConnection as a real connection or a stub. Smart, isn't?

It is not just database connections that are useful to mock. Any collaborator of a tested unit can potentially be mocked during testing. Whether to test a unit using a mock or a real collaborator depends on the situation. Proxies make it possible to do both at the same time, and even stub some method calls along the way. For more information about mock testing with Testimonial, see the Testimonial project page.

posted @ 2015-01-15 10:35  如来藏  阅读(412)  评论(0编辑  收藏  举报