TDD的测试周期

测试驱动开发的编写周期

(简单的总体把握)

应该知道,没有测试,就没有功能代码。测试驱动开发这种编程方式怎么开始呢,那就是先写一个新的测试

梗概如下:

  1. 添加一个测试;

    /**
    要明白,现在这个测试里所用到的类和方法都是不存在的。此时的测试甚至是编译无法通过!
    */
    @Test
    public void testMovieRating() {
        Movie starWars = new Movie("Star Wars");
        starWars.addRating(3);
        starWars.addRating(5);
        assertEquals("Bad average rating", 4, starWars.getAverageRating());
    }
  1. 运行所有的测试并查看失败的;

    /**
    通过运行所有的测试,我们将会收到错误的信息提示,告诉了我们现在测试中存在的问题。可想见如下:
    1.不存在Movie类
    2.不存在addRating()
    3.不存在getAverageRating()
    如何解决?很简单,按照提示,利用开发环境(编译器如IDea)的强大功能迅速创建一个Movie类、方法。
    */
    public class Movie {
        public Movie(String name) {}
        public void addRating(int newRating) {}
        public int getAverageRating() { return 0;}
    }

可以看见上面示例,所有的类和方法都是一个外壳,仅仅提供了外部行为,内部的实现都是哑实现(有着返回值的方法都应该初步返回一个简单的默认值!)。可想见,现在测试已经通过了编译,再运行试试呢?失败!提示我们:Bad average rating 期待的是4,实际的是0。 为了使测试能够通过,我们要“不折手段”了!既然想要4,那么我们这样做:

public int getAverageRating() { return 4;}

现在,重新运行所有!测试呢:通过!!接下来要做的事情就是重构。

  1. 对测试进行微小的改动;(书中原话——测试驱动开发 Kent Beck)【为什么不是对功能代码进行微小修改,怎么是对测试呢。。】
  2. 通过重构去掉重复部分。(重复部分是指测试中的数据与程序之中的数据之间的重复)

    /**
    上面的功能代码仅仅能完成那个期待4的测试。要想程序更加通用,接下来我们要泛化程序。
    目标就是:让程序能够计算后返回电影评分的平均值。于是我们需要对程序里的4这个常量做出思考:
      要想泛化,就不能是常量(在这儿来看),所以要将它替换为一个变量。
      4从何来?可以想见:Movie类里有着添加评分的方法addRating(),
      所以4=(3+5)/2,即 平均分=总评分/评分次数。于是:
    */
    public class Movie {
        private int totalRating = 0;
        private int numberOfRatings = 0;
        public Movie(String name) {}
        public void addRating(int newRating) { 
            totalRating += newRating;
            numberOfRatings++; 
            /**
             从上往下,我们已经做了很多修改,记住:本应该修改就要重新测试,不过明显从前面到这儿的修改,
    不会出错,那么就省去每添一句新的代码就重新测试这样繁杂、死板的步骤吧。
    不过现在我们已经设计到了一个方法的很多修改,我想是时候去运行一遍测试,祈祷不会出现问题吧。
            */
            //很好,,,测试可以工作!那么继续修改
        }
        public int getAverageRating() { 
            // return 4;
            return totalRating / numberOfRatings;
            // 刚才我们已经把常量替换掉了!测试是否还能工作呢?运行,,通过!
            /**
            例子结束了,现在可以选择一件事情,就是再添加一个测试,来验证这个功能代码是否真的被我们泛化了。
            同时从这可以引申出,我们是否可以考虑在 return 4 的时候
            就编写这样一个新的不同的平均分(比如测试平均分为5)的测试,然后
            为了测试能够工作,而对功能代码进行泛化呢?
            所以这就有了选择,看你自己,是否愿意在 return 4 这样一个常量的时候去
            编写一个同样目的但测试的期待结果不同的新测试来强制提醒你将功能代码泛化;
            还是说愿意在之后写一个为了检验是否泛化的新测试!
            */
        }
    }

要明白:测试驱动开发的要点不在于采取了哪些措施,而在于能够取得这些微小进步本身。(微小进步,有多微小呢?它甚至使得我们每一次的前进步伐控制在了幅度非常小,比如仅仅创建一个类的外壳、方法的外壳、字段的创建,这样微小的前进步伐)因为如能够做到很小的改进,也一定可以完成你所需要规模的改进。

从上面例子归纳出:使指示条迅速变绿的三项策略:

  1. 伪实现:也就是那些微小进步的一个体现,通过创建出外壳,直接返回测试所期待的一个常量值来让测试通过,然后逐渐用变量取代它!
  2. 使用显明实现:直接键入真正的代码实现。
  3. 三角测量:目的是使得我们从不同角度去看待问题,从而得出结果,比如为了写出判断两个对象相等的功能代码,我们还可以测试其不相等的情况!

实践中使用TDD时候,通常交替使用伪实现和显明实现:当所有事情变得顺畅时候,使用显明实现吧;一旦测试未通过,意外出现了红色指示条,那就做好备份回到伪实现!

END

2019年11月3日,11点26分

上一篇:最适合和最不适合新手使用的几款 Linux 发行版


下一篇:在浏览器中运行Keras模型,并支持GPU