Lab 1

JUnit

JUnit is a simple framework which allows us to write automatically rerunnable unit tests. Having a good, comprehensive unit tests suite, offering proper overall coverage with respect to various coverage metrics, remains one of the best software development practices, ensuring better quality and stability for any software product.

In order to use JUnit 5, you need the following imports:

   1 import org.junit.jupiter.api.*;

JUnit 5 Annotations

Below, you can see the most frequently used annotations supported by JUnit 5. Each of them will be then presented in further detail.

   1 @BeforeEach
   2 @BeforeAll
   3 @AfterEach
   4 @AfterAll
   5 @Test
   6 @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
   7 @Disabled

@BeforeEach and @AfterEach annotations

In JUnit 5 we no longer have the conventional methods setUp() and tearDown() from JUnit 3. Instead, there have been introduced the annotations @BeforeEach and @AfterEach, which fulfill the same purpose. Basically, by using annotation @BeforeEach on a method, that method will behave like the setUp() method from JUnit 3, being always invoked before the execution of each unit test, in order to properly set up the test environment. Similarly, a method annotated with @AfterEach will behave like the tearDown() method from JUnit 3 and will always be invoked after the execution of each unit test, in order to clean up the test environment.

   1     @BeforeEach
   2     public void setUp() {
   3         System.out.println("@BeforeEach method will execute before every JUnit 5 test!");
   4     }
   5  
   6     @AfterEach
   7     public void tearDown() {
   8         System.out.println("@AfterEach method will execute after every JUnit 5 test!");
   9     }

@BeforeAll and @AfterAll annotations

The @BeforeAll and @AfterAll annotations work similarly to the @BeforeEach and @AfterEach annotations, with one important difference: the methods annotated with @BeforeAll or @AfterAll will be executed only once per test suite (junit test class). These annotations are specifically meant to do the set up / clean up of test environment at test suite level (instead of test level, as @BeforeEach and @AfterEach do).

   1     @BeforeAll
   2     public static void setUpClass() throws Exception {
   3         System.out.println("@BeforeAll method will be executed before JUnit test for a Class starts");
   4     }
   5 
   6     @AfterAll
   7     public static void tearDownClass() throws Exception {
   8          System.out.println("@AfterAll method will be executed after JUnit test for a Class Completed");
   9     }

@Test annotation

The @Test annotation replaces the previous JUnit 3 convention of having the test prefix for each test method. In earlier JUnit versions it was required for a JUnit test class to extend org.junit.TestCase and for each test method to have its name starting with the test prefix. However, in JUnit 5, it is only needed to annotate each test method with the @Test annotation, just like in the below example.

   1   @Test
   2   public void testMethod() {
   3       System.out.println("");
   4   }

@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)

Sometimes we might have long running tests. In order to put a time limit on test execution, we can use the @Timeout annotation, which takes an integer value, expressed in milliseconds (the unit can be changed). This parameter can be very useful, especially when we are calling services which should answer within a predefined time period.

   1   @Test
   2   @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
   3   public void failsIfExecutionTimeExceeds500Milliseconds() {
   4       // fails if execution time exceeds 500 milliseconds.
   5   }

Exception Testing

It is very common for us to need to test methods that throw exceptions, and we wish to check that they are actually throwing the right exception in the right situation. In order to make sure that the code under test does throw the right exception, we can use assertThrows, which has the expected exception class as parameter (please notice the sintax: ExceptionClassName.class).

   1   @Test
   2   public void exceptionTesting() {
   3       Exception exception = assertThrows(ArithmeticException.class, () -> calculator.divide(1, 0));
   4       assertEquals("/ by zero", exception.getMessage());
   5   }

@Disabled

Sometimes we end up writing unit tests while the actual code is still under development (for example when doing Test Driven Development). It may happen that we wish to keep the unit tests testing the yet unimplemented features, while still running the test suite and getting relevant feedback for the already implemented part, and we would prefer an all green status if the implemented features work flawlessly. In this case, we can annotate the test methods in question with @Disabled("Not yet implemented"), and their execution will be skipped. JUnit 5 ignores all unit tests annotated with the @Disabled annotation.

   1   @Test
   2   @Disabled("for demonstration purposes")
   3   public void skippedTest() {
   4      // not executed
   5   }

JUnit 5 assertions

The JUnit 5 library offers many types of assertions. You can find the most used ones below.

   1 assertEquals(expected, actual, "failure - objects not same (mesaj optional)");
   2 assertArrayEquals(expected, actual, "failure - byte arrays not same (mesaj optional)");
   3 assertTrue(true, "success - this is true");
   4 assertFalse(false, "failure - should be false");
   5 assertNull(null, "should be null");
   6 assertNotNull(new Object(), "should not be null");
   7 assertSame(aNumber, aNumber, "should be same");
   8 assertNotSame(new Object(), new Object(), "should not be same Object");
   9 assertThrows(NumeClasaExceptie.class, () -> {});

It is important to notice that almost all JUnit 5 assertions have the same parameters, in the same, standard order: the expected value, the actual value and optional failure message (which should have a meaningful content when used in practice).

For further details, please refer to the JUnit testing links below.

Follow the principles below:

  • write tests and code in parallel, beginning with the tests
  • give your tests meaningful names, that would descrive the tested case
  • write a separate test for each thing you wish to test

Example

   1   public class Function {
   2      public int calculate(int a, int b) {
   3          if (b == 0) {
   4              return -1;
   5          }
   6
   7          if (a > b) {
   8              return a + b;
   9          }
  10          
  11          return a / b;
  12      }
  13   }
   1   import org.junit.jupiter.api.BeforeEach;
   2   import org.junit.jupiter.api.Test;
   3   import static org.junit.jupiter.api.Assertions.assertEquals;
   4   class FunctionTest {
   5
   6       private Function function;
   7       @BeforeEach
   8       void setup() {
   9           function = new Function();
  10       }
  11
  12       @Test
  13       void givenBEqualTo0_whenCalculate_thenResultIsMinusOne() {
  14           int calculate = function.calculate(2, 0);
  15           assertEquals(-1, calculate);
  16       }
  17
  18       @Test
  19       void givenAGreaterThanB_whenCalculate_thenResultIsSumOfAAndB() {
  20           int calculate = function.calculate(2, 1);
  21           assertEquals(3, calculate);
  22       }
  23
  24       @Test
  25       void givenALessThanB_whenCalculate_thenResultIsDivisionOfAAndB() {
  26           int calculate = function.calculate(1, 2);
  27           assertEquals(0, calculate);
  28       }
  29   }

Exercise 1

You will have to implement and test the class whose draft is presented below. The method under test is, of course, strToInt.

   1 public class StringConv {
   2  public int strToInt(String str) throws NumberFormatException {
   3    return 0;
   4  }
   5 }

The mandatory test data to use is: "0", "1", "", "8". You are, however, free to also write any additional tests you find suitable.

Below you can find several useful methods (and a relevant example for each), which you can use for the implementation of method strToInt,

   1  String str = "unString";
   2  str.charAt(x); // returneaza char-ul de la pozitia x din str
   3  str.length(); // returneaza lungimea sirului str
   4  Character.isDigit(char c); 
   5  Math.pow(baza,exponentul);

You can also find a reference implementation below

   1 public class StringConv {
   2  public int strToInt(String str) throws NumberFormatException {
   3   int sum=0;
   4    for (int i = 0; i < str.length(); i++) {
   5      int digit = (int)str.charAt(str.length()-i-1) - (int)'0';
   6      sum = sum + digit * (int)Math.pow(10, i);
   7    }
   8    return sum;
   9  }
  10 }

Exercise 2

Let us consider the class below

   1 class VVSDate {
   2     public VVSDate(int day, int month, int year);
   3     public int getDays(VVSDate other);
   4 }

Implement a class VVSDate having a method meant to return the distance (pozitive or negative) in days between the date represented by the current object and another date, passed as parameter.

Keep adding tests and functionality in parallel. Do so until you end up with a test suite you consider to properly cover all representative/relevant cases.

If during the implementation of VVSDate you will also define other auxiliary methods, unit tests will always be written before their implementation (for example the method that identifies an year as bissextile or not).

   1 public class VVSDate {
   2         private int day,month,year;
   3 
   4         public VVSDate(int day, int month, int year) {
   5                 this.day = day;
   6                 this.month = month;
   7                 this.year = year;
   8         }
   9 
  10         public int getDays(VVSDate other) {
  11                 return daysSinceZero() - other.daysSinceZero();
  12         }
  13 
  14         private int daysSinceZero() {
  15                 int[] daySums = new int[]{0,31,59,90,120,151,181,212,243,273,304,334,365};
  16                 int d = day + daySums[month-1] + (year*365) + year/4 - year/100 + year/400;
  17                 if ((year%400==0 || (year%4==0 && year%100!=0)) && month<=2)
  18                         d--;
  19                 return d;
  20         }
  21 
  22 }