我目前正在阅读一本关于使用testNG和Mockito进行测试的书籍,以便提高我使用该工具的技能,扩展我对测试的总体知识并提高我创建的代码质量。
第4章之后,"测试驱动开发"我应该做很多练习来加强我刚学到的关于TDD的知识,并养成循环红色测试的习惯 - >实施代码 - >绿色测试 - >重构。其中一个名为" PasswordValidator",它应该验证密码的强度。
这种开发的推荐方法之一是从最简单的测试开始,逐步增加,编写更复杂的测试用例,实现更复杂的功能。说实话,通常我会通过编写一个正确的正则表达式来解决这个问题(比如这个问题:https://stackoverflow.com/a/5142164/2576122),编写一些测试来覆盖我可能想象的任何边界情况并完成它。
在这里,我首先创建PasswordValidatorTest类,并在其中测试如下:
@Test
public void returnsTrueWhenPasswordIsAtLeastEightCharsLong() {
assertTrue(atLeastEightCharactersLong(EIGHT_CHARACTERS_LONG_PASSWORD));
}
我检查它是否失败,在PasswordValidator中实现逻辑:
public static boolean atLeastEightCharactersLong(String passwordToValidate) {
if (passwordToValidate.length() >= 8) {
return true;
} else {
return false;
}
}
我检查它是否为绿色,然后重构:
public static boolean atLeastEightCharactersLong(String passwordToValidate) {
return (passwordToValidate.length() >= 8);
}
同样适用于包含数字,特殊标志等的测试。在这样的程序的某些时候,我最终得到了最终测试:
@Test
public void shouldReturnTrueIfPasswordIsValid() {
assertTrue(PasswordValidator.isValid(VALID_PASSWORD_EXAMPLE));
}
实施的目的是:
public static boolean isValid(String passwordToValidate) {
return atLeastEightCharactersLong(passwordToValidate) && containsAtLeastTwoDigits(passwordToValidate) &&
containsAtLeastOneSpecialSign(passwordToValidate);
}
然后在测试为绿色之后我可以清楚地看到,所有早期测试的方法应该从一开始就是私有方法。
是否经常发生,并且它是否被接受,在编码过程中,TDD中的某些测试在开始时是否有用?因为有一件事是肯定的 - 这些方法根本不应该暴露给那个类用户,所以回答诸如"让它们受到保护"不要诉我。
或许我的方法从一开始就无效,我应该只编写一个isValid()方法的测试来测试API的行为?如果我这样做,这样的测试会过于笼统,并且不允许我在开发过程中检查所有边界情况,或者我可能完全错了?在测试什么和不应该测试什么之间是否存在界限?什么应该是粒度?
答案 0 :(得分:2)
或许我的方法从一开始就无效,我应该只为isValid()方法编写测试
是的,这个。
所以你写这样的东西作为起点:
public static boolean isValid(String passwordToValidate) {
return false;
}
然后你可以写下你的第一个测试:
@Test
public void returnsTrueWhenPasswordIsAtLeastEightCharsLong() {
assertTrue(isValid(EIGHT_CHARACTERS_LONG_PASSWORD));
}
哪个会失败:RED。
因此,您对要测试的代码进行了最简单的更改,以使其通过:
public static boolean isValid(String passwordToValidate) {
return true;
}
然后测试将通过:GREEN。
但是你还没有完全测试8个字符的限制:
@Test
public void returnsFalseWhenPasswordIsLessThanEightCharsLong() {
assertFalse(isValid(SEVEN_CHARACTERS_LONG_PASSWORD));
}
这将失败 - RED - 因为测试中的方法现在总是返回true。
因此,您进行了最小的更改以使所有测试通过:
public static boolean isValid(String passwordToValidate) {
return passwordToValidate.length() >= 8;
}
你又是绿色了。
你继续这样做,添加其他测试,每次更改你正在测试的代码,尽可能少地通过测试。
您可能会发现在某个阶段重构isValid
方法,将其拆分为您调用的某些私有方法很有用。这没关系:它是 refactor 步骤的一部分,由于公共接口没有改变,你的测试仍然应该通过。
答案 1 :(得分:0)
这里的关键点是你缺少关于创建代表密码的抽象的主要概念,它为你提供了起点。由于缺少该实体,您失去了焦点,并以这种方式编写了测试用例。 您想要表示一个密码,因此您应该有一个模拟该实体的类,就域驱动设计而言,它是一个值对象。一旦创建了密码,它实际上代表了它建模的概念,所以我只有两个测试,并且会在构造函数中逐步添加检查警卫
testCreatesInvalidPassword() {
// catch exception
Password.fromString("asda");
Password.fromString("asdas1");
Password.fromString("asdas1...");
}
testCreatesValidPassword() {
Password.fromString("asdasdsad");
Password.fromString("asdas123");
Password.fromString("asdas123");
}
答案 2 :(得分:0)
一件红色 - >绿色 - >您可能忽略的重构循环是重构步骤。此步骤也适用于您的测试。因此,如果你达到了测试之间重复的程度,你应该重构那些,如果这意味着删除测试,那么你应该确信不再需要那个测试。