TDD的实践demo

TDD三定律

1、在编写不能通过的单元测试前,不可编写生产代码。
2、只可编写刚好无法通过的单元测试,不能编译也算不通过。
3、只可编写刚好足以通过当前失败测试的生产代码。

TDD的关键步骤

  • 添加一个小的测试
  • 运行所有测试并且失败
  • 做一点修改
  • 运行所有测试并且成功
  • 重构以消除重复

基于这个思想,上codewars找一道题实践一把tdd。

ATM machines allow 4 or 6 digit PIN codes and PIN codes cannot contain anything but exactly 4 digits or exactly 6 digits.
If the function is passed a valid PIN string, return true, else return false.
eg:
Solution.validatePin("1234") === true
Solution.validatePin("12345") === false
Solution.validatePin("a234") === false

根据题目来看,写一个方法,输入是一个密码,返回一个boolean值。根据tdd关键步骤,先添加一个测试。
codewars已经有示例了。

@Test
    public void validPins() {
        assertEquals(true, Solution.validatePin("1234"));
        assertEquals(true, Solution.validatePin("0000"));
        assertEquals(true, Solution.validatePin("1111"));
        assertEquals(true, Solution.validatePin("123456"));
        assertEquals(true, Solution.validatePin("098765"));
        assertEquals(true, Solution.validatePin("000000"));
        assertEquals(true, Solution.validatePin("090909"));
    }
    

接下来到第二步,运行所有测试并失败。很显然会失败,因为Solution就没有这个类,这里不运行,先创建solution类。(第三步做一点修改)

public class Solution {
}

good news Solution不报错了。但是validatePin报错了,因为还没有这个方法。运行测试,失败了。哦哦,来继续做一点修改以通过测试。添加validatePin方法。

 public static boolean validatePin(String pin){
        return true;
    }

ok不报错了。
我们看第一个测试方法validPins,全部验证的是true。那我们直接返回true,运行测试。



测试成功了。到此结束了吗?好像没有,我们的测试用例没有覆盖到需求。还需要写更多的测试用例以满足需求。看看需求:四位或六位的数字。添加测试:

    @Test
    public void nonDigitCharacters() {
        assertEquals(false, Solution.validatePin("a234"));
        assertEquals(false, Solution.validatePin(".234"));
    }

    @Test
    public void invalidLengths() {
        assertEquals(false, Solution.validatePin("1"));
        assertEquals(false, Solution.validatePin("12"));
        assertEquals(false, Solution.validatePin("123"));
        assertEquals(false, Solution.validatePin("12345"));
        assertEquals(false, Solution.validatePin("1234567"));
        assertEquals(false, Solution.validatePin("-1234"));
        assertEquals(false, Solution.validatePin("1.234"));
        assertEquals(false, Solution.validatePin("00000000"));
    }

ok,这里添加了2个测试方法,一个是测试非数字的,一个是测试位数的。重复tdd步骤,运行测试。



2个失败了,1个通过了。做一点小修改,以通过测试用例。当输入“a234”的时候或者“.234”的时候,返回false。修改方法validatePin

public static boolean validatePin(String pin){
        if("a234".equals(pin) || ".234".equals(pin)){
            return false;
        }
        return true;
    }

这里因为练习pdd的步骤,所以每一步严格按照tdd的步骤走了,所以看起来这里的代码比较蠢。
ok运行测试


image.png

棒棒的,我们严格满足了定律3,只编写刚好通过测试用例的代码。ok,暂时搞定2个测试用例了,我们进行下一步。位数测试,修改一点点代码满足测试3

public static boolean validatePin(String pin){
        if("a234".equals(pin) || ".234".equals(pin)){
            return false;
        }
        if(pin.length()!=4 && pin.length()!=6){
            return false;
        }
        return true;
    }

运行测试



全通过了,进行下一步,重构以消除重复。
现在的方法看起来是这样

public static boolean validatePin(String pin){
        if("a234".equals(pin) || ".234".equals(pin)){
            return false;
        }
        if(pin.length()!=4 && pin.length()!=6){
            return false;
        }
        return true;
    }

看到问题了,里面有硬编码,所以我们要消除硬编码。这段硬编码我们是为了满足测试2,测试2是为了测试是不是纯数字,做一点小修改

public static boolean validatePin(String pin){
        Pattern pattern = Pattern.compile("^-?\\d+(\\.\\d+)?$");
        Matcher isNum = pattern.matcher(pin);
        if (!isNum.matches()) {
            return false;
        }
        if(pin.length()!=4 && pin.length()!=6){
            return false;
        }
        return true;
    }

运行测试,ok还是通过了。
下一步,重构以消除重复。这个方法细分来看,做了2件事,第一是判断是否包含数字之外的字符,第二是判断位数,那么把这两个事可以单独提出来提炼一个方法。重构如下

public class Solution {
    public static boolean validatePin(String pin){
        return validateNonDigitCharacters(pin) && invalidLengths(pin);
    }
    private static boolean validateNonDigitCharacters(String pin){
        Pattern pattern = Pattern.compile("^-?\\d+(\\.\\d+)?$");
        Matcher isNum = pattern.matcher(pin);
        return isNum.matches();
    }
    private static boolean invalidLengths(String pin){
        return !(pin.length()!=4 && pin.length()!=6);
    }
}

invalidLengths方法似乎还有些不是很能一眼看懂,不过仔细看看还是能懂,guess what,我不改了。
运行测试



测试通过了,到此结束。


总结

我把tdd理解为 逻辑代码未动,测试代码先行。通过不停的满足覆盖全面的测试代码,一点点修改逻辑代码,直到满足所有测试。

TODO

可以在原需求基础增加一个需求,继续迭代开发,体现出tdd的优势

bug

测试用例没有覆盖完全,没有考虑空值

最优解

public static boolean validatePin(String pin) {
    return pin.matches("\\d{4}|\\d{6}");
  }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,504评论 4 365
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,898评论 1 300
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,218评论 0 248
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,322评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,693评论 3 290
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,812评论 1 223
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,010评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,747评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,476评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,700评论 2 251
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,190评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,541评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,206评论 3 240
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,129评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,903评论 0 199
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,894评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,748评论 2 274