Tutorial 3: Unit tests and JUnit Runtime logic errors, such as contract violations, are among the more frequent in a poorly debugged program. Logic error should be fixed. However, to fix an error we need to identify it first. Testing is one of the more powerful ways of identifying bugs. However, we should keep in mind that testing only reveals errors; it does not proof their absence. In this tutorial we will be exercising unit tests and the JUnit framework. In addition, we will see good examples of Java code. Unit testing allows us to check the correctness of a simple unit of code the smallest testable part of a program. Ideally unit tests should be simple enough for a fast running time, independent from each other, and should be independent of their execution order. With a Unit Testing framework, such as JUnit, unit test will run automatically each time the code is compiled. Tests should be written before the code. This is the basic principle of the test-driven approach to software development. In this approach, 1. First, we write the tests 2. Second we write the simplest code that passes the test 3. and then, we improve the quality of the written code. For the sake of introduction to JUnit and unit tests in software development we will be use the problem Pirates described in the appendix below. 0. Thoroughly read the problem description given in the appendix. Make sure you fully understand the problem. Simulate example 1 using pen and paper, if necessary. Repeat for example 2. Should you have any doubt about the problem, do not hesitate to ask; 1. To the best of our knowledge, there is no other way of solving this problem than simulate the procedure of the Machiavellian, and irascible captain. 2. One way of performing this simulation starts by converting the input into a list of elements representing the crew circle in the deck. For instance, the input 3151 would be converted into the following list of characters ('E','E','E','F','E','E','E','E','E','F'), where E refers to a enemy, and F to a friend. 3. Based on this list, we can develop a function that checks whether a given step n would kill any captain friend, let s call this function throwoverboardwithstep (int n). It returns true if the captain or one of his friends will be thrown overboard, false otherwise. 4. Now what we need to is call the above function, for n=1, 2, 3,, until a step is found such as no captain friend is killed. 5. The problem description states that all inputs have a solution, therefore the above sequence of calls will surely ends. http://w3.ualg.pt/~jvo/poo POO Tutorial 3-1
6. No need to say that we are going to implement the above mentioned procedure within a class. Let that class be PirateSolver. 7. Once you got a clear picture of what needs to be done, and how code will be organized, we can move to (test driven) implementation. 8. Open eclipse, and create a new java project called Pirates; 9. Add to the project a new class, and call it Main. This class, the client, will read as: import java.util.scanner; public class Main { public static void main(string [] args) { Scanner sc = new Scanner (System.in); PirateSolver solver = new PirateSolver(sc.next()); System.out.println(solver.solution()); sc.close(); 10. We will now prepare the project for test-driven development. Add to the project a new JUnit Test Case (File -> New > Other > Java > JUnit JUnit Test Case). 11. At the top select New JUnit 4 test and name it PiratesUnitTests, then press Finnish. 12. A dialog may open asking this: JUnit 4 is not on the build path. Do you want to add it? If this appears press OK. 13. Now, within the file PiratesUnitTests.java we should have: import static org.junit.assert.*; import org.junit.test; public class PiratesUnitTests { @Test public void test() { fail("not yet implemented"); 14. Rigth after the last import, add: import java.util.*; 15. The first test we will develop will test the behaviour of the PirateSolver constructor A constructor can be seen as a special function, with the same name as the class and without return type, that will be called automatically each time a new class object is created It will be within this constructor that the process described in 2 will take place. For this, replace the function named test() above by the following test: http://w3.ualg.pt/~jvo/poo POO Tutorial 3-2
@Test public void testconstructor() { PirateSolver p = new PirateSolver("3151"); List<Character> sa = new ArrayList<Character>(); // line 4 Collections.addAll(sa, 'E','E','E','F','E','E','E','E','E','F'); // line 5 assertequals(p.getcrew(), sa); PirateSolver q = new PirateSolver("14115123"); assertequals(q.getcrew().size(), 18); Later on this course we will fully understand line 4 and 5 above. For now it is enough to realize that, in line 4, we are creating a list of characters, named sa, implemented as an ArrayList. On the other hand, in line 5 we are adding a set of elements to the list sa. 16. In this test we are using assertequals using two integer arguments. See below, the many possible arguments that assertequals may run with. 17. Run the test just created as JUnit Test (Run -> Run As -> JUnit test). 18. At this time, we have just created the test, and run it before writing any program code. We can observe immediately that the test is doing its job by providing us with a red bar indicating that the test has failed it is the test job to fail whenever no source code for testing exists. 19. Now write the simplest code that passes this test. That is, add the class PirateSolver to the project and implement its constructor. That is complete the code bellow: import java.util.*; class PirateSolver { private List<Character> piratelist; private int noenemies; public PirateSolver(String s) { piratelist = new ArrayList<Character>(); int len = s.length(); noenemies = 0; // To be completed. //From String s form list piratelist as specified in step 2 20. Also implement the public method getcrew() that returns the list representing the crew (i.e., the variable piratelist). 21. You are going to need to know the following. To assess the character at position i in the String s use Character c = s.charat(i); To convert a character c to its corresponding integer i use: int i = Character.digit(c, 10); http://w3.ualg.pt/~jvo/poo POO Tutorial 3-3
To add a Character c to the piratelist use at position j; piratelist.add(j, c); or to add c at the end of the list use: piratelist.add(c); 22. Now run the test again. 23. If everything was ok, the JUnit offer you a magnificent green bar, denoting that the code has passed the test. 24. We can now refine your constructor code, if necessary. 25. Now, let s write the test for our function boolean throwoverboardwithstep (int n). This test can read as: @Test public void testthrowoverboardwithstep() { PirateSolver p = new PirateSolver("3151"); asserttrue(p.throwoverboardwithstep(1)); assertfalse(p.throwoverboardwithstep(3)); 26. After writing this test, should we need to say anything else about the behaviour of throwoverboardwithstep? As you can see, sometimes, a test can be also a convenient way of documenting code. 27. Now and only now write the code for public boolean throwoverboardwithstep (int n), as described in 3. 28. Run all tests again. Did you get the green bar? Great! 29. Now write a test for the function public int solution(). 30. Write the simplest code that passes the test just written. Refine it. 31. Finally, submit your program (not the tests) to POO 2016/17 mooshak (http://mooshak.deei.fct.ualg.pt/~mooshak) problem E, and get and an Accepted. Note: The file where unit tests are implemented: PiratesUnitTests.java, cannot be submitted to Mooshak. or a Compile Time Error will be issued. The Java environment currently installed in Mooshak does not have the classes required to support unit tests. Please copy, comment, and past the developed unit tests to the Main.java file. 32. There are many other things that we can do with the JUnit 4.0 framework. Here is a list of the available assertions in JUnit 4.0. Possible arguments are given within parenthesis. asserttrue http://w3.ualg.pt/~jvo/poo POO Tutorial 3-4
(boolean) Reports an error if boolean is false (String, boolean) Adds error String to output assertfalse (bolean) Reports an error if boolean is true (String, boolean) Adds error String to output assertnull (Object) Reports an error if object is not null (String, Object) Adds error String to output assertnotnull (Object) Reports an error if object is null (String, Object) Adds error String to output assertsame (Object, Object) Reports error if two objects are not identical (String, Object, Object) Adds error String to output assertnotsame (Object, Object) Reports error if two objects are identical (Styring, Object, Object) Adds error String to output assertequals (Object, Object) Reports error if two objects are not equal (String, Object, Object) Adds error String to output (String, String) Reports delta between two strings if the two strings are not equal (String, String, String) Adds error String to output (boolean, boolean) Reports error if the two booleans are not equal (String, boolean, boolean) Adds error String to output (byte,byte) Reports error if the two bytes are not equal (String, byte, byte) Adds error String to output (char, char)reports error if two chars are not equal (String, char, char) Adds error String to output (short, short) Reports error if two shorts are not equal (String, short, short) Adds error String to output (int, int) Reports error if two ints are not equal (String, int, int) Adds error String to output (long, long) Reports error if two longs are not equal (String, long, long) Adds error String to output (float, float, float) Reports error if the first two floats are not within range specified by third float (String, float, float, float) Adds error String to output (double, double, double) Reports error if the first two doubles are not within range specified by third double (String, double, double, double) Adds error String to output (object[], object[]); Reports error if either the legnth or element of each array are not equal. New to JUnit 4 (String, object[], object[]) Adds error String to output. 33. [Advanced: Return here later] Besides that, you can use the arguments of annotation @Test for testing important behaviour of your code, such as Exception throwing, or code running time. http://w3.ualg.pt/~jvo/poo POO Tutorial 3-5
34. Supposed that you want to avoid clonage in class PirateSolver. You can write a test for checking whether the appropriated Exception is actually being thrown each time the clone() method is invoked. Such a test can be given by: @Test (expected=clonenotsupportedexception.class) public void testclonenotsupportedexception() throws CloneNotSupportedException { PirateSolver b1 = new PirateSolver("3151"); PirateSolver b2 = (PirateSolver) b1.clone(); 35. Also, we can make sure that your code does not take more than a certain amount of time to execute. For instance, you can make sure that solving a given instance of our problem does not take more than 3s: @Test (timeout=3000) public void testeficientsolve() { PirateSolver p = new PirateSolver("4444"); assertequals(p.solution(), 5851); 36. Learn more on JUnit at http://www.junit.org http://w3.ualg.pt/~jvo/poo POO Tutorial 3-6
Appendix: Pirates In a dreadful storm, Malbolge, the terrifying pirate ship raging the Algarve coast, can only be saved by lightening its cargo. The Machiavellian, irascible captain grabbed this opportunity to get rid of his enemies in the crew, whom, he believed, were plotting against him. To achieve this, he convinced all the men to form a circle on the deck and then, starting at one of them, every nth man will to be thrown overboard, in a way that appeared random to the crew, who are not particularly gifted mathematicians. Astutely, the captain managed to set up the sitting arrangement beforehand in such a way that all his enemies would be thrown out. However, the captain is thick as a brick and forgot the step number (the step number is the n, in the phrase every nth man above.) This is dangerous, because unless he is able to recall the right step immediately, some of his friends, and perhaps the captain himself, may end up feeding the sharks. Task Write a program that, given a sequence of numbers describing the sitting arrangement of friends and enemies around the circle, computes the step n by which all captain s enemies are cast overboard, before that happens to any of the captain s friends. The sequence is coded by a decimal number where the leftmost digit stands for the first few enemies, the next digit for the first few friends, then enemies again, and so on. The throw overboard counting starts at the first enemy. For example, the number 2251 represents a sequence starting with two enemies, followed by two friends, then five enemies and finally one friend. If n is 5, the first man to be thrown overboard is indeed one of the enemies, the first one in the second group. Input The input contains one line only with an integer number F representing the sequence of captain enemies and friends (including himself) in the circle, as explained. F is such that 10 < F < 99999999, has an even number of digits, and its decimal representation does not contain any zeros. Output The output shall contain only one line, in which there is one positive integer representing the smallest step n by which the captain and all his friends are saved and all the captain enemies are thrown overboard. More precisely, the smallest step such that all enemies are thrown overboard before that would happen to any of the friends. (We suppose that when all the enemies are gone, the captain will suspend the operation.) Remember, the captain holds a feasible sequence. Therefore, all inputs have a solution that will not be greater than 9999. Example of input 1 Example of output 1 3151 3 Example of input 2 Example of output 2 14115123 12 Credits: Problem D from TIUP 2008, Stage 3, May 21, Universidade do Algarve http://w3.ualg.pt/~jvo/poo POO Tutorial 3-7