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

  • 在我的项目中,只使用传统的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)”部分,会同其它内容隔离。如果出于某些原因不需要使用这些类,你依然拥有单元测试支持。

  • 不幸的是,我的客户的开发系统中的主数据质量太差了,以至于我用不了单元测试。
    又有好的消息:尽管客户的开发系统有着糟糕的数据质量,你还是可以做单元测试!单元测试的最大优势之一,就是可以独立地测试单一的代码单元——测试不依赖任何数据库条目,不依赖其它中途调用的函数模块。如果测试在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 ...
    ...
  • 我的单元测试包含语法错误,但是它对生产类没影响,因为单元测试只在开发系统中进行。对吗?
     不是的。单元测试不可以在生产系统中执行,但是类中的单元部分里面的语法错误会破坏完整的类,导致访问类的任何属性或方法时会出现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.

    记着,无论如何,问题不会由单元测试而是全局数据引起。单元测试只是发现问题,而不是导致问题。因此最佳的解决方式是排除类中的全局数据。

  • 我要怎样做能让我的测试代码变得更加可读?

    • 无论在任何时候,尽可能地使用隐式的“函数”表示法进行方法调用,特别是像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:
      `. Palette ( `,
      ` . Lieferung, . Pos, % `,
      ` ) `,
      `. Palette ( `,
      ` . Lieferung, . Pos, Rest `,
      ` ) `. _call_parser. _assert_rows 'Pack data' gt_packdata_template . _assert_n_fields_in_row 'Pack data' gt_packdata_template 'exidv;vepos;vbeln;posnr;vemng;vemeh;unvel' :
      'E1;1;1;1;50;!%;',
      '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视频教程,推荐观看

ABAP单元测试最佳实践的更多相关文章

  1. 【转】.NET(C#):浅谈程序集清单资源和RESX资源 关于单元测试的思考--Asp.Net Core单元测试最佳实践 封装自己的dapper lambda扩展-设计篇 编写自己的dapper lambda扩展-使用篇 正确理解CAP定理 Quartz.NET的使用(附源码) 整理自己的.net工具库 GC的前世与今生 Visual Studio Package 插件开发之自动生

    [转].NET(C#):浅谈程序集清单资源和RESX资源   目录 程序集清单资源 RESX资源文件 使用ResourceReader和ResourceSet解析二进制资源文件 使用ResourceM ...

  2. 关于单元测试的思考--Asp.Net Core单元测试最佳实践

    在我们码字过程中,单元测试是必不可少的.但在从业过程中,很多开发者却对单元测试望而却步.有些时候并不是不想写,而是常常会碰到下面这些问题,让开发者放下了码字的脚步: 这个类初始数据太麻烦,你看:new ...

  3. 一些通过SAP ABAP代码审查得出的ABAP编程最佳实践

    1. 这两个IF ELSE分支里检测的条件其实逻辑上来说都是同一类,应该合并到一个IF分支里进行检查: It is an expensive operation to open a file in a ...

  4. Junit内部解密之四: Junit单元测试最佳实践

    我们做使用Junit工具来做单页测试或接口测试时,需要注意一些问题,包括我们的编码规范,test规范,以及编写测试代码的策略,以下个人的总结. 1.为还没有实现的测试代码抛出一个异常.这就避免了该测试 ...

  5. Atitit.log日志技术的最佳实践attilax总结

    Atitit.log日志技术的最佳实践attilax总结 1. 日志的意义与作用1 1.1. 日志系统是一种不可或缺的单元测试,跟踪调试工具1 2. 俩种实现[1]日志系统作为一种服务进程存在 [2] ...

  6. 基于AWS的云服务架构最佳实践

    ZZ from: http://blog.csdn.net/wireless_com/article/details/43305701 近年来,对于打造高度可扩展的应用程序,软件架构师们挖掘了若干相关 ...

  7. atitit. 日志系统的原则and设计and最佳实践(1)-----原理理论总结.

    atitit. 日志系统的原则and设计and最佳实践总结. 1. 日志系统是一种不可或缺的单元测试,跟踪调试工具 1 2. 日志系统框架通常应当包括如下基本特性 1 1. 所输出的日志拥有自己的分类 ...

  8. [转]Android开发最佳实践

    ——欢迎转载,请注明出处 http://blog.csdn.net/asce1885 ,未经本人同意请勿用于商业用途,谢谢—— 原文链接:https://github.com/futurice/and ...

  9. NET中异常处理的最佳实践

    NET中异常处理的最佳实践 本文翻译自CodeProject上的一篇文章,原文地址. 目录 介绍 做最坏的打算 提前检查 不要信任外部数据 可信任的设备:摄像头.鼠标以及键盘 “写操作”同样可能失效 ...

随机推荐

  1. Microsoft Loves Linux

    微软新任CEO纳德拉提出的“Microsoft Loves Linux”,并且微软宣布.NET框架的开源,近期Microsoft不但宣布了Linux平台的SQL Server,还宣布了Microsof ...

  2. Summary of Critical and Exploitable iOS Vulnerabilities in 2016

    Summary of Critical and Exploitable iOS Vulnerabilities in 2016 Author:Min (Spark) Zheng, Cererdlong ...

  3. 《JavaScript设计模式 张》整理

    最近在研读另外一本关于设计模式的书<JavaScript设计模式>,这本书中描述了更多的设计模式. 一.创建型设计模式 包括简单工厂.工厂方法.抽象工厂.建造者.原型和单例模式. 1)简单 ...

  4. 【JS基础】循环

    for 循环的语法: for (语句 1; 语句 2; 语句 3) { 被执行的代码块 } 语句 1 在循环(代码块)开始前执行 语句 2 定义运行循环(代码块)的条件 语句 3 在循环(代码块)已被 ...

  5. NSStringCompareOptions

    typedefNS_OPTIONS(NSUInteger, NSStringCompareOptions) { NSCaseInsensitiveSearch = 1,    //不区分大小写比较 N ...

  6. Atitit.项目修改补丁打包工具 使用说明

    Atitit.项目修改补丁打包工具 使用说明 1.1. 打包工具已经在群里面.打包工具.bat1 1.2. 使用方法:放在项目主目录下,执行即可1 1.3. 打包工具的原理以及要打包的项目列表1 1. ...

  7. 一条Sql语句分组排序并且限制显示的数据条数

    如果我想得到这样一个结果集:分组排序,并且每组限定记录集的数量,用一条SQL语句能办到吗? 比如说,我想找出学生期末考试中,每科的前3名,并按成绩排序,只用一条SQL语句,该怎么写? 表[TScore ...

  8. 在Ubuntu下搭建Spark群集

    在前一篇文章中,我们已经搭建好了Hadoop的群集,接下来,我们就是需要基于这个Hadoop群集,搭建Spark的群集.由于前面已经做了大量的工作,所以接下来搭建Spark会简单很多. 首先打开三个虚 ...

  9. python select网络编程详细介绍

    刚看了反应堆模式的原理,特意复习了socket编程,本文主要介绍python的基本socket使用和select使用,主要用于了解socket通信过程 一.socket模块 socket - Low- ...

  10. D3.js学习(六)

    上节我们学习了如何绘制多条曲线, 以及给不同的曲线指定不同的坐标系.在这节当中,我们会对坐标轴标签相关的处理进行学习.首先,我们来想一个问题, 如何我们的x轴上的各个标签的距离比较近,但是标签名又比较 ...