0

In a school assignment, I should write blackbox test for a method that returns true for a parameter < 80 and false otherwise. Currently my approach would be

for (int i = 0; i < 80; i++) {
    assertTrue(someMethod(i));
}
for (int i = 80; i <= 100; i++) {
    assertFalse(someMethod(i));
}

However, this would require 100 seperate assertions. Is there a best/better practice method? If relevant, I'm using JUnit 5 but could switch to JUnit 4 if required (it's just a school assignment after all). Regards.

5 Answers5

1

For JUnit 5 consider the repeated test feature:

https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests

For your case you could repeat 100 times:

@ParameterizedTest
@MethodSource("args")
public void so(int argument)
{
   assertTrue((argument < 80) == someMethod(argument));
}

private static IntStream args()
{
   return IntStream.range(0, 100);
}

I would agree checking 1 - 100 is not totally necessary, just check around 80 (79, 80, 81). The only other case that might be worthwhile is checking a negative number but even that seems overkill in this case.

If you do decide to just check 79,80,81 then the ValueSource annotation is cleaner:

@ParameterizedTest
@ValueSource(ints = { 79, 80, 81 })
public void so(int argument)
{
   assertTrue((argument < 80) == someMethod(argument));
}
Mike
  • 4,722
  • 1
  • 27
  • 40
  • ParameterizedTest is a good tool for the job. Logic in tests is not a good idea however. You suggest replicating the <80 logic of the code under test in the test itself. This is a code smell and should be avoided – Kotse Dec 08 '18 at 20:19
0

You'd usually be fine with just testing the edge cases, and maybe one additional representative of one of the ranges, eg

assertTrue(someMethod(79));
assertFalse(someMethod(80));

// not reaaaally necessary
assertTrue(someMethod(50));
assertFalse(someMethod(100));

In your case, you might also want to add the cutoff as a constant (eg static final int THRESHOLD = 80) and use it in your test cases; this way it's easier to change if the threshold changes.

daniu
  • 14,137
  • 4
  • 32
  • 53
0

What you could do (in JUnit5) is the following:

Write (at least) three test cases that cover the three main use cases, those are an input < 80, an input > 80 and the input = 80.

It would be looking like this:

@Test
@DisplayName("[< 80]")
@Tag("below_eighty")
void numberBelowEightyTest(TestInfo testInfo) {
    int number = 79;
    assertTrue(someMethod(number));
}

@Test
@DisplayName("[= 80]")
@Tag("equals_eighty")
void numberEqualsEightyTest(TestInfo testInfo) {
    int number = 80;
    assertFalse(someMethod(number));
}

@Test
@DisplayName("[> 80]")
@Tag("above_eighty")
void numberAboveEightyTest(TestInfo testInfo) {
    int number = 81;
    assertFalse(someMethod(number));
}

Having tested those three use cases, you can be sure for all remaining numbers due to having tested the border directly as well as the nearest neighbours.

deHaar
  • 17,687
  • 10
  • 38
  • 51
0

In case someone using JUnit 4:

@RunWith(Parameterized.class)
public class PerformanceStatusTest {

    @Parameterized.Parameters
    public static List<Integer> data() {
        return IntStream.range(0, 100)
                .boxed()
                .collect(Collectors.toList());
    }

    private int testValue;

    public PerformanceStatusTest(final int testValue) {
        this.testValue = testValue;
    }

    @Test
    public void someMethodTest() {
        assertTrue((testValue < 80) == someMethod(testValue));
    }
} 
Ulad
  • 1,083
  • 8
  • 17
0

To mention another option which will - in this simple case - do full range checking: Use property-based testing with jqwik on the JUnit 5 platform:

import org.junit.jupiter.api.*;

import net.jqwik.api.*;
import net.jqwik.api.constraints.*;

class PartitionedFunctionProperty {

    @Property
    void below80returnTrue(@ForAll @IntRange(min= 0, max = 79) int aNumber) {
        Assertions.assertTrue(someMethod(aNumber));
    }

    @Property
    void from80returnFalse(@ForAll @IntRange(min = 80, max = 100) int aNumber) {
        Assertions.assertFalse(someMethod(aNumber));
    }

    private boolean someMethod(int aNumber) {
        if (aNumber < 80) return true;
        return false;
    }
}

If you want to really see that the full range is really being generated, add annotation @Report(Reporting.GENERATED) to the property method and you'll get the following output:

timestamp = 2018-12-05T14:50:36.668, generated = [80]
timestamp = 2018-12-05T14:50:36.671, generated = [81]
timestamp = 2018-12-05T14:50:36.672, generated = [82]
timestamp = 2018-12-05T14:50:36.672, generated = [83]
...
timestamp = 2018-12-05T14:50:36.676, generated = [98]
timestamp = 2018-12-05T14:50:36.676, generated = [99]
timestamp = 2018-12-05T14:50:36.677, generated = [100]

timestamp = 2018-12-05T14:50:36.679
    tries = 21
    checks = 21
    generation-mode = EXHAUSTIVE
    after-failure = SAMPLE_ONLY
    seed = 4490524914178941008

Find the full working example on github.

johanneslink
  • 4,877
  • 1
  • 20
  • 37