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。它还允许您更容易地替换行为,以防您需要添加更多复杂性