红灯-绿灯-重构

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

  2. 编写一个测试:

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

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

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

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

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

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

    1. 实现

      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. @Test 
         public void whenYOutsideBoardThenRuntimeException() { 
         exception.expect(RuntimeException.class); 
         ticTacToe.play(2, 5); 
         }
        
    3. 实现

      1. 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. @Test 
         public void whenOccupiedThenRuntimeException() { 
         ticTacToe.play(2, 1); 
         exception.expect(RuntimeException.class); 
         ticTacToe.play(2, 1); 
         }
        
    5. 实现

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

      2. 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. 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. 编写一个测试,发现他不能通过,编写实现代码,发现所有测试都通过,只要有机会就重构代码使其变得更好,并重复这个过程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值