Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 68 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
68
Dung lượng
1,74 MB
Nội dung
528 CHAPTER 17 Unit testing assertEquals() method is heavily overloaded, with versions that take all Java primitives and objects. The versions of the method that check floats and doubles include an additional parameter for an error factor. Comparing two floating- point numbers for equality almost never yields the same result. The last parame- ter is the delta, indicating the maximum tolerance for inequality. 17.2.3 Running tests JUnit features a couple of ways to run the tests. The framework includes text- based and Swing-based test runners. The test runners point to an individual test case or a package containing test cases and runs everything that begins with test . When pointed at a package, it loads every class starting with Test that implements TestCase and tries to run the methods starting with test . In this way, JUnit allows you to create new tests that are automatically picked up and run. The results of running the AllTests suite (which includes TestShoppingCart ) in the Swing- based test runner are shown in figure 17.1. The test runner displays the test class name at the top, along with a Run button. When invoked, the test runner performs the tests. The bar in the center turns either green or red, with obvious connotations. If a single test fails to run, the bar turns red and the test was a failure. The results window under the progress bar shows the tests that were run, along with the results. The successful tests show up with green checkmarks, and the failures show up in red. The Failures tab shows a stack trace for the failed test runs. Figure 17.1 The Swing-based test runner automatically runs the test cases found in a particular package. Unit testing and JUnit 529 Figure 17.2 shows the results when one of the tests in the suite fails to run. In this case, the testGetCartTotal() test failed, dooming the entire test run to failure. 17.2.4 Test suites Figures 17.1 and 17.2 show a collection of tests running. JUnit allows you to bun- dle a group of tests together into a test suite. The test suite is a collection of individ- ual test cases that run as a group. Our project includes two test cases that are related and thus should be run in the same suite. The AllTests suite appears in listing 17.2. package com.nealford.art.emotherearth.test; import junit.framework.*; public class AllTests extends TestCase { public AllTests(String s) { super(s); } public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(com.nealford.art.emotherearth. boundary.test.TestOrderDb.class); Figure 17.2 The results progress bar glows red when even a single test fails to run. Listing 17.2 The AllTests suite registers tests that run as a group. 530 CHAPTER 17 Unit testing suite.addTestSuite(com.nealford.art.emotherearth. util.test.TestShoppingCart.class); return suite; } } The AllTests test suite is very simple. It is itself a TestCase child that includes a static suite() method. Inside this method, a new TestSuite is created, and each test case class is added to it. The parameter for addTestSuite() is a Class class, so the passed values are the class objects for the test case classes. When the frame- work encounters a test suite, it executes the test cases inside it in order. JUnit is designed to automatically pick up test cases in a particular package. The test suite lets the developer control which tests are run together. 17.2.5 Testing boundaries Testing boundary classes is difficult because of the elaborate fixtures that must exist to support the tests. In the case of the eMotherEarth application, the most complex (and therefore most critical to test) boundary is the one that adds new orders to the database. Because it uses so many classes and must interact with the database, this boundary class is more complex than the test case shown earlier. The first portion of the class is shown in listing 17.3. public class TestOrderDb extends TestShoppingCart { private OrderDb orderDb = null; private int addedOrderKey; private DBPool dbPool; private Connection connection; private static final String SQL_DELETE_ORDER = "delete from orders where order_key = ?"; private static final String SQL_SELECT_ORDER = "select * from orders where order_key = ?"; private static final String DB_URL = "jdbc:mysql://localhost/eMotherEarth"; private static final String DRIVER_CLASS = "com.mysql.jdbc.Driver"; private static final String USER = "root"; private static final String PASSWORD = "marathon"; private static final String TEST_CC_EXP = "11/1111"; private static final String TEST_CC_NUM = "1111111111111111"; private static final String TEST_CC_TYPE = "Visa"; private static final String TEST_NAME = "Homer"; private static final int TEST_USER_KEY = 1; Listing 17.3 The declaration section of TestOrderDb Unit testing and JUnit 531 public TestOrderDb(String name) { super(name); } The first item of note in the TestOrderDb class is the parent class, which is the Test- ShoppingCart unit test created earlier. We subclass it because one of the fixture items we need is a populated shopping cart. The TestShoppingCart test case needs the same fixture, so we inherit from it to cut down on the duplicate code we would need otherwise. The top of this class consists primarily of constants that define the characteristics of SQL statements and test data. The constants for connecting to the database reside in this class because we cannot easily get them from the web appli- cation deployment descriptor. This test case is not part of the web application and does not have access to the services provided by the servlet engine. The next two methods of the TestOrderDb test case are the inherited setUp() and tearDown() methods, shown in listing 17.4. protected void setUp() throws Exception { super.setUp(); orderDb = new OrderDb(); dbPool = new DBPool(DRIVER_CLASS, DB_URL, USER, PASSWORD); orderDb.setDbPool(dbPool); connection = dbPool.getConnection(); } protected void tearDown() throws Exception { deleteOrder(addedOrderKey); dbPool.release(connection); orderDb = null; super.tearDown(); } The setup() and teardown() methods are typically protected so that other test cases may inherit from them just as we have done. It is important to remember to invoke the superclass’s setUp() as the first line of the setUp() method and invoke the superclass’s tearDown() as the last line of that method. The setUp() method creates the necessary fixtures for an Order object and gets a connection for use by the non-order code in the test case. The tearDown() method releases resources and deletes the order added by the test case. For perfectly encapsulated tests, you should make sure that the test cleans up after itself. Depending on the database in Listing 17.4 The setUp() and tearDown() methods of the TestOrderDb test case 532 CHAPTER 17 Unit testing use, you might not have to do this. For example, if you know that the application is always tested with a test database where partial and meaningless records are tol- erated, you don’t have to make sure that the test cases clean up after themselves. However, if there is any chance that the test runs against production data, you should make sure that the test is well encapsulated. The next two methods (listing 17.5) are part of the fixture of the test. They get an inserted order from the database (to compare against the one that was added) and delete the new order upon tear-down. private Order getOrderFromDatabase() { Order o = new Order(); PreparedStatement ps = null; ResultSet rs = null; try { ps = connection.prepareStatement(SQL_SELECT_ORDER); ps.setInt(1, addedOrderKey); rs = ps.executeQuery(); rs.next(); o.setOrderKey(rs.getInt("order_key")); o.setUserKey(1); o.setCcExp(rs.getString("CC_EXP")); o.setCcNum(rs.getString("CC_NUM")); o.setCcType(rs.getString("CC_TYPE")); } catch (Exception ex) { throw new RuntimeException(ex.getMessage()); } finally { try { if (ps != null) ps.close(); } catch (SQLException ignored) { } } return o; } private void deleteOrder(int addedOrderKey) { Connection c = null; PreparedStatement ps = null; int rowsAffected = 0; try { ps = connection.prepareStatement(SQL_DELETE_ORDER); ps.setInt(1, addedOrderKey); rowsAffected = ps.executeUpdate(); if (rowsAffected != 1) throw new Exception("Delete failed"); Listing 17.5 These two methods are part of the database fixture of the test case. Unit testing and JUnit 533 } catch (Exception ex) { throw new RuntimeException(ex.getMessage()); } finally { try { if (ps != null) ps.close(); if (c != null) c.close(); } catch (SQLException ignored) { } } } The last method of the test case is the actual test method. It creates a simulated order, uses the Order object to add it to the database, and then compares the results by querying the database to retrieve the record. Listing 17.6 shows this method. public void testAddOrder() throws SQLException { Order actualOrder = new Order(); actualOrder.setCcExp(TEST_CC_EXP); actualOrder.setCcNum(TEST_CC_NUM); actualOrder.setCcType(TEST_CC_TYPE); actualOrder.setUserKey(TEST_USER_KEY); orderDb.addOrder(shoppingCart, TEST_NAME, actualOrder); addedOrderKey = orderDb.getLastOrderKey(); Order dbOrder = getOrderFromDatabase(); assertEquals("cc num", actualOrder.getCcNum(), dbOrder.getCcNum()); assertEquals("cc exp", actualOrder.getCcExp(), dbOrder.getCcExp()); assertEquals("cc type", actualOrder.getCcType(), dbOrder.getCcType()); assertEquals("user key", actualOrder.getUserKey(), dbOrder.getUserKey()); deleteOrder(addedOrderKey); } Unlike the test case in listing 17.1, the test case in listing 17.6 has numerous assert methods for checking the various characteristics of the order. This method gener- ates an order using the shopping cart generated by the inherited setUp() method and the constants defined at the top of the class. Next, it adds the order by using Listing 17.6 The lone test method in the boundary test case 534 CHAPTER 17 Unit testing the addOrder() method. Once the order has been added, the record is retrieved from the database to ensure that the values are correct. If you refer back to figure 17.1, you will notice that when this test case runs, it also runs the test case from its parent class, testGetCartTotal() . Because the order test case inherits from the shopping cart test case, both tests are run via the framework. Building test cases for boundaries is complex because of the amount of hand- generated SQL required. Here is a case where using helper classes eliminates the redundant nature of this kind of code. For example, it is quite common to build a JDBCFixture class that encapsulates most of the generic details of interacting with the database. Alternatively, you can use components normally reserved for client/ server development to ease generating test code. For example, many IDEs include components that wrap much of the complexity of JDBC. While you might be reluc- tant to use the components in your web applications because of the overhead, the speed of development is more important in unit tests, and scalability and over- head are secondary concerns. One of the utilities available on the JUnit web site is a set of helper classes called DbUnit , which automates much of the testing of boundary classes. If you don’t want to write the database access code yourself, DbUnit makes it easy to gen- erate test code against relational databases. 17.2.6 Tool support Many IDEs, both commercial and open source, now support JUnit. Like the Ant build tool, it has become ubiquitous in Java development circles. IDE support ranges from predefined test case templates for building the main infrastructure to test runners that run tests inside the IDE. Figure 17.3 JBuilder includes prebuilt fixtures and other support classes for JUnit. Unit testing and JUnit 535 JBuilder’s JUnit support Figure 17.3 shows the JBuilder New gallery, which features an entire page of pre- built JUnit test classes. Figure 17.4 shows the TestOrderDb test running inside the JBuilder IDE, which supplies its own graphical test runner. NetBean’s JUnit support The NetBeans IDE also includes support for JUnit, both in test generation and test running. For any class, you can right-click, choose Tools, and let NetBeans gener- ate JUnit tests for you. Figure 17.5 shows the dialog box that lets you specify what JUnit characteristics you want to implement in your test case. NetBeans also has a custom test runner, based on the JUnit text test runner. Automating regression testing You must run unit tests as regression tests to receive the full benefit of unit testing. However, no one wants to sit at a computer and run regression tests all day. One of the aspects of testing that make it useful is the invisibility of needless details. Figure 17.4 The JBuilder test runner runs tests inside the IDE with its own test runner interface. Figure 17.5 NetBeans assists in creating JUnit tests for any class. 536 CHAPTER 17 Unit testing Another open-source tool you are probably already using facilitates running regression tests. The Ant build tool includes a JUnit task in its optional tasks. Using Ant, you can set up a build file that runs the unit tests for multiple suites overnight. Depending on how much you want to automate the process, you can run the tests with Ant and have it email you a list of the tests that failed so that you can address them the next morning. Listing 17.7 shows a sample Ant invocation of the JUnit task. <junit printsummary="withOutAndErr" haltonfailure="yes" fork="true"> <classpath> <pathelement location="${build.tests}" /> <pathelement path="${java.class.path}" /> </classpath> <formatter type="plain" /> <test name="my.test.TestCase" haltonfailure="no" outfile="result" > <formatter type="xml" /> </test> <batchtest fork="yes" todir="${reports.tests}"> <fileset dir="${src.tests}"> <include name="*Test.java" /> <exclude name="**/AllTests.java" /> </fileset> </batchtest> </junit> Ant is an extraordinarily popular build tool, used by virtually every Java project under the sun. You can find out much more about Ant from the excellent Java Development with Ant, by Erik Hatcher and Steve Loughran. 17.3 Web testing with JWebUnit One of the most difficult kinds of applications to unit test are web applications. Web applications rely on a deployment platform, the browser, which is completely out of the control of the developers of the application. Web applications also have a strong visual component, for which it is also difficult to automate testing. Com- mercial products are available to test web applications; they generally allow a user to interact with the application while recording keystrokes and mouse gestures. Listing 17.7 The Ant JUnit task simplifies the setup and execution of JUnit tests. Web testing with JWebUnit 537 These records are then played back to simulate a user’s interaction. These tools are specialized and very expensive. The open-source world hasn’t produced a tool exactly like the commercial ones yet. However, the open-source world hasn’t totally ignored this problem. One of the adjuncts to the JUnit project was a project named HttpUnit. It features exten- sions to JUnit for building a framework that tests applications running over HTTP. It is an effective tool for verifying that the actual output is what you expected. Other open-source tools are aimed at testing atomic behavior of web applications. For example, tools exist that test the JavaScript on a web page. Recently, another project popped up on the JUnit site that combines many of the existing open-source web testing frameworks, including HTTPUnit. Like its precursors, JWebUnit is an open-source testing framework. It encapsulates many of the existing open-source tools to create a more comprehensive package. It also provides new classes that encapsulate many of the existing HTTPUnit classes to reduce the amount of code a developer must write. You can download JWebUnit from the JUnit web site. You should also download HTTPUnit while you are there because JWebUnit relies on some classes that come from HTTPUnit. 17.3.1 JWebUnit TestCases Because JWebUnit is based on JUnit, the concepts of test cases, suites, and fixtures are the same. Let’s create tests for a couple of the pages of the eMotherEarth application as an example. One of the setup items common to all the test cases in JWebUnit is the BaseURL . All the other URLs in the test case are based on this URL. Instead of replicating the same setup code across multiple test cases, let’s create a base test case that handles this setup chore. The BaseWebTestCase appears in listing 17.8. package com.nealford.art.emotherearth.test; import net.sourceforge.jwebunit.WebTestCase; public class BaseWebTestCase extends WebTestCase { public BaseWebTestCase(String name) { super(name); } public void setUp() throws java.lang.Exception { super.setUp(); Listing 17.8 The BaseWebTestCase handles setting the base URL for all test cases that inherit from it. [...]... JWebUnit contains methods for testing sophisticated HTML elements such as tables Listing 17 .10 shows a test case that tests some of the table properties of the catalog page Listing 17 .10 This test case tests for the presence and validity of table elements package com.nealford .art. emotherearth.test; import import import import java. io.File; java. io.FileNotFoundException; java. io.FileOutputStream; java. io.PrintStream;... of code JUnit has made it relatively easy to write unit tests for everything but servlets and web user interfaces JWebUnit, based on JUnit, includes methods that help automate the testing of the visual aspect of web pages By constructing tests to check for the presence of elements and dumping the contents of complex data structures to files, JWebUnit enables you to test the visual portion of your web. .. As web developers, you cannot avoid this topic In the near future, you will work on a web application that must support web services (if you aren’t already) This chapter provides a brief overview of web services and, more important, the issues you face retrofitting an existing web application to take advantage of this new paradigm It covers the essentials of web services concepts (There are plenty of. .. of your web application—the most difficult part to test In the next chapter, we cover web services and how to incorporate them into web applications Web services and Axis This chapter covers I I I Defining web services concepts Using Axis Retrofitting web applications to expose web services 543 544 CHAPTER 18 Web services and Axis Over the last few years, web services have been the industry-specific... language representation of classes and objects and the distributed format for the same constructs Let’s look at an example of what WSDL 2Java produces Consider the WSDL file generated from the simple web service defined in listing 18.1 Running WSDL 2Java on that document produces four Java source files, summarized in table 18.1 Table 18.1 WSDL 2Java output Java Source File Description Simple .java The remotable... creating the web services infrastructure The developer of the web service need only worry about delivering the information The OrderInfo class that defines the web service methods appears in listing 18.9 Listing 18.9 The OrderInfo class defines the web service methods package com.nealford .art. emotherearth.ws; import import import import java. sql.SQLException; com.nealford .art. emotherearth.boundary.OrderDb;... an order listing 18 .10 560 CHAPTER 18 Web services and Axis Listing 18 .10 The OrderInfo interface, generated by WSDL 2Java /** * OrderInfo .java * * This file was auto-generated from WSDL * by the Apache Axis WSDL 2Java emitter */ package localhost; public interface OrderInfo extends java. rmi.Remote { public java. lang.String getWsDescription() throws java. rmi.RemoteException; public java. lang.String getOrderStatus(int... console application in C# that calls the eMotherEarth status web service The C# class appears in listing 18.12 Listing 18.12 This C# class calls the eMotherEarth status web services using System; namespace art_ emotherearth_ws { class Class1 { [STAThread] static void Main() { com.nealford .art. emotherearth.OrderInfoService orderInfo = new com.nealford .art. emotherearth OrderInfoService(); Console.WriteLine("Order... the primary protocol of interest The Axis project is a complete rewrite of the previous open-source project that enabled SOAP messaging from Java It includes a framework and tools to make it easy to add support for web services to Java applications, especially web applications Axis includes tools that generate Java classes from WSDL documents and that generate WSDL documents from Java classes It also... demonstrated in the eMotherEarth application Most of the changes dealt with the logistics of connection pooling and startup code execution because of the stateless nature of the web services call The actual methods we added to the OrderDb boundary class and the web service class itself were minimal Once the web service exists, client applications (written in any language that supports web services) can call . kinds of applications to unit test are web applications. Web applications rely on a deployment platform, the browser, which is completely out of the control of the developers of the application. Web. in listing 17.8. package com.nealford .art. emotherearth.test; import net.sourceforge.jwebunit.WebTestCase; public class BaseWebTestCase extends WebTestCase { public BaseWebTestCase(String name) { super(name); . as tables. Listing 17 .10 shows a test case that tests some of the table properties of the catalog page. package com.nealford .art. emotherearth.test; import java. io.File; import java. io.FileNotFoundException; import