ABAP单元测试最佳实践

 

  本文包含了我在开发项目中经历过的实用的ABAP单元测试指导方针。我把它们安排成为问答的风格,欢迎任何人添加更多的Q&A's,以完成这个列表。
html

 

  • 在个人项目中,只使用传统的ABAP report。因此很不幸我不能使用ABAP单元测试了,是吗?
    有个好消息:不管你正在使用哪种ABAP代码对象进行开发,均可以经过添加单元测试使得它更加稳定和更易于扩展。对于reports,模块池(module pools)和函数组(function groups),能够经过添加手写本地类的方式添加单元测试。假设一个简单的情形,在一个report中你想要测试子程序xyz的最直接调用,下面的代码骨架就能够作到,这段代码能够定义为代码模板,以便于插入到report。
    class lcl_test definition for testing  "#AU Duration Short
      inheriting from cl_aunit_assert. "#AU Risk_Level Harmless
     private section.
        methods test_xyz_simple_call for testing.
    endclass.
     
    class lcl_test implementation.
      method test_xyz_simple_call.
    * Setup parameters for the call...
    * Perform the call
     perform xyz using ...
    * Check returned values
        assert_equals( act = ... exp = ... ).
      endmethod.
    endclass.

    固然,使用ABAP面向对象有不少好处,好比,会有ABAP类的单元测试模板的自动生成功能。一样地,生产代码和测试代码的分界会更清晰。测试类声生成在一个用于单元测试的“包含(incluede)”部分,会同其它内容隔离。若是出于某些缘由不须要使用这些类,你依然拥有单元测试支持。java

 

  • 不幸的是,个人客户的开发系统中的主数据质量太差了,以致于我用不了单元测试。
    又有好的消息:尽管客户的开发系统有着糟糕的数据质量,你仍是能够作单元测试!单元测试的最大优点之一,就是能够独立地测试单一的代码单元——测试不依赖任何数据库条目,不依赖其它中途调用的函数模块。若是测试在500客户端没问题,那它在000客户端一样能够运行得很好。

  

  • 不是真的想让我为全部开发过的代码对象写单元测试吧?

    不,并无。为某些代码写单元测试会致使时间的浪费。它们是:数据库

    1. 自动生成的代码,好比视图维护函数组,静态系统信息报表,BSP扩展基础类,以及其它类似的东西。
    2. 大多数数据库查询。在多数状况下,数据库查询不该该在单元测试内执行(关于该点请看下文)。有一些例外,好比DAOs(Data Access Object)。这些是单个数据库的专家(?)。在某些特殊状况下,为了测试功能性而建立测试条目(而且在teardown阶段移除)是行得通的。
    3. 链接dynpro和abap代码的代码。有一些须要重定向dynpro的胶水代码,就像PAI(Process After Input)中一个特定的对ABAP代码块的链式请求所起的做用那样。一般是不值得花费努力为这样的胶水代码进行单元测试的。

 

  • 某个类作的事情是不重要的,不值得为它作测试。
    也许你是对的,但一般,你错了。只是你认为你的代码不重要,由于你只是完成了它的编写。经验代表,一年后,先前不重要的代码,看起来不再会对你不重要了。你的同事也一样不会认为它不重要。若是你只是实现了一个适配器类,将一个数据格式映射为另外一个,接着调用一个API,也许你是对的:对这样的类进行单元测试也许是过分工程。可是随着源代码体积的上升,看起来不重要的代码也许包含某些bug,这种bug只有在被调用的时候才会显现。为何不实现一个能够自动检查指望结果的调用呢?这样作能够保证该类在任什么时候候都工做正常。

 

  • 单元测试须要同测试驱动开发(TDD)共同进行吗?
    基本上,TDD是一种意为“首先实现测试,以后添加可使得测试经过的生产代码”的编程实践。这是一个“乒乓球”过程,你将老是在新的测试代码和新的生产代码间转向。你不须要实践TDD,可是若是你习惯了它,会在很大程度上帮助你避免bug,并所以变得更有效率。即便不使用测试驱动,你依然会受益于单元测试:能够向已存在的代码对象添加过后比较检验(post-hoc test)。 

 

  • 单元的外部测试怎么样?使用单独的测试对象。
    能够在一个类的属性标签中指定其做为单元测试类。可是这应当用于经过继承来提取几个类似的本地单元测试类的测试代码的状况,而不该用在单个单元的测试上面。一般,在外部测试一个单元是不建议的作法,由于这使得单元测试在工做台菜单路径中的“模块测试”里成为不可用的状态。若是你的类被某人改动了,他也许没有意识到代码应当经过外部程序的测试。所以更好的作法是把它包含在生产代码所在的同一个对象当中。

 

  • 若是我不测试全部的代码,测试覆盖中会出现断层!
    虽然单元测试是个颇有用的工具,但并不能回应全部的需求。我在上面已经提到,这种断层不建议使用单元测试处理,而是应该使用被称为集成测试的其它技术进行覆盖,好比eCATT, QTP或者其它。

 

  • 我该怎样设计单元测试?

    要点在于:应该将它们设计的尽量简单。单元测试一样起着单元功能文档的做用。一样地,若是执行修改后,单元测试失败,会很容易从代码中看出哪一个功能失败了。尝试避免测试方法中的多余代码。将重复代码包装到方法中甚至宏之中,以保证在测试下功能的实质更加可读。直率地命名变量、方法、类和宏,使得代码在测试时尽量的具备表达力。单元的每一个特性,都须要能够按照如下三步测试:编程

    1. 创建测试数据——填充接口参数的内表或属性,以及/或者
    2. 调用测试方法——一般正好是对公共方法的调用。
    3. 检测方法输出的异常。
      这三步应当被包含在一个测试方法中。在每一个测试方法附近,一般是测试对象构建的地方,会有一个创建步骤(对于类的每个方法都是相同的),若是有须要的话,会提供桩。一样的,每一个测试方法的调用后伴随着一个teardown调用。

 

  • 我怎样识别本身的方法其实在被单元测试调用,而不是真的用户?我想在这种情形下作点不一样的事情。
    别这样!不要将生产代码和测试代码混在一块儿,若是想要为了测试而消除生产代码中的一部分,应当使用桩和依赖注入来代替。可是,在生产代码使用一个“测试模式”的标识,会破坏单元测试的概念,而且致使的代码变得更糟糕。

 

  • 我要怎样组织本身的代码?
    没有用于组织单元测试的通行方案。有时让每一个方法有一个单元测试类、每一个输入数据的等价类有一个测试方法是好的作法。但这不是通常的规则。通常来讲,测试方法在正交时会变得有用:理想状况下,每一个方法测试测试一个不依赖其它存在的单一功能。不要让测试方法负担过多的断言。

 

  • 如何测试一个将数据库查询和它本身的业务逻辑混合到一块儿而且调用了其它函数模块的程序(方法/函数模块)?
    The redefined helper classes like lcl_api_test and lcl_db_test is what the test people call stubs.首先让代码成为可测试的,例如使用桩:将数据库查询和函数模块调用包装到本地帮助类中(数据库方面我使用LCL_DB,调用其它代码单元方面我使用LCL_API ),提取这些代码到本身的方法里。为这些方法使用具备表达性的名字,使用适配器模式为它们设计一个良好的接口。以后你的LCL_API和LCL_DB将只包含外部函数模块调用和数据库操做(select, insert, update, enqueue, ...),也许会有几行映射代码,用于将你设计的好的接口映射到你调用的模块的传统接口。
    在你的对象中应有像go_api和godb这样的全局的帮助类实例可用。在测试方法中重定义其方法,控制他们的行为。像lcl_api_test和lcl_db_test这样的重定义过的帮助类就是测试人员所说的的“桩”。

 

  • 听起来是复杂的。
    你说得对,它不是直接的。为了保持测试代码简单可理解,你应当尝试在任什么时候候尽量避免桩的使用。能够经过在业务逻辑、API调用和数据库操做之间提供更好的分隔,来避免桩。例如,不在相同的方法里面查询数据、对数据执行检查。能够首先查询数据,接着将数据条目做为导入参数在本身的方法里进行检查。经过这种方法让代码变得可测试,一般——做为反作用——会提升其可读性。

 

  • 我应该测试受保护方法或者私有方法么?
    一般不用。一般,你会关注一个类的公共界面。私有属性或方法也许会在重构期间消失,或者被其它组件代替。若是它对公共方法调用没有任何影响,就算删除它,也许都是安全的;若是它对公共方法调用有影响,那就测试公共方法——保持将来重构的自由。若是测试私有方法,接着,想要改变这些组件的时候,就不得不改变它们的单元测试,这致使代码的可变性不好。

 

  • 好的——可是,我在某种特别的情况下(blabla...)真的很须要测试私有方法和受保护的方法。我要怎样提供这个?
    由于,像任何其它类同样,本地类是独立于它们的包含工做台类的,你须要声明本地测试类为包含类的友元。若是zcl_testee是包含类,lcl_test是单元测试类,须要在本地测试类中添加以下代码:
    class lcl_test definition deferred.
    class zcl_testee definition local friends lcl_test.
    ...
    class lcl_test definition for testing ...
    ...

 

  • 个人单元测试包含语法错误,可是它对生产类没影响,由于单元测试只在开发系统中进行。对吗?
    (warning) 不是的。单元测试不能够在生产系统中执行,可是类中的单元部分里面的语法错误会破坏完整的类,致使访问类的任何属性或方法时会出现SYNTAX_ERROR的short dump。

 

  • 个人测试对象是一个单例。为了不反作用,我想至少对每一个方法的测试得建立一个新的实例。
    若是你的单例包含全局数据,它们也许会被测试改变,在测试调用之间生成丑陋的依赖。你能够在测试期间经过属性“create public”建立一个对象的子类,按照以下方法进行。
    若是你只是须要类行为的这种改变,你甚至不须要子类的“class...implementation”部分。
    class lcl_testee definition inheriting from zcl_someclass create public.
    endclass.
    ...
    class lcl_test implementation.
      method setup.
        create object go_testee type lcl_testee.
      endmethod.
    endclass.

    记着,不管如何,问题不会由单元测试而是全局数据引发。单元测试只是发现问题,而不是致使问题。所以最佳的解决方式是排除类中的全局数据。api

 

  • 我要怎样作能让个人测试代码变得更加可读?
    • 不管在任什么时候候,尽量地使用隐式的“函数”表示法进行方法调用,特别是像assert( ), assert_initial(), assert_subrc()等等这种调用。
    • 若是你不须要测试类的继承层次(为何须要?),你也许会让测试类继承自cl_aunit_assert。能够像这样写:

      assert_subrc( sy-subrc ).
      而不是

      call method cl_aunit_assert=>assert_subrc
        exporting
          act = sy-subrc.
      • 若是调用是复杂的(好比含有不少参数),使用宏填充内表和调用测试方法。省去调用自身的重复代码,也省去了用于填充内表的本地变量好比工做区。咱们使用一种宏包含子程序池的结合来填充内表,减小了用于工做区的辅助本地变量的须要。
        若是你须要一个例子:这里是一个用于解析器的测试方法,能够将指定的包装规则转换为自由文本并将其放入内表中,内表中包含以预约义格式存在的相关信息。创建自由文本、调用解析器方法、检查结果内表的特定组件,这三种行为,在约20个不一样方法中是重复的,只有自由文本的内容和修改的内表中的预期结果会改变。
        宏_assert_n_fields_in_row检查指定内表的指定行的指定的组件含有指定的值!
    • method test_2_lief_2_pal.
       
      * Test assignment of deliveries to handling units
       
          _set_code:
            `1. Palette  (                 `,
            `  1. Lieferung, 1. Pos, 50%   `,
            `  )                           `,
            `2. Palette  (                 `,
            `  2. Lieferung, 2. Pos, Rest  `,
            ` )                            `.
       
          _call_parser.
       
          _assert_rows 'Pack data' gt_packdata_template 2.
       
          _assert_n_fields_in_row 'Pack data' gt_packdata_template 'exidv;vepos;vbeln;posnr;vemng;vemeh;unvel' :
             1 'E1;1;1;1;50;!%;',
             2 'E2;1;2;2;REST;;'.
       
        endmethod.
      用这种方式提取重复的代码,减小上面提到过的用于“创建——测试调用——校验”三个步骤的方法,以明确受测试的功能。
    • 读一些好书,好比Martin Fowler的《重构》,或者Robert C. Martin的《代码整洁之道》以获取更多关于代码如何变得更加可读的思想。

 

  • 使用宏对调试不利吗?
     视状况而定,若是只是使用宏来“去掉噪音”,好比,用来提取老是同样而且频繁使用到的代码序列,那么在调试器里面使用F6跳过它的执行就不是问题。若是你有一个隐藏了像上面例子中的_call_parser同样的方法调用的宏,你可使用F5进入该方法,即便调用隐藏在宏里面。此外,在这种情形下,你只是失去了代码中无趣的部分。

 

  • 在一个做业中周期性地运行单元测试是有意义的吗?
    一般,单元测试和新代码的开发相关联。与集成测试相反,在夜间做业运行它们并不让人意外,由于结果只在代码改变的时候改变,所以代码的最后修改者应该知道结果——若是他测试了他的单元!若是你的团队中有不使用单元测试的开发者,或者代码的最后一个修改者仅仅是忘记了运行单元测试,有个做业来通知失败,会很不错(好比经过发送邮件给TADIR的拥有者)。你可使用代码检查器(code inspector)运行单元测试。别忘记在单元测试类定义中的有关风险等级的伪代码注释和期间,由于,不然的话,代码检查其也许会不执行测试:
    class lcl_test definition for testing  "#AU Duration Short
      inheriting from cl_aunit_assert. "#AU Risk_Level Harmless
     ...



  • 在传输请求将要发布的时候检查单元测试是可行的吗?
    能够,并且我认为它颇有用。最简单的达成方式是打开传输发布的代码检查器检查,并在检查变量中选择“单元测试”。
    在咱们的实践中,我选择了一个更 复杂的方式,使用传输组织器的BAdI和一个函数模块调用单元测试。虽然这个功能没有获得SAP的保障(短文本中包含危险修订“for SAP only”),它仍是看起来工做的至关好。咱们从两年前开始使用它,到如今也没出问题。方法cl_aunit_prog_info=> contain_programs_testcode( )也许能够用于找出特定的程序(根据指定主程序的报表源的名字)是否包含单元测试。若是仅仅是程序、类或者函数模块的一部分改变了,你也许不得不找出LIMU的父对象。为实现这点,可使用函数模块TR_CHECK_TYPE。

 

本文连接:http://www.cnblogs.com/hhelibeb/p/6038202.html数组

原文连接:ABAP Unit Best Practices 安全

 

2018.04.22更新:现有一个Open SAP的Writing Testable Code for ABAP 视频教程,推荐观看less