假设我们有一个类Cat:
class Cat{
public eat(String food){
if (food.contains("cat")){
burp();
}
}
private burp(){
System.out.println("burp!!");
}
}我们有下面的代码来测试它(使用Mockito和TestNG):
@DataProvider
public Object[][] dataProviderForTestFeedCat() {
return new Object[][] {
{"catfood", true},
{"birdfood", false},
{"dogfood", false},
{"food for cat", true}
};
}
@Test(dataProvider = "dataProviderForTestFeedCat")
public void testFeedCat(String food, boolean shouldBurp){
Cat cat = new Cat();
cat.eat(food)
verify(cat, times(shouldBurp ? 1 : 0)).burp();
}有人向我建议,最好避免测试体内的shouldBurp ? 1 : 0逻辑,而不是由数据提供程序直接提供计数:
@DataProvider
public Object[][] dataProviderForTestFeedCat() {
return new Object[][] {
{"catfood", 1},
{"birdfood", 0},
{"dogfood", 0},
{"food for cat", 1}
};
}
@Test(dataProvider = "dataProviderForTestFeedCat")
public void testFeedCat(String food, int burpCount){
Cat cat = new Cat();
cat.eat(food)
verify(cat, times(burpCount)).burp();
}他们的论点是,测试体本身应该尽可能简单。
但是,一个相反的论点是,数据提供程序应该是简单的,并且应该只提供原始数据。它不应该关心测试实际上是从布尔值中生成一个整数,以验证方法是否被调用。
布尔值也感觉更加直观和自然,因为在实际代码中的单个调用中,这个特定方法不太可能被执行两次。
更好的方法是什么?
发布于 2020-12-04 15:42:13
我认为您的同事的观点是正确的,但专注于一个过于细粒度的细节,而不是您的测试所要达到的目标。但是,作为您的同事的辩护,您的代码意味着它针对这些细节,而我怀疑它实际上并不需要它。
例如,如果打嗝的量是可变的,例如一只猫在吃“鲶鱼”时会打嗝两次,那么我希望你立即看到需要测试特定的打嗝量。您的当前测试实际上不允许这样做(因为您已经对1和0进行了硬编码),而您的同事建议的测试将允许这样做。
换句话说,在你同事提出的测试中,断言是“猫多久打嗝一次?”答案是一个数字,所以用数字表示预期结果是有意义的,这意味着你的同事是对的。
然而,我推测你目前测试的目标不是计算打嗝,而是确认猫打嗝(至少一次)。这里的说法是“猫打嗝了吗?”答案要么是“是”,要么是“否”,因此将预期的结果表示为布尔值是有意义的。
但是
..。如果第二种情况确实是测试的目的,那么您不应该测试猫是否精确地打了一次嗝(shouldBurp ? 1 : 0),而应该测试> 0的数量。你在测试猫是否打嗝,而不是它是否准确地打嗝一次。
我认为这是你同事的建议的基础。他看到您测试了一个特定的数量(即变量),但是您并没有将它作为方法参数传递。
这里的答案是,您应该更愿意以一种与此测试所代表的问题相匹配的方式来表示您的预期结果。首先问自己这个测试想要回答什么问题,然后将答案以与问题相匹配的格式(数据类型)进行处理。
我建议,根据打嗝量是否与测试相关,要么遵循同事的建议(如果数量相关),要么断言> 0 (如果数量与测试无关)。
布尔值也感觉更加直观和自然,因为在实际代码中的单个调用中,这个特定方法不太可能被执行两次。
每当我听到有人说某件事“不太可能”时,就会认为它是不应该被解释的理由,这就是立即发出的危险信号。
有些事情不是可能的就是不可能的。你应该永远忽略那些不可能的事情。如果这是极不可能的,那就意味着这不是不可能的,因此你不能忽视它。
然而,有可能并不意味着它与这个特定的测试相关。正如我在上面所建立的,如果您的测试根本不关心打嗝的数量,并且只对其(非)零性质感兴趣,那么您实际上可以忽略这个测试中存在的多个打嗝。
发布于 2020-12-04 15:48:11
return new Object[][] {
{"catfood", true},
{"birdfood", false},
{"dogfood", false},
{"food for cat", true}
};您考虑过使用更丰富的语言来描述您的域吗?
return new Object[][] {
{"catfood", BURPS},
{"birdfood", DOES_NOT_BURP},
{"dogfood", DOES_NOT_BURP},
{"food for cat", BURPS}
};BURPS/ an _NOT_BURP可以是一个原始值,也可以是实现验证条件的对象:
public void testFeedCat(String food, ???? desiredBehavior){
Cat cat = new Cat();
cat.eat(food);
desiredBehavior.verify(cat);
}另一种可能是,您正在挣扎于这样一个事实:您的测试定义耦合到您的输入,而不是您的规范。
public void catsBurpWhenFedCatFood(String catFood) {
Cat cat = new Cat();
cat.eat(catFood);
BURPS.verify(cat)
}凯夫林·亨尼有很多关于测试设计的非常好的视频。
在此过程中,您还可以考虑将您所做的度量与度量的评估分开。
private Cat catThatHasBeenFed(String food) {
Cat cat = new Cat();
cat.eat(food);
return cat;
}
// "catfood", "food for cat"
public void catsBurpWhenFedCatFood(String catFood) {
Cat cat = catThatHasBeenFed(catFood);
BURPS.verify(cat)
}
// "dogfood", "birdfood"
public void catsDoNotBurpWhenEatingNotCatFood(String notCatFood) {
Cat cat = catThatHasBeenFed(catFood);
DOES_NOT_BURP.verify(cat)
}当然,在这里,我们并不真正感兴趣测量猫,而是猫发出的噪音。
private List catThatHasBeenFed(String food) {
Microphone microphone = new Microphone();
microphone.start();
{
Cat cat = new Cat();
cat.eat(food);
}
microphone.stop();
List recordings = microphone.recordings();
return recordings;
}
public void catsBurpWhenFedCatFood(String catFood) {
List recordings = catThatHasBeenFed(catFood);
BURPS.verify(recordings)
}https://softwareengineering.stackexchange.com/questions/419648
复制相似问题