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

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

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

    stone2083

    IBatis下DAO單元測(cè)試另類(lèi)思路

    在說(shuō)另類(lèi)思路之前,先說(shuō)下傳統(tǒng)的測(cè)試方法:
    0.準(zhǔn)備一個(gè)干凈的測(cè)試數(shù)據(jù)庫(kù)環(huán)境
      這個(gè)是前提
    1.測(cè)試數(shù)據(jù)準(zhǔn)備
      使用文本,excel,或者wiki等,準(zhǔn)備測(cè)試sql以及測(cè)試數(shù)據(jù)
      利用dbfit,dbutil等工具將準(zhǔn)備的測(cè)試數(shù)據(jù)導(dǎo)入到數(shù)據(jù)庫(kù)中
    2.執(zhí)行dao方法
      執(zhí)行被測(cè)試的dao方法
    3.測(cè)試結(jié)果斷言
      利用dbfit,dbutil等工具,斷言測(cè)試結(jié)果數(shù)據(jù)和預(yù)計(jì)是否一致
    4.所有數(shù)據(jù)回滾

    其實(shí),對(duì)于這個(gè)流程來(lái)說(shuō),目前的dao測(cè)試框架,支持的已經(jīng)比較完美了
    但是此類(lèi)測(cè)試方法,也有明顯的缺點(diǎn)(或者不能叫缺點(diǎn),叫使用比較麻煩的地方)
    如下:
    1.背上了一個(gè)數(shù)據(jù)庫(kù)環(huán)境.
      不輕量
      這是一個(gè)共享環(huán)境,誰(shuí)也無(wú)法確保環(huán)境數(shù)據(jù)是否真正的干凈
    2.測(cè)試數(shù)據(jù)準(zhǔn)備是一件麻煩的事情
      新表,10幾個(gè)字段毫不為奇;老表,50幾個(gè)字段甚至百來(lái)個(gè)字段,也偶有可見(jiàn);無(wú)論是使用文本,excel,wiki,準(zhǔn)備工作量,都是巨大的.
      準(zhǔn)備的數(shù)據(jù),部分字段內(nèi)容可以是無(wú)意義的,部分字段內(nèi)容又是需要符合測(cè)試意圖(testcase設(shè)計(jì)目的),部分字段還是其他表的關(guān)聯(lián)字段.從而導(dǎo)致后續(xù)維護(hù)人員無(wú)法了解準(zhǔn)備數(shù)據(jù)意圖.
      (實(shí)踐中,也出現(xiàn)過(guò),一同事在維護(hù)他人單元測(cè)試時(shí),由于無(wú)法了解測(cè)試數(shù)據(jù)準(zhǔn)備意圖,寧可重新刪除,自己準(zhǔn)備一份)
    3.預(yù)計(jì)結(jié)果數(shù)據(jù)準(zhǔn)備也是一件麻煩的事情
      理由如上

    所以,理論上是完美的測(cè)試方案,在實(shí)踐過(guò)程中,卻是一件麻煩的事情.導(dǎo)致DAO單元測(cè)試維護(hù)困難.


    分析了現(xiàn)狀,我們?cè)賮?lái)分析下,IBatis下DAO,程序員主要做了哪些編碼:
    1. 寫(xiě)了一份sqlmap.xml配置文件
    2. 通過(guò)getSqlMapClientTemplate.doSomething($sqlID,$param), 執(zhí)行語(yǔ)句
    (當(dāng)然,沒(méi)有使用spring的同學(xué),也是使用了類(lèi)似sqlMapClient.doSomething($sqlID,$param)方法)

    而步驟2其實(shí)是框架替我們做了的事情,按照MOCK的思想,其實(shí)這部分代碼可以被MOCK的,那么我們是否可以做如下假設(shè):
    只要sqlmap.xml中配置信息(主要包括resultmap和statement)是正確的,那么執(zhí)行結(jié)果也應(yīng)該是正確的.

    而我所謂的另類(lèi)思路,就是基于這個(gè)假設(shè),得出的:
    IBatis下,DAO單元測(cè)試,我們拋棄背負(fù)的數(shù)據(jù)庫(kù)環(huán)境,只要根據(jù)不同的條件,斷言不同的sql即可.

    于是乎,封裝了一個(gè)IbatisSqlTester,可以根據(jù)sqlmap中的statement和傳入的條件參數(shù),生成sql語(yǔ)句.
    那么,DAO單元測(cè)試就簡(jiǎn)單了,脫離下數(shù)據(jù)庫(kù)環(huán)境:
    public class ScoreDAOTest extends TestCase {
     
        @SpringBeanByName
        
    private IbatisSqlTester ibatisSqlTester;  //通過(guò)spring配置,需要注入sqlmapclient對(duì)象
     
        @Test
        
    public void testListTpScores() {
            Map
    <String, Object> param = new HashMap<String, Object>(1);
            param.put(
    "memberIds"new String[] { "stone""stone2083" });
            SqlStatement sql 
    = ibatisSqlTester.test("MS-LIST-SCORES", param);
            
    // sql全部匹配
            SqlAssert.isEqual("select * from score where member_id in ('stone','stone2083')", sql.toString());
            
    // sql包含member_id,athena2002,stone關(guān)鍵詞
            SqlAssert.keyWith(sql.toString(), "member_id""stone""stone2083");
            
    // sql符合某個(gè) 正則
            SqlAssert.regexWith(".* where member_id in .*", sql.toString());
            
            
    //其中,SqlAssert也可以換 成want.string()中的方法.
        }
    }

    優(yōu)勢(shì):
      脫離了數(shù)據(jù)庫(kù)環(huán)境
      脫離了表結(jié)構(gòu)數(shù)據(jù)準(zhǔn)備
      脫離了預(yù)計(jì)結(jié)果數(shù)據(jù)準(zhǔn)備
      讓單元測(cè)試變成sql的斷言,編寫(xiě)相對(duì)更簡(jiǎn)單
    缺點(diǎn):
     
    row mapper過(guò)程無(wú)法被測(cè)試


    最后,附上兩個(gè)核心的代碼類(lèi)(還未完成),供大家參考:
    SqlStatement.java
    /**
     * <pre>
     * SqlStatement:Sql語(yǔ)句對(duì)象.
     * 包含:
     *  1.sql語(yǔ)句,類(lèi)似  select * from offer where id = ? and member_id = ?
     *  2.參數(shù)值,類(lèi)似 [1,stone2083]
     *  
     *  toString方法,返回執(zhí)行的sql語(yǔ)句,如:
     *  select * from offer where id = '1' and member_id = 'stone2083'
     * </pre>
     * 
     * 
    @author Stone.J 2010-8-9 下午02:55:36
     
    */
    public class SqlStatement {

        
    //sql
        private String   sql;
        
    //sql參數(shù)
        private Object[] param;

        
    /**
         * <pre>
         * 輸出最終執(zhí)行的sql內(nèi)容.
         * 將sql和param進(jìn)行merge,產(chǎn)生最終執(zhí)行的sql語(yǔ)句
         * </pre>
         
    */
        @Override
        
    public String toString() {
            
    return merge();
        }

        
    /**
         * <pre>
         * 將sql進(jìn)行格式化.
         * 
         * 目前只是簡(jiǎn)單進(jìn)行格式化.去除前后空格,已經(jīng)重復(fù)空格
         * TODO:請(qǐng)使用統(tǒng)一格式化標(biāo)準(zhǔn)規(guī),建議使用SqlFormater類(lèi),進(jìn)行處理
         * </pre>
         * 
         * 
    @param sql
         * 
    @return
         
    */
        
    protected String format(String sql) {
            
    if (sql == null) {
                
    return null;
            }
            
    return sql.toLowerCase().trim().replaceAll("\\s{1,}"" ");
        }

        
    /**
         * <pre>
         * 將sql和param進(jìn)行merge.
         * TODO:請(qǐng)嚴(yán)格按照SQL標(biāo)準(zhǔn),進(jìn)行merge sql內(nèi)容
         * </pre>
         
    */
        
    protected String merge() {
            
    if (param == null || param.length == 0) {
                
    return this.sql;
            }
            String ret 
    = sql;
            
    for (Object p : param) {
                ret 
    = ret.replaceFirst("\\?""'" + p.toString() + "'");
            }
            
    return ret;
        }

        
    public String getSql() {
            
    return sql;
        }

        
    public void setSql(String sql) {
            
    this.sql = format(sql);
        }

        
    public Object[] getParam() {
            
    return param;
        }

        
    public void setParam(Object[] param) {
            
    this.param = param;
        }
    }

    IbatisSqlTester.java
    /**
     * <pre>
     * IBtatis SQL 測(cè)試
     * 一般IBatis DAO單元測(cè)試,主要就是在測(cè)試ibatis的配置文件.
     * IbatisSqlTester將根據(jù)提供的Sql Map Id 和 對(duì)應(yīng)的參數(shù),返回 {
    @link SqlStatement}對(duì)象,提供最終執(zhí)行的sql語(yǔ)句
     * 通過(guò)外部SqlAssert對(duì)象,將預(yù)計(jì)Sql和實(shí)際產(chǎn)生的Sql進(jìn)行對(duì)比,判斷是否正確
     * </pre>
     * 
     * 
    @author Stone.J 2010-8-9 下午02:58:46
     
    */
    public class IbatisSqlTester {

        
    // sqlMapClient
        private ExtendedSqlMapClient sqlMapClient;

        
    /**
         * 根據(jù)提供的SqlMap ID,得到 {
    @link SqlStatement}對(duì)象
         * 
         * 
    @param sqlId: sql map id
         * 
    @return @see {@link SqlStatement}
         
    */
        
    public SqlStatement test(String sqlId) {
            
    //得到MappedStatement對(duì)象
            MappedStatement ms = sqlMapClient.getMappedStatement(sqlId);
            
    if (ms == null) {
                
    //TODO:建議封轉(zhuǎn)自己的異常對(duì)象
                throw new RuntimeException("can't find MappedStatement.");
            }

            
    //按照Ibatis代碼,得到Sql和Param信息
            RequestScope request = new RequestScope();
            ms.initRequest(request);
            Sql sql 
    = ms.getSql();
            String sqlValue 
    = sql.getSql(request, null);

            
    //組轉(zhuǎn)返回對(duì)象
            SqlStatement ret = new SqlStatement();
            ret.setSql(sqlValue);
            
    return ret;
        }

        
    /**
         * 根據(jù)提供的SqlMap ID和對(duì)應(yīng)的param信息,得到 {
    @link SqlStatement}對(duì)象
         * 
         * 
    @param sqlId: sql map id
         * 
    @param param: 參數(shù)內(nèi)容
         * 
    @return @see {@link SqlStatement}
         
    */
        
    public SqlStatement test(String sqlId, Object param) {
            
    //得到MappedStatement對(duì)象
            MappedStatement ms = sqlMapClient.getMappedStatement(sqlId);
            
    if (ms == null) {
                
    //TODO:建議封轉(zhuǎn)自己的異常對(duì)象
                throw new RuntimeException("can't find MappedStatement.");
            }

            
    //按照Ibatis代碼,得到Sql和Param信息
            RequestScope request = new RequestScope();
            ms.initRequest(request);
            Sql sql 
    = ms.getSql();
            String sqlValue 
    = sql.getSql(request, param);
            Object[] sqlParam 
    = sql.getParameterMap(request, param).getParameterObjectValues(request, param);

            
    //組轉(zhuǎn)返回對(duì)象
            SqlStatement ret = new SqlStatement();
            ret.setSql(sqlValue);
            ret.setParam(sqlParam);
            
    return ret;
        }

        
    /**
         * 設(shè)置SqlMapClient對(duì)象
         
    */
        
    public void setSqlMapClient(ExtendedSqlMapClient sqlMapClient) {
            
    this.sqlMapClient = sqlMapClient;
        }

        
    /**
         * <pre>
         * 不推薦使用
         * 推薦使用: {
    @link IbatisSqlTester#setSqlMapClient(ExtendedSqlMapClient)}
         * TODO:請(qǐng)去除這個(gè)方法,或者增加初始化的方式
         * </pre>
         * 
         * 
    @param sqlMapConfig sqlMapConfig xml文件
         
    */
        
    public void setSqlMapConfig(String sqlMapConfig) {
            InputStream in 
    = null;
            
    try {
                File file 
    = ResourceUtils.getFile(sqlMapConfig);
                in 
    = new FileInputStream(file);
                
    this.sqlMapClient = (ExtendedSqlMapClient) SqlMapClientBuilder.buildSqlMapClient(in);
            } 
    catch (Exception e) {
                
    throw new RuntimeException("sqlMapConfig init error.", e);
            } 
    finally {
                
    if (in != null) {
                    
    try {
                        in.close();
                    } 
    catch (IOException e) {
                    }
                }
            }
        }

    }


    最后的最后附上所有代碼(通過(guò)單元測(cè)試代碼,可以看如何使用).歡迎大家的討論.
    sqltester
    builder

    posted on 2010-08-12 09:03 stone2083 閱讀(3530) 評(píng)論(9)  編輯  收藏 所屬分類(lèi): java

    Feedback

    # re: IBatis下DAO單元測(cè)試另類(lèi)思路[未登錄](méi) 2010-08-12 11:12 kylin

    可以看看DDStep
    http://www.ddsteps.org/  回復(fù)  更多評(píng)論   

    # re: IBatis下DAO單元測(cè)試另類(lèi)思路 2010-08-12 12:18 sgz

    lz 很有想法  回復(fù)  更多評(píng)論   

    # re: IBatis下DAO單元測(cè)試另類(lèi)思路 2010-08-12 16:58 stone2083

    @kylin
    簡(jiǎn)單地看了下ddsteps,它是一套集成測(cè)試工具.主要包括:
    dbunit,selenium,mock web(http) server,easymock和spring.
    它對(duì)DB的支持,也是采用傳統(tǒng)的方式.(沒(méi)猜錯(cuò)的話(huà),是使用了,最多封裝了dbunit)
    并不能解決我現(xiàn)在遇到的問(wèn)題:
    1.背負(fù)數(shù)據(jù)庫(kù)環(huán)境
    2.準(zhǔn)備測(cè)試數(shù)據(jù)
    3.準(zhǔn)備預(yù)計(jì)結(jié)果數(shù)據(jù)
    這些麻煩的工作量.

    而且在公司中,也已經(jīng)有一套測(cè)試框架,我們要做的,并不是選擇框架(替換框架),而是選擇一種合適敏捷單元測(cè)試的思路和方案. 讓框架支持敏捷的方案而已.  回復(fù)  更多評(píng)論   

    # re: IBatis下DAO單元測(cè)試另類(lèi)思路 2010-08-12 17:02 stone2083

    @sgz
    大多數(shù)時(shí)候,想法是被現(xiàn)實(shí)逼出來(lái)的 :)
    在我們這邊,dao幾乎沒(méi)有復(fù)雜的業(yè)務(wù)邏輯,僅僅是對(duì)SqlMapClientTemplate的使用而已.
    但是在針對(duì)DB的單元測(cè)試時(shí),代價(jià)又是如此的巨大(主要還是在數(shù)據(jù)準(zhǔn)備上).
    成本,收益比,不劃算,開(kāi)發(fā)們抱怨多.

    分析現(xiàn)狀和開(kāi)發(fā)們實(shí)際需求(寫(xiě)dao,主要是擔(dān)心sql寫(xiě)錯(cuò))后,才萌生了這個(gè)想法.  回復(fù)  更多評(píng)論   

    # re: IBatis下DAO單元測(cè)試另類(lèi)思路 2010-08-13 07:51 藍(lán)劍

    @stone2083
    既然業(yè)務(wù)邏輯不復(fù)雜,那么準(zhǔn)備的數(shù)據(jù)就不會(huì)很復(fù)雜,那還在乎這點(diǎn)工作量?
    sql既然都能測(cè)試了,還在乎這點(diǎn)數(shù)據(jù)準(zhǔn)備?
    數(shù)據(jù)映射都不測(cè)試,還用ibatis干什么,直接jdbc就是了  回復(fù)  更多評(píng)論   

    # re: IBatis下DAO單元測(cè)試另類(lèi)思路 2010-08-13 08:31 stone2083

    @藍(lán)劍
    DAO業(yè)務(wù)邏輯不復(fù)雜只指:在DAO方法中,不會(huì)有復(fù)雜的分支流程,往往只會(huì)調(diào)用一條SqlID執(zhí)行sql.但是這不意味sql不復(fù)雜.
    打個(gè)比方,報(bào)表的生成,業(yè)務(wù)邏輯非常簡(jiǎn)單(根據(jù)什么樣的條件,能看到什么樣的數(shù)據(jù)),但是sql絕對(duì)的復(fù)雜. :)
    dao方法,也只會(huì)有一句 getSqlMapClientTemplate.queryForList("....",param); // 簡(jiǎn)單吧 :)

    數(shù)據(jù)準(zhǔn)備的工作量低嗎?維護(hù)成本低嗎?至少在我實(shí)踐的項(xiàng)目中,沒(méi)有像sample那樣低(dbfit,dbunit,dbutil等sample,都是單表的說(shuō)明,單表字段往往少于5個(gè)字段). 實(shí)際情況是:
    1.字段多
    2.表關(guān)聯(lián) (尤其在tree結(jié)構(gòu)的表,父節(jié)點(diǎn)的依賴(lài),光是這樣的準(zhǔn)備,都非常容易寫(xiě)錯(cuò))
    3.對(duì)于查詢(xún)的語(yǔ)句(尤其是分頁(yè)),需要根據(jù)動(dòng)態(tài)條件,準(zhǔn)備好需要的數(shù)據(jù)
    4.數(shù)據(jù)準(zhǔn)備意圖需要被傳承
    在我看來(lái),并且實(shí)踐過(guò)來(lái),挺不容易的.

    sql的assert,只要根據(jù)條件參數(shù)的不同,做不同預(yù)計(jì)sql的assert,成本絕對(duì)比結(jié)果數(shù)據(jù)校驗(yàn),來(lái)得低.

    至于最后一點(diǎn).
    是用ibatis,還是jdbc;不是單元測(cè)試成本(方案)決定的;而是需求,應(yīng)用,架構(gòu)設(shè)計(jì),部門(mén)崗位情況等決定的.
    我們有專(zhuān)業(yè)的dba,對(duì)所有sql要做review,總不能給一堆jdbc的文件給他們吧.ibatis就挺好了.
    再說(shuō)了,對(duì)于ibatis下dao編碼,錯(cuò)誤率是sql寫(xiě)錯(cuò)的高呢?還是row mapper錯(cuò)誤高呢?所以如果因?yàn)檫@點(diǎn),來(lái)否決全部,挺不公平的.  回復(fù)  更多評(píng)論   

    # re: IBatis下DAO單元測(cè)試另類(lèi)思路 2010-08-13 08:46 stone2083

    提供這個(gè)思路,并不是說(shuō)替代之前的方案,更不是對(duì)傳統(tǒng)測(cè)試方案的否定. 僅僅是為了多一種選擇.

      回復(fù)  更多評(píng)論   

    # re: IBatis下DAO單元測(cè)試另類(lèi)思路[未登錄](méi) 2010-08-13 11:10 kylin

    說(shuō)說(shuō)我們的單元測(cè)試:
    DAO的代碼是工具自動(dòng)生成的,后臺(tái)是用iBtais實(shí)現(xiàn)的,sqlmap也是自動(dòng)生成,所以不用單元測(cè)試,因?yàn)槟J蕉际且粯拥模_(kāi)發(fā)人員不必寫(xiě)SQL。
    Service API調(diào)用DAO接口,完成業(yè)務(wù)邏輯,這部分是需要做單元測(cè)試的,使用DDStep框架,測(cè)試的框架代碼也是自動(dòng)生成,開(kāi)發(fā)人員在框架代碼的基礎(chǔ)上需要做以下幾件事:
    1.準(zhǔn)備測(cè)試用例(準(zhǔn)備數(shù)據(jù)庫(kù)結(jié)構(gòu),測(cè)試數(shù)據(jù)輸入)
    2.編寫(xiě)結(jié)果校驗(yàn)代碼(結(jié)果數(shù)據(jù)輸出)
    其中:準(zhǔn)備數(shù)據(jù)庫(kù)結(jié)構(gòu),測(cè)試數(shù)據(jù)輸入都是通過(guò)excel完成,容易修改,不用編代碼,看代碼(DDStep框架提供的機(jī)制)

    比較費(fèi)時(shí)的就是校驗(yàn)代碼的編寫(xiě)。  回復(fù)  更多評(píng)論   

    # re: IBatis下DAO單元測(cè)試另類(lèi)思路 2010-08-13 11:54 stone2083

    @kylin
    明白你們這邊的情況了.

    Service層的測(cè)試相對(duì)還是容易的,我們這邊也有一套測(cè)試框架(類(lèi)似DDSteps,也是對(duì)一些業(yè)界測(cè)試工具的整合加改進(jìn)).并且對(duì)Service依賴(lài)的外部環(huán)境,都做了隔離,主要包括:
    1.Mock Dao impl
    2.Mock Core Service impl (外部核心業(yè)務(wù)服務(wù))
    3.Mock Search Engine impl
    4.Mock Cach impl
    ....
    測(cè)試重心,主要集中在Service內(nèi)部邏輯的測(cè)試上.
    而公司使用的測(cè)試框架很好的支持了這些需求.


    難點(diǎn)還在DAO的測(cè)試上.
    數(shù)據(jù)準(zhǔn)備的復(fù)雜度,取決于表設(shè)計(jì)的復(fù)雜度和sql的復(fù)雜度.尤其在ibatis支持dynamic語(yǔ)句下,要準(zhǔn)備覆蓋測(cè)試sql語(yǔ)句的數(shù)據(jù).挺繁瑣的.
    這并不是說(shuō)使用excel,還是wiki等的問(wèn)題.而是數(shù)據(jù)內(nèi)容的準(zhǔn)備上.

    每個(gè)測(cè)試數(shù)據(jù)的準(zhǔn)備,都是為了一個(gè)特定的testcase設(shè)計(jì)目的的.而當(dāng)字段多,并且表設(shè)計(jì)相對(duì)復(fù)雜的時(shí)候,這個(gè)準(zhǔn)備意圖,挺難被傳承下去的.
    隨著項(xiàng)目,小需求的進(jìn)行,我們這邊,差不多幾十人,都有可能修改同一個(gè)sql代碼 :(  回復(fù)  更多評(píng)論   

    主站蜘蛛池模板: 亚洲一区二区三区精品视频 | 亚洲国产品综合人成综合网站| 亚洲视频在线免费| 免费a级黄色毛片| 免费一看一级毛片全播放| 日本午夜免费福利视频| 成年女人永久免费观看片| 国产男女猛烈无遮挡免费视频网站 | 一级毛片全部免费播放| 精品亚洲永久免费精品| 无码人妻精品中文字幕免费 | 日韩a毛片免费观看| 又长又大又粗又硬3p免费视频| 免费视频成人国产精品网站| 一级毛片免费观看不收费| 国产精品九九久久免费视频| 亚洲精品国产日韩无码AV永久免费网| 亚洲国产天堂久久综合| 亚洲一区精品无码| 亚洲AV日韩AV永久无码下载| 中文字幕亚洲综合久久| 亚洲一区二区三区免费视频| 亚洲精品无码av中文字幕| 国产亚洲精品成人久久网站| 51午夜精品免费视频| 全免费a级毛片免费看| 1000部拍拍拍18勿入免费视频软件 | 亚洲女人影院想要爱| 亚洲色成人网站WWW永久四虎| 女bbbbxxxx另类亚洲| 亚洲国产视频网站| 亚洲精品V天堂中文字幕| 成人午夜影视全部免费看| 免费h视频在线观看| 成人免费a级毛片| 免费一级毛片在线播放| 亚洲成人中文字幕| 亚洲熟妇无码一区二区三区导航| 一级午夜a毛片免费视频| 91高清免费国产自产拍2021| 女人18特级一级毛片免费视频|