• 《单元测试的艺术》读书笔记----测试代码的最佳实践


    • 测试层次和组织

            1、在自动化每日构建中运行单元测试和集成测试,如使用持续集成工具自动化构建;

            2、基于速度和类型布局测试:

            根据运行测试所花费的时间很容易就能区分集成测试和单元测试,把集成和单元测试分开放置,放在不同的目录,指定单元测试和集成测试运行的频率。

            3、确保测试时源代码管理的一部分,共同放在版本管理器进行管理。

            4、将测试类映射到被测试代码

            创建测试类时,应该怎样组织和放置它们呢?我们希望可以找到一个项目的所有相关测试,一个类的所有相关测试,一个方法的所有相关测试。我们可以采用以下方式:

            (1)测试类和被测试类放到同一个项目内;

            (2)测试类和被测试类尽量保持相同或相似的包层次;

            (3)针对同一个被测试方法的多个测试方法命名,可以采用如userLoginTest_Success,userLoginTest_Fail等。

           5、构建测试API,如使用测试类继承模式,创建测试工具类等;

    • 优秀单元测试的支柱

            1、编写可靠的测试

          (1)决定何时删除或修改测试

            单元测试何时会执行失败?

            产品缺陷,不必修改测试,只需修复产品缺陷;

            测试缺陷,需要修复测试;

            产品语义或API变更,使用方式改变了,需要修改测试;

            重命名含义不清的测试,重构不可读的测试。

            删除重复测试。

            (2)避免测试中的逻辑

            如果单元测试包含了switch、if、else、foreach、for、while等语句就说明你的测试里包含了不应有的逻辑。

            如果需要复杂大型测试,如多线程测试,你应该在标明为集成测试的包里编写这种测试。

            如下测试代码也包含了不应有的逻辑,无意中重复了产品代码的逻辑user + greeting,        

    1 public void addString(){
    2         String user = "USER";
    3         String greeting = "GREETING";
    4         String actual = MessageBuilder.Build(user, greeting);
    5 
    6         assertEqual(user + greeting, actual);
    7 }

            改成如下代码就消除了引入逻辑:

    1 public void addString(){
    2         String user = "USER";
    3         String greeting = "GREETING";
    4         String actual = MessageBuilder.Build(user, greeting);
    5 
    6         assertEqual("USER GREETING", actual);
    7 }

            (3)只测试一个关注点

            一个测试方法里保持只有一个断言,我们就更容易诊断出了什么问题。

            (4)把单元测试和集成测试分开

            单元测试很容易运行,集成测试很可能失败,如果不够稳定,开发人员就会跳过所有测试,无法发挥单元测试的作用。

            (5)用代码审查确保代码覆盖率

            如果没有做代码审查,代码覆盖率统计的结论没有说服力。因为开发人员可能在测试方法里不写一个断言,测试总能通过。

            代码审核有助于提升团队的技术水平,还可以创造出可读、高质量、能够持续使用多年的代码,并使你充满自信。

            2、编写可维护的测试

            (1)测试私有或受保护的方法

            使方法成为公共方法;

            把方法抽取到新类;

            使方法成为静态方法。

            (2)去除重复代码

            抽取辅助方法去除重复代码。

            使用@Before或者@After去除重复代码;

            (3)已可维护的方法使用@Before

            局限性:

            @Before方法只用于需要进行初始化工作时;

            @Before方法应该只包含适用于当前测试类中所有测试的代码,否则这个方法会更难以阅读和理解。

            尽量不用@Before方法,而封装辅助初始化方法,每个测试方法手动调用。这样增加代码可读性。

            (4)实施测试隔离

            定义:一个测试应该总是能独立运行,不依赖于任何其他测试。

            测试隔离的臭味道:

            强制的测试顺序:测试需要特定的顺序执行,或者来自其他测试结果的信息;

            隐藏的测试调用:测试调用其他测试;

            共享状态损坏:测试共享内存里的状态,却没有回滚状态;

            外部共享状态损坏:集成测试共享资源,却没有回滚资源;

            (5)避免对不同关注点多次断言       

    1 @Test
    2 public void CheckVariousUsmResult(){
    3         assertEqual(3, sum(1001, 1, 2));
    4         assertEqual(3, sum(1, 1001, 2));
    5         assertEqual(3, sum(1, 2, 1001));
    6 }

            以上单元测试使用了三个简单的断言,进行了三个不同的子功能测试,希望能节省一些时间。这样做法有什么问题呢?如果断言失败,会抛出异常,后续的断言将得不到执行,即后续的功能得不到测试。但这种情况下,即便一个断言失败了,你还是会希望知道其他的断言结果。

            你可以才起别的方式实现这个测试:

            给每个断言创建一个单独的测试;

            使用参数化测试(.Net支持,Java目前好像不支持);

            把断言放在一个try-catch块中。

            (6)对一个对象的多个状态的比较时,有两种方式:

            方法一、多断言方式        

     1 @Test
     2 public void compare(){
     3         String userName = "zhangf";
     4         String realName = "张飞";
     5         String id = "1001";
     6         User user = new User(id, userName, realName);
     7  
     8         assertEqual(id, user.getId());
     9         assertEqual(userName, user.getUserName());
    10         assertEqual(realName, user.getRealName());
    11 }

             方法二、单个断言方式,toString()比较

    1 @Test
    2 public void compare(){
    3         String userName = "zhangf";
    4         String realName = "张飞";
    5         String id = "1001";
    6         User user = new User(id, userName, realName);
    7           assertEqual("id:"+id+",userName:"+userName+",realName:"+realName, user.toString());
    8 }

             第一种方式让人看起来以为对多个功能做测试,可读性差,第二种方式可读性强。推荐第二种方式。

            (7)避免过度指定

            过度指定是对被测试单元如何实现其内部行为进行了假设,而不只是检查其最终行为的正确性。

            主要有以下几种情况:

            测试对一个被测试对象的春内部状态进行了断言;

            测试使用了多个模拟对象;

            测试在需要存根时使用模拟对象;

            测试在不必要的情况下指定顺序或使用了精确匹配。如对返回的字符串进行精确匹配断言,而实际只需对字符串的一部分做断言就可以了,我们可以不适用String.equal(),而使用String.contains()。

            3、编写可读的单元测试

            (1)单元测试命名

            测试方法名包括三部分:被测试方法名,测试场景,预期行为。

            如测试用户登录,场景是多次登陆后要求使用验证码,预期行为是密码错误而失败,可命名为void userLogin_requirePictureNum_fail(){...}

            (2)变量命名

            合理的命名变量,可以确保阅读测试的人容易理解你要验证什么。因为单元测试不仅起到测试的作用,还是作为API的一种文档。

            不好的命名如魔法数字,assertEqual(-100, result),无法看出-100是什么意义,将-100赋值给一个富含表达性命名的变量,如" COULD_NOT_READ_FILE = -100;",然后用变量做equal,则更容易理解断言的目的。

            (3)有意义的断言

              尽量不要编写自己的定制断言信息,如果必须编写,请命名清楚明白。

            (4)断言和操作分离

             反例:

             assertEqual(COULD_NOT_READ_FILE, log.GetLineCount("aaa.txt"))   

             正例:

            int result = log.GetLineCount("aaa.txt"); 

            assertEqual(COULD_NOT_READ_FILE, result);

            (5)@Before和@After

             这两个方式经常被滥用,以至于方法完全不可读。

            一种滥用的情况:在@Before中准备存根和模拟对象,导致阅读测试的人意识不到测试中使用了模拟对象,也不知道对象的预期值是什么。

            如果由测试方法自己直接设置初始化模拟对象,设置所有的预期值,测试可读性会更好。

            要点:测试要随着被测试系统一同成长和变化。

  • 相关阅读:
    二叉树的最大距离
    MS CRM 2011 RibbonExport Utility下载以及实用说明
    MS CRM 2011中的解决方案——使用
    MS CRM 2011的自定义与开发(5)——关系编辑器
    MS CRM 2011 RC中的新特性(5)——定期约会
    MS CRM2011中的事件脚本——入门
    MS CRM 2011 汇总更新 3
    MS CRM 4中模拟PartyList字段的方法
    MS CRM 2011的自定义与开发(4)——属性编辑器
    MS CRM 2011中的解决方案Solution_简介
  • 原文地址:https://www.cnblogs.com/markcd/p/9006189.html
Copyright © 2020-2023  润新知