这两天在写累积量模块的单元测试用例,由于AcmCalc.cpp里面涉及大量数据库操作,三千多行代码,为了增强对该文件的防护,提升覆盖率,就想着通过mockdb实现一下,但事与愿违,一直在碰壁,好好的数据放到mockdb怎么就捞不出来呢,于是花了两天时间研究了一下mockdbmockdb的实现。
其实很简单,从代码里看来,无外乎几个STL标准模板
typedef vector<TMockDBField> MockFields;
typedef list<MockFields> MockRecords;
typedef map<string,MockRecords> MockTables;
typedef map<string,MockTables *> MockDatas;
从上面几个容器,就构建出了一个简易数据库。第一个容器,就是用来存放表里的每个字段,TMockDBField类里存放的数据有如下几个:
int data_type;
long long lvalue;
string svalue;
char name[128];
bool bNull;
将一个表里的所有字段存放在第一个vector里面之后,就可以生成一个表,记录了同一张表里的多条记录。通过第二个list存放,这时就需要有一个表了,于是第三个map就出现了第三个map里的前面string就是表名,后面的list就是这个表里的所有记录。对于第四个map,这里应该是为了创建多个数据库准备的。大概了解mockdb数据存放方式之后,就可以进行插入更新删除操作了。
可惜的是,这些DML操作也实现得太简单了,具体情况如下:
本来我是通过:
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";
语句去实现更新的,如果有记录,则更新。如果没有,则实现如下语句:
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)";
这没问题。当我第一个用例去实现订户日累积的时候,能正常插入数据。
但是当我第二个用例去对商品每天累积时,由于是同一张表SUBS_ACM_DAILY我先去实现update操作,mockdb居然就把我第一个用例的数据给更新掉了,更新掉了。。。。SUBS_ACM_DAILY表字段如下:
*SUBS_ID
*RESOURCE_ID
*DATE_STAMP
VALUE
我在想,它是通过什么去更新的。发现它自己能创建出索引,所有update语句where后面所带的字段,它都自动认为是索引。那也是很好的,既然能自动为SUBS_ID、RESOURCE_ID、DATE_STAMP加上索引,那就进行匹配吧。结果匹配得也是神奇,三个字段,只要有一个字段匹配上,就算是找到记录了,前两个字段都没有匹配,只有第三个DATE_STAMP相同,于是乎就update了。如下函数就是去找数据库匹配的记录:
bool TMockDBQuery::FindSatisfiedRecord()
{
bool bSatisfied = false;
for (MockFields::iterator itF = m_fields.begin(); itF !=m_fields.end(); itF++)//检查绑定的参数
{
TMockDBField tInputField = *itF;//待检查的参数
if(!IsIndex(tInputField.AsName()))//这里看上去是在匹配索引
{
continue;
}
MockFields vFields = *itCur;
for (MockFields::iterator it = vFields.begin(); it != vFields.end(); it++)//对比fields
{
TMockDBField tSaveField = *it;//record中的参数
if (tInputField == tSaveField)
{
bSatisfied = true;//这里只有一个索引值匹配时,就认为找到返回了,泪奔。。。
break;
}
else
{
bSatisfied = false;
}
}
}
return bSatisfied;
}
接下来的更新也是很神奇的。update语句里的SET VALUE = VALUE + :VALUE根本就不会去执行,只是将新记录的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++)//检查绑定的参数
{
TMockDBField tBindField = *itF;//绑定的参数
MockFields vFields =*itCur;
for (MockFields::iteratoriter = itCur->begin(); iter != itCur->end(); iter++)//对比fields
{
TMockDBFieldtRecordField = *iter;//record中的参数
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中的参数
(gdb) n
这里之前的累积量就是60,本次累积量就是240,直接覆盖。。。
导致第二个测试用例去捞数据的时候,就没捞到
再次详细分析了一下为什么没捞到,是怎么匹配的关键字
select语句的索引创建时,是在SET_PARAM时将需要匹配的条件字段放在了vector m_fields中,这个存放规则也有问题,因为如果你有多个匹配字段,不小心将最后一个需要比较的字段放到里面,而此时表里正好有一条记录的这个字段能够匹配,程序却也认为这条记录就是你需要捞取的记录。这段代码如下:
for (; itCur != m_pRecords->end();itCur++)//遍历表里每一条record
{
for (MockFields::iterator itF =m_fields.begin(); itF !=m_fields.end(); itF++)//检查绑定的参数,就是你select时where里的条件
{
TMockDBField tInputField = *itF;//待检查的参数
if(!IsIndex(tInputField.AsName()))
{
continue;
}
MockFields vFields = *itCur;
for (MockFields::iterator it =vFields.begin(); it != vFields.end(); it++)//对比fields
{
TMockDBField tSaveField =*it;//record中的参数
if (tInputField == tSaveField)
{
bFind = true;//当最后一个需要匹配的条件字段恰好跟表里当时那条记录对应字段相等时,这里就认为找到记录了
break;
}
else
{
bFind = false;
}
}
}
if (m_fields.size() == 0)
{
bFind = true;
}
if (bFind)
{
m_outputFields = *itCur;
itCur++;
return true;
}
}
这个地方我通过在设置:
原始:
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);//因为这个字段跟第一个用例里的相同,所以最后匹配这个字段时相同,就认为是捞取出来了
最终结果跑出来确是正确的:
[ 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了
还没想到能有什么好的方式去规避这个问题
想过每个用例跑完之后rollback,但是之前订户资料,资费等信息也在里面,担心rollback所有数据都没有,而且也没有提供这样的接口,因为一开始就没有commit动作
感觉mockdb只能是最简单的存放单条数据,而且不能写复杂的sql,包括where后面带and或者or,也不能在update里带计算。