zoukankan      html  css  js  c++  java
  • 打造第二代测试框架TestDriven 2.0(一)—— Assert

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

    前言 Preface

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

    本文是第二代测试框架系列文章,同时也是软件工程革命三部曲中的技术文献。

    本人对现有的测试技术统称为第一代测试框架;本人总结他们的优缺点后,提出第二代测试框架理论并实现。

    阅读本文的门槛比较高,需要您掌握一定的测试技术和理论,例如单元测试、回归测试、代码覆盖率等;同时需要掌握.net的IL,c#词法分析等。 

    本文所提出的原理都会附上源代码。

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

    开篇 Introduction

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

    第一篇文章先定位是篇口水文,让大家读起来不费力。

    测试驱动历史不算悠久,所以国内很多兄弟并没有、也根本不想系统的学习。我个人觉得几乎90%的人写代码都是一边写一边debug。通过掉断点查看问题所在。

    在testdriven.net出来之前,开发是个比较痛苦的事情,即使有了vs200x强大的调试引擎,可是每次调试都要F5运行整个项目,实在费力。于是我当时的做法就是开一个demo项目,把需要调试的代码copy过去,调好了在copy回来。后来Testdriven.net出来之后,写好代码之后,直接通过鼠标右键点击运行查看断点,基本上调试速度快了n倍。

    似乎到达这里,已经能够解决了大部分人的需求了。所以看了所谓的测试先行,心里就想:简直弱智,代码还没有写好,就写什么测试代码,简直自己找抽!

    不过今天,我已经读了大部分测试框架的源码和理论,我的观点依然是: 简直弱智,代码还没有写好,就写什么测试代码,简直自己找抽!测试是需要的, 但是测试先行不是所有开发都要遵循。准确的说,测试先行只有在极少数的情况下才有效果。

    测试驱动是国外大牛提出来的理论,他们提出的历史背景是已有一大堆的代码库基础上,开发额外的功能。换句话说,他们现在手头已经有了很多的业务逻辑代码,添加的新功能占了整个项目估计不超过10%。

    这个时候,他们对代码的运行结果、影响范围等有了充分的估计,所以使用测试先行,让设计师写好接口和测试代码,再让手下的民工完成业务逻辑。

    虽然大部分的测试驱动书籍里面开篇就说的TD好像神乎其神,用了就腿不疼了、腰不酸了。但是他们却刻意的回避了他们的历史背景,因此我们和书的作者不是在一条起跑线上。

    我们,即使是小公司(就算是大公司,例如爱立信),在开发中,经常都会修改接口、修改public 方法,甚至删除类。因为根本没有人能保证咱们一下手就是对的。 所以搞什么测试先行,结果连测试代码都要修改多次,麻烦!

    所以,我的结论就是,新项目开发,测试放在中间和最后。一旦新项目开发完成进入稳定运行期后,完善测试用例;日后的迭代使用测试驱动。

    这个才是可行的测试驱动。

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

    第二代测试框架概述 TestDriven 2.0 Introduction

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

    第二代测试框架,就是为了考虑到80%的需求提出来的。除了是nunit+mock+ncover,我还引入了代码分析、关联分析、测试代码自动更新等技术。

    先展望一下美好的未来:

    1. 我们开辟了一个新项目后,鼠标点击生成测试代码;就得到一个.testdriven文件夹,里面的测试代码自动根据您的代码之间关联排序,建立起基础到复杂的关联测试。

    2. 当我们完成第一个testcase的时候,第二个已经自动准备好了,而且完成了50%。慢慢到最后最复杂的测试代码,基本上已经自动完成了80%了。

    3. 当我们发现项目开发出来之后并不能符合实际需求,大改之后,鼠标再轻轻右键点击更新测试代码,系统又自动分析了代码的接口和上一次测试代码,自动合并更新出本次的测试代码;当然历史测试代码已经被打包归档了。

    4. 当我们的系统稳定运行了n天之后,客户提出了新的需求,我们发现原有代码不能支持,需要修改接口。我们轻轻在需要修改的方法点击查看影响范围,立刻获得受到这个方法影响的dll。当修改完毕之后,系统自动根据影响范围自动对所有相关的测试用例进行回归测试;


    这个就是我看得到的未来,也是到了目前,微软还没有提出的未来!!网络有web 2.0,体现了人人之间的沟通,那么测试驱动也有2.0,体现了不同类之间的沟通。单元测试不再是独立的,而是相互关联的。 

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

    Assert 2.0 技术

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

    有人认为我在吹,我可没有功夫花几个小时打一篇水文出来。接下来我就介绍自己如何实现这个TestDriven2.0。首先最基础的,当然是Assert技术,即所谓的断言。

    传统我会使用Console.Writeline()进行测试。因为直观,直接从控制台看到结果。正如一名测试工程师说的,写一堆代码让系统判断,还不如直接打出来,人看。所以我介绍的Assert技术结合了nunit的优点和console的优点。

    先看看效果,测试代码:

    代码
    namespace Pixysoft.TestDrivens
    {
        
    class Class1
        {
            
    public void test001()
            {
                
    new Assertion().IsEqual(123new Class2().GetValue(null3));
            }
            
    class Class2
            {
                
    public Class2()
                {
                }
                
    public double GetValue(Class2 value1, int value2)
                {
                    
    return 123;
                }
            }
        }
    }

    控制台的输出是:

    PASS    [123= [123] :: Pixysoft.TestDrivens.Class1+Class2.GetValue(Class2 value1,Int32 value2)

    一目了然,控制台清楚的告诉了我被测试的方法是什么,返回值是什么,期望值是什么,测试结果是什么。

    这个就是assert 2.0 技术。

    还没有缓过神来的兄弟再仔细对比一下和nunit的Assert.AreEqual有什么不同。

    1. 名字不同,我压根就没搞明白什么用AreEqual,几乎99%的方法都是Is开头,非让我用个Are。

    2. 我们不再需要写以下的测试代码:

    Console.Writeline("现在我们开始测试Class2.GetValue方法, 返回值是123.");

    Assert.AreEqual(123, xxxxx)

    因为需要被测试的方法已经自动被打印出来了。

    就这么简单的一步,估计让我们再次节省了50%的时间。 

    为了实现这个功能,让我忙了2天。具体的思路如下:

    1. 在assert.isEqual里面,使用了 StackTrace,获取调用堆栈StackFrame,指向调用堆栈public void test001()和方法assert.isequal的IL偏移量。

    2. 别高兴的太早,掌握IL的兄弟都知道, new Class2().GetValue(null3) 这段代码在调用堆栈是看不到了。

    因为IL里面已经运行出了结果,把结果压入了计算栈.

    所以我们只能获得 Assert.IsEqual和上一个栈是 public void test001().

    3.  这里开始,就需要打开IL,查看assert.isequal的参数入栈情况。首先我们从“1”,得知了test001()这个方法,和assert.isequal的il偏移量,然后通过GetMethodBody().GetILAsByteArray();,获取test001的IL代码片段。

    4.  翻译出assert.isequal对应的IL代码,获取入口参数个数(当然 = 2了,这不废话!)

    5. 接下来在IL代码片段中,从assert.isEqual的il偏移量开始,反向搜索出压入栈的参数,例如: OpCodes.Ldcxxx / OpCodes.Ldarg / OpCodes.Newarr之类的。这里非常需要时间,由于我实在没有功夫把整本IL看完,只能自己写出一系列的测试代码去测可能的情况,到目前,还有部分情况无法覆盖到。

    6. 只要发现压入的参数符合要求,就返回,例如是OpCodes.Call,我就知道这个参数是某个方法的返回值。

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

    后续

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

    大功告成!assert 2.0的核心原理就介绍到这里了。要实现TestDriven 2.0,是需要一点技术和时间的,希望有兴趣的兄弟多留言,给点思路。下一篇,我将解剖 NUnit的核心机制,然后扩展他的代码,成为UnitTest 2.0.

    下篇再见。 

  • 相关阅读:
    innodb-mvcc
    5.7-mysql不同隔离级别下执行sql的上锁情况-building
    shardingsphere自定义分分片
    shardingsphere自定义分布式主键如何配置
    线程池源码ThreadPoolExecutor分析
    一些知识的总结
    账户余额的批量入账与扣账实现
    jstack
    Java——总结
    Java——重写
  • 原文地址:https://www.cnblogs.com/zc22/p/1674489.html
Copyright © 2011-2022 走看看