ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JUnit
    TestMetric 2019. 9. 1. 14:24

    1. Overview

    JUnit is one of the most popular unit-testing frameworks in the java ecosystems and contains a number of useful invocations. This is a method contained in a class which is only used for testing. Setting up JUnit is just including a dependency to pom.xml which require Java 8 to work below 5.x.0 version. To define that a certain method is a test method, annotate it with the @Test annotation.

    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.1.0</version>
        <scope>test</scope>
    </dependency>

    2. Description

    2.1 Annotations of JUnit 4

    JUnit 4 Description
    import org.junit.* Import statement for using the following annotations
    @Test Identifies a method as a test method
    @Before Executed before each test. It is used to prepare the test environment(e.g., read input data, initialize the class)
    @After Executed after each test. It is used to clean up the test environment(e.g., delete temporary data, restore defaults). It can also save memory by cleaning up expensive memory structures.
    @BeforeClass Executed once, before the start of all test. It is used to perform time-intensive activities, for example, to connect to a database. Methods marked with this annotation need to be defined as static to work with JUnit
    @AfterClass Executed once, after all, tests have been finished. It is used to perform clean-up activities to disconnect from a database. Methods annotated with this annotation need to be defined as static to work with JUnit.
    @Ignore or @Ignore("Why disabled") Marks that the test should be disabled. This is useful when the underlying code has been changed and the test case has not yet been adopted. Or if the execution time of this test is too long to be included. It is best practice to provide the optional description, why the test is disabled.
    @Test (expected = Exception.class) Fails if the method does not throw the named exception
    @Test(timeout=100) Fails if the method takes longer than 100 milliseconds.

    2.2 Comparison of annotations between JUnit4 and 5

    JUnit 5 JUnit 4 Description
    import org.junit.jupiter.api.* import org.junit.* Import statement for using the following annotations.
    @Test @Test Identifies a method as a test method.
    @BeforeEach @Before Executed before each test. It is used to prepare the test environment(e.g., read input data, initialize the class).
    @AfterEach @After Executed after each test. It is used to clean up the test environment(e.g., delete temporary data, resource defaults). It can also save memory by cleaning up expensive memory structures.
    @BeforeAll @BeforeClass Executed once, before the start of all tests. It is used to perform time-intensive activities to connect to a database. Methods marked with this annotation need to be defined as static to work with JUnit.
    @AfterAll @AfterClass Executed once, after all, tests have been finished. It is used to perform clean-up activities to disconnect from a database. Methods annotated with this annotation need to be defined as static to work with JUnit.
    @Disabled or @Disabled("Why disabled") @Ignored or @Ignored("Why disabled") Marks that the test should be disabled. 
    Not available, is replaced by org.junit.jupiter.api.Assertion.expectThrows() @Test(Expected = Exception.class) Fails if the method does not throw the named exception.
    Not available, is replaced by AssertTimeout.assertTimeout() and AsertTimeout.assertTimeoutPreemtively() @Test(timeout=100) Fails if the method takes longer than 100 milliseconds.

    2.3 Assert

    Statement Description
    fail([message]) Let the method fail. It might be used to check that a certain part of the code is not reached or to have a failing test before the test code is implemented. The message parameter is optional.
    assertTrue([message,] boolean condition) Checks that the boolean condition is true
    assertFalse([message,] boolean condition) Checks that the boolean condition is false
    assertEquals([message,] expected, actual) Tests that two values are the same. Note: for arrays, the reference is checked not the content of the arrays.
    assertEquals([message,] expected, actual, tolerance) Test that float or double values match. The tolerance is the number of decimals which must be the same.
    assertNull([message,] object) Checks that the object is null
    assertNotNull([message,] object) Checks that the object is not null
    assertSame([message,] expected, actual) Checks that both variables refer to the same object
    assertNotSame([message,] expected, actual) Checks that both variables refer to different objects

    3. Example

    3.1 JUnit test suites

    • Combine several test classes into a test suite
    • Executes all test classes in that suite in the specified order
    • Also, contain other tests suites
    import org.junit.runner.RunWith;
    import org.junit.runners.Suite;
    import org.junit.runners.Suite.SuiteClasses;
    
    @RunWith(Suite.class)
    @SuiteClasses({
            MyClassTest.class,
            MySecondClassTest.class })
    
    public class AllTests {
    
    }

    3.2 Disabling tests

    • @Ignore annotation
    • Assume.assumeFalse or Assume.assumeTrue
    Assume.assumeFalse(System.getProperty("os.name").contains("Linux"));

    3.3 Parameterized test

    • Make a test class into parameterized test by annotating @RunWith(Parameterized.class)
    • Can contain one test method and this method is executed with different parameters provied
    • Must contain a static method annotated with the @Parameters annotation.
      • That method generates and returns a collection of arrays
      • Each item in this collection is used as parameter for the test method
    • Can use the @parameter annotation on public fields to get the test values injected in the test
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.junit.runners.Parameterized;
    import org.junit.runners.Parameterized.Parameters;
    
    import java.util.Arrays;
    import java.util.Collection;
    
    import static org.junit.Assert.assertEquals;
    import static org.junit.runners.Parameterized.*;
    
    @RunWith(Parameterized.class)
    public class ParameterizedTestFields {
    
        // fields used together with @Parameter must be public
        @Parameter(0)
        public int m1;
        @Parameter(1)
        public int m2;
        @Parameter(2)
        public int result;
    
    
        // creates the test data
        @Parameters
        public static Collection<Object[]> data() {
            Object[][] data = new Object[][] { { 1 , 2, 2 }, { 5, 3, 15 }, { 121, 4, 484 } };
            return Arrays.asList(data);
        }
    
    
        @Test
        public void testMultiplyException() {
            MyClass tester = new MyClass();
            assertEquals("Result", result, tester.multiply(m1, m2));
        }
    
    
        // class to be tested
        class MyClass {
            public int multiply(int i, int j) {
                return i *j;
            }
        }
    
    }

    Alternatively, to using the @Parameter annotation you can use a constructor in which you store the values for each test.

    import static org.junit.Assert.assertEquals;
    
    import java.util.Arrays;
    import java.util.Collection;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.junit.runners.Parameterized;
    import org.junit.runners.Parameterized.Parameters;
    
    @RunWith(Parameterized.class)
    public class ParameterizedTestUsingConstructor {
    
        private int m1;
        private int m2;
    
        public ParameterizedTestUsingConstructor(int p1, int p2) {
            m1 = p1;
            m2 = p2;
        }
    
        // creates the test data
        @Parameters
        public static Collection<Object[]> data() {
            Object[][] data = new Object[][] { { 1 , 2 }, { 5, 3 }, { 121, 4 } };
            return Arrays.asList(data);
        }
    
    
        @Test
        public void testMultiplyException() {
            MyClass tester = new MyClass();
            assertEquals("Result", m1 * m2, tester.multiply(m1, m2));
        }
    
    
        // class to be tested
        class MyClass {
            public int multiply(int i, int j) {
                return i *j;
            }
        }
    
    }

    3.4 JUnit Rules

    • Via JUnit rules, you can add behavior to each test in a test class
    • Can create objects which can be used and configured in the test methods
    import org.junit.Rule;
    import org.junit.Test;
    import org.junit.rules.ExpectedException;
    
    public class RuleExceptionTesterExample {
    
      @Rule
      public ExpectedException exception = ExpectedException.none();
    
      @Test
      public void throwsIllegalArgumentExceptionIfIconIsNull() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Negative value not allowed");
        ClassToBeTested t = new ClassToBeTested();
        t.methodToBeTest(-1);
      }
    }

    TemporaryFolder which is useful rule implementations allows us to setup files and folders which are automatically removed after each test run.

    import static org.junit.Assert.assertTrue;
    
    import java.io.File;
    import java.io.IOException;
    
    import org.junit.Rule;
    import org.junit.Test;
    import org.junit.rules.TemporaryFolder;
    
    public class RuleTester {
    
      @Rule
      public TemporaryFolder folder = new TemporaryFolder();
    
      @Test
      public void testUsingTempFolder() throws IOException {
        File createdFolder = folder.newFolder("newfolder");
        File createdFile = folder.newFile("myfilefile.txt");
        assertTrue(createdFile.exists());
      }
    }
    public static class UsesExternalResource {
        Server myServer = new Server();
    
        @Rule public ExternalResource resource = new ExternalResource() {
            @Override
            protected void before() throws Throwable {
                myServer.connect();
            };
    
            @Override
            protected void after() {
                myServer.disconnect();
            };
        };
    
        @Test public void testFoo() {
            new Client().run(myServer);
        }
    }

     

    3.5 Writing custom JUnit rules

    • Need to implement the TestRule interface
      • apply(Statement, Description) return an instance of Statement
      • Statement represent the tests within the JUnit runtime and Statement#evaluate() run these.
      • Description describes the individual test which allows reading information about the test via reflection
    import android.util.Log;
    
    import org.junit.rules.TestRule;
    import org.junit.runner.Description;
    import org.junit.runners.model.Statement;
    
    public class MyCustomRule implements TestRule {
        private Statement base;
        private Description description;
    
        @Override
        public Statement apply(Statement base, Description description) {
            this.base = base;
            this.description = description;
            return new MyStatement(base);
        }
    
        public class MyStatement extends Statement {
            private final Statement base;
    
            public MyStatement(Statement base) {
                this.base = base;
            }
    
            @Override
            public void evaluate() throws Throwable {
                System.
                Log.w("MyCustomRule",description.getMethodName() + "Started" );
                try {
                    base.evaluate();
                } finally {
                    Log.w("MyCustomRule",description.getMethodName() + "Finished");
                }
            }
        }
    }
    @Rule
    public MyCustomRule myRule = new MyCustomRule();

    3.6 Categories

    • Include or exclude tests based on annotations
    public interface FastTests { /* category marker */
    }
    
    public interface SlowTests { /* category marker */
    }
    
    public class A {
        @Test
        public void a() {
            fail();
        }
    
        @Category(SlowTests.class)
        @Test
        public void b() {
        }
    }
    
    @Category({ SlowTests.class, FastTests.class })
    public class B {
        @Test
        public void c() {
        }
    }
    
    @RunWith(Categories.class)
    @IncludeCategory(SlowTests.class)
    @SuiteClasses({ A.class, B.class })
    // Note that Categories is a kind of Suite
    public class SlowTestSuite {
        // Will run A.b and B.c, but not A.a
    }
    
    @RunWith(Categories.class)
    @IncludeCategory(SlowTests.class)
    @ExcludeCategory(FastTests.class)
    @SuiteClasses({ A.class, B.class })
    // Note that Categories is a kind of Suite
    public class SlowTestSuite {
        // Will run A.b, but not A.a or B.c
    }

    3.7 Mocking

    • The real object is exchanged by a replacement which has a predefined for the test.
    • Several frameworks such as Mockito, and so on.

    3.8 Testing Exception

    • The @Test(expected = Exception.class) is limited as it can only test for one exception.
    • to test multiple exceptions, following pattern
    try {
       mustThrowException();
       fail();
    } catch (Exception e) {
       // expected
       // could also check for message of exception, etc.
    }

    3.9 Overview of JUnit 5

    • JUnit Platform: foundation layer which enables different testing frameworks to be launched on the JVM
    • Junit jupiter: is the JUnit 5 test framework which is launched by JUnit Platform
    • JUnit Vintage: legacy TestEngine which runs older tests

    4. References

    https://www.vogella.com/tutorials/JUnit/article.html

    https://www.baeldung.com/junit

    https://www.tutorialspoint.com/junit/index.htm

    https://github.com/junit-team/junit4/wiki

    https://stackoverflow.com/questions/2597271/easy-way-to-get-a-test-file-into-junit

    'TestMetric' 카테고리의 다른 글

    Metric of Performance  (0) 2020.02.26
    Unit Testing vs Integration Testing  (0) 2020.02.23
    Guideline for testing performance  (0) 2019.09.03
    TDD processing with examples  (0) 2019.08.27

    댓글

Designed by Tistory.