OOPAA

          Focusing on OO, Patterns, Architecture, and Agile
          posts - 29, comments - 75, trackbacks - 0, articles - 0
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          利用 Spring2.5 和 Reflection 簡化測試中的mock

          Posted on 2008-09-22 09:58 mingj 閱讀(1996) 評論(0)  編輯  收藏 所屬分類: Spring
          spring2.5最大的特色就是全面使用annotation代替xml配置,包括IOC Container、springMVC和 TestContext測試框架等,給我們開發帶來了極大的便利。springMVC的新特性在這篇文章里面已經有了比較詳盡的介紹,而對于spring的新TestContext測試框架,大家也可以從這里得到詳細的例子說明,有興趣的可以去仔細閱讀,本文不再贅述。總而言之,通過spring2.5提供的 annotation,我們可以讓我們的類——包括controller,Test等職責特殊的類——更 POJO 化,更易于測試,也提高了 TestCase的開發效率。

          在開發過程中,我們通常需要mock特定的對象來測試預期行為,或者使用stub對象來提高單元測試效率。最常見的例子就是在多層webapp中,在controller類的測試方法里mock或 stub底層dao類的方法,從而減輕單元測試時數據庫操作的開銷,加快單元測試速率。至于Reflection,已不是java的新概念了,各樣框架基本上都有使用Reflection來增強Runtime的動態性。而java5里Reflection效率的提升和annotation的引入,更是極大地提高java語言的動態性,讓開發人員得到更多Runtime的靈活性。本文將演示如何使用spring2.5和Reflection簡化測試中的 mock,使用的JUnit框架是JUnit4.4,mock框架是Easymock2.4。

          讓我們先看看最原始的使用mock對象的測試(假設基于jdk5進行開發,使用了包括static import,varargs等新特性):
          import static org.easymock.EasyMock.*;

          public void HelloworldTest extends AbstractSingleSpringContextTests {
              
          private Foo foo = createMock(Foo.class);
              
          private Bar bar = createMock(Bar.class);
              
          private Helloworld helloworld;
              
              @Before
              
          public void before() {
                  reset(foo, bar);
                  helloworld 
          = new Helloworld(foo, bar);
              }
              
              @After
              
          public void after() {
                  verify(foo, bar);
              }
              
              @Test
              
          public void shouldSayHello() {
                  
          //set expectations about foo/bar
                  replay(foo, bar);
                  
                  helloworld.sayHello();
                  
          //assert verification
              }
              
              
          //
          }

          可以看到,因為使用了 Spring 老版本的 TestContext,上面的代碼至少有兩個方面是需要加強的:
          1. 需要大量的 mock 對象創建操作,與真正的 Test Case 無關的繁瑣代碼,而且還引入了對Spring Context Test 類的繼承依賴
          2. 針對不同的 Test 類,因為用到不同的 mock 對象,每次都需要顯式去指明 reset/replay/verify 用到的 mock 對象

          針對上面的兩個問題,我們有相應的解決方案來改進:
          1. 使用spring來替我們創建mock對象,由spring IOC Container在runtime注入需要的mock對象
          2. 提供更通用的rest/replay/verify機制來驗證mock對象,而不是每個 Test 類都需要單獨處理

          1. 每個mock對象都需要手工創建么?答案當然是否定的,我們有FactoryBean。通過在配置文件中指定bean的定義,讓spring來替我們創建mock對象。如下是針對Foo類的定義:
          <bean id="mockFoo" class="org.easymock.EasyMock" factory-method="createMock">
              
          <constructor-arg index="0" value="Foo"/>
          </bean>

          < /constructor-arg>與此同時,Spring TestContext框架提供了 @ContextConfiguration annotation 允許開發人員手工指定 Spring 配置文件所在的位置。這樣,開發過程中,如果開發人員遵循比較好的配置文件組織結構,可以維護一套只用于測試的對象關系配置,里面只維護測試用到的 mock 對象,以及測試中用到的對 mock 對象有依賴關系的對象。在產品代碼中則使用另一套配置文件,配置真實的業務對象。

          JUnit4.4 之后,Test 類上可以通過 @RunWith 注解指定測試用例的 TestRunner ,Spring TestContext框架提供了擴展于 org.junit.internal.runners.JUnit4ClassRunner 的 SpringJUnit4ClassRunner,它負責總裝 Spring TestContext 測試框架并將其統一到 JUnit 4.4 框架中。這樣,你可以把 Test 類上的關于 Spring Test 類的繼承關系去掉,并且使用 JUnit4 之后引入的 annotation 去掉其他任何 JUnit3.8 需要的約定和方法繼承,讓 Test 類更加 POJO。

          Test 類也是“純正” 的 java 對象,自然也可以通過 Spring 來管理依賴關系:在 Test 類的成員變量上加上 @Autowired 聲明,使用 SpringJUnit4ClassRunner 運行 Test Case。Spring 會很聰明地幫助我們擺平 Test 依賴的對象,然后再運行已經“合法”的 Test Case,只要你在用于測試的配置文件里面定義了完整的依賴關系,一如其他正常對象。
          <bean id="Helloword" class="Helloworld" autowire="byType"/>

          這樣,經過上面三點變化,例子代碼變成了這樣:

          import static org.easymock.EasyMock.*;

          @RunWith(SpringJUnit4ClassRunner.
          class)
          @ContextConfiguration(
          "test-context.xml")
          public void HelloworldTest {
              @Autowired
              
          private Foo foo;
              
              @Autowired
              
          private Bar bar;
              
              @Autowired
              
          private Helloworld helloworld;
              
              @Before
              
          public void before() {
                  reset(foo, bar);
              }
              
              @After
              
          public void after() {
                  verify(foo, bar);
              }
              
              @Test
              
          public void shouldSayHello() {
                  
          //set expectations about foo/bar
                  replay(foo, bar);
                  
                  helloworld.sayHello();
                  
          //assert verification
              }
              
              
          //
          }

          2. 現在看上去是不是好多了?嗯,對象間的依賴關系和mock對象的創建都由 Spring 來替我們維護,再也不用費心了。不過,reset/verify 是不是還是看上去那么舒服?我們觀察一下,通常為了簡化對 mock 對象的驗證,我們對 Test 類中使用到的 mock 對象都是一起reset/replay /verify,要是能有resetAll()/replayAll()/verifyAll()方法就好了,也省得不同的 Test 類寫一大串對不同的 Mock 對象驗證的方法。OK,這時候我們就要借助 Reflection 來完成這項任務了:通過 Reflection 得到 Test 類中所有加上 @Autowired 聲明的成員變量,驗證它們是不是由代理或者字節碼增強,從而得到該 Test 類的所有由 Spring 創建的 mock 對象,進行 reset/replay/verify。

          根據這個思路,我們引入這樣一個 mock 測試的Helper類:
          import static org.easymock.EasyMock.*;

          final class MockTestHelper {

              
          public static void resetAll(Object testObject) {
                  reset(getDeclaredMockedFields(testObject));
              }

              
          public static void verifyAll(Object testObject) {
                  verify(getDeclaredMockedFields(testObject));
              }

              
          public static void replayAll(Object testObject) {
                  replay(getDeclaredMockedFields(testObject));
              }

              
          private static Object[] getDeclaredMockedFields(Object testObject) {
                  Field[] declaredFields 
          = testObject.getClass().getDeclaredFields();
                  List declaredMockedFields 
          = new ArrayList();
                  
          for (Field field : declaredFields) {
                      
          if (field.isAnnotationPresent(Autowired.class)) {
                          
          boolean isAccessible = field.isAccessible();
                          
          try {
                              field.setAccessible(
          true);
                              Object value 
          = field.get(testObject);
                              
          if (isClassProxy(value.getClass())) {
                                  declaredMockedFields.add(value);
                              }
                          } 
          catch (IllegalAccessException e) {
                              e.printStackTrace();
                          }
                          
          finally {
                              field.setAccessible(isAccessible);
                          }
                      }
                  }
                  
          return declaredMockedFields.toArray();
              }

              
          private static boolean isClassProxy(Class clazz) {
                  String className 
          = clazz.getName();
                  
          return className.contains("$Proxy"|| className.contains("$$EnhancerByCGLIB$$");
              }

          }

          好了,有了這么一個 Helper 類,寫 mock 對象的Test 類就簡單了許多。還是以上面的例子為例,經過這么一重構,變成如下:

          這樣看起來就好多了,以后不管在 Test 類里面添加多少個 Test 類需要的 mock 對象,我們都不需要再修改對 mock 對象的驗證了,Helper類會
          自動幫我們完成所有的工作。
          綜上所述,使用Spring2.5里面引入的 Test Cntext 和 annotations 的確幫助我們減輕了大量的測試代碼量,而且讓我們的 Test 類更加POJO,更易于讓人理解其職責,成為對 feature 的 specification。而 Reflection的小技巧,則能很方便的改進原來代碼中不夠動態的地方,進一步簡化代碼量和維護難度。當然我們可以看到,即使這樣,代碼里面還是有不少resetAll/replayAll/verifyAll的地方,作為 mock 框架帶來的一些約束,我們沒有辦法來省略。這里推薦一種新的 mock 框架—— mockito,是有我的外國同事開發的,它不僅把mock、stub、spy等double的概念區分更清楚,而且讓我們的 mock 測試更易寫,更易讀。

          更多敬請期待本博的其他文章。

          References:
          Spring 2.5:Spring MVC中的新特性:http://www.infoq.com/cn/articles/spring-2.5-ii-spring-mvc
          使用 Spring 2.5 TestContext 測試框架:https://www.ibm.com/developerworks/cn/java/j-lo-spring25-test/
          mockito project homepage:http://code.google.com/p/mockito/

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 清流县| 潮安县| 龙泉市| 谢通门县| 恩平市| 邢台县| 康保县| 克东县| 资阳市| 庆城县| 玉田县| 吉水县| 大田县| 封开县| 荥阳市| 南雄市| 都昌县| 绍兴市| 东乡| 富川| 璧山县| 化德县| 平谷区| 信宜市| 达拉特旗| 东兰县| 泰和县| 永寿县| 嵊州市| 万源市| SHOW| 丹寨县| 高平市| 洪泽县| 西林县| 贵德县| 邢台县| 南宫市| 调兵山市| 星子县| 桐乡市|