Unit Testing in Java with JUnit

Published in the Random group

What is unit testing in Java?

Before we get into learning JUnit in Java, let’s briefly overview what unit testing is and why it’s so popular (if you know this stuff already, skip to ‘How do I write a JUnit Test in Java?’). Unit testing in Java makes large-scale software development much more efficient and effortless. It can help both individuals, and teams cut countless hours off of debugging and streamline the collaboration process immensely. Unit Testing in Java with JUnit - 1

https://junit.org/junit4/

The essential idea of unit testing is this: write atomic tests of individual features (called unit tests) and slowly add more features after testing and ensuring the previous ones work. It’s an extremely simple but powerful idea. As an example of how this process might look, imagine you were building a virtual scientific calculator. On top of the apparent arithmetic operators (+, -, x, %), this calculator would have advanced features that require other subfeatures to work within it. To calculate exponents, your calculator needs to be able to multiply correctly. So a unit testing approach to building and testing this calculator would be:
  • Write an addition function. Test it carefully, change it, repeat until it works.
  • Do the same for subtraction, multiplication, division functions.
  • Use these base operators to write more advanced operator functions like exponents, then test those functions as well.
This ensures that features that build off other smaller subfeatures not only work properly in their own right but do not have faulty subfeatures within it. For example, if I am testing the exponent function and something is going wrong, I know that the bug probably isn’t in the multiplication subfeature, because the multiplication function was already extensively tested. This vastly eliminates the total amount of code I need to backtrace and inspect to find the bug. Hopefully, this trivial example makes clear how the thought process around Unit Testing is structured. But how does unit testing interact with the rest of the software development process? What if you have even more complex features, which need to be able to work and communicate together? Unit testing is insufficient to ensure that such complex features can work properly together. In fact, it is just the first step of the Four Levels of Software Testing (I use capital letters because I refer to the industry standard or the most common approach to testing software). The last three steps are Integration Testing, System Testing, and Acceptance Testing. These all probably mean exactly what you think they do, but let me clarify: Integration testing is what we would do to assure the those as mentioned above, “complex features,” properly interact with each other. (e.g., making sure the calculator can handle “3 + 7 * 4 - 2”) System testing is testing the overall design of a particular system; there are often multiple systems of complex features working together in a product, so you group these into systems and test them individually. (e.g. if you were building a graphing calculator, you’d first build the arithmetic ‘system’ to deal with numbers, testing until it works as intended, and then you’d build and test the graphing ‘system’ to deal withdrawing, as it would build off of the arithmetic system). Acceptance testing is user-level testing; it’s seeing if all systems can work in sync to create a finished product ready to be accepted by users (e.g., users testing the calculator). Software developers can sometimes ignore this final step of the process, as companies will often have other employees deploy user (beta) tests separately.

How do I write a JUnit test in Java?

Now that you have a clearer idea of the benefits and limitations of unit testing, let’s take a look at some code! We will be using a popular Java testing framework called JUnit (another popular one is TestNG, which you can also use if you like. They are very similar, syntactically; TestNG is inspired by JUnit). You can download and install JUnit here. For this example code, we will continue off of the ‘scientific calculator’ example I was mentioning earlier; it’s pretty simple to wrap your head around, and the test code is super easy. Conventional practice is to write separate test classes for each of your classes, so that’s what we’ll do. Let’s assume that at this point, we have a Math.java file with all the math functions in it (including Math.add), and we are writing a MathTests.java file in the same package. Now let’s set up import statements and class body: (POSSIBLE JUnit INTERVIEW QUESTION: You may be asked where to place your JUnit test and whether or not you need to import your source files. If you are writing your test classes in the same package as your main classes, then you do not need any import statements for your source files in the test class. Otherwise, make sure you are importing your source files!)

import org.junit.jupiter.Test;    //gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; //less typing :) 

public class MathTests {
	//...
}
The first import statement gives us the @Test header. We write ‘@Test’ directly on top of every test function definition, so that JUnit knows that this is a singular unit test that can be run separately. Later on, I will show you how you can run specific unit tests using this header. The second import statement saves us a little bit of typing. The primary JUnit function we use to test our functions is to Assert.assertEquals(), which takes two parameters (actual value and expected value) and makes sure they are equal. Having this second import statement allows us just to type ‘assertEquals(...’ instead of having to specify every time which package it is a part of. Now let’s write a very simple test case to verify that 2 + 2 is indeed 4!

import org.junit.jupiter.Test; // gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; // less typing :) 


public class MathTests {
	@Test
	public void add_twoPlusTwo_returnsFour(){
	final int expected = 4;
	final int actual = Math.add(2, 2);
	assertEquals(“2+2 is 4”, actual, expected);
	}
}
Let’s go over each of test function’s five lines and what they do: Line 5: This @Test header specifies that the function definition below add_twoPlusTwo_returnsFour() is indeed a test function that JUnit can run separately. Line 6: This is the function signature for our test case. Test cases are always very singular; they only test one specific example, such as 2+2=4. It is convention to name your test cases in the form “[function]_[params]_returns[expected](),” where [function] is the name of the function you are testing, [params] are the specific example parameters you are testing, and [expected] is the expected return value of the function. Test functions almost always have a return type of ‘void’ because the main point of the whole function is to run assertEquals, which will output to the console whether or not your test passed; you do not need any other data to be returned anywhere. Line 7: We declare a ‘final’ variable of the return type of Math.add (int), and name it ‘expected’ by convention. Its value is the answer we are expecting (4). Line 8: We declare a ‘final’ variable of the return type of Math.add (int), and name it ‘actual’ by convention. Its value is the result of Math.add(2, 2). Line 9: The golden line. This is the line that compares actual and expected and tells us we passed the test only if they are equal. The first parameter passed “2+2 is 4” is a description of the test function.

What if my function should throw an exception?

If your specific test example should throw an exception instead of asserting that an actual and expected value are equal, then JUnit has a way of clarifying this in the @Test header. Let’s take a look at an example below. Assuming we have a function in Math.java called Math.divide, we want to make sure that inputs cannot be divided by 0. Instead, trying to call Math.divide(a, 0) for any ‘a’ value should throw an exception (ArithmeticException.class). We specify so in the header as such:

import org.junit.jupiter.Test; // gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; // less typing :) 


public class MathTests {
	@Test (expectedExceptions = ArithmeticException.class)
	public void divide_byZero_throwsException() throws ArithmeticException{
	Math.divide(1, 0);
	}
}
You can have more than one exception for expectedExceptions, just make sure to use brackets and commas to list your exception classes, as such:

expectedException = {FirstException.class, SecondException.class, … }

How do I run my JUnit tests in Java?

How to add JUnit to IntelliJ: https://stackoverflow.com/questions/19330832/setting-up-junit-with-intellij-idea You can run your project as you normally would run the tests. Running all tests in a test class will run them in alphabetical order. In JUnit 5, you can add a priority to the tests by adding a @Order tag. An example:

@TestMethodOrder(OrderAnnotation.class)
public class Tests {
…
@Test
@Order(2)
public void a_test() { … }

@Test
@Order (1)
public void b_test() { … }
…
}
Even though a_test() comes before b_test() alphabetically and in the code, b_test() will run before a_test() here, because 1 comes before 2 in Order. So that’s about all for the basics of JUnit. Now, let’s tackle a couple of common JUnit Interview Questions, and learn some more about JUnit along the way!

JUnit Interview Questions (Additional Information)

Here I’ve collected the most popular JUnit interview questions. If you have something to add — feel free to do this in the comments below. Q: What method can you call in your test method to automatically fail a test? A: fail(“error description here!”); Q: You are testing a Dog class; to test a Dog object, you need to instantiate it before you can run tests on it. So you write a setUp() function to instantiate the Dog. You only want to run this function once during all of the testing. What must you put directly above the setUp() function signature so JUnit knows to run setUp() before running the tests? A: @BeforeClass (@BeforeAll in JUnit 5) Q: What must be the function signature of the setUp() function described above? A: public static void. Any function with @BeforeClass (@BeforeAll in JUnit 5) or @AfterClass (@AfterAll in JUnit 5) needs to be static. Q: You are done testing the Dog class. You write void tearDown() function which cleans up data and prints information to console after each test. You want this function to run after every single test. What must you put directly above the tearDown() function signature so JUnit knows to run tearDown() after running each test? A: @After (@AfterEach in JUnit 5)
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION