等不来白富美,何不妨亲自去追,也论QA职责

QA的工作看起来好像是每天都在等dev的bug fix,等build,等每天的daily build,跑跑smoke,跑跑automation,执行几组测试用例,再或者搞搞探索性测试。

有没有想过从某一天build突然就嗝屁了安装不上去了,daily smoke飞掉了,这时我们紧张滴提一个bug,兴冲冲满怀希望dev能给出最快的fix,毕竟这是P0的case啊,按理,不对,按照规定,要在12小时内搞定,可是事情就是这麽不给力,bug看起来似乎很难解,12小时过去了,一天,两天,四个工作日了,这个问题还摆在那里,

看起来遥遥无望,按照概率学统计学根本解释不通嘛,难道所有操蛋的事情看来只能依墨菲定律解释?

看dev们忙的焦头烂额无比憔悴胡子拉碴,我们却在这里外表光鲜地喝白开水等build,内心却也同样苦逼:日了,C姨喔可是要求20号交付给客户的,这他妈的肯定要delay了! 这时的QA不仅苦逼,还得担心另一种很坏的可能发生那就是无路如何,军令如山,交付日期不能变。这意味着我们要减少至少5天的测试时间,却还得保证产品质量(待会还会提到这一点),而刚刚的弹出新闻窗口正在播报某某山公司的一位工程师又因操劳过度而离开人间。。

如果是你在负责这个产品的测试,面对P0的case迟迟不解你会怎麽做?还要继续等你梦中的白富美嘛?

下面就是我的一次历险:

第一天:今天build安装不上去,错误信息为某核心组件安装失败。应对:赶紧提bug,notify相关dev——当日无解

第二天:仍然是同样的问题,这次我增加了几个测试环境,在几个老的以前是正常环境中发现问题依然存在,看来该问题不是因为新环境新系统的原因导致的,基本可以确定是dev自身checkin代码导致的,dev开始反省最近几天checkin的所有changelist,逐一排查,我不知道这种方法是不是有点笨,但是满怀希望。——当日无解,dev们仍在排查

第三天:因为问题还在,dev提了一个work round的版本,可以安装到底,只不过跳过了核心组件出错的地方,这样我们可以把产品的其他部分和需要的文件都装进去,都助于调试问题。dev发现这是一个COM调用的错误,但是为什麽会出错不得而知,而那个COM组件的代码并没有最近的code change,只不过升级了一下.NET版本从2.0到4.0而已,按理说不应该有影响的啊——当日仍然无解,我等得有点急了,我开始push dev manager,why?why?why?wait,wait,wait.

第四天:下班前后,问题依旧,有点想骂娘(理解万岁),这麽干等不是个办法,活马当死马医,我决定自个儿下厨,看看啥回事。于是加个班,和dev通力合作,研究错误日志,和相关模块的代码,基本理清了出错的点相关的几个模块的依赖关系:

首先是下面这张图,显示了错误日志指向的点,就是安装程序在创建一个COM对象的时候,蹦出错误Class Not Registered.

image

这个错误信息看起来像是说COM组件没有注册成功,通过check注册表信息,我们发现COM的注册信息的确写在注册表里了啦,怎麽会会说没有注册呢,难道是注册的方式不对?这时dev想起来曾经改过注册COM的那一部分脚本,我们的COM是.NET编译的,之前是2.0,现在升级到4.0,而相应的注册COM的regasm也换到了4.0目录下的regAsm,而4.0目录下有两个版本的regasm,一个是for 32,还有一个for 64,dev当时觉得我们的产品环境现在只支持64位,所以就用了64位版本的regasm。那麽,难道真是这个问题导致的?至少在COM组件这一个模块的代码工程里,唯一改动的就是这一个地方了,这至少给了我们一点点救命稻草似的希望。于是我们开始猜测是不是due to 32/64位的问题,这时通过google 搜索那条错误日志信息,发现的确有人因为用32位的程序调用64位的COM而产生同样的错误代码。

可是疑惑依然没有解开,排列组合,做了几个环境的实验,我们首先排查出我们安装程序是64位的,然后发现那个COM是注册到LOCAL_MACHINE\CLASS下,也就意味着调用for 64位的regasm的确会把COM注册到正确的位置了,我又试了一下for 32的regasm会把COM注册到wowo64for32node下,应该如此。这也就意味着我们的情况和google到的不一样,我们是64 call 64呀,难道是?

这是脑海一丝灵光闪现,莫非是我们的COM是for x86 target编译的?而错误的注册成了64位的COM?检查code,很快这一思路被否定,我们的COM工程是target for any cpu,所以在64位的机器上必然是64位的版本。

搞来搞去,离夜深越来越近,我们不犯困,却有点更加迷惑了,不make sense呀!

 

那笨人有笨招,细化问题吧!

 

实验一:

我把COM组件的工程单独领出来,修改工程的属性,启用编译后自动注册开关,编译成功,看注册表应该注册成功;在创建一个新的工程来调用COM组件:

   1:                  Type comType = Type.GetTypeFromProgID("COM ProgID");
   2:                  object comObj = System.Activator.CreateInstance(comType);
   3:                  Type[] t = comType.GetInterfaces();
   4:   
   5:                  object x=comType.InvokeMember("MethodName", BindingFlags.InvokeMethod, null, comObj, new object[3] { null,null,null});
   6:                  MethodInfo[] method = comType.GetMethods();
   7:                  if (method != null)
   8:                  {
   9:   
  10:                  }

 

执行程序,在第2出错,和之前的安装程序的错误日制一致。(COM异常就不说了,不影响理解问题)

 

这个实验只能在此证明问题还没有解决,不在那里,就在这里转动眼睛

 

紧接着:

实验二:

我把COM组件中的具体实现代码全部注释掉,如果函数需要返回值,就仅仅返回一个fake的值,这样我的COM近似等于一个空壳,不依赖于任何第三方,编译,注册成功,在用上面的代码创建COM对象的实例。

实验结果:成功!

 

这是一大进步,通过实验二可以大胆确定问题出在COM对象的实现代码里,那究竟是哪一块代码呢,很明显,应该是类的构造函数里,这意味着COM对象在构造的时候抛出未捕获异常,导致最终显示给我们的那段错误日志信息!

 

 

这时候,再去review构造函数,发现构造函数是有try catch的,只不过。。只不过。。构造函数的第一行没有包含在try catch block里,这一行只不过是打印了一条调试日志而已啊,真不敢相信这就是元凶,这行代码like:

utilDebug.Log(.....)

我相信你不是无辜的。

 

现在,再来看看这个utilDebug到底是啥玩意:

image

原来这个utilDebug是托管程序集,它内里又通过pinvoke的方式调用了另一个本地dll里函数,也叫utilDebug,而所有的日志实现都在native dll中。

这时,我突然想来之前dev的一次关于这个utilDebug的checkin,增加了一个函数,我赶紧去看changelist history,我看到了如下的改动:

   1:          [DllImport("utilDebug.dll", CharSet=CharSet.Unicode), SuppressUnmanagedCodeSecurity]
   2:          private static extern void DebugLogEx(System.Int32 iCategory, System.String szFilename, System.Int32 iLine, System.String sLog);
   3:   
   4:          private static extern bool IsDebugEnabled();

 

加红色下划线部分就是新加的部分,直到此时我已经恍然大悟,多麽低级的一个错误啊,IsDebugEnabled的实现显然来自native dll,你想要extern之,那就加一个DllImport的attribute吧!

现在基本可以断点,最初的错误就是因为这个低级错误而导致的,不信我们看看,这个托管的程序集utiDebug的确能编译通过,但是当你试图通过代码调用它的时候,别说调用了,就是定义一个该模块类的变量,不用初始化,都会抛出异常,异常信息告诉我们IsDebugEnabled方法找不到实现。

于是赶紧修改代码,改动如下:

   1:          [DllImport("utilDebug.dll", CharSet=CharSet.Unicode), SuppressUnmanagedCodeSecurity]
   2:          private static extern void DebugLogEx(System.Int32 iCategory, System.String szFilename, System.Int32 iLine, System.String sLog);
   3:   
   4:          [DllImport("utilDebug.dll", CharSet = CharSet.Unicode), SuppressUnmanagedCodeSecurity]
   5:          private static extern bool IsDebugEnabled();

 

提交build,

现在满心欢喜等新build,然后去开个会先(此时已经是第二天中午),没想到的是,会后回来,被dev告知还是安装出错,近乎同一个错误信息,这不是惊天霹雳,因为至少我们已经修复了一个问题,我们已经再逼近真相,我打开native的c++工程,自习看看问题是不是出在这里(显而易见的推理逻辑),很快,就发现,依旧是一个很低级的问题,当然应该算是一个knowledge不足的问题,我发现之前被托管代码调用的函数都会用extern C{}包起来,而新加的函数IsDebugEnabled却没有在这里,我把这一发现告诉了dev,dev有点不解,这很难理解,其实不难,stackoverflow一下,道理在明显不过:

http://stackoverflow.com/questions/8534917/export-dll-method-from-c-to-c-why-i-need-extern-c

 

再次修复新问题,

终于得到一个可以安装的build,那麽这个地方问题都解决了麽,我表示深深担忧,因为IsDebugEnabled的实现又调用了一个第三方的模块来返回属性值,而我担忧的就是:

这个调用自测过了嘛?

 

很明显,这一次dev新加的函数没有任何自测就信心满满checkin,而作为QA,我也没有在第一时间发现代码的低级错误,而我们的开发流程似乎也有点问题,只有dev lead做code review,代码checkin出build才进行测试,一开始的测试发现问题又怀疑错了方向,作为QA的我没有在第一时间与dev通力合作,而等了几天之后才加入进去,每一方都有责任,都需要吸取教训,改进!

 

但是我依然觉得这其中最紧要的问题是这篇文章里提到的一点:

文章:论程序员应具备的职业素质 http://www.cppblog.com/mzty/archive/2006/09/05/12041.html

哪一点呢:就是这一点: 6:测试习惯

作为一些商业化正规化的开发而言,专职的测试工程师是不可少的,但是并不是说有了专职的测试工程师程序员就可以不进行自测;软件研发作为一项工程而言,一个很重要的特点就是问题发现的越早,解决的代价就越低,程序员在每段代码,每个子模块完成后进行认真的测试,就可以尽量将一些潜在的问题最早的发现和解决,这样对整体系统建设的效率和可靠性就有了最大的保证。

自测,哪怕是最简单的测试都能发现并避免这样低等却无比严重而一旦出现问题却比较难调试的情况。

 

你说呢?

posted @ 2012-09-05 11:31  Dance With Automation  Views(237)  Comments(0Edit  收藏  举报