美文网首页
TDD的实践demo

TDD的实践demo

作者: 写代码的杰西 | 来源:发表于2019-12-25 15:56 被阅读0次

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}");
  }

相关文章

网友评论

      本文标题:TDD的实践demo

      本文链接:https://www.haomeiwen.com/subject/akpcoctx.html