Java Mockito风格的anyXXX单元测试方法
在对某些方法进行单元测试时,可能会出现一些场景,其中某些参数的值无关紧要,可以是任何值 例如,在这段代码中:Java Mockito风格的anyXXX单元测试方法,java,unit-testing,mockito,hamcrest,Java,Unit Testing,Mockito,Hamcrest,在对某些方法进行单元测试时,可能会出现一些场景,其中某些参数的值无关紧要,可以是任何值 例如,在这段代码中: public void method(String arg1, String arg2, int arg3){ if(arg1 == null) throw new NullPointerException("arg1 is null"); //some other code } 单元测试当arg1为null时必须抛出NPE的行为,其他参数的值无关
public void method(String arg1, String arg2, int arg3){
if(arg1 == null) throw new NullPointerException("arg1 is null");
//some other code
}
单元测试当arg1
为null
时必须抛出NPE的行为,其他参数的值无关紧要,它们可以是任意值或null
因此,我想记录一个事实,即这些值对于被测试的方法来说并不重要
我想到了以下选择:
选项1:定义ANY\u XXX的常数
我想显式地创建常量ANY_STRING
和ANY_INT
,它们包含一个固定值,记录它可以是任何值,并且被测试的方法不关心实际值
我可以将所有这些常量放在一个名为Any
的类中,并在所有测试类中重用它们
选项2:任意_XXX的随机值
这个选项对我来说似乎有点不妥,因为我在某个地方读到过,不应该将随机性引入测试用例中。但在这种情况下,这种随机性将不可见,因为参数不会产生任何副作用
哪种方法更适合于更好的可读性测试
更新:
虽然我可以通过在ANY
类中定义常量来使用任何_XXX方法,但我也在考虑使用一些约束生成任何_XXX值,例如
Any.anyInteger().nonnegative();
Any.anyInteger().negative();
Any.anyString().thatStartsWith("ab");
我想也许Hamcrest Matchers可以用来创建这个链接。但我不确定这种方法是否好。类似的anyObject()
方法已经由提供,但这些方法仅适用于模拟和间谍,而不适用于普通对象。我想对普通对象实现同样的功能,以便进行更具可读性的测试
我为什么要这么做?
假设我有一节课
class MyObject{
public MyObject(int param1, Object param2){
if(param1 < 0) throw new IllegalArgumentException();
if(param2 == null) throw new NullPointerException();
}
}
我经常看到他们俩
就我个人而言,我不同意不应将随机性纳入测试。在某种程度上使用随机性会使测试更加健壮,但不一定更容易阅读
如果您使用第一种方法,我不会创建一个constants类,而是直接传递值(或null),因为这样您就可以看到传入的内容,而无需查看另一个类—这将使您的测试更具可读性。如果以后需要其他参数,您也可以轻松地修改测试使用调用方方法作为实际方法怎么样
//This is the actual method that needs to be tested
public void theMethod(String arg1, String arg2, int arg3, float arg4 ){
}
创建一个调用方方法,该方法使用所需参数和其余参数的默认(或null)值调用该方法,并在此调用方方法上运行测试用例
//The caller method
@Test
public void invokeTheMethod(String param1){
theMethod(param1, "", 0, 0.0F); //Pass in some default values or even null
}
尽管您必须非常确定,为其他参数传递方法(…)
上的默认值不会导致任何NPE。我看到3个选项:
永远不要传递空值,禁止你的团队传递空值。空值是邪恶的。传递null应该是一个异常,而不是一个规则
只需在生产代码中使用注释:@NotNull或类似的东西。如果使用lombok,此注释也将进行实际验证
如果你真的必须在测试中这样做,那么只需创建一个具有适当名称的测试:
static final String ANY\u String=“whatever”;
@试验
当第一个参数为null()时,public void应该抛出{
方法(null,任意_字符串,任意_字符串);//使用catch异常或junit的
}
如果您愿意试一试,您可以将测试参数化,为参数指定有意义的名称:
@Test
@Parameters({
"17, M",
"2212312, M" })
public void shouldCreateMalePerson(int ageIsNotRelevant, String sex) throws Exception {
assertTrue(new Person(ageIsNotRelevant, sex).isMale());
}
我总是赞成常数法。原因是我相信它比链接几个匹配者更具可读性
而不是你的例子:
class MyObjectTest{
@Test(expected=NullPointerException.class)
public void testConstructor_ShouldThrowNullpointer_IfSecondParamIsNull(){
new MyObject(Any.anyInteger().nonnegative(), null);
}
}
// when
service.fooBar(ANY_INT, ANY_INT, ANY_INT, ANY_INT, 5);
我想:
class MyObjectTest{
private static final int SOME_NON_NEGATIVE_INTEGER = 5;
@Test(expected=NullPointerException.class)
public void testConstructor_ShouldThrowNullpointer_IfSecondParamIsNull(){
new MyObject(SOME_NON_NEGATIVE_INTEGER, null);
}
}
此外,我更喜欢使用“SOME”而不是“ANY”,但这也是个人品味的问题
如果您正在考虑使用您所提到的许多不同的变体测试构造函数(nonNegative()
,negative()
,thattstartswith()
,等等),我建议您编写参数化测试。为此,我推荐JUnitParams,以下是我的做法:
@RunWith(JUnitParamRunner.class)
class MyObjectTest {
@Test(expected = NullPointerException.class)
@Parameters({"-4000", "-1", "0", "1", "5", "10000"})
public void testConstructor_ShouldThrowNullpointer_IfSecondParamIsNull(int i){
new MyObject(i, null);
}
...
}
我的偏好是建立一个常量实用类,以及帮助创建测试常量值的方法,例如:
public final class Values {
public static final int ANY_INT = randomInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
public static final int ANY_POSITIVE_INT = randomInt(0, Integer.MAX_VALUE);
public static final String ANY_ISBN = randomIsbn();
// etc...
public static int randomInt(int min, int max) { /* omitted */ }
public static String randomIsbn() { /* omitted */ }
// etc...
}
// given
final String isbn1 = randomIsbn();
final String isbn2 = randomIsbn();
final Book[] books = { new Book(isbn1), new Book(isbn2) };
// when
bookRepository.store(books);
然后,我将使用静态导入来提取特定测试类所需的常量和方法
我只在我不关心值的情况下使用ANY_uu
常量,我发现它们可以使测试的意图更清楚,例如:
class MyObjectTest{
@Test(expected=NullPointerException.class)
public void testConstructor_ShouldThrowNullpointer_IfSecondParamIsNull(){
new MyObject(Any.anyInteger().nonnegative(), null);
}
}
// when
service.fooBar(ANY_INT, ANY_INT, ANY_INT, ANY_INT, 5);
很明显,值5
具有一定的意义-尽管作为局部变量更好
在设置测试时,实用方法可用于临时生成值,例如:
public final class Values {
public static final int ANY_INT = randomInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
public static final int ANY_POSITIVE_INT = randomInt(0, Integer.MAX_VALUE);
public static final String ANY_ISBN = randomIsbn();
// etc...
public static int randomInt(int min, int max) { /* omitted */ }
public static String randomIsbn() { /* omitted */ }
// etc...
}
// given
final String isbn1 = randomIsbn();
final String isbn2 = randomIsbn();
final Book[] books = { new Book(isbn1), new Book(isbn2) };
// when
bookRepository.store(books);
同样,这有助于让测试类关注测试本身,而不是数据设置
除此之外,我还使用了来自域对象的类似方法。当你将这两种方法结合起来时,它会非常强大。e、 g:
public final class Domain {
public static Book book() {
return new Book(randomIsbn());
}
// etc...
}
我建议您对那些可能是任意的参数使用常量。添加随机性会使测试运行不可重复。即使参数值在这里“无关紧要”,实际上唯一“有趣”的情况是测试失败,并且添加了随机行为,您可能无法轻松重现错误。此外,更简单的解决方案往往更好,也更容易维护:使用常量肯定比使用随机数更简单
当然,如果使用常量值,可以将这些值放在静态final
字段中,但也可以将它们放在方法中,名称为arbitraryInt()
(返回例如0)等等。我发现使用方法的语法比使用常量的语法更清晰,因为它类似于Mockito的any()
matchers。它还允许您更容易地替换行为,以防您需要添加更多复杂性