打造第二代测试框架TestDriven 2.0(七)—— 让测试驱动更加的自动吧!

--------------------

前言 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 

---------------------  

首先是一段测试代码。

        public void test00001()
        {
            
object pojo = CreatePojo();

            Assert.SaveOrVerify(
"test000 create pojo", pojo);
        }

 

这段代码的目的是 测试生成的pojo对象是否符合预期。 而一个Pojo对象可能是下面一个接口的实例:

代码
    interface IinterfaceWithAllCollection
    {
        
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文件;这些数据本质上就是预期结果的序列化值。然后逐一和这些值做对比。

 

那么您一定好奇,这些期望的值从哪里来?答案就是,代码本身生成。当第一次执行的时候,是没有预期值的,那么框架会给出警告,然后将当前数据持久到磁盘。例如:

代码
------ Test started: Assembly: Pixysoft.Framework.Configurations.dll ------

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出来时候,所有代码都会采取开源的策略。不过是一种新的开源策略。

 

如果各位有急着需要的,咱们可以相互讨论交流下。希望大家多提供些思路,多提供您期望的需求。谢谢! 

posted @ 2010-06-08 08:34    阅读(1946)  评论(15编辑  收藏  举报
IT民工