574 WebSphere Studio Application Developer Version 5 Programming Guide What is JUnit? JUnit is an open source testing framework that is used to develop and execute unit tests in Java. It was written by Erich Gamma, one of four notable authors, who wrote the classic book Design Patterns ; and Kent Beck, who has also written extensively about object development and first described the eXtreme Programming (XP) software development process. A good starting point for finding information about JUnit on the Web is the JUnit Web site: http://www.junit.org/ This site contains documentation and links, as well as a free download that includes both the JUnit source and compiled code. Unit testing Unit tests are informal tests that are generally executed by the developers of the application code. They are often quite low-level in nature, and test the behavior of individual software components such as individual Java classes, servlets, or EJBs. Because unit tests are usually written and performed by the application developer, they tend to be white-box in nature, that is to say they are written using knowledge about the implementation details and test specific code paths. This is not to say all unit tests have to be written this way; one common practice is to write the unit tests for a component based on the component specification before developing the component itself. Both approaches are valid and you may want to make use of both when defining your own unit testing policy. Why unit testing? On the surface, this is a question with a straightforward answer. We test to find defects in our code, and to verify that changes we have made to existing code do not break that code. Perhaps it is more useful to look at the question from the opposite perspective, that is to say, why do developers not perform unit tests? In general the simple answer is because it is too hard, and because nobody forces them to. Writing an effective set of unit tests for a component is not a trivial undertaking. Given the pressure to deliver that many developers find themselves subjected to, the temptation to postpone the creation and execution of unit tests in favor of delivering code fixes or new functionality is often overwhelming. Chapter 17. JUnit and component testing 575 In practice, this usually turns out to be a false economy—developers very rarely deliver bug-free code, and the discovery of code defects and the costs associated with fixing them are simply pushed further out into the development cycle. This is inefficient—the best time to fix a code defect is immediately after the code has been written, while it is still fresh in the developer’s mind. Furthermore, a defect discovered during a formal testing cycle must be written up, prioritized, and tracked—all of these activities incur cost, and may mean that a fix is deferred indefinitely, or at least until it becomes critical. Based on our experience, we believe that encouraging and supporting the development and regular execution of unit test cases ultimately leads to significant improvements in productivity and overall code quality. The creation of unit test cases does not have to be a burden—developers often find the intellectual challenge quite stimulating and ultimately satisfying. The thought process involved in creating a test can also highlight shortcomings in a design, which may not otherwise have been identified when the main focus is on implementation. We recommend that you take the time to define a unit testing strategy for your own development projects. A simple set of guidelines, and a framework that makes it easy to develop and execute tests, pays for itself surprisingly quickly. Benefits of a unit testing framework Once you have decided to implement a unit testing strategy in your project, the first hurdles to overcome are the factors that dissuade developers from creating and running unit tests in the first place. A testing framework can help by making it easier to: Write tests Run tests Rerun a test after a change Tests are easier to write, because a lot of the infrastructure code that you require to support every test is already available. A testing framework also provides a facility that makes it easier to run and re-run tests, perhaps via a GUI. The more often a developer runs tests, the quicker problems can be located and fixed, because the difference between the code that last passed a unit test, and the code that fails the test, is smaller. Testing frameworks also provide other benefits: Consistency—Because every developer is using the same framework, all of your unit tests work in the same way, can be managed in the same way, and report results in the same format. 576 WebSphere Studio Application Developer Version 5 Programming Guide Maintenance—Because a framework has already been developed and is already in use in a number of projects, you spend less time maintaining your testing code. Ramp-up time—If you select a popular testing framework, you may find that new developers coming into your team are already familiar with the tools and concepts involved. Automation—A framework may offer the ability to run tests unattended, perhaps as part of a daily or nightly build. Testing with JUnit A unit test is a collection of tests designed to verify the behavior of a single unit within a class. JUnit tests your class by scenario, and you have to create a testing scenario that uses the following elements: Instantiate an object Invoke methods Verify assertions This simple test case tests the result count of database query: //Test method public void testGetAccount(){ //instantiate Banking banking = new Banking(); //invoke a method Account account = banking.getAccount("104-4001"); //verify an assertion assertEquals(account.getAccountId(),"104-4001"); } In JUnit, each test is implemented as a method that should be declared as public void and take no parameters. This method is then invoked from a test runner defined in a different package. If the test method name begins with test , the test runner finds it automatically and runs it. This way, if you have a large number of test cases, there is no need to explicitly define all the test methods to the test runner. Automatic builds: A common practice in many development environments is the use of daily builds. These automatic builds are usually initiated in the early hours of the morning by a scheduling tool. Note: An assertion is a statement that allows you to test the validity of any assumptions made in your code. Chapter 17. JUnit and component testing 577 TestCase class The core class in the JUnit test framework is junit.framework.TestCase . All of our test cases inherit from this class: import junit.framework.TestCase; public class BankingTest extends TestCase { /** * Constructor for BankingTest. * @param arg0 */ public BankingTest(String arg0) { super(arg0); } All test cases must have a constructor with a string parameter. This is used as a test case name to display in the log. Tests are executed using a test runner. To run this test, use TestRunner as follows. public static void main (String[] args) { junit.textui.TestRunner.run (BankingTest); } TestSuite class Test cases can be organized into test suites, managed by the junit.framework.TestSuite class. JUnit provides tools that allow every test in a suite to be run in turn and to report on the results. TestSuite can extract the tests to be run automatically. To do so, you pass the class of your TestCase class to the TestSuite constructor. TestSuite suite = new TestSuite(BankingTest.class); This constructor creates a suite containing all methods starting with test and that take no arguments. Alternatively, you can add new test cases using the addTest method of TestSuite: TestSuite suite = new TestSuite(); suite.addTest(new BankingTest("testBankingConnection")); suite.addTest(new BankingTest("testBanking")); 578 WebSphere Studio Application Developer Version 5 Programming Guide Creating the test case Application Developer contains wizards to help you build JUnit test cases and test suites. We will use this wizard to create the BankingTestTest test class to test the BankingTest JavaBean, which is a facade of a banking application that allows you to get information about your account, withdraw, and deposit funds. We use a copy of the banking model of the ItsoProGuideJava project. In our example, we use a Java project called ItsoProGuideJUnit. Create the Java project ( New -> Project -> Java -> Java Project ). Enter its name and click Next . Importing the model We will use the banking model of the ItsoProGuideJava project. You can either copy the Java packages from that project or import the JAR file: Select the ItsoProGuideJUnit project and Import (context). Select ZIP file , click Next , locate the \sg246957\sampcode\dev-java\BankingModel.jar file, and click Finish . Preparing for JUnit To use JUnit in the Application Developer Workbench, you have to add the JUnit packages to the classpath of the Java project. The jar file is: c:\WSAD5\eclipse\plugins\org.junit_3.7.0\junit.jar Open the ItsoProGuideJUnit project properties. On the Libraries page add a JUNIT variable that points to the junit.jar file. Also add the DB2JAVA variable to the build path (Figure 17-1). Figure 17-1 Updating Java build path Chapter 17. JUnit and component testing 579 Creating a test case Here we create a test case for the deposit method of the BankingTest facade. To begin, use the Java perspective of Application Developer, and expand ItsoProGuideJUnit -> itso.bank.model.facade, select the BankingTest.java class and New -> Other (context). Expand Java , select JUnit -> TestCase and click Next . Figure 17-2 Create a JUnit Test Case Figure 17-3 shows the next page of the wizard, where you enter details about the new test case: Set the source folder to ItsoProGuideJUnit (where we want to store the test case). Enter itso.junit as package name. Select public static void main(String[] args) and Add TestRunner statement for and select text ui in the combo box. The creates a main method in the test case, and adds a line of code that executes the TestRunner to run the test methods and output the results. Select setUp() to create a stub for this method in the generated file. 580 WebSphere Studio Application Developer Version 5 Programming Guide Figure 17-3 Specify test case and test class We leave the default values for all other fields on this page. The other options on this page are: Test case—The name of the new test case class is generated as the name of the source class we want to test, with Test as a suffix. Test class—The Java class that this new test case is testing. Superclass—By default the JUnit TestCase class, but can be changed if you extend the JUnit package yourself. Method stubs—Which method stubs do you want to generate: – main is the method that is executed to start the JUnit test case as a Java application. This is not required, as within Application Developer, we can run Java classes as JUnit test cases. – The check box, Add TestRunner statement for:, has three options: text ui, swing ui, awt ui. These options add a single line of code to the main method to run the test case and output the results in three different user interfaces. Text is plain text, while swing and awt are graphical outputs. – setUp is a method that is executed before the tests. – tearDown is a method that is executed after the tests. Chapter 17. JUnit and component testing 581 Click Next to proceed to the next page of the wizard. Figure 17-4 shows the available methods for which stubs should be created. We select the deposit method. Figure 17-4 Test methods Think of the stub methods as just a suggestion for a few scenarios to test in your test case. You can add as many methods to the generated file as you would like, and the naming conventions are up to you. This page of the wizard gets you started. Click Finish to complete the wizard. BankingTestTest.java is generated and can now be used to test the BankingTest class. All that remains to do is to write your testing code. Note: A stub is a skeleton method, generated so that you can add the body of the method yourself. 582 WebSphere Studio Application Developer Version 5 Programming Guide The setUp and tearDown methods Typically, you run several tests in one test case. To make sure there are no side effects between test runs, the JUnit framework provides the setUp and tearDown methods. Every time the test case is run, setUp is called at the start and tearDown at the end of the run. In our new BankingTestTest test case, we must add the following private object that will be instantiated before starting the test: //Banking facade private itso.bank.facade.BankingTest banking; In the setUp method, add the following code to create an instance of the Banking facade: public void setUp(){ super.setUp(); banking = new itso.bank.facade.BankingTest(); } In our example, there is no tearDown method. This method could be used to clean up the application by performing tasks such as disconnecting from a database. In our case, no cleanup is required. Test methods Next we update the stub testDeposit method by adding the code shown in Figure 17-5. This method retrieves an account balance, then deposit funds into that account. Then it retrieves the account balance again and verifies that the balance before plus the deposit amount is equal to the balance after the deposit was made. After entering the code, select Source -> Organize Imports from the context menu to fix any unresolved errors. Make sure that you import java.math.BigDecimal when prompted. Note: We first tested the balance by retrieving the Account object before and after the deposit. This logic fails because, with the in-memory model of the ItsoProGuideJava project, the same Account object is retrieved. This does work if new objects are created by the model. Chapter 17. JUnit and component testing 583 Figure 17-5 Method to test and verify depositing funds to an account Now we add another test method called testDepositInvalidAccount. This method verifies that if you enter an invalid account ID, you receive the proper AccountDoesNotExistException exception (Figure 17-6). Figure 17-6 Method to test depositing to an invalid account Save and close BankingTestTest.java. /** * Method to deposit funds to an account, and verify that the * balance afterwards is equal to the balance before plus the * deposited amount */ public void testDeposit() { try { // invoke three methods to get account info, deposit // funds, then get account info after deposit BigDecimal balanceBefore =banking.getAccount("104-4001").getBalance(); banking.deposit("104-4001", new BigDecimal(100)); BigDecimal balanceAfter = banking.getAccount("104-4001").getBalance(); //verify an assertion assertEquals( balanceBefore.add( new BigDecimal(100) ), balanceAfter ); } catch (Exception ex) { fail(ex.getMessage()); } } /** * Method to test making a deposit to an invalid account number */ public void testDepositInvalidAccount() { try { // test getting an invalid account banking.deposit("AAAA", new BigDecimal(1)); fail("Got account even though used invalid account id"); } catch (AccountDoesNotExistException accex) { assertTrue(true); } catch (Exception ex) { fail(ex.getMessage()); } } . BankingTest("testBankingConnection")); suite.addTest(new BankingTest("testBanking")); 57 8 WebSphere Studio Application Developer Version 5 Programming Guide Creating the test case Application Developer contains wizards to help you build. 57 4 WebSphere Studio Application Developer Version 5 Programming Guide What is JUnit? JUnit is an open source testing framework. setUp() to create a stub for this method in the generated file. 58 0 WebSphere Studio Application Developer Version 5 Programming Guide Figure 17-3 Specify test case and test class We leave the