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

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

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

    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 閱讀(1987) 評論(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/

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


    網站導航:
     
    主站蜘蛛池模板: 久久WWW免费人成人片| 国产亚洲情侣一区二区无| 成全视频在线观看免费高清动漫视频下载| 精品剧情v国产在免费线观看 | 亚洲欧洲免费无码| 久久精品国产亚洲5555| 一级做性色a爰片久久毛片免费| 999国内精品永久免费视频| 国产日韩成人亚洲丁香婷婷| 一级毛片一级毛片免费毛片| 亚洲精品456播放| 亚洲熟妇av午夜无码不卡| 无码午夜成人1000部免费视频| 亚洲国产精品无码久久久久久曰 | 亚洲男人的天堂www| 国产亚洲视频在线观看| 国产乱子精品免费视观看片| 亚洲午夜一区二区电影院| 七色永久性tv网站免费看| 久久久久噜噜噜亚洲熟女综合| 久久毛片免费看一区二区三区| 在线观看免费为成年视频| 亚洲美女免费视频| a级毛片视频免费观看| 亚洲色欲久久久久综合网| 色偷偷亚洲男人天堂| 妞干网手机免费视频| 少妇亚洲免费精品| 亚洲成人高清在线| 无人在线观看免费高清| 亚洲午夜一区二区三区| 最新欧洲大片免费在线| 国产AV无码专区亚洲AV琪琪| 亚洲啪啪综合AV一区| 无码少妇一区二区浪潮免费| 白白色免费在线视频| 久久精品国产亚洲av日韩| 99re6在线精品视频免费播放 | 99re6在线精品免费观看| 亚洲国产成人综合| 亚洲区小说区图片区|