-
JUnitTestMetric 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