Frage

While unit testing some methods, there can be some scenarios where value of some parameters do not matter and can be any value.

For example in this piece of code:

public void method(String arg1, String arg2, int arg3){
    if(arg1 == null) throw new NullPointerException("arg1 is null");

    //some other code
}

unit testing the behavior that when arg1 is null then NPE must be thrown, the values of other arguments do not matter, they can be any value or be null.

So I wanted to document the fact that the values do not matter for the method under test.

I thought of following options:

Option 1: Define constants of ANY_XXX

I thought of explicitly creating constants ANY_STRING and ANY_INT, which contain a fixed value which documents that it can be any value and the method under test does not care about the actual value.

I can put all these constants in a single class called Any and reuse them across all test classes.

Option 2: Random values for ANY_XXX

This option seems a bit hacky to me as I have read somewhere that randomness should not be brought into test cases. But in this scenario this randomness will not be visible as the parameters will not create any side effect.

Which approach would be more suitable for better, readable tests?

UPDATE:

While I can use ANY_XXX approach by defining constants in Any class, but I am also thinking of generating ANY_XXX values with some constraints such as

Any.anyInteger().nonnegative();
Any.anyInteger().negative();

Any.anyString().thatStartsWith("ab");

I am thinking that maybe Hamcrest Matchers can be used for creating this chaining. But I am not sure if this approach is a good one. Similar methods for anyObject() are already provided by Mockito but those only work on Mocks and spies and not on normal objects. I want to achieve the same for normal objects for more readable tests.

Why I want to do this?

Suppose I have a class

class MyObject{

     public MyObject(int param1, Object param2){
          if(param1 < 0) throw new IllegalArgumentException();
          if(param2 == null) throw new NullPointerException();
     }
}

And now while writing tests for constructor

class MyObjectTest{

     @Test(expected=NullPointerException.class)
     public void testConstructor_ShouldThrowNullpointer_IfSecondParamIsNull(){
          
          //emphasizing the fact that value of first parameter has no relationship with result, for better test readability
          new MyObject(Any.anyInteger().nonnegative(), null);
     }
}
War es hilfreich?

Lösung 2

My preference is to build up a utility class of constants along with methods to help with the creation of the constant values for tests, e.g.:

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...
}

Then I would use static imports to pull the constants and methods I needed for a particular test class.

I use the ANY_ constants only in situations where I do not care about the value, I find that they can make the intent of the test clearer, for example:

// when
service.fooBar(ANY_INT, ANY_INT, ANY_INT, ANY_INT, 5);

It's clear that the value 5 is of some significance - although it would be better as a local variable.

The utility methods can be used for adhoc generation of values when setting up tests, e.g.:

// given
final String isbn1 = randomIsbn();
final String isbn2 = randomIsbn();
final Book[] books = { new Book(isbn1), new Book(isbn2) };

// when
bookRepository.store(books);

Again, this can help to keep the test classes concerned about the tests themselves and less about data set up.

In addition to this I have also used a similar approach from domain objects. When you combine the two approaches it can be quite powerful. e.g.:

public final class Domain {
    public static Book book() {
        return new Book(randomIsbn());
    }
    // etc...
}

Andere Tipps

I see both og them quite a lot

Personally I disagree that randomness should not be brought into tests. Using randomness to some degree should make your tests more robust, but not necessarily easier to read

If you go for the first approach I would not create a constants class, but rather pass the values (or nulls) directly, since then you see what you pass in without the need to have a look in another class - which should make your tests more readable. You can also easily modify your tests later if you need the other parameters later on

I've faced the same problem when i've started to write unit tests for my project and had to deal with numerous of arrays, lists, integer inputs, strings etc. So I decided to use QuickCheck and create a generator util class.

Using Generators in this library, you can generate primitive data types and String easily. For example, when you want to Generate an integer; simply use IntegerGenerator class.You can define maximum and minimum values in the constructor of the generator.You can also use CombinedGeneratorSamples class to generate data structures like lists, maps and arrays. Another feature of this library is implementing Generator interface for custom class generators.

You're overthinking and creating unnecessary barriers for your project :

  • if you want to document your method, do it with words! that's why the Javadoc is here for

  • if you want to test your method with "any positive int" then just call it with a couple different positive ints. In your case, ANY does not mean testing every possible integer value

  • if you want to test your method with "a string that starts with ab", call it with "abcd", then "abefgh" and just add a comment on the test method !

Sometimes we are so caught with frameworks and good practices that it takes common sense away.

In the end : most readable = simplest

How about using a caller method for the actual method.

//This is the actual method that needs to be tested
public void theMethod(String arg1, String arg2, int arg3, float arg4 ){

}

Create a caller method that calls the method with the required parameters and default(or null) values for the rest of the params and run your test case on this caller method

//The caller method
@Test
public void invokeTheMethod(String param1){
    theMethod(param1, "", 0, 0.0F); //Pass in some default values or even null
}

Although you will have to be pretty sure that passing default values on theMethod(...) for the other parameters wont cause any NPE.

i see 3 options:

  1. never pass nulls, forbid your team passing nulls. nulls are evil. passing null should be an exception, not a rule
  2. simply use annotation in production code: @NotNull or sth like that. if u use lombok, this annotation will also do the actual validation
  3. and if u really have to do it in tests then simply create a test with proper name:
static final String ANY_STRING = "whatever";

@Test
public void should_throw_NPE_when_first_parameter_is_null() {

  object.method(null, ANY_STRING, ANY_STRING); //use catch-exception or junit's expected
}

If you're willing to give JUnitParams' framework a go, you could parametrize your tests specifying meaningful names to your parameters:

@Test
@Parameters({ 
        "17, M", 
        "2212312, M" })
public void shouldCreateMalePerson(int ageIsNotRelevant, String sex) throws Exception {
    assertTrue(new Person(ageIsNotRelevant, sex).isMale());
}

I'm always in favor of the constants approach. The reason is that I believe it gets more readable than chaining several matchers.

Instead of your example:

class MyObjectTest{

    @Test(expected=NullPointerException.class)
    public void testConstructor_ShouldThrowNullpointer_IfSecondParamIsNull(){
        new MyObject(Any.anyInteger().nonnegative(), null);
    }
}

I would d:

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);
    }
}

Also, I prefer the use of 'SOME' over 'ANY', but that's also a matter of personal taste.

If you're considering testing the constructor with a number of different variants as you mentioned (nonNegative(), negative(), thatStartsWith(), etc.), I would that instead you write parameterized tests. I recommend JUnitParams for that, here's how I'd do it:

@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);
    }

    ...
}

I suggest you go with constant values for those parameters which may be arbitrary. Adding randomness makes your test runs not repeatable. Even if parameter values "don't matter" here, actually the only "interesting" case is when a test fails, and with random behavior added in, you might not be able to reproduce the error easily. Also, simpler solutions are often better, and easier to maintain: using a constant is certainly simpler than using random numbers.

Of course if you go with constant values, you could put these values in static final fields, but you could also put them in methods, with names such as arbitraryInt() (returning e.g. 0) and so on. I find the syntax with methods cleaner than with constants, as it resembles Mockito's any() matchers. It also allows you to replace the behavior more easily in case you need to add more complexity later on.

In case you want to indicate that a parameter doesn't matter and the parameter is an object (not primitive type), you can also pass empty mocks, like so: someMethod(null, mock(MyClass.class)). This conveys to a person reading the code that the second parameter can be "anything", since a newly created mock has only very basic behavior. It also doesn't force you to create your own methods for returning "arbitrary" values. The downside is it doesn't work for primitive types or for classes which can't be mocked, e.g. final classes like String.

Ok.... I see a big Problem with you approach!

The other value doesn't matter? Who guarantees this? The Writer of the Test, the writer of the Code? What if you have a Method, which throws some unrelated Exception if the first Parameter is exactly 1000000 even if the second parameter is NULL ?

You have to formulate your Test-Cases: What is the Test-Specification... What do you want to proof? Is it:

  1. In some cases if the first parameter is some arbitrary value and the second is null, this method should throw a NullPointerException

  2. For any possible first Input value, if the second value is NULL the method should always throw a NullPointerException

If you want to test the first case - your approach is ok. Use a constant, a random value, a Builder... whatever you like.

But if your specification actually requires the 2nd condition all of your presented solutions are not up for the task, since they only test some arbitrary value. A good test should still be valid if the programmer changes some code in the method. This means the right way to test this method would be a whole series of Testcases, testing all corner-cases as with all other methods. So each critical value which can lead to a different execution-path should be checked - or you need a testsuite which checks for code-path completeness...

Otherwise your test is just bogus and there to look pretty...

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top