在看了足够多的鼓吹和宣传之后, 我试图开始尝试单元测试, 看看它是否真的是那么的必需, 真的那么好, 真的是可行的吗? 真的是对实实在在的项目有用的吗? 能度量出来它对项目的贡献, 对个人代码质量提供的促进作用吗?
现在我多数时间用.NET/C#
比起C/C++来说, .NET/C#由于框架的设计和metadata, reflection等机制的存在, 天然的更容易做单元测试, 也更容易产生大量的辅助工作, 比如, 由于编译后的代码中已经没有类型信息, 就无法象nunit那样在正式编译好的(包括release版本)的assembly中发现通过 Attribute标注的方法。
不知道什么原因, 对于言过其实的东西, 我在尝试时往往第一时间碰到它的缺点/短处, 就拿单元测试来说, 我老早就接触过nunit, 但是, 仅是接触而已, 没有深究, 照样子写过几个单元测试, 能运行, 能让绿灯都亮, 但是, 这能给我带来什么, 仅仅是用单元测试本身, 而没有深究下去做到什么程度, 决不会神奇地就提高你的代码质量, 我根本没有体会到所谓对自己的代码有了极大的信心的感觉。
有那么几次, 把单元测试放下, 跟着编码时长期慢慢养成的惯性, 尽量警觉小心地写代码, 按老路走, 老路当然有老问题, 程序免不了bug, 每次查找那些麻烦的bug时会让你想起这样那样的方法学, 这样那样的工具。再一次捡起单元测试, 试几下, 总是摸不着要领, 要怎么执行才算真正的单元测试, 才能真正领受它的好处呢? 我始终找不到这种感觉。
在《单元测试之道》中定义的好的单元测试的必备特征中,其中一条是彻底的,仅这一点我就觉得做不到。不知道该写多少单元测试才算够得上彻底,才会让自己内心神奇地生成那种叫做信心的东西。
另一个问题是,尝试写单元测试时,速度慢了下来,时间放在了琢磨工具方法本身上面,尽管鼓吹单元测试的人一再说,你此时所花的时间会被未来的debug时间所补偿, 不光补偿,你还会大大赚上一票。 但是, 未来还没有来,光是这样说并不能抵消心里的那种隐隐的念头:这么折腾对路么。
为一个类做单元测试,往往需要对这些类解依赖,在我最近一次又试图接近单元测试的时候,我强烈地相信这是单元测试难做的最重要原因,甚至我都不说之一。因为这个问题,mock技术是单元测试的必备品,而非可有的附属之物。
在网上有很多blog讨论mock框架的,我奇怪他们无一例外地展示那些看似非常顺理成章的“成功”的例子。在考虑到typemock 需要收费之后,我锁定了Rhino mock想了解一下,我手头的项目是.NET/C#写的主UI程序,后面有一个C/C++写的native DLL, export出来一大堆函数供.NET 来pinvoke, 由于要用到第三方的组件(也是我们公司以前的产品,我们直接把assembly和那些native DLL拿过来做组件重用),所以把这些第三方组件放到一个单独的目录中,而这个第三方组件内部,又有了下面一些假设:
1. 假设exe 主执行程序所在路径下必需有固定名字的DLL, 其路径下又有固定名字的目录名
2. 并在上述目录名中以 GetFiles("*.dll")这样的方式查找 plugin的DLL
首先, 我发现nunit的工作方式是把你的DLL 复制到临时目录中去运行,这样, 你代码中如果有
Application.StartupPath 这样的访问, 你得到的是nunit 的位置, 而不是你的主执行程序的, 毕竟这个进程是nunit, 你的单元测试只是它的一个动态加载的模块来运行。
然后, 在试图mock 一个静态函数时碰到了钉子,一查文档,rhino mock明确说: 不能mock静态函数,更让我跌破眼镜的是,还不能mock 非virtual的那些普通方法,mock框架实现不容易,但是,mock缺少上述两个功能,无论其它方面做的再好,也可以认为没用。
幸运的是,typemock 可以做到上述rhino mock缺乏的东西,由于它的工作原理与大多数mock框架不同,所以具有一些多数mock 框架不具备的能力。它是通过CLR profiler在运行时改变可执行代码的行为。
不管是typemock还是 rhino mock, 观察它们所做的神奇的事情的确让人觉得不可思议。最终typemock帮我搞定了静态方法的mock问题,也就是让它返回一个假的 Application StartupPath. 说话时, 这是.NET代码。 但是, 测试时我还是得到了异常,挂上调试器找到原因是native 代码中也对程序相对路径/文件名做了假设,在路径中查找 *.dll这样的模式, nunit只在需要加载某个assembly时把它copy出一份临时文件来Load, 它不会也不可能预见你的代码要对某个目录进行这种访问从而把那些*.dll文件都复制过去。 .net mock在碰到这样的问题时, 也只能认了。
最后我发现,要运行我的一个单元测试实例,我已经花上了一天的时间来折腾这些工具/框架,查找失败的原因,看文档,了解这些工具所能做的事,猜测哪些是它真正能做的,哪些只是广告而已,判断哪些人是在blog中记录真正他们所做过的,做过以后真正对他们有用的,哪些是copy别人网页上的内容而已。我最终还是成功运行了那个一上来就让我踢到铁板的测试函数,但是却是通过曲意满足整套系统无理要求实现的:它要在某个目录下访问哪些文件, 我就事先把那些文件给copy过去,这算哪门子事? 把我的被测试程序下某个子目录下的程序, 复制到相对于nunit可执行程序的目录下去, 只因为它是这个进程的主执行程序。
也许是我对工具过于挑剔,跟计算机打交道这么长时间,真正让我觉得好用的东西屈指可数:
bash, vim, photoshop, resharper, reflector, C# ... 再列也能再凑一些,但已经不是第一时间出现在我脑子里了。
写到这里,我仍然不知道单元测试本身在我们这个写代码的圈子里的真正位置是什么,倒是在 看到了一些跟我的怀疑不谋而合的结论: 单元测试仍然不是主流, 单元测试仍然是很困难的, 单元测试是否必要也还在被争论。 Don knuth 甚至在被采访时说过: 我所知道的几乎任何关于极限编程的东西都是错误的,只有一点例外,就是互相阅读/学习源代码。 牛人有牛人的逻辑,适用于他的往往不适用于一般人, 我只想找到单元测试能在普通开发人员如我者工作中的应有位置。
阅读(906) | 评论(0) | 转发(0) |