just_curious
just_curious

Reputation: 15

JUnit what asserts should I use

I would like to ask for a help and suggestions what is a correct approach in my case (probably its easy but I'm just starting with JUnit). Here is a part of my code

public boolean arrayListEmpty()
    {
        if(numericalSequence.isEmpty() == true)
            return true;
        else
            return false;
    }

This is a public method from model which I suppose i should test, it's just returning true if my numericalsequence is empty as you can see. I cant check it directly invoking numericalSequence.isEmpty (in controller where I need it) because it is private. So obvious thing is to check

assertEquals(true, test.arrayListEmpty());

So my question is about suggestions what other asserts should I use/what cases should I predict which can come out. Should I in Test method fill numericalSequence with some values and assert it also? (for me its not necessary because any value inserted into sequence = not null but maybe it is not so easy)

Upvotes: 1

Views: 73

Answers (1)

Vite Falcon
Vite Falcon

Reputation: 6645

First of all, welcome to Stack Overflow!

Looking at your question, it sounds like you're new to unit-testing (correct me if I'm wrong). So, I'll break my answers in to two sections; (1) answering your question, and (2) to give a general direction of how to write good tests and testable classes.

1. Answering your question

There are a couple more use cases you can think of here:

  1. What happens if numericalSequence is null?
  2. What if numericalSequence has 1 element?
  3. What if numericalSequence has a null element?

Some of the cases above may not be possible depending on how your class is set up, but it's a test worth having so that any changes to the class that violates the "previously agreed behavior" of one of these test cases would fail.

2. Writing good tests

There are no strict guidelines on what to do in order to write good tests, however, if you structure your code to be testable, you'll find that writing good tests becomes easier, and would be less of chore. Allow me to explain.

NOTE: This is not a comprehensive guide, but is meant to start your journey in to the path of how to write better code, and better tests

So assume a class that needs to be tested is written like this:

class MyClass {
  // NOTE: This is not `final`
  private List<Integer> numericalSequence;

  public MyClass() {
    this.numericalSequence = new ArrayList<>();
  }

  public void doSomething(Integer x) {
    if (x < 0) {
      numericalSequence = new ArrayList<>();
    } else {
      numericalSequence.add(2 * x);
    }
  }

  public boolean arrayListEmpty() {
    // NOTE: Your sample code can be reduced to this one-liner
    return numericalSequence.isEmpty();
  }
}

Here are some of the flaws in the above code:

  1. doSomething method allows nullable (boxed integer) values and so can cause NullPointerException in the first if statement.
  2. The numericalSequence member is not final and hence the code that sets it to null is valid inside the doSomething method
  3. The arrayListIsEmpty method does not check if numericalSequence is null
  4. If I want to test how arrayListIsEmpty behaves when numericalSequence is null, or has null elements, it is hard to do so, unless you know how to get the class to that state.

If the class was re-written to the following:

class MyClass {
  private final List<Integer> numericalSequence;

  public MyClass() {
    this(new ArrayList<>());
  }

  // Should be made public only if the classes that use this one needs to
  // initialize this instance with the sequence. Make this package-private
  // otherwise.
  public MyClass(List<Integer> sequence) {
    this.numericalSequence = sequence;
  }

  public void doSomething(int x) {
    if (x < 0) {
      // COMPILE ERROR: Cannot assign to a final member
      // numericalSequence = new ArrayList<>();
      numericalSequence.clear();
    } else {
      numericalSequence.add(2 * x);
    }
  }

  public boolean arrayListEmpty() {
    return numericalSequence == null || numericalSequence.isEmpty();
  }
}

A few things to note about the above structure:

  1. There are two constructors; the default invokes the one that takes a list of integers as the sequence, so that it reuses any logic that both would need to share. There are no logic in this example, but hopefully you'll come across one soon.
  2. The doSomething() method doesn't accept Integer value, but int value that makes sure that x is never null (Java numerical primitive types cannot be null). This also means, numericalSequence cannot contain null values through doSomething().
  3. Since numericalSequence can be initialized from outside of this class, the arrayListEmpty() method makes sure to check that numericalSequence is either null is truly empty.

Now you can write test cases like so:

@Test
public void arrayListEmpty_WhenListIsNull() {
  MyClass test = MyClass(null);
  assertTrue(test.arrayListEmpty());
}

@Test
public void arrayListEmpty_WhenListIsEmpty() {
  MyClass test = MyClass();
  assertTrue(test.arrayListEmpty());
}

@Test
public void arrayListEmpty_WhenListHasOnlyOneNonNullElement() {
  List<Integer> sequence = new ArrayList<>();
  sequence.add(0);
  MyClass test = new MyClass(sequence);
  assertFalse(test.arrayListEmpty());
}

@Test
public void arrayListEmpty_WhenListHasOnlyOneNullElement() {
  List<Integer> sequence = new ArrayList<>();
  sequence.add(null);
  MyClass test = new MyClass(sequence);
  assertFalse(test.arrayListEmpty());
}

Since doSomething() adds/clears the sequence, when writing tests for doSomething() make sure the call and verify the state of the class by checking the return value of arrayListEmpty().

For example:

@Test
public void doSomething_WhenInputLessThanZero() {
  List<Integer> sequence = new ArrayList<>();
  sequence.add(0);
  MyClass test = new MyClass(sequence);
  test.doSomething(-1);
  assertTrue(test.arrayListEmpty());
}

My intention was to show a couple of things:

  1. Structure your tests cases to be small and concise
  2. Design your class to be easily testable

Hope this helps.

Upvotes: 2

Related Questions