這兩天在寫累積量模塊的單元測試用例,由于AcmCalc.cpp里面涉及大量數(shù)據(jù)庫操作洲脂,三千多行代碼恐锦,為了增強(qiáng)對該文件的防護(hù),提升覆蓋率陕贮,就想著通過mockdb實(shí)現(xiàn)一下潘飘,但事與愿違,一直在碰壁局骤,好好的數(shù)據(jù)放到mockdb怎么就撈不出來呢暴凑,于是花了兩天時間研究了一下mockdbmockdb的實(shí)現(xiàn)。
其實(shí)很簡單凯傲,從代碼里看來嗦篱,無外乎幾個STL標(biāo)準(zhǔn)模板
typedef vector<TMockDBField> MockFields;
typedef list<MockFields>?MockRecords;
typedef map<string,MockRecords>?MockTables;
typedef map<string,MockTables *>?MockDatas;
從上面幾個容器灸促,就構(gòu)建出了一個簡易數(shù)據(jù)庫涵卵。第一個容器荒叼,就是用來存放表里的每個字段,TMockDBField類里存放的數(shù)據(jù)有如下幾個:
?int data_type;?
?long long lvalue;?
?string svalue;?
?char name[128];?
?bool bNull;
將一個表里的所有字段存放在第一個vector里面之后坏晦,就可以生成一個表昆婿,記錄了同一張表里的多條記錄蜓斧。通過第二個list存放,這時就需要有一個表了多律,于是第三個map就出現(xiàn)了第三個map里的前面string就是表名搂蜓,后面的list就是這個表里的所有記錄辽装。對于第四個map,這里應(yīng)該是為了創(chuàng)建多個數(shù)據(jù)庫準(zhǔn)備的殉挽。大概了解mockdb數(shù)據(jù)存放方式之后斯碌,就可以進(jìn)行插入更新刪除操作了肛度。
可惜的是,這些DML操作也實(shí)現(xiàn)得太簡單了冠骄,具體情況如下:
本來我是通過:
static const char * const UPDATE_SUBS_ACM_DAILY_SQL="UPDATE SUBS_ACM_DAILY SET VALUE = VALUE + :VALUE \n"
" WHERE \n"
" SUBS_ID = :SUBS_ID AND RESOURCE_ID = :RESOURCE_ID AND DATE_STAMP = :DATE_STAMP";
語句去實(shí)現(xiàn)更新的加袋,如果有記錄职烧,則更新防泵。如果沒有蝗敢,則實(shí)現(xiàn)如下語句:
static const char * const INSERT_SUBS_ACM_DAILY_SQL="INSERT INTO SUBS_ACM_DAILY (SUBS_ID,RESOURCE_ID,VALUE,DATE_STAMP) \n"
" VALUES \n"
" (:SUBS_ID,:RESOURCE_ID,:VALUE,:DATE_STAMP)";
這沒問題前普。當(dāng)我第一個用例去實(shí)現(xiàn)訂戶日累積的時候,能正常插入數(shù)據(jù)骡湖。
但是當(dāng)我第二個用例去對商品每天累積時峻厚,由于是同一張表SUBS_ACM_DAILY我先去實(shí)現(xiàn)update操作,mockdb居然就把我第一個用例的數(shù)據(jù)給更新掉了浦夷,更新掉了辜王。。肥缔。汹来。SUBS_ACM_DAILY表字段如下:
*SUBS_ID
*RESOURCE_ID
*DATE_STAMP?
VALUE
我在想收班,它是通過什么去更新的。發(fā)現(xiàn)它自己能創(chuàng)建出索引摔桦,所有update語句where后面所帶的字段,它都自動認(rèn)為是索引瘦穆。那也是很好的赊豌,既然能自動為SUBS_ID、RESOURCE_ID熙兔、DATE_STAMP加上索引,那就進(jìn)行匹配吧麸锉。結(jié)果匹配得也是神奇舆声,三個字段,只要有一個字段匹配上碱屁,就算是找到記錄了蛾找,前兩個字段都沒有匹配打毛,只有第三個DATE_STAMP相同狈醉,于是乎就update了耍共。如下函數(shù)就是去找數(shù)據(jù)庫匹配的記錄:
bool TMockDBQuery::FindSatisfiedRecord()
{
? ? bool bSatisfied = false;
? ? for (MockFields::iterator itF = m_fields.begin(); itF !=m_fields.end(); itF++)//檢查綁定的參數(shù)
? ? {
? ? ? ? TMockDBField tInputField = *itF;//待檢查的參數(shù)
? ? ? ? if(!IsIndex(tInputField.AsName()))//這里看上去是在匹配索引
? ? ? ? {
? ? ? ? ? ? continue;
? ? ? ? }
? ? ? ? MockFields vFields = *itCur;
? ? ? ? for (MockFields::iterator it = vFields.begin(); it != vFields.end(); it++)//對比fields
? ? ? ? {
? ? ? ? ? ? TMockDBField tSaveField = *it;//record中的參數(shù)
? ? ? ? ? ? if (tInputField == tSaveField)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? bSatisfied = true;//這里只有一個索引值匹配時奥邮,就認(rèn)為找到返回了,淚奔脚粟。核无。。
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? }
? ? ? ? ? ? else
? ? ? ? ? ? {
? ? ? ? ? ? ? ? bSatisfied = false;
? ? ? ? ? ? }? ? ? ? ? ? ? ?
? ? ? ? }
? ? }
? ? return bSatisfied;
}
接下來的更新也是很神奇的噪沙。update語句里的SET VALUE = VALUE + :VALUE根本就不會去執(zhí)行吐根,只是將新記錄的value去對原記錄的覆蓋。
??? case OPER_UPDATE:
??? if( it != pTables->end())
??? {
??????? m_pRecords=&(it->second);
??????? itCur=m_pRecords->begin();
??????? while(itCur!=m_pRecords->end())
??????? {
??????????? //查找滿足條件的記錄
??????????? bool bSatisfied =FindSatisfiedRecord();
??????????? if (bSatisfied)
??????????? {
??????????????? //更新
??????????????? for (MockFields::iterator itF =m_fields.begin(); itF !=m_fields.end(); itF++)//檢查綁定的參數(shù)
??????????????? {
????????????????? ??TMockDBField tBindField = *itF;//綁定的參數(shù)???????????????????
??????????????????? MockFields vFields =*itCur;
??????????????????? for (MockFields::iteratoriter = itCur->begin(); iter != itCur->end(); iter++)//對比fields
??????????????? ????{
??????????????????????? TMockDBFieldtRecordField = *iter;//record中的參數(shù)
??????????????????????? if(!iter->IsName(tBindField.AsName()))//名字不同喜爷,跳過更新
??????????????????????????? continue;
???????????????????????if(!IsIndex(iter->AsName())) //如果不是索引字段萄唇,更新
??????????????????????? {
??????????????????????????? cout <<"before: " <ToString()<
??????????????????????????? *iter = tBindField;
??????????????????????????? cout <<"after : " <ToString()<
??????????????????????? }
??????? ????????????}
??????????????? }
??????????????? iRowsAffected++;
??????????????? cout<<"TDBQuery::Execute() for update->OK!" << endl;
??????????? }
??????????? itCur++;
??????? }
??? }
gdb打印的日志顯示:
(gdb) n
before:TDBField[VALUE]:? Type?? = 0;?Values = ;? lvalue = 60;
243???????????????????????????????????? *iter =tBindField;
(gdb) n
244???????????????????????????????????? cout<<? "after : "<ToString()<
(gdb) n
after :TDBField[VALUE]:? Type?? = 0;?Values = ;? lvalue = 240;
236???????????????????????????????? TMockDBFieldtRecordField = *iter;//record中的參數(shù)
(gdb) n
這里之前的累積量就是60另萤,本次累積量就是240四敞,直接覆蓋。俩由。癌蚁。
導(dǎo)致第二個測試用例去撈數(shù)據(jù)的時候,就沒撈到
再次詳細(xì)分析了一下為什么沒撈到碘梢,是怎么匹配的關(guān)鍵字
select語句的索引創(chuàng)建時伐蒂,是在SET_PARAM時將需要匹配的條件字段放在了vector m_fields中逸邦,這個存放規(guī)則也有問題,因?yàn)槿绻阌卸鄠€匹配字段雷客,不小心將最后一個需要比較的字段放到里面桥狡,而此時表里正好有一條記錄的這個字段能夠匹配,程序卻也認(rèn)為這條記錄就是你需要撈取的記錄部逮。這段代碼如下:
??? for (; itCur != m_pRecords->end();itCur++)//遍歷表里每一條record
??? {
??????? for (MockFields::iterator itF =m_fields.begin(); itF !=m_fields.end(); itF++)//檢查綁定的參數(shù)嫂易,就是你select時where里的條件
??????? {
??????????? TMockDBField tInputField = *itF;//待檢查的參數(shù)
??????????? if(!IsIndex(tInputField.AsName()))
??????????? {
??????????????? continue;
??????????? }
??????????? MockFields vFields = *itCur;
??????????? for (MockFields::iterator it =vFields.begin(); it != vFields.end(); it++)//對比fields
??????????? {
??????????????? TMockDBField tSaveField =*it;//record中的參數(shù)
??????????????? if (tInputField == tSaveField)
??????????????? {???????????????????
??????????????????? bFind = true;//當(dāng)最后一個需要匹配的條件字段恰好跟表里當(dāng)時那條記錄對應(yīng)字段相等時怜械,這里就認(rèn)為找到記錄了
??????????????????? break;
??????????????? }
??????????????? else
??????????????? {???????????????????
??????????????????? bFind = false;
??????????????? }???????????????
??????????? }
??????? }
??????? if (m_fields.size() == 0)
??????? {
??????????? bFind = true;
??????? }
??????? if (bFind)
??????? {
??????????? m_outputFields = *itCur;
??????????? itCur++;
??????????? return true;
??? ????}
??? }
這個地方我通過在設(shè)置:
??? 原始:
???m_pOdbcQuery->SetParameter("SUBS_ID",pRatableEvent->GetSubsId());
???m_pOdbcQuery->SetParameter("DATE_STAMP",iDate);
???m_pOdbcQuery->SetParameter("RESOURCE_ID", iResourceId);
??? 改為:
???m_pOdbcQuery->SetParameter("SUBS_ID",pRatableEvent->GetSubsId());
???m_pOdbcQuery->SetParameter("RESOURCE_ID", iResourceId);
???m_pOdbcQuery->SetParameter("DATE_STAMP",iDate);//因?yàn)檫@個字段跟第一個用例里的相同,所以最后匹配這個字段時相同享完,就認(rèn)為是撈取出來了
最終結(jié)果跑出來確是正確的:
[?????? OK ] TAcmCalcTest.Give_duration1_AcmType7_When_rum60_Then_Acm60(7 ms)
[ RUN ]TAcmCalcTest.Give_duration181_AcmType9_When_rum60_Then_Acm240
[?????? OK ]TAcmCalcTest.Give_duration181_AcmType9_When_rum60_Then_Acm240 (9 ms)
[----------] 2
tests from TAcmCalcTest (16 ms total)
[----------]
Global test environment tear-down
[==========] 2
tests from 1 test case ran. (497 ms total)
[? PASSED?] 2 tests.
這里的成功般又,也多虧了前面update時沒有加上原始值的bug,不然這里撈出來就是240+60=300了
還沒想到能有什么好的方式去規(guī)避這個問題
想過每個用例跑完之后rollback巍佑,但是之前訂戶資料,資費(fèi)等信息也在里面萤衰,擔(dān)心rollback所有數(shù)據(jù)都沒有堕义,而且也沒有提供這樣的接口,因?yàn)橐婚_始就沒有commit動作
感覺mockdb只能是最簡單的存放單條數(shù)據(jù)脆栋,而且不能寫復(fù)雜的sql,包括where后面帶and或者or,也不能在update里帶計算倦卖。