--------------------
前言 Preface
--------------------
本文介绍了一种新颖的测试思路,并制作了原型系统展示其效果。
此技术将作为测试驱动框架2.0的一个部分(Testdriven 2.0) 。
而测试驱动2.0的目的是:让代码之间沟通,让变化更加容易。
--------------------
测试分类 与 本文的讨论对象 Catalog
--------------------
测试包含了很多种,每一种需要特定的技术去解决,例如:
1. Winform类的界面测试:通常使用钩子等Win32接口去捕捉用户的操作,然后模拟回放进行测试(此技术不在讨论范围)
2. Web界面测试:一般使用JS嵌入测试页面,同样模拟用户操作;或者使用IE内核等调用内部函数实现用户操作(此技术不讨论)
3. Web的Http模拟测试:在.net 2.0比较难实现,到了3.0之后的版本,微软对HttpContext这个庞然大物体做了重构,因此让测试变得稍微简单了。(此技术不讨论)
4. 类库、代码测试:这个是我需要讨论的终点,包含了各种框架、逻辑应用等。现有的技术主要是UNit / Testdriven.net / Mock等。他们也貌似很好的解决了一些问题。但是。。。
所以,本文接下来将针对第4种测试 (类库、代码测试),也是最常见的测试进行讨论。
---------------------
现有的问题 Problem
---------------------
现在的测试,很大一部份是在回归。开发者梦想编写好自动测试代码,日后如果有变动,通过做回归,就知道是否破坏了之前的功能、是否产生了bug。朝着这个目标,诞生了很多测试工具。可是在我看来,他们只是从一个小坑跳到了另外一个大坑。(也就是我之前说的,掉入自己挖的坑里了)
首先,测试的结果是否准确,完全取决于测试数据是否全面准确。 那么编写测试数据本身就存在了人为的bug。
其次,测试代码和业务代码紧密联系,一旦业务代码修改,测试代码往往是全盘否定的。这就出现一个矛盾:如果测试代码写的马虎,日后基本上不能用;如果测试代码写的精细,一旦修改起来,之前的工作都白费了。
所以,我们真正期望的是:能够自动分析业务代码,自动编写测试代码,而不是人去写。这个目标现在还很难实现(微软在VS2010里面已经大量引入了Code Gen等技术,可是。。)
可是难,不等于不行,下面我将尝试迈出一小步。
---------------------
原理分析 Analysis
---------------------
要让测试变得自动,首先需要抽象出测试过程,然后逐一攻破。经过我分析,一段测试代码主要包含了三个方面:
1. 测试数据生成。
直接决定了测试代码是否有效;因此要求全面、准确、也业务逻辑精密绑定。 这部分工作目前是没有更好的思路。
2. 调用对应方法。
这个很简单,就是调用一个方法,传入测试数据。没有优化的必要。
3. 查看测试结果是否符合预期。
这部分以往是写这非常无聊的Assert.IsEqual等。既无聊,又浪费时间。而恰恰这部分我发现了提升的空间。
测试结果无非就是字符串、对象等。就是c#的ValueType / class。 而如果是对象,也一定是对象的某些属性(Property) 。而这些数据都是可以被序列化、反序列化的!
因此 这个过程完全可以被机器代替,从而让测试代码变得更加灵活,立马减少50%的工作量!现在我就展示一下目前的原型系统效果。
---------------------
原型系统效果 ProtoType
---------------------
首先是一段测试代码。
{
object pojo = CreatePojo();
Assert.SaveOrVerify( " test000 create pojo " , pojo);
}
这段代码的目的是 测试生成的pojo对象是否符合预期。 而一个Pojo对象可能是下面一个接口的实例:
{
byte [] Image { get ; set ;}
string Name { get ; set ;}
double Fee { get ; set ;}
int Age { get ; set ;}
IinterfaceWithAllCollection[] pojos { get ; set ;}
List < IinterfaceWithAllCollection > pojos2 { get ; set ;}
ObjectWithValue objPojo { get ; set ;}
ObjectWithValue[] objPojos { get ; set ;}
List < ObjectWithValue > objPojos2 { get ; set ;}
}
按照以往的做法,一定是要展开这个对象,获取每一个属性,然后做Assert。现在我仅仅需要一句话就完成了测试结果验证:
Assert.SaveOrVerify("test000 create pojo", pojo);
不知道您感受到了其中的魅力和惊喜没有?
调用了这句话, 框架首先会搜索保存在磁盘的结果数据,一般是xml文件;这些数据本质上就是预期结果的序列化值。然后逐一和这些值做对比。
那么您一定好奇,这些期望的值从哪里来?答案就是,代码本身生成。当第一次执行的时候,是没有预期值的,那么框架会给出警告,然后将当前数据持久到磁盘。例如:
WARNING: expected value do not existed. create new one for test000 create pojo
Verifying IinterfaceWithAllCollection.Image.
TRUE: expected =% 01 , actual =% 01
Verifying IinterfaceWithAllCollection.Name.
TRUE: expected = hello, actual = hello
Verifying IinterfaceWithAllCollection.Fee.
。。。。。 省略
这个时候,我调用一个配置界面,就可以查看第一次生成的值,然后修改成为预期值:
这样一个序列化结构就可以通过 树形结构 展示出来。 现在我们只要查看第一次运行的结果是否正确,调整ExpectedValue。 这样这段测试代码就可以永远被重复调用了。
----------------------
小结 Summary
----------------------
因为是原型系统,目前还不能发出第一个release,不过很快就可以完成了。主要是我写的代码和屎一样烂,需要进行优化才好意思拿出来。
等release出来时候,所有代码都会采取开源的策略。不过是一种新的开源策略。
如果各位有急着需要的,咱们可以相互讨论交流下。希望大家多提供些思路,多提供您期望的需求。谢谢!