Build aggregate tests that test entire use cases Build a threaded tester that repeatedly performs these tests

return ACCOUNT_WAS_LOCKED; } catch NegativeAmountException negativeAmountException { if amountToWithdraw.isNegative { return SUCCESS; } else { return FAILURE; } } ifamountToWithdraw.greaterThanbalance { return FAILURE; } if amountToWithdraw.isNegative { return FAILURE; } if correctResult.equalsactualResultingBalance { return SUCCESS; } else { return FAILURE; } } } There are two things to note here. The first is that we really are using our distributed exceptions quite heavily. Its important to make sure that the server really does catch improper arguments e.g., an attempt to withdraw a negative amount, and that when the server does throw an exception, it is does so for the correct reasons. The second point to note is that its not entirely obvious what a failure means. Suppose performActualTest fails. This could be due to either of the following reasons: • The lock wasnt set, and another client thread managed to perform an operation. • The actual withdrawal code is flawed. The right way to distinguish between these is to write additional tests that check only the locking mechanism. If the locking mechanism works, then we know the withdrawal code must be flawed. In our case, its not such a big deal; our codebase is small enough to simply spot errors once we have a good hint. However, in larger applications, distinguishing between possible causes of error is incredibly useful. This is an important point. When youre building fine-grained tests, they rely, to a large extent, on the existence of many other fine-grained tests. Every test you add makes the others work better, and makes the testing suite more effective. The general rule of thumb: if you can talk about something and have a name for it e.g., the locking mechanism, you should be able to test it.

13.1.3.2 Build aggregate tests that test entire use cases

The use case weve been relying on, first defined in Chapt er 5 , basically consists of a sequence of simple tests performed on the same account. If this were an industrial application, wed implement this as a single test for the reasons outlined in the next section. However, the code to do so is quite similar to the code for our other tests, and doing so so only serves to make the testing framework more complex; it serves no pedagogical purpose at all.

13.1.3.3 Build a threaded tester that repeatedly performs these tests

The next step is to build an object that can repeatedly invoke tests. In our case, weve chosen to do this by extending Thread . Instances of TestThread repeatedly create tests purely at random, invoke the test, and then store the test object in an instance of TestResultHolder . After doing this a predetermined number of times the argument numberOfOperations is passed into TestThread s constructor, the thread notifies its owner, an instance of TestAppFrame , that it is done: public class TestThread extends Thread { private static final int MILLISECONDS_TO_PAUSE = 2000; private static int _idNumberCounter; private NameRepository _nameRepository; private TestResultHolder _testResultHolder; private int _numberOfOperationsLeft; private TestAppFrame _owner; private String _idNumber; public TestThreadNameRepository nameRepository, int numberOfOperations, TestResultHolder testResultHolder, TestAppFrame owner { _testResultHolder = testResultHolder; _nameRepository = nameRepository; _numberOfOperationsLeft = numberOfOperations; _owner = owner; _idNumber = String.valueOf_idNumberCounter++; } public void run { while_numberOfOperationsLeft 0 { Test testToPerform = getRandomTest ; testToPerform.performTest_idNumber; _testResultHolder.addResulttestToPerform; try { Thread.sleepMILLISECONDS_TO_PAUSE; } catch Exception ignored{} _numberOfOperationsLeft--; } _owner.testThreadFinishedthis; } private Test getRandomTest { double choice = Math.random ; if choice .1 { return new GetBalance_nameRepository; } ifchoice .6 { return new MakeDeposit_nameRepository; } return new MakeWithdrawal_nameRepository; } } The only curious thing here is how we determine what test to use. The answer is that we randomly pick one. At first, this might seem a little disturbing. It may make for more convincing testing if we followed scripts or what we think an actual user session would be like. The answer to this objection is twofold. The first is that, to a large extent, if wed encoded the use cases as tests, those tests would be scripts and would reflect what we think an actual user session would be like. However, even past that, random testing has a significant positive aspect. If an application can handle random method invocations well, it can handle pretty much anything that gets thrown at it. If, on the other hand, our tests reflect what we think the user will do, we havent really tested how robust the application is at all. This leads to a compromise. We can make TestThread an abstract class with a single abstract method: protected abstract Test getRandomTest Then, we create two concrete subclasses of TestThread : getRandomThread Type 1 Randomly chooses from among all the tests available. getRandomThread Type 2 Also makes random choices. But Type 2 chooses only from among the use-case tests in an attempt to simulate the real world more accurately. The reason for having two subclasses of TestThread is simple. They actually return slightly different types of information. Type 1 ensures that the application functions correctly and is reasonably bulletproof. Type 2 can give much more accurate information about application performance and scalability. In our case, since we have no use-case tests, weve implemented only Type 1. 13.1.3.4 Build a thread container that launches many threads and stores the resultsof the test in an indexed structure In our case, this is the main GUI component, TestAppFrame . The Perform Test button has the following action listener attached to it: private class TestLauncher implements ActionListener { public void actionPerformedActionEvent event { try { reset ; numberOfThreads = Integer.valueOf_numberOfThreadsField.getText intValue ; int numberOfOperations = Integer.valueOf_numberOfOperationsField getText.intValue ; int counter; for counter = 0; counter _numberOfThreads; counter++ { TestThread nextThread = new TestThread_nameRepository, numberOfOperations, _testResultHolder, TestAppFrame.this; nextThread.start ; Thread.sleep100; wait a little bit to spread out the load } while someThreadsNotFinished { Thread.sleep10000; 10 seconds. Its not bad } } catch Exception exception {} finally {resetGUI ;} } } This resets all the data structures in TestAppFrame by calling TestAppFrame s reset method: private void reset { _testResultHolder = new TestResultHolder ; _numberOfFinishedThreads = 0; } After this is done, TestLauncher creates a number of instances of TestThread , based on the value the user typed into _numberOfThreadsField , and starts them running. Finally, when all the threads have finished, TestLauncher calls TestAppFrame s resetGUI method, which simply computes the very simple statistics we display in the main text area: private void resetGUI { _testResultHolder.sortResults ; _accountChooser.removeActionListener_chooserListener; _accountChooser.removeAllItems ; _accountChooser.addItemALL_ACCOUNTS; Iterator i = _testResultHolder.getAccountNames.iterator ; whilei.hasNext { _accountChooser.addItemi.next ; } _resultsArea.setText; _accountChooser.addActionListener_chooserListener; computeSummaryforAllAccounts ; }

13.1.3.5 Build a reporting mechanism