<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    jinfeng_wang

    G-G-S,D-D-U!

    BlogJava 首頁 新隨筆 聯(lián)系 聚合 管理
      400 Posts :: 0 Stories :: 296 Comments :: 0 Trackbacks

    Mock Objects in Unit Tests Mock Objects in Unit Tests

    by Lu Jian
    01/12/2005

    The use of mock objects is a widely employed unit testing strategy. It shields external and unnecessary factors from testing and helps developers focus on a specific function to be tested.

    EasyMock is a well-known mock tool that can create a mock object for a given interface at runtime. The mock object's behavior can be defined prior encountering the test code in the test case. EasyMock is based on java.lang.reflect.Proxy, which can create dynamic proxy classes/objects according to given interfaces. But it has an inherent limitation from its use of Proxy: it can create mock objects only for interfaces.

    Mocquer is a similar mock tool, but one that extends the functionality of EasyMock to support mock object creation for classes as well as interfaces.

    Introduction to Mocquer

    Mocquer is based on the Dunamis project, which is used to generate dynamic delegation classes/objects for specific interfaces/classes. For convenience, it follows the class and method naming conventions of EasyMock, but uses a different approach internally.

    MockControl is the main class in the Mocquer project. It is used to control the the mock object life cycle and behavior definition. There are four kinds methods in this class.

    • Life Cycle Control Methods
      
          public void replay();
          public void verify();
          public void reset();
          

      The mock object has three states in its life cycle: preparing, working, and checking. Figure 1 shows the mock object life cycle.

      Mock Object Life Cycle
      Figure 1. Mock object life cycle

      Initially, the mock object is in the preparing state. The mock object's behavior can be defined in this state. replay() changes the mock object's state to the working state. All method invocations on the mock object in this state will follow the behavior defined in the preparing state. After verify() is called, the mock object is in the checking state. MockControl will compare the mock object's predefined behavior and actual behavior to see whether they match. The match rule depends on which kind of MockControl is used; this will be explained in a moment. The developer can use replay() to reuse the predefined behavior if needed. Call reset(), in any state, to clear the history state and change to the initial preparing state.

    • Factory Methods
      
          public static MockControl createNiceControl(...);
          public static MockControl createControl(...);
          public static MockControl createStrictControl(...);
          

      Mocquer provides three kinds of MockControls: Nice, Normal, and Strict. The developer can choose an appropriate MockControl in his or her test case, according to what is to be tested (the test point) and how the test will be carried out (the test strategy). The Nice MockControl is the loosest. It does not care about the order of method invocation on the mock object, or about unexpected method invocations, which just return a default value (that depends on the method's return value). The Normal MockControl is stricter than the Nice MockControl, as an unexpected method invocation on the mock object will lead to an AssertionFailedError. The Strict MockControl is, naturally, the strictest. If the order of method invocation on the mock object in the working state is different than that in the preparing state, an AssertionFailedError will be thrown. The table below shows the differences between these three kinds of MockControl.

        Nice Normal Strict
      Unexpected Order Doesn't care Doesn't care AssertionFailedError
      Unexpected Method Default value AssertionFailedError AssertionFailedError

      There are two versions for each factory method.

      
       public static MockControl createXXXControl(Class clazz);
       public static MockControl createXXXControl(Class clazz,
          Class[] argTypes, Object[] args);
          

      If the class to be mocked is an interface or it has a public/protected default constructor, the first version is enough. Otherwise, the second version factory method is used to specify the signature and provide arguments to the desired constructor. For example, assuming ClassWithNoDefaultConstructor is a class without a default constructor:

      
          public class ClassWithNoDefaultConstructor {
            public ClassWithNoDefaultConstructor(int i) {
              ...
            }
            ...
          }
          

      The MockControl can be obtained through:

      
          MockControl control = MockControl.createControl(
            ClassWithNoDefaultConstructor.class,
            new Class[]{Integer.TYPE},
            new Object[]{new Integer(0)});
          
    • Mock object getter method
      
          public Object getMock();
          

      Each MockControl contains a reference to the generated mock object. The developer can use this method to get the mock object and cast it to the real type.

      
          //get mock control
          MockControl control = MockControl.createControl(Foo.class);
          //Get the mock object from mock control
          Foo foo = (Foo) control.getMock();
          
    • Behavior definition methods
      
          public void setReturnValue(... value);
          public void setThrowable(Throwable throwable);
          public void setVoidCallable();
          public void setDefaultReturnValue(... value);
          public void setDefaultThrowable(Throwable throwable);
          public void setDefaultVoidCallable();
          public void setMatcher(ArgumentsMatcher matcher);
          public void setDefaultMatcher(ArgumentsMatcher matcher);
          

      MockControl allows the developer to define the mock object's behavior per each method invocation on it. When in the preparing state, the developer can call one of the mock object's methods first to specify which method invocation's behavior is to be defined. Then, the developer can use one of the behavior definition methods to specify the behavior. For example, take the following Foo class:

      
          //Foo.java
          public class Foo {
            public void dummy() throw ParseException {
              ...
            }
            public String bar(int i) {
              ...
            }
            public boolean isSame(String[] strs) {
              ...
            }
            public void add(StringBuffer sb, String s) {
              ...
            }
          }
          
      The behavior of the mock object can be defined as in the following:
      
          //get mock control
          MockControl control = MockControl.createControl(Foo.class);
          //get mock object
          Foo foo = (Foo)control.getMock();
          //begin behavior definition
      
          //specify which method invocation's behavior
          //to be defined.
          foo.bar(10);
          //define the behavior -- return "ok" when the
          //argument is 10
          control.setReturnValue("ok");
          ...
      
          //end behavior definition
          control.replay();
          ...
          

      Most of the more than 50 methods in MockControl are behavior definition methods. They can be grouped into following categories.

      • setReturnValue()

        These methods are used to specify that the last method invocation should return a value as the parameter. There are seven versions of setReturnValue(), each of which takes a primitive type as its parameter, such as setReturnValue(int i) or setReturnValue(float f). setReturnValue(Object obj) is used for a method that takes an object instead of a primitive. If the given value does not match the method's return type, an AssertionFailedError will be thrown.

        It is also possible to add the number of expected invocations into the behavior definition. This is called the invocation times limitation.

        
              MockControl control = ...
              Foo foo = (Foo)control.getMock();
              ...
              foo.bar(10);
              //define the behavior -- return "ok" when the
              //argument is 10. And this method is expected
              //to be called just once.
              setReturnValue("ok", 1);
              ...
              

        The code segment above specifies that the method invocation, bar(10), can only occur once. How about providing a range?

        
              ...
              foo.bar(10);
              //define the behavior -- return "ok" when the
              //argument is 10. And this method is expected
              //to be called at least once and at most 3
              //times.
              setReturnValue("ok", 1, 3);
              ...
              

        Now bar(10) is limited to be called at least once and at most, three times. More appealingly, a Range can be given to specify the limitation.

        
              ...
              foo.bar(10);
              //define the behavior -- return "ok" when the
              //argument is 10. And this method is expected
              //to be called at least once.
              setReturnValue("ok", Range.ONE_OR_MORE);
              ...
              

        Range.ONE_OR_MORE is a pre-defined Range instance, which means the method should be called at least once. If there is no invocation-count limitation specified in setReturnValue(), such as setReturnValue("Hello"), it will use Range.ONE_OR_MORE as its default invocation-count limitation. There are another two predefined Range instances: Range.ONE (exactly once) and Range.ZERO_OR_MORE (there's no limit on how many times you can call it).

        There is also a special set return value method: setDefaultReturnValue(). It defines the return value of the method invocation despite the method parameter values. The invocation times limitation is Range.ONE_OR_MORE. This is known as the method parameter values insensitive feature.

        
              ...
              foo.bar(10);
              //define the behavior -- return "ok" when calling
              //bar(int) despite the argument value.
              setDefaultReturnValue("ok");
              ...
              
      • setThrowable

        setThrowable(Throwable throwable) is used to define the method invocation's exception throwing behavior. If the given throwable does not match the exception declaration of the method, an AssertionFailedError will be thrown. The invocation times limitation and method parameter values insensitive features can also be applied.

        
              ...
              try {
                foo.dummy();
              } catch (Exception e) {
                //skip
              }
              //define the behavior -- throw ParseException
              //when call dummy(). And this method is expected
              //to be called exactly once.
              control.setThrowable(new ParseException("", 0), 1);
              ...
              
      • setVoidCallable()

        setVoidCallable() is used for a method that has a void return type. The invocation times limitation and method parameter values insensitive features can also be applied.

        
              ...
              try {
                foo.dummy();
              } catch (Exception e) {
                //skip
              }
              //define the behavior -- no return value
              //when calling dummy(). And this method is expected
              //to be called at least once.
              control.setVoidCallable();
              ...
              
      • Set ArgumentsMatcher

        In the working state, the MockControl will search the predefined behavior when any method invocation has happened on the mock object. There are three factors in the search criteria: method signature, parameter value, and invocation times limitation. The first and third factors are fixed. The second factor can be skipped by the parameter values insensitive feature described above. More flexibly, it is also possible to customize the parameter value match rule. setMatcher() can be used in the preparing state with a customized ArgumentsMatcher.

        
              public interface ArgumentsMatcher {
                public boolean matches(Object[] expected,
                                       Object[] actual);
              }
              

        The only method in ArgumentsMatcher, matches(), takes two arguments. One is the expected parameter values array (null, if the parameter values insensitive feature applied). The other is the actual parameter values array. A true return value means that the parameter values match.

        
              ...
              foo.isSame(null);
              //set the argument match rule -- always match
              //no matter what parameter is given
              control.setMatcher(MockControl.ALWAYS_MATCHER);
              //define the behavior -- return true when call
              //isSame(). And this method is expected
              //to be called at least once.
              control.setReturnValue(true, 1);
              ...
              

        There are three predefined ArgumentsMatcher instances in MockControl. MockControl.ALWAYS_MATCHER always returns true when matching, no matter what parameter values are given. MockControl.EQUALS_MATCHER calls equals() on each element in the parameter value array. MockControl.ARRAY_MATCHER is almost the same as MockControl.EQUALS_MATCHER, except that it calls Arrays.equals() instead of equals() when the element in the parameter value array is an array type. Of course, the developer can implement his or her own ArgumentsMatcher.

        A side effect of a customized ArgumentsMatcher is that it defines the method invocation's out parameter value.

        
              ...
              //just to demonstrate the function
              //of out parameter value definition
              foo.add(new String[]{null, null});
              //set the argument match rule -- always
              //match no matter what parameter given.
              //Also defined the value of out param.
              control.setMatcher(new ArgumentsMatcher() {
                public boolean matches(Object[] expected,
                                       Object[] actual) {
                   ((StringBuffer)actual[0])
                                      .append(actual[1]);
                   return true;
                }
              });
              //define the behavior of add().
              //This method is expected to be called at
              //least once.
              control.setVoidCallable(true, 1);
              ...
              
        setDefaultMatcher() sets the MockControl's default ArgumentsMatcher instance. If no specific ArgumentsMatcher is given, the default ArgumentsMatcher will be used. This method should be called before any method invocation behavior definition. Otherwise, an AssertionFailedError will be thrown.
        
              //get mock control
              MockControl control = ...;
              //get mock object
              Foo foo = (Foo)control.getMock();
        
              //set default ArgumentsMatcher
              control.setDefaultMatcher(
                             MockControl.ALWAYS_MATCHER);
              //begin behavior definition
              foo.bar(10);
              control.setReturnValue("ok");
              ...
              
        If setDefaultMatcher() is not used, MockControl.ARRAY_MATCHER is the system default ArgumentsMatcher.

    An Example

    Below is an example that demonstrates Mocquer's usage in unit testing.

    Suppose there is a class named FTPConnector.

    
    package org.jingle.mocquer.sample;
    
    import java.io.IOException;
    import java.net.SocketException;
    
    import org.apache.commons.net.ftp.FTPClient;
    
    public class FTPConnector {
        //ftp server host name
        String hostName;
        //ftp server port number
        int port;
        //user name
        String user;
        //password
        String pass;
    
        public FTPConnector(String hostName,
                            int port,
                            String user,
                            String pass) {
            this.hostName = hostName;
            this.port = port;
            this.user = user;
            this.pass = pass;
        }
    
        /**
         * Connect to the ftp server.
         * The max retry times is 3.
         * @return true if succeed
         */
        public boolean connect() {
            boolean ret = false;
            FTPClient ftp = getFTPClient();
            int times = 1;
            while ((times <= 3) && !ret) {
                try {
                    ftp.connect(hostName, port);
                    ret = ftp.login(user, pass);
                } catch (SocketException e) {
                } catch (IOException e) {
                } finally {
                    times++;
                }
            }
            return ret;
        }
    
        /**
         * get the FTPClient instance
         * It seems that this method is a nonsense
         * at first glance. Actually, this method
         * is very important for unit test using
         * mock technology.
         * @return FTPClient instance
         */
        protected FTPClient getFTPClient() {
            return new FTPClient();
        }
    }
    

    The connect() method can try to connect to an FTP server and log in. If it fails, it can retry up to three times. If the operation succeeds, it returns true. Otherwise, it returns false. The class uses org.apache.commons.net.FTPClient to make a real connection. There is a protected method, getFTPClient(), in this class that looks like nonsense at first glance. Actually, this method is very important for unit testing using mock technology. I will explain that later.

    A JUnit test case, FTPConnectorTest, is provided to test the connect() method logic. Because we want to isolate the unit test environment from any other factors such as an external FTP server, we use Mocquer to mock the FTPClient.

    
    package org.jingle.mocquer.sample;
    
    import java.io.IOException;
    
    import org.apache.commons.net.ftp.FTPClient;
    import org.jingle.mocquer.MockControl;
    
    import junit.framework.TestCase;
    
    public class FTPConnectorTest extends TestCase {
    
        /*
         * @see TestCase#setUp()
         */
        protected void setUp() throws Exception {
            super.setUp();
        }
    
        /*
         * @see TestCase#tearDown()
         */
        protected void tearDown() throws Exception {
            super.tearDown();
        }
    
        /**
         * test FTPConnector.connect()
         */
        public final void testConnect() {
            //get strict mock control
            MockControl control =
                 MockControl.createStrictControl(
                                    FTPClient.class);
            //get mock object
            //why final? try to remove it
            final FTPClient ftp =
                        (FTPClient)control.getMock();
    
            //Test point 1
            //begin behavior definition
            try {
                //specify the method invocation
                ftp.connect("202.96.69.8", 7010);
                //specify the behavior
                //throw IOException when call
                //connect() with parameters
                //"202.96.69.8" and 7010. This method
                //should be called exactly three times
                control.setThrowable(
                                new IOException(), 3);
                //change to working state
                control.replay();
            } catch (Exception e) {
                fail("Unexpected exception: " + e);
            }
    
            //prepare the instance
            //the overridden method is the bridge to
            //introduce the mock object.
            FTPConnector inst = new FTPConnector(
                                      "202.96.69.8",
                                      7010,
                                      "user",
                                      "pass") {
                protected FTPClient getFTPClient() {
                    //do you understand why declare
                    //the ftp variable as final now?
                    return ftp;
                }
            };
            //in this case, the connect() should
            //return false
            assertFalse(inst.connect());
    
            //change to checking state
            control.verify();
    
            //Test point 2
            try {
                //return to preparing state first
                control.reset();
                //behavior definition
                ftp.connect("202.96.69.8", 7010);
                control.setThrowable(
                               new IOException(), 2);
                ftp.connect("202.96.69.8", 7010);
                control.setVoidCallable(1);
                ftp.login("user", "pass");
                control.setReturnValue(true, 1);
                control.replay();
            } catch (Exception e) {
                fail("Unexpected exception: " + e);
            }
    
            //in this case, the connect() should
            //return true
            assertTrue(inst.connect());
    
            //verify again
            control.verify();
        }
    }
    

    A strict MockObject is created. The mock object variable declaration has a final modifier because the variable will be used in the inner anonymous class. Otherwise, a compilation error will be reported.

    There are two test points in the test method. The first test point is when FTPClient.connect() always throws an exception, meaning FTPConnector.connect() will return false as result.

    
    try {
        ftp.connect("202.96.69.8", 7010);
        control.setThrowable(new IOException(), 3);
        control.replay();
    } catch (Exception e) {
        fail("Unexpected exception: " + e);
    }
    

    The MockControl specifies that, when calling connect() on the mock object with the parameters 202.96.96.8 as the host IP and 7010 as the port number, an IOException will be thrown. This method invocation is expected to be called exactly three times. After the behavior definition, replay() changes the mock object to the working state. The try/catch block here is to follow the declaration of FTPClient.connect(), which has an IOException defined in its throw clause.

    
    FTPConnector inst = new FTPConnector("202.96.69.8",
                                         7010,
                                         "user",
                                         "pass") {
        protected FTPClient getFTPClient() {
            return ftp;
        }
    };
    

    The code above creates a FTPConnector instance with its getFTPClient() overridden. It is a bridge to introduce the created mock object into the target to be tested.

    
    assertFalse(inst.connect());
    

    The expected result of connect() should be false on this test point.

    
    control.verify();
    

    Finally, change the mock object to the checking state.

    The second test point is when FTPClient.connect() throws exceptions two times and succeeds on the third time, and FTPClient.login() also succeeds, meaning FTPConnector.connect() will return true as result.

    This test point follows the procedure of previous test point, except that the MockObject should change to the preparing state first, using reset().

    Conclusion

    Mock technology isolates the target to be tested from other external factors. Integrating mock technology into the JUnit framework makes the unit test much simpler and neater. EasyMock is a good mock tool that can create a mock object for a specified interface. With the help of Dunamis, Mocquer extends the function of EasyMock. It can create mock objects not only for interfaces, but also classes. This article gave a brief introduction to Mocquer's usage in unit testing. For more detailed information, please refer to the references below.

    References

    Lu Jian is a senior Java architect/developer with four years of Java development experience.

    posted on 2005-03-20 18:16 jinfeng_wang 閱讀(974) 評論(0)  編輯  收藏 所屬分類: ZZJunit
    主站蜘蛛池模板: 亚洲精品乱码久久久久久| 亚洲成熟xxxxx电影| 福利免费在线观看| 亚洲国产综合91精品麻豆| 无码一区二区三区AV免费| 精品女同一区二区三区免费播放 | 国产羞羞的视频在线观看免费| 久久青青草原亚洲av无码app| 妞干网免费视频在线观看| 久久免费观看视频| 日韩亚洲不卡在线视频中文字幕在线观看| 一区国严二区亚洲三区| 一级毛片免费不卡在线| 国产成人亚洲精品91专区高清| 亚洲va在线va天堂va888www| 免费的涩涩视频在线播放| 久久精品免费观看| 亚洲国产精品无码久久久秋霞1| 久久99国产亚洲高清观看首页| 女人18毛片a级毛片免费视频| 男人的天堂网免费网站| 久久亚洲精品无码gv| 精品亚洲成a人片在线观看| 日韩亚洲国产二区| 国产黄色免费网站| 成人久久免费网站| 美女视频黄a视频全免费网站色 | 亚洲av日韩av综合| 亚洲精品成人片在线观看精品字幕| 午夜时刻免费入口| 69精品免费视频| 东方aⅴ免费观看久久av| WWW国产亚洲精品久久麻豆| 亚洲欧洲精品在线| 国产精品亚洲A∨天堂不卡| 亚洲av无码乱码在线观看野外 | 亚洲youwu永久无码精品| 亚洲精品第一国产综合精品| 国产亚洲综合久久系列| 亚洲第一视频在线观看免费| 免费黄色小视频网站|