[解锁新姿式] 兄dei 我感受你在写bug

前言:

继上篇 [解锁新姿式] 兄dei,你代码须要优化了 介绍一些代码的优化的小技巧java

可是咱们除了在代码编写上须要优雅, 还须要编写对应的测试用例, 以此来保证代码的质量。git

在这篇咱们继续在学习如何编写有保证质量的代码。github

背景

在刚刚学习编程的时候,因为没有接触过单元测试/TDD 相关知识, 只是知道有这么回事,不觉得然。致使工做的时候,拿到一个新需求,只知道埋头苦写。会出现如下场景:编程

产品:增长一个新功能 blalala.
我:好的没问题,一个星期撸出来
一个月后.......
我:个人接口写好了!能够测试了。
测试:你本身测过没 ??
我:放心 postman 测过了,稳的一批。(/滑稽)
一分钟后.......
测试:你的接口又双叕 报 500 了 (黑人问号??) 我:不可能!确定是你的问题api

一次偶然的机会,接触到 TDD 开发模式,今后打开新的世界。原来编程还能够这么玩 : )框架

TDD

TDD是测试驱动开发(Test-Driven Development)的英文简称, 是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码以前,先编写单元测试用例代码,测试代码肯定须要编写什么产品代码。post

说白了,就是开发功能代码以前,先编写测试用例代码`单元测试

咱们先来看一个“经典”的 TDD 问题,入手学习 TDD 开发,感觉 TDD 开发的魅力所在学习

需求:

编写一个程序,打印出从1到100的数字,将其中3的倍数替换成“Fizz”,5的倍数替换成“Buzz”。既能被3整除、又能被5整除的数则替换成“FizzBuzz”。以下图所示 测试

分析

在动手写代码以前,咱们要首先弄清需求的范围优先级,例如给出的FizzBuzz 需求,1-100就是范围,对应优先级是1,肯定了需求的范围和优先级后,而后咱们就能够开始coding~

编码

Step1

咱们首先新建一个FizzBuzz测试类

public class FizzBuzzTest {

    @Test
    void show_raw_number(){
        FizzBuzz fizzbuzz = new FizzBuzz(1);
        assertThat(fizzbuzz.getResult()).isEqualTo("1");
    }
}

public class FizzBuzz {

    public FizzBuzz(int input) {
    }

    public String getResult() {
        return "";
    }
}
复制代码

舒适提示:

  • 测试类推荐使用 驼峰式命名,测试方法 推荐使用下划线命名
  • 这里我使用的是 AssertJ 单元测试框架,这款框架主要亮点之一是支持链式调用。
  • assertThat方法是org.assertj.core.api.Assertions.assertThat静态方法.
  • assertThat(A).isEqualTo(B),意思是 断言(预测) A 等于 B,若是 A == B的话,返回 True 表示经过测试用例,不然 False,表示测试失败,不经过。
  • 更多的使用方法能够参考AssertJ 官方网站

咱们先建立一个 FizzBuzz 类, 建立 getResult()方法,空实现, 传入参数 1,断言(预期)fizzbuzz 输出结果是 1,先运行测试用例,会出现以下效果

小伙伴:你会不会写啊,这么简单的还用测试?还报错!!
别着急,测试驱动开发,讲究是的按部就班的节奏
听个人,往下看 相信你会 getfeel :)

看到红灯 程序提示,咱们预期结果(Expected)是 “1”, 但实际结果(Actual)是0,说明咱们的程序有错。
接下来咱们进一步来修改咱们的测试用例,以此来经过测试用例。 修改以下:

public String getResult() {
    return "1";
}
复制代码

而后再次运行咱们的测试用例

舒适提示:
编写刚恰好经过测试用例的代码。

此次“完美”经过测试!而后咱们开始编写下一个测试用例~

完美?? 黑人问号?? 拿刀哪一个,先把刀放下 你听我说。

Step2

咱们来测试 第一种状况,将其中3的倍数替换成“Fizz”,编写 3 的测试用例

@Test
public void show_fizz(){
    FizzBuzz fizzBuzz = new FizzBuzz(3);
    assertThat(fizzBuzz.getResult()).isEqualTo("Fizz");
}
复制代码

建立一个show_fizz 方法, 此次输入参数为 3,而后继续运行测试用例

看到咱们熟悉的 “红灯报错”,而后我能够继续修改咱们的 getResult 方法 以此来经过测试用例。

public String getResult() {
    if (input % 3 == 0){
        return "Fizz";
    }
    return String.valueOf(input);
}
复制代码

而后再次运行测试用例~

绿色!!经过测试!

看到这里,咱们能够发现一个规律

红灯行,绿灯停
当测试用例是“红灯” 时,咱们就应该动手编写出,能经过测试的测试用例。
当测试用例是 “绿灯”时,咱们就应该停下来思考,下一个测试用例改如何编写。

Step3

咱们继续小步前进,继续编写第二种状况,5的倍数替换成“Buzz”, 编写 入参 5 的测试用例

@Test
public void show_buzz(){
    FizzBuzz fizzBuzz = new FizzBuzz(5);
    assertThat(fizzBuzz.getResult()).isEqualTo("Buzz");
}
复制代码

不出意外的话,是 红灯

咱们继续修改getResult()方法,以此来经过测试用例。

public String getResult() {
    if (input % 3 == 0){
        return "Fizz";
    }

    // 新增
    if (input % 5 == 0){
        return "Buzz";
    }

    return String.valueOf(input);
}
复制代码

而后继续运行测试用例,绿灯经过~

Step4

咱们继续小步前进,考虑第三种状况,既能被3整除、又能被5整除的数则替换成“FizzBuzz”,编写 入参 15 的测试用例

@Test
public void show_fizz_buzz(){
    FizzBuzz fizzBuzz = new FizzBuzz(15);
    assertThat(fizzBuzz.getResult()).isEqualTo("FizzBuzz");
}
复制代码

这里相信你们能猜到结果,红灯,这里我就演示结果了。而后咱们修改对应 getResult 方法,经过测试用例

public String getResult() {
    if (input % 15 == 0){
        return "FizzBuzz";
    }

    if (input % 3 == 0){
        return "Fizz";
    }

    if (input % 5 == 0){
        return "Buzz";
    }

    return String.valueOf(input);
}
复制代码

重构

Step1

咱们编写完测试用例,可是代码出现了代码的坏味道——重复代码,有了测试用例,咱们就能够很轻松的重构代码,接下来开始重构咱们的代码。

public String getResult() {
    if (isDivisibleBy(15)){
        return "FizzBuzz";
    }

    if (isDivisibleBy(3)){
        return "Fizz";
    }

    if (isDivisibleBy(5)){
        return "Buzz";
    }

    return String.valueOf(input);
}

private boolean isDivisibleBy(int i) {
    return input % i == 0;
}
复制代码

抽取一个 isDivisibleBy 方法,而后运行测试,看看修改后是否引入bug

测试经过,没问题,但在这里咱们须要注意一点

每一次修改,都需运行一遍测试,避免修改引入bug,确保代码的正确性

Step2

测试经过后,咱们继续进一步优化。

public String getResult() {
    String result = "";
    if (isDivisibleBy(3)) {
        result += "Fizz";
    }

    if (isDivisibleBy(5)) {
        result += "Buzz";
    }

    return result;
}
复制代码

继续修改 getResult 方法,提取一个变量 result 做为返回值,而后运行测试。

出乎意料,此次修改居然出现bug,正如刚刚所说,每次修改后,都须要运行测试用例保证代码的正确性。咱们再来检查一下代码,并做出以下修改:

public String getResult() {
    String result = "";
    if (isDivisibleBy(3)) {
        result += "Fizz";
    } 
        
    if (isDivisibleBy(5)) {
        result += "Buzz";
    } 
        
    if (result.isEmpty()){
        result += input + "";
    }

    return result;
}
复制代码

经过测试用例,发现当输入为1的时候,没有进行处理,添加相应判断。再次运行测试用例

测试经过,大功告成!

最后

最后剩下遍历 1~100 状况,相信难不倒你,感兴趣的朋友,能够自行编写~

至此,我尽可能演示一个相对完整的TDD开发流程,麻雀虽小,五脏俱全。但愿你能感觉到 getfeel ~

咱们再来总结一下:

  • 一、建立测试类
    • 名推荐使用 驼峰式命名,测试方法 推荐使用下划线命名
  • 二、分析需求范围优先级
  • 三、根据优先级编写写测试用例
  • 四、再编写业务代码
  • 五、而后编写测试用例恰好经过的代码
    • 红灯行,绿灯停
      • 当测试用例是“红灯” 时,咱们就应该动手编写出,能经过测试的测试用例。
      • 当测试用例是 “绿灯”时,咱们就应该停下来思考,下一个测试用例改如何编写。
  • 五、根据测试用例,重构,优化代码。
    • 每一次修改,都需运行一遍测试,避免修改引入bug,确保代码的正确性
  • 六、最终完成功能

第5点,不分前后,能够交替执行。

最后的最后

有些人以为为何要弄那么复杂,还要浪费时间写单元测试,为什么不一把梭呢?
没有测试用例覆盖的代码,你敢保证代码的质量吗?
没有测试用例覆盖的代码,你敢进行代码重构吗? 你品你细品 (滑稽)

其实TDD 开发模式,主要看开发者意愿,不强求,可是单元测试 必须的!

以上就是所有内容,但愿能帮助到你~ 若有不妥,欢迎指出,你们一块儿交流学习。

相关文章
相关标签/搜索