红灯-绿灯-重构

news/2024/10/5 17:43:46 标签: 重构, java, 单元测试
  1. 代码在周期内的状态:处于红灯状态时,代码不管用,处于绿灯状态时,一切都想预期的那样工作,但并不一定是最佳的,到了重构阶段,我们知道测试很好的覆盖了各项功能,可以充满信息地修改他,让他变得更好

  2. 编写一个测试:

    1. 每次添加新功能时,都要首先编写一个测试,当前处于红灯状态,因为测试执行时以失败告终,即测试对代码的期望和代码实际的功能之间存在差距,更具体的说,没有代码满足最后一个测试的期望,因为我们还没有编写这样的代码,在这个阶段,可能所有的测试都通过了,但这表示存在问题
  3. 运行所有测试并确认最后一个未通过:

    1. 确认最后一个测试未通过后,就能断定他不会在没有引入新代码的情况下错误通过,如果这个测试通过了,就意味着要么相关功能早就存在,要么测试本身存在误报的问题,如果测试无论怎么实现都能通过,就意味着他毫无价值,应该删除。最后一个测试不仅必须未通过,还必须是预期原因导致的,在这个阶段,我们依然处于红灯状态,运行测试,但最后一个未通过
  4. 编写实现代码

    1. 这个阶段的目标是编写代码使最后一个测试通过。不要试图让代码完美无缺,也不要为编写花过多时间。即便编写的不好或者不是最后的,也没有关系,后面还有改进的机会。我们的真实意图是打造一个由测试构成的安全网,并确认这些测试都能通过。不要试图引入最后一个测试未描述的功能。要想引入新功能,必须回到第一步,先编写新测试。然而,仅当所有既有测试都通过后,我们才能这么做。在这个阶段,我们依然处于红灯状态。虽然已编写的代码可能让所有测试都通过,但这种假设还未得到证实。
  5. 运行所有测试

    1. 应运行所有测试,而不是只运行最后编写的那个测试,这至关重要。你刚编写的代码可能让最后一个测试得以通过,但同时破坏了其他功能。通过运行所有测试,不仅可确认最后一个测试的实现是正确的,还可确认它没有破坏整个应用程序的完整性。如果整个测试集执行速度缓慢,就昭示着测试编写得不好或者代码耦合度太高。耦合度太高将导致难以隔离外部依赖,进而增加执行测试所需的时间。在这个阶段,我们处于绿灯状态:所有测试都通过,且应用程序的行为符合预期
  6. 重构

    1. 前面所有步骤都是必不可少的,但这一步是可选的。虽然很少在每个周期结束后都进行重构,但迟早需要甚至必须这样做。并非每个测试的实现都需要重构;没有明确的规定说什么时候该重构、什么时候不用重构。一旦认为可以更佳或更优的方式重写代码,那就是重构的最佳时机。
    2. 什么样的代码需要重构呢?这个问题不好回答,因为重构的原因有很多:代码难以理解、代码位置不合理、代码重复、名称没有清晰阐述意图、方法太长、类的功能太多等——这个清单可不断列下去。不管原因是什么,最重要的规则是重构不能改变任何既有功能。
  7. 重复

    1. 所有步骤都完成后(其中重构是可选的),再重复它们。编写110行代码后就切换到下一步,因此整个周期的持续时间为几秒几分钟。如果更长,就说明测试的范围太大,应将其分成多个更小的测试。一定要快速前进,快速失败并更正,然后再重复。
  8. 案例:3*3的棋盘下棋

    1. 实现

      java">public class TicTacToeSpec { 
       @Rule 
       public ExpectedException exception = 
       ExpectedException.none(); 
       private TicTacToe ticTacToe; 
       @Before 
       public final void before() { 
       ticTacToe = new TicTacToe(); 
       } 
       @Test 
       public void whenXOutsideBoardThenRuntimeException() 
       { 
       exception.expect(RuntimeException.class); 
       ticTacToe.play(5, 2); 
       } 
      }
      
      1. expected属性,你可以用它来指定一个Throwble类型,如果方法调用中抛出了这个异常,这条测试用例就算通过了

      2. 这个测试中,我们指出调用方法ticTacToe.play(5,2)时,期望的结果是引发RuntimeException异常,这个测试只需创建方法play,并确保他在参数x小于1或者大于3时引发RuntimeException异常

      3. 应该测试三次,第一次运行时,他应该不通过,因为此时还没有方法play,添加这个方法后,测试也应该不通过,因为他没有引发异常RuntimeException,第三次运行时应该通过,因为他实现了与这个测试相关联的所有代码

    2. 测试

      1. 验证Y轴

      2. java">@Test 
         public void whenYOutsideBoardThenRuntimeException() { 
         exception.expect(RuntimeException.class); 
         ticTacToe.play(2, 5); 
         }
        
    3. 实现

      1. java">public void play(int x, int y) { 
         if (x < 1 || x > 3) { 
         throw 
         new RuntimeException("X is outside board"); 
         } else if (y < 1 || y > 3) { 
         throw 
         new RuntimeException("X is outside board"); 
         } 
         }
        
      2. 为让最后一个测试通过,添加一条“检查参数Y是否在棋盘内”的else子句。

    4. 测试

      1. 确定棋子在棋盘边界内后,还需确保它放在未被别的棋子占据的地方

      2. java">@Test 
         public void whenOccupiedThenRuntimeException() { 
         ticTacToe.play(2, 1); 
         exception.expect(RuntimeException.class); 
         ticTacToe.play(2, 1); 
         }
        
    5. 实现

      1. 为实现最后一个测试,应将既有棋子的位置存储在一个数组中。每当玩家放置新棋子时,都应确认棋子放在未占用的位置,否则引发异常

      2. java">private Character[][] board = {{'\0', '\0', '\0'}, 
         {'\0', '\0', \0'}, {'\0', '\0', '\0'}}; 
         public void play(int x, int y) { 
         if (x < 1 || x > 3) { 
         throw 
         new RuntimeException("X is outside board"); 
         } else if (y < 1 || y > 3) { 
         throw 
         new RuntimeException("Y is outside board"); 
         } 
         if (board[x - 1][y - 1] != '\0') { 
         throw 
         new RuntimeException("Box is occupied"); 
         } else { 
         board[x - 1][y - 1] = 'X'; 
         } 
         }
        
    6. 重构

      1. 重构play方法

      2. java">public void play(int x, int y) { 
         checkAxis(x); 
         checkAxis(y); 
         setBox(x, y); 
         } 
         private void checkAxis(int axis) { 
         if (axis < 1 || axis > 3) { 
         throw 
         new RuntimeException("X is outside board"); 
         } 
         } 
         private void setBox(int x, int y) { 
         if (board[x - 1][y - 1] != '\0') { 
         throw 
         new RuntimeException("Box is occupied"); 
         } else { 
         board[x - 1][y - 1] = 'X'; 
         } 
         }
        
  9. 编写一个测试,发现他不能通过,编写实现代码,发现所有测试都通过,只要有机会就重构代码使其变得更好,并重复这个过程


http://www.niftyadmin.cn/n/5691104.html

相关文章

Telnet、SSH、RDP和VNC

Telnet、SSH、RDP和VNC都是远程访问和管理的协议或工具&#xff0c;它们各自具有不同的特点和适用场景。 一、基本概念与用途 Telnet 定义&#xff1a;一种基于命令行界面的远程管理协议&#xff0c;允许用户通过网络远程访问和管理计算机。用途&#xff1a;主要用于远程登录和…

PostgreSQL 创建账号与数据库:从连接到权限配置的完整指南

PostgreSQL 创建账号与数据库&#xff1a;从连接到权限配置的完整指南 文章目录 PostgreSQL 创建账号与数据库&#xff1a;从连接到权限配置的完整指南一 使用 psql 连接数据库二 创建数据库和账号三 退出当前连接四 用新的账号登录连接五 运行 SQL 文件六 App 连接 本文详细讲…

Python办公自动化教程(005):Word添加段落

文章目录 2.1 Python-docx介绍2.2 安装2.3 实例 2.1 Python-docx介绍 python-docx 是一个用于创建和修改 Microsoft Word 文档&#xff08;.docx 格式&#xff09;的 Python 库。它可以方便地生成和处理 Word 文档&#xff0c;而无需直接与 Microsoft Word 程序交互。 2.2 安…

算法闭关修炼百题计划(二)

为了减轻复习压力&#xff0c;一篇blog只会写十题左右 1.重排链表2.轮转数组3.除自身以外数组的乘积4.字母异位词分组5.搜索二维矩阵II6.矩阵置零7.岛屿数量 1.重排链表 class Solution { public://找中间节点ListNode* midNode(ListNode* head){ListNode* slow head, *fast …

智能手表(Smart Watch)项目

文章目录 前言一、智能手表&#xff08;Smart Watch&#xff09;简介二、系统组成三、软件框架四、IAP_F411 App4.1 MDK工程结构4.2 设计思路 五、Smart Watch App5.1 MDK工程结构5.2 片上外设5.3 板载驱动BSP5.4 硬件访问机制-HWDataAccess5.4.1 LVGL仿真和MDK工程的互相移植5…

vue3+vite@4+ts+elementplus创建项目详解

1、第一步创建项目cnpm init vite4 2、设置vue3.2局域网可访问配置&#xff1a; 找到项目路径下的package.json目录下找到script对象下面添加一下代码&#xff1a; "serve": "vite --host 0.0.0.0" 启动项目命令不在是dev而是&#xff1a;cnpm run serve 3…

物理学基础精解【54】

文章目录 鞍点概述鞍点的定义鞍点的数学原理与推导鞍点的性质鞍点的例子鞍点的例题 鞍点公式鞍点在数学、物理学、经济学、机器学习等多个领域都有广泛的应用场景数学领域物理学领域经济学领域机器学习领域其他领域 鞍点与导数之间存在密切的关系一阶导数&#xff08;梯度&…

IO操作同步、异步、阻塞、非阻塞之间的联系

本质来说&#xff0c;同步&#xff0c;异步&#xff0c;阻塞、非阻塞没有什么必要的关系。同步可以是阻塞的&#xff0c;也可以是非阻塞的&#xff1b;异步也可以是非阻塞的&#xff0c;&#xff08;也可以是阻塞的, 异步IO其实本质上是非阻塞的&#xff0c;但在特殊情况下&…