The code under test in Practical 2 raises several difficulties when it comes to coverage and testing. Part of this practical is concerned with showing what happens when code isn't designed to be tested, but perhaps these comments will get your creative juices flowing:
There are a couple of techniques you could use in order to test private methods if there's no way of adequately exercising them or verifying their operation through the public interface. One is to use reflection, and another is to use inner classes. Let's look at testing this simple (and useless) counter class:
Source for st.Counter : |
---|
package st; public class Counter { private int m_count = 0; private void add(int amount) { m_count += amount; } public void tick() { add(1); } } |
The only public method is tick(), which uses the private method add() to increase the private count variable. So to see that anything is actually happening here we'll need to dig into its private members.
Reflection takes more work but is cleaner since the tests remain separate from the software under test, and the software under test is unchanged. However we need to locate the private fields and methods, which takes a bit of work, and then modify them so that they're accessible to us (i.e. no longer private).
Source for st.CounterTest : |
---|
package st; import static org.junit.Assert.*; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import org.junit.Before; import org.junit.Test; public class CounterTest { Counter m_counter; Class<?> m_cClass; Field m_cCountField; Method m_cAddMethod; @Before public void setUp() throws NoSuchFieldException, NoSuchMethodException { m_counter = new Counter(); m_cClass = m_counter.getClass(); // Find the private counter field: m_cCountField = m_cClass.getDeclaredField("m_count"); // Modify the field so we can access it: m_cCountField.setAccessible(true); // To find the private add method, we have to identify it by name and // parameter signature: Class params[] = { int.class }; m_cAddMethod = m_cClass.getDeclaredMethod("add", params); // Make the method accessible: m_cAddMethod.setAccessible(true); } @Test public void testConstructor() throws IllegalAccessException { int cCount = m_cCountField.getInt(m_counter); assertEquals("Count not initialised to 0!", cCount, 0); } @Test public void testTick() throws IllegalAccessException { m_counter.tick(); int cCount = m_cCountField.getInt(m_counter); assertEquals("tick() didn't increase count to 1!", cCount, 1); } @Test public void testAdd() throws IllegalAccessException, InvocationTargetException { Object params[] = { -1 }; m_cAddMethod.invoke(m_counter, params); int cCount = m_cCountField.getInt(m_counter); assertEquals("add(-1) didn't decrease count to -1!", cCount, -1); } } |
Here you can see though that the actual test code is much simpler.
Note that your inner test class must be static
(otherwise
it's expected to have a reference to a container instance, which JUnit
doesn't know how to provide).
Source for st.Counter , with InnerCounterTest added: |
---|
package st; import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; public class Counter { private int m_count = 0; private void add(int amount) { m_count += amount; } public void tick() { add(1); } public static class InnerCounterTest { Counter m_counter; @Before public void setUp() { m_counter = new Counter(); } @Test public void testConstructor() { assertEquals("Count not initialised to 0!", m_counter.m_count, 0); } @Test public void testTick() { m_counter.tick(); assertEquals("tick() didn't increase count to 1!", m_counter.m_count, 1); } @Test public void testAdd() { m_counter.add(-1); assertEquals("add(-1) didn't decrease count to -1!", m_counter.m_count, -1); } } } |
Here's a test suite to bind the above together:
Source for st.AllTests : |
---|
package st; import org.junit.runner.RunWith; import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ st.CounterTest.class, st.Counter.InnerCounterTest.class }) public class AllTests { // Everything's done by the annotations above. } |
main()
There's nothing to stop you from calling main()
yourself. The only question is how you can test whether it's done what
you expect.
Some of the methods perform actions which you may find difficult to verify programmatically. If you really can't program your test suite to tell whether those actions have taken place, maybe your test suite could stop and ask somebody else to check those actions?
Finally, bear in mind that one of the critical decisions you always need to make is when to stop testing. This assignment is intended to take you a certain amount of time, and you should be careful to balance how you use that time. Look at the distribution of the marks, and make sure you apportion your effort appropriately!
Version 1.2, 2011/02/17 17:16:34
Informatics Forum, 10 Crichton Street, Edinburgh, EH8 9AB, Scotland, UK
Tel: +44 131 651 5661, Fax: +44 131 651 1426, E-mail: school-office@inf.ed.ac.uk Please contact our webadmin with any comments or corrections. Logging and Cookies Unless explicitly stated otherwise, all material is copyright © The University of Edinburgh |