JUnit Example

junit

UPDATE – I received a separate email message from a fellow software developer. He is interested in providing a text file and creating a new test method to process it. The idea is to provide a text file with the number of triangles to test followed by the triangle sides and associated type so we would be able to test them all at once. This new test was NOT part of the initial HackerRank challenge.

UPDATE – I received an email message from a fellow software developer. Went back (about three years) and located the code for this post in one of my older work-spaces. I updated the format of the post so it is easier to follow. I also added the code to my GitHub repository (see below).

:::: :::: :::: :::: ::::

After the post on unit testing I received a request to produce and explain sample code using JUnit. This is what I came up after searching the web using Google. I decided to use Eclipse for this example.

First let’s take a look at the output produced by the main application:

>>> a: 3
>>> b: 4
>>> c: 5
<<< a: 3 b: 4 c: 5
<<< t is: Scalene

An equilateral triangle has all sides the same length. An isosceles triangle has two sides of equal length. A scalene triangle has all its sides of different lengths.scalene

The source for Solution.java follows:

 

 

package junit.test.sample;

import java.util.Scanner;

/*
 * 
 */
public class Solution {

	/*
	 * 
	 */
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		System.out.print(">>> a: ");
		int a = sc.nextInt();
		System.out.print(">>> b: ");		
		int b = sc.nextInt();
		System.out.print(">>> c: ");				
		int c = sc.nextInt();	
		System.out.println("<<< a: " + a + " b: " + b + " c: " + c);
		Triangle t = new Triangle(a, b, c);
		System.out.println("<<< t is: " + t.triangleType());
		sc.close();
	}

}

The program prompts for the 3 sides of a triangle. A triangle is instantiated. The type of triangle is then obtained and printed. The code for the Triangle.java class follows:

package junit.test.sample;


/*
 * 
 */
public class Triangle {

	// **** ****
	
	enum TriangleTypes {
		Equilateral, Isosceles, Scalene
	}
	
	// **** attributes ****
	
	int 	a;
	int 	b;
	int 	c;
	
	// **** constructor ****

	public Triangle(int a, int b, int c) {
		this.a = a;
		this.b = b;
		this.c = c;
	}
	
	// **** methods ****
	
	public String triangleType() {
		
		String	type	= "";
		String	errMsg	= "";
		
		// **** check for invalid side sizes ****
		
		if (a <= 0) {
			errMsg = "a <= 0";
		}
		if (b <= 0) {
			errMsg = "b <= 0";
		}
		if (c <= 0) {
			errMsg = "c <= 0";
		}
		
		// **** check for a side too long ****
		
		if ((a + b <= c) || (b + c <= a) || (c + a <= b)) {
			errMsg = String.format("not a valid triangle a: " + a + " b: " + b + " c: " + c);
		}
		
		// **** classify the triangle (if needed) ****
		
		if (errMsg.length() != 0) {
			type = errMsg;
		} else {
			if ((a == b) && (b == c)) {
				type = TriangleTypes.Equilateral.name();
			} else if ((a == b) || (b == c) || (c == a)) {
				type = TriangleTypes.Isosceles.name();
			} else {
				type = TriangleTypes.Scalene.name();
			}	
		}

		// **** ****
		
		return type;
	}
	
}

The triangleType() method performs some tests and is they all pass it classifies the specified triangle. Now let’s take a look at the JUnit test output:

<<< setUpClass ...
<<< setUp ...
<<< testIsosceles ...
<<< type ==>Isosceles<===
<<< expected ==>Scalene<==
<<< tearDown ...
<<< setUp ...
<<< testScalene ...
<<< type ==>Scalene<==
<<< expected ==>Scalene<==
<<< tearDown ...
<<< setUp ...
<<< testEquilateral ...
<<< type ==>Equilateral<==
<<< expected ==>Equilateral<==
<<< tearDown ...
<<< tearDownClass ...

The JUnit code follows from the TriangleTest.java file:

package junit.test.sample;


/*
 * 
 */
public class Triangle {

	// **** ****
	
	enum TriangleTypes {
		Equilateral, Isosceles, Scalene
	}
	
	// **** attributes ****
	
	int 	a;
	int 	b;
	int 	c;
	
	// **** constructor ****

	public Triangle(int a, int b, int c) {
		this.a = a;
		this.b = b;
		this.c = c;
	}
	
	// **** methods ****
	
	public String triangleType() {
		
		String	type	= "";
		String	errMsg	= "";
		
		// **** check for invalid side sizes ****
		
		if (a <= 0) {
			errMsg = "a <= 0";
		}
		if (b <= 0) {
			errMsg = "b <= 0";
		}
		if (c <= 0) {
			errMsg = "c <= 0";
		}
		
		// **** check for a side too long ****
		
		if ((a + b <= c) || (b + c <= a) || (c + a <= b)) {
			errMsg = String.format("not a valid triangle a: " + a + " b: " + b + " c: " + c);
		}
		
		// **** classify the triangle (if needed) ****
		
		if (errMsg.length() != 0) {
			type = errMsg;
		} else {
			if ((a == b) && (b == c)) {
				type = TriangleTypes.Equilateral.name();
			} else if ((a == b) || (b == c) || (c == a)) {
				type = TriangleTypes.Isosceles.name();
			} else {
				type = TriangleTypes.Scalene.name();
			}	
		}

		// **** ****
		
		return type;
	}
	
}

Of interest are the import statements. They are satisfied by referencing the junit-4.10.jar file. There are annotations for most of the methods. The following table provides a brief description:

Annotation Description
@Test Turns a public method into a JUnit test case. Adding a timeout will cause the test case to fail after time milliseconds. Adding an expected exception will cause the test case to fail if exception is not thrown.
@Before Method to run before every test case.
@After Method to run after every test case.
@BeforeClass Method to run once, before any test cases has run.
@AfterClass Method to run once, after all test cases has run.

Even though the sample code does not show @Test(timeout = 1234) I included it in the description of the @Test annotation because it is useful and always used by HackerRank :o)junit_methods

The following table contains a list of useful assertions:

Method Description
assertTrue(test) Fails if the Boolean test is false.
assertFalse(test) Fails if the Boolean test is true.
assertEquals(expected, actual) Fails if the values are not equal.
assertSame(expected, actual) Fails if the values are not the same (by ==).
assertNotSame(expected, actual) fails if the values are the same (by ==).
assertNull(value) fails if the given value is not null.
assertNotNull(value) Fails if the given value is null.
fail() Causes current test to immediately fail.

A sample for the test file used to specify a set of triangles follows:

3
3 4 5 S
10 3 10 I
7 7 7 E

The file starts with the count of triangles which in this case is three. Each triangle is defines by the length of its sides. The file is classified by a single uppercase letter, S for Scalene, I for Isosceles and E for Equilateral. We could have used the entire words but for simplicity we will use single characters.

The new JUnit test follows:

	// **** tests to read n triangles from file ****
	@Test
	public void testTriangles() {
		
		// **** ****
		System.out.println("<<< testTriangles ...");

		// **** open file with test cases (one per line) ****
		BufferedReader br = null;
		try {
			br = new BufferedReader(new FileReader("c:\\Temp\\triangles.txt"));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}

		// **** read count of triangles ****
		String line = null;
		try {
			line = br.readLine();
		} catch (IOException e) {
			e.printStackTrace();
		}
		System.out.println("<<< line ==>" + line + "<==");
		
		// **** convert to integer ****
		int n = Integer.parseInt(line);
		System.out.println("<<< count: " + n);

		// **** loop once per triangle ****
		for (int i = 0; i < n; i++) {

			// **** read sides and triangle type ****
			try {
				line = br.readLine();
			} catch (IOException e) {
				e.printStackTrace();
			}
			System.out.println("<<< line ==>" + line + "<==");
		
			// **** split the line ****
			String s[] = line.split(" ");
			
			// **** extract triangle sides ****
			int a = Integer.parseInt(s[0]);			
			int b = Integer.parseInt(s[1]);
			int c = Integer.parseInt(s[2]);
			System.out.println("<<< a: " + a + " b: " + b + " c: " + c);
			
			// **** extract triangle type ****
			String expected = null;
			switch (s[3]) {
			case "I":
				expected = TriangleTypes.Isosceles.name();
				break;
			case "E":
				expected = TriangleTypes.Equilateral.name();
				break;
			case "S":
				expected = TriangleTypes.Scalene.name();
				break;
			}
			System.out.println("<<< expected ==>" + expected + "<===");
			
			// **** instantiate triangle****
			Triangle t = new Triangle(a, b, c);

			// **** compute the triangle type ****
			String type = t.triangleType();
			System.out.println("<<< type ==>" + type + "<===");
			
			// **** ****
			assertEquals(type, expected);
		}

		// **** close file ****
		try {
			br.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

The idea is to replicate one of the existing cases but provide the information from a file and loop as many times as needed.

The output using the sample text file follows:

<<< setUpClass ...
<<< setUp ...


<<< testTriangles ...       <----
<<< line ==>3<==
<<< count: 3
<<< line ==>3 4 5 S<==
<<< a: 3 b: 4 c: 5
<<< expected ==>Scalene<===
<<< type ==>Scalene<===
<<< line ==>10 3 10 I<==
<<< a: 10 b: 3 c: 10
<<< expected ==>Isosceles<===
<<< type ==>Isosceles<===
<<< line ==>7 7 7 E<==
<<< a: 7 b: 7 c: 7
<<< expected ==>Equilateral<===
<<< type ==>Equilateral<===
<<< tearDown ...


<<< setUp ...
<<< testIsosceles ...
<<< type ==>Isosceles<===
<<< expected ==>Isosceles<==
<<< tearDown ...
<<< setUp ...
<<< testScalene ...
<<< type ==>Scalene<==
<<< expected ==>Scalene<==
<<< tearDown ...
<<< setUp ...
<<< testEquilateral ...
<<< type ==>Equilateral<==
<<< expected ==>Equilateral<==
<<< tearDown ...
<<< tearDownClass ...

It is extremely important to design classes and methods so they can be tested. Always include unit testing when developing code. If possible use some tool to automatically and continuously run tests on your code.

My entire code for this solution may be found in my HackerRank repository.

If you have comments or questions regarding this or any other post in this blog, or if you need assistance with any aspect of the SDLC of a product, feel free to leave me a note bellow. Requests for help will not be made public.

Keep on reading and experimenting. That is the best way to learn.

John

Follow me on Twitter:  @john_canessa

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.