我的单元测试是否违反了 "test should do one thing principle"?
Is my unit test violating "test should do one thing principle"?
我围绕我的 class 编写了一些单元测试,最后我很困惑是否将这些测试结合起来,因为它们失败的原因是相同的。
模式生成器class
public class PatternBuilder {
private static final String PATTERN_INITIALS = "#0.%s";
String buildPatternFromScale(int scale) {
return String.format(Locale.ENGLISH, PATTERN_INITIALS, StringUtils.repeat("0", scale));
}
}
上述实现的单元测试
@RunWith(Parameterized.class)
public class PatternBuilderTest {
private PatternBuilder patternBuilder;
@Parameterized.Parameter
public int scale;
@Parameterized.Parameter(1)
public String expectedPattern;
@Before
public void setUp() {
patternBuilder = new PatternBuilder();
}
@Parameterized.Parameters(name = "buildPatternFromScale({0}) = {1}")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{1, "#0.0"},
{2, "#0.00"},
{5, "#0.00000"},
});
}
@Test
public void testShouldVerifyThatNonNullPatternIsBuilt() {
//given
//when
String pattern = patternBuilder.buildPatternFromScale(scale);
//then
assertThat(pattern, is(notNullValue()));
}
@Test
public void testShouldVerifyThatCorrectPatternOfSpecifiedScaleShouldBeCreated() {
//when
String pattern = patternBuilder.buildPatternFromScale(scale);
//then
assertThat(pattern, is(equalTo(expectedPattern)));
}
}
如果我将第一个测试与第二个测试结合起来,使其断言无空值和正确的模式字符串,我是否会违反 'test should do only one thing'?
实际上,一个测试将有以下两个断言-
assertThat(pattern, is(notNullValue()));
assertThat(pattern, is(equalTo(expectedPattern)));
原因 - 我正在考虑将它们结合起来,因为如果创建了空模式字符串,那么它会因为一个原因而无法通过两个测试
Will I violate the 'test should do only one thing' if I combine first
test with second such that it asserts for no-null-value and correct
pattern string ?
在这种情况下我不认为。
为什么 ?
因为第一个测试方法:
assertThat(pattern, is(notNullValue()));
没有使 buildPatternFromScale()
方法的任何预期行为有效。
此方法旨在 return 一个 Pattern
根据它接收到的参数。
要检查的唯一一件事是。
通过编写一个测试方法来执行非空断言,您不会覆盖该方法的规范。您编写的只是验证它的第一步。
如果您的方法在某些情况下可以 return null
而在其他情况下不能 null
,那么它就有意义了。
但事实并非如此。
所以我会替换:
assertThat(pattern, is(notNullValue()));
assertThat(pattern, is(equalTo(expectedPattern)));
仅:
assertThat(pattern, is(equalTo(expectedPattern)));
"One assertion per test method" 规则的一个目的是只有测试方法的名称才能为您提供足够的信息来了解测试失败的原因。
如果您在测试中有多个断言 - 您需要阅读失败消息以了解测试失败的原因。
还有多个断言,如果第一个断言失败,其他断言将不会被执行。
因此,为了获得 "full picture" 可能的原因,您需要在修复第一个断言
后重新 运行 测试
在您的特定情况下,非空断言与预期结果相等断言相同。如果预期不为空,则空结果将始终失败。
鉴于您的代码:您不需要第一个测试。第二个测试确保您的构建器 returns 一个与预期模式 相等 的对象。
如果构建器 returns 一个空对象,这个测试显然会 失败 !
从这个意义上说:无论如何,进行两次不同的测试不会增加太多价值!
换句话说:您可能 开始 这两个测试,或者更准确地说:当您遵循 TDD 时;您可能会从第一个测试用例开始。稍后,当您实现了真正的构建器,并且 "real" 测试用例就位时 - 那么您就不再需要第一个 check-for-null 测试用例了。
请记住:源代码的任何行 also 对您来说都类似于 cost。只要它存在,就可能会被阅读并需要被理解(修复错误或添加功能)。因此,即使是单元测试也应该为您提供 "return on investment"。含义:不会为您的测试桶增加重要价值的测试可能会被删除。
根据对问题的更改进行编辑。附加测试用例
@Test
public void testShouldVerifyThatNonNullPatternIsBuilt() {
//given
int scale = 1;
仍然是多余的。 "worse" - 请记住,@Parameterized 测试以某种方式 与 "normal JUnit" 测试不同。您会看到,使用@Parameterized runner,each 测试被调用用于each 数据值。含义:上面的测试被调用三次次。这个想法是 all 测试使用数据元素。从这个意义上说:在那里进行 不 使用 "data values" 的测试是没有意义的。特别是考虑到这里的测试测试了 other 测试已经检查过的东西。
换句话说:当模式为空时,assertThat(pattern, is(equalTo(expectedPattern)))
将 失败(除非您碰巧将 null 放入您的 expectedPattern).
我想我会坚持使用两个单独的 tests.here。如果这是使用 TDD 完成的,您很可能最终会进行两次测试。
虽然值得一提的是,一个测试可以有一个单一的概念(而不是一个事物),这可能意味着在一个测试用例中有多个断言,如果所有这些都在逻辑上相关的话。
如前所述,您期待的关键是测试的清晰度,以快速识别失败的原因和原因,您拥有它的方式可以为您提供这一点,这对于从测试中增加的价值而言很重要。
我围绕我的 class 编写了一些单元测试,最后我很困惑是否将这些测试结合起来,因为它们失败的原因是相同的。
模式生成器class
public class PatternBuilder {
private static final String PATTERN_INITIALS = "#0.%s";
String buildPatternFromScale(int scale) {
return String.format(Locale.ENGLISH, PATTERN_INITIALS, StringUtils.repeat("0", scale));
}
}
上述实现的单元测试
@RunWith(Parameterized.class)
public class PatternBuilderTest {
private PatternBuilder patternBuilder;
@Parameterized.Parameter
public int scale;
@Parameterized.Parameter(1)
public String expectedPattern;
@Before
public void setUp() {
patternBuilder = new PatternBuilder();
}
@Parameterized.Parameters(name = "buildPatternFromScale({0}) = {1}")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{1, "#0.0"},
{2, "#0.00"},
{5, "#0.00000"},
});
}
@Test
public void testShouldVerifyThatNonNullPatternIsBuilt() {
//given
//when
String pattern = patternBuilder.buildPatternFromScale(scale);
//then
assertThat(pattern, is(notNullValue()));
}
@Test
public void testShouldVerifyThatCorrectPatternOfSpecifiedScaleShouldBeCreated() {
//when
String pattern = patternBuilder.buildPatternFromScale(scale);
//then
assertThat(pattern, is(equalTo(expectedPattern)));
}
}
如果我将第一个测试与第二个测试结合起来,使其断言无空值和正确的模式字符串,我是否会违反 'test should do only one thing'?
实际上,一个测试将有以下两个断言-
assertThat(pattern, is(notNullValue()));
assertThat(pattern, is(equalTo(expectedPattern)));
原因 - 我正在考虑将它们结合起来,因为如果创建了空模式字符串,那么它会因为一个原因而无法通过两个测试
Will I violate the 'test should do only one thing' if I combine first test with second such that it asserts for no-null-value and correct pattern string ?
在这种情况下我不认为。
为什么 ?
因为第一个测试方法:
assertThat(pattern, is(notNullValue()));
没有使 buildPatternFromScale()
方法的任何预期行为有效。
此方法旨在 return 一个 Pattern
根据它接收到的参数。
要检查的唯一一件事是。
通过编写一个测试方法来执行非空断言,您不会覆盖该方法的规范。您编写的只是验证它的第一步。
如果您的方法在某些情况下可以 return null
而在其他情况下不能 null
,那么它就有意义了。
但事实并非如此。
所以我会替换:
assertThat(pattern, is(notNullValue()));
assertThat(pattern, is(equalTo(expectedPattern)));
仅:
assertThat(pattern, is(equalTo(expectedPattern)));
"One assertion per test method" 规则的一个目的是只有测试方法的名称才能为您提供足够的信息来了解测试失败的原因。
如果您在测试中有多个断言 - 您需要阅读失败消息以了解测试失败的原因。
还有多个断言,如果第一个断言失败,其他断言将不会被执行。
因此,为了获得 "full picture" 可能的原因,您需要在修复第一个断言
在您的特定情况下,非空断言与预期结果相等断言相同。如果预期不为空,则空结果将始终失败。
鉴于您的代码:您不需要第一个测试。第二个测试确保您的构建器 returns 一个与预期模式 相等 的对象。
如果构建器 returns 一个空对象,这个测试显然会 失败 !
从这个意义上说:无论如何,进行两次不同的测试不会增加太多价值!
换句话说:您可能 开始 这两个测试,或者更准确地说:当您遵循 TDD 时;您可能会从第一个测试用例开始。稍后,当您实现了真正的构建器,并且 "real" 测试用例就位时 - 那么您就不再需要第一个 check-for-null 测试用例了。
请记住:源代码的任何行 also 对您来说都类似于 cost。只要它存在,就可能会被阅读并需要被理解(修复错误或添加功能)。因此,即使是单元测试也应该为您提供 "return on investment"。含义:不会为您的测试桶增加重要价值的测试可能会被删除。
根据对问题的更改进行编辑。附加测试用例
@Test
public void testShouldVerifyThatNonNullPatternIsBuilt() {
//given
int scale = 1;
仍然是多余的。 "worse" - 请记住,@Parameterized 测试以某种方式 与 "normal JUnit" 测试不同。您会看到,使用@Parameterized runner,each 测试被调用用于each 数据值。含义:上面的测试被调用三次次。这个想法是 all 测试使用数据元素。从这个意义上说:在那里进行 不 使用 "data values" 的测试是没有意义的。特别是考虑到这里的测试测试了 other 测试已经检查过的东西。
换句话说:当模式为空时,assertThat(pattern, is(equalTo(expectedPattern)))
将 失败(除非您碰巧将 null 放入您的 expectedPattern).
我想我会坚持使用两个单独的 tests.here。如果这是使用 TDD 完成的,您很可能最终会进行两次测试。
虽然值得一提的是,一个测试可以有一个单一的概念(而不是一个事物),这可能意味着在一个测试用例中有多个断言,如果所有这些都在逻辑上相关的话。
如前所述,您期待的关键是测试的清晰度,以快速识别失败的原因和原因,您拥有它的方式可以为您提供这一点,这对于从测试中增加的价值而言很重要。