270 likes | 857 Views
Unit Testing. Testing – A Methodology. For small problems, you write code, test it at the terminal, turn it in, never to see it again. Large systems have a long lifetime. Sitting-at-terminal-type testing is inappropriate for large systems that evolve over time.
E N D
Testing – A Methodology • For small problems, you write code, test it at the terminal, turn it in, never to see it again. • Large systems have a long lifetime. • Sitting-at-terminal-type testing is inappropriate for large systems that evolve over time. • You must have a testing methodology that permanently captures the test cases and can be rerun at will. • Fortunately, serious developers have built frameworks for unit testing: • JUnit is for Java. • CppUnit for C++.
JUnit – The Philosopy • Never throw a test away. • Make every thought a test. • Instead of a print statement or a debugger expression, write it as a test instead. • No test is too hard to encode. • Anything is better than testing at a terminal. • Once tests run, keep them running. • Write a few lines of code, then write a few tests. • When you can't think up any more tests, you're done.
How JUnit Works class Money { private int fAmount; private String fCurrency; public Money(int amount, String currency) { fAmount= amount; fCurrency= currency; } public int amount() { return fAmount; } public String currency() { return fCurrency; } }
Add Monies • public Money add(Money m) { • return new Money(amount()+m.amount(), currency()); • } • Now what? Write More Code? • No, write some tests.
A Simple Test Case public class MoneyTest extends TestCase { //… public void testSimpleAdd() { Money m12CHF= new Money(12, "CHF"); // (1) Money m14CHF= new Money(14, "CHF"); Money expected= new Money(26, "CHF"); Money result= m12CHF.add(m14CHF); // (2) Assert.assertTrue(expected.equals(result)); // (3) } }
Need to override equals public boolean equals(Object anObject) { if (anObject instanceof Money) { Money aMoney= (Money)anObject; return aMoney.currency().equals(currency()) && amount() == aMoney.amount(); } return false; }
Now more "equals" tests public void testEquals() { Money m12CHF= new Money(12, "CHF"); Money m14CHF= new Money(14, "CHF"); Assert.assertTrue(!m12CHF.equals(null)); Assert.assertEquals(m12CHF, m12CHF); Assert.assertEquals(m12CHF, new Money(12, "CHF")); // (1) Assert.assertTrue(!m12CHF.equals(m14CHF)); }
Now a little refactoring… public class MoneyTest extends TestCase { private Money f12CHF; private Money f14CHF; protected void setUp() { f12CHF= new Money(12, "CHF"); f14CHF= new Money(14, "CHF"); } // We can rewrite the two test case methods, removing the common setup code: public void testEquals() { Assert.assertTrue(!f12CHF.equals(null)); Assert.assertEquals(f12CHF, f12CHF); Assert.assertEquals(f12CHF, new Money(12, "CHF")); Assert.assertTrue(!f12CHF.equals(f14CHF)); } public void testSimpleAdd() { Money expected= new Money(26, "CHF"); Money result= f12CHF.add(f14CHF); Assert.assertTrue(expected.equals(result)); } }
Just Two More Steps • define how to run an individual test case, • define how to run a test suite. Let's concentrate on the test suite
A Test Suite public static Test suite() { TestSuite suite= new TestSuite(); suite.addTest(new MoneyTest("testEquals")); suite.addTest(new MoneyTest("testSimpleAdd")); return suite; } or you can do the following: public static Test suite() { return new TestSuite(MoneyTest.class); }
And then to run it… • for the batch TestRunner type: java junit.textui.TestRunner junit.samples.MoneyTest • for the graphical TestRunner type: java junit.awtui.TestRunner junit.samples.MoneyTest • for the Swing based graphical TestRunner type: java junit.swingui.TestRunner junit.samples.MoneyTest
How About Testing Agents? • Send it a message. • Are the state variables set correctly on message reception? • Does the scheduler pick the right actions, if any, to execute? • Do the actions do the right things? • Do they update their state correctly; • Do they send the right messages out.
Checking Message Reception To test the “I’m hungry(customer)” message: • waitingCustomers list is not null. • customer is in the list. • customer is at the end of the list (if you expect a FIFO discipline). • Make sure the semaphore is full.
Controlling the Scheduler • The scheduler’s “automatic” mode interferes with our testing. • Can we turn off the scheduler and call it ourselves from the testing code? • Yes! • Call stopThread() • Make respondToStateChange() public
Our Testing Code So Far Customer c = new Customer(“John”); Waiter w = new Waiter(“Bill”) Host h = new Host(); h.stopThread(); h.msgI’mHungry(c); //now comes code to test the variables Assert.assertTrue(waitingCustomers NEQ NIL)); //other tests … respondToStateChange(); //calls the scheduler
Does The Right Action Fire? • If there are tables empty, you want the waiter assignment action to fire. • If the tables are full you want to make sure that nothing happens. • You have to set up several experiments to check out ALL the possibilities. • Let’s look at the case where tables are free and we expect the waiter assignment action to fire.
Does The Right Action Fire? • The first line action code is a Print • The Junit discussion, they said whenever you have a print statement in your code, you should consider writing a test. • Put the string into a public data structure, such as an array of strings, which can be tested. • Assume your action outputs the string “Assigning waiter Bill to sit customer John at table 1.” • Then, you can write a test: Assert.assertTrue(output[0].equals(“Assigning waiter Bill to sit customer John at table 1”));
Testing the Rest of an Action • Testing state changes will involve techniques we’ve already explored. • What about testing whether the agent sends the right messages to other agents? • That’s not so easy. Mock Objects to the rescue.
Mock Objects • Mock Objects are simulation versions of those that your component/agent uses in doing its work. • With parallel development, the real versions of those objects may not exist. • You must checkout your objects as best you can in preparation for integration.
Testing the Sent Message • Testing to make sure the message that this Host action composes is compliant with the Waiter’s messaging interface. • Mock objects are fake implementations of objects, but have the same interface as the real objects. • Their purpose is to SIMULATE the real objects strictly for testing.
Mock Object Message Handler • It outputs a string that describes the message and its parameters. That string is going to be what you test against. • It then does WHATEVER you need it to do in order to advance your simulation and testing. • Remember it is pure simulation. • It might simply send a message back to your agent. • It might send an error message back. • It might start a timer and send a message back later. You design it.
The Mock Waiter • It needs to output what’s in the message, e.g. “Waiter Bill receives message to seat customer John at table 1.” [You will verify this output in your test code.] • The real waiter would set some variables, seat the customer, take his order, etc. When the customer is done, the waiter informs the Host. • All we care about is the message back to Host. • The mock Waiter can send the message; or • Your testing code can send it (after testing that it received the right message).
The Mock Agents Environment • The real agent should not care whether it is running in the real or test environment. • The mock agents must have the same class names as the real agents. • The Ant environment must store mock objects “near” the real agent and the unit test suites. • Don’t forget, if the interface of an agent changes, the mock agents, and the tests need to be fixed.