Topic: JUnit Primer

  Print this page

1.JUnit Primer Copy to clipboard
Posted by: wishmaster
Posted on: 2002-07-26 15:51

JUnit Primer www.junit.org

Summary
This article demonstrates how to write and run simple test cases and test suites using the JUnit testing framework.
  
Introduction

The goal of this article is to demonstrate a quick and easy way to write and run JUnit test cases and test suites. We'll start by reviewing the key benefits of using JUnit and then write some example tests to demonstrate its simplicity and effectiveness.

This article contains the following sections:

Introduction
Why Use JUnit?
Design Of JUnit
Step 1: Write A Test Case
Step 2: Write A Test Suite
Step 3: Run The Tests
Step 4: Organize The Tests
Testing Idioms
Training and Mentoring
Resources

Before you start, make sure you have downloaded and installed the following software:

JUnit (3.1 or later, current version: 3.7)
  
Why Use JUnit?

Before we begin, it's worth asking why we should use JUnit at all. The subject of unit testing always conjures up visions of long nights slaving over a hot keyboard trying to meet the project's test case quota. However, unlike the draconian style of conventional unit testing, using JUnit actually helps you write code faster while increasing code quality. Once you start using JUnit you'll begin to notice a powerful synergy emerging between coding and testing, ultimately leading to a development style of only writing new code when a test is failing.

Here are just a few reasons to use JUnit:

JUnit tests allow you to write code faster while increasing quality.
Yeah, I know, it sounds counter-intuitive, but it's true! When you write tests using JUnit, you'll spend less time debugging, and you'll have confidence that changes to your code actually work. This confidence allows you to get more aggressive about refactoring code and adding new features. Without tests, it's easy to become paranoid about refactoring or adding new features because you don't know what might break as a result. With a comprehensive test suite, you can quickly run the tests after changing the code and gain confidence that your changes didn't break anything. If a bug is detected while running tests, the source code is fresh in your mind, so the bug is easily found. Tests written in JUnit help you write code at an extreme pace and spot defects quickly.

JUnit is elegantly simple.
Writing tests should be simple - that's the point! If writing tests is too complex or takes too much time, there's no incentive to start writing tests in the first place. With JUnit, you can quickly write tests that exercise your code and incrementally add tests as the software grows. Once you've written some tests, you want to run them quickly and frequently without disrupting the creative design and development process. With JUnit, running tests is as easy and fast as running a compiler on your code. In fact, you should run your tests every time you run the compiler. The compiler tests the syntax of the code and the tests validate the integrity of the code.

JUnit tests check their own results and provide immediate feedback.
Testing is no fun if you have to manually compare the expected and actual result of tests, and it slows you down. JUnit tests can be run automatically and they check their own results. When you run tests, you get simple and immediate visual feedback as to whether the tests passed or failed. There's no need to manually comb through a report of test results.

JUnit tests can be composed into a hierarchy of test suites.
JUnit tests can be organized into test suites containing test cases and even other test suites. The composite behavior of JUnit tests allows you to assemble collections of tests and automatically regression test the entire test suite in one fell swoop. You can also run the tests for any layer within the test suite hierarchy.

Writing JUnit tests is inexpensive.
Using the JUnit testing framework, you can write tests cheaply and enjoy the convenience offered by the testing framework. Writing a test is as simple as writing a method that exercises the code to be tested and defining the expected result. The framework provides the context for running the test automatically and as part of a collection of other tests. This small investment in testing will continue to pay you back in time and quality.

JUnit tests increase the stability of software.
The fewer tests you write, the less stable your code becomes. Tests validate the stability of the software and instill confidence that changes haven't caused a ripple-effect through the software. The tests form the glue of the structural integrity of the software.

JUnit tests are developer tests.
JUnit tests are highly localized tests written to improve a developer's productivity and code quality. Unlike functional tests, which treat the system as a black box and ensure that the software works as a whole, unit tests are written to test the fundamental building blocks of the system from the inside out. Developer's write and own the JUnit tests. When a development iteration is complete, the tests are promoted as part and parcel of the delivered product as a way of communicating, "Here's my deliverable and the tests which validate it."

JUnit tests are written in Java.
Testing Java software using Java tests forms a seamless bond between the test and the code under test. The tests become an extension to the overall software and code can be refactored from the tests into the software under test. The Java compiler helps the testing process by performing static syntax checking of the unit tests and ensuring that the software interface contracts are being obeyed.

JUnit is free.

  
  Design Of JUnit

JUnit is designed around two key design patterns: the Command pattern and the Composite pattern.

A TestCase is a command object. Any class that contains test methods should subclass the TestCase class. A TestCase can define any number of public testXXX() methods. When you want to check the expected and actual test results, you invoke a variation of the assert() method.

TestCase subclasses that contain multiple testXXX() methods can use the setUp() and tearDown() methods to initialize and release any common objects under test, referred to as the test fixture. Each test runs in the context of its own fixture, calling setUp() before and tearDown() after each test method to ensure there can be no side effects among test runs.

TestCase instances can be composed into TestSuite hierarchies that automatically invoke all the testXXX() methods defined in each TestCase instance. A TestSuite is a composite of other tests, either TestCase instances or other TestSuite instances. The composite behavior exhibited by the TestSuite allows you to assemble test suites of test suites of tests, to an arbitrary depth, and run all the tests automatically and uniformly to yield a single pass or fail status.

Step 1: Write A Test Case

First, we'll write a test case to exercise a single software component. We'll focus on writing tests that exercise the component behavior that has the highest potential for breakage, thereby maximizing our return on testing investment.

To write a test case, follow these steps:

Define a subclass of TestCase.
Override the setUp() method to initialize objectMoon under test.
Override the tearDown() method to release objectMoon under test.
Define one or more public testXXX() methods that exercise the objectMoon under test and assert expected results.
Define a static suite() factory method that creates a TestSuite containing all the testXXX() methods of the TestCase.
Optionally define a main() method that runs the TestCase in batch mode.

The following is an example test case:

(The complete source code for this example is available in the Resources section).

Example Test Case
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

public class ShoppingCartTest extends TestCase {

    private ShoppingCart _bookCart;
    private Product _defaultBook;

    /**
     * Constructs a ShoppingCartTest with the specified name.
     *
     * @param name Test case name.
     */
    public ShoppingCartTest(String name) {
        super(name);
    }

    /**
     * Sets up the test fixture.
     *
     * Called before every test case method.
     */
    protected void setUp() {

        _bookCart = new ShoppingCart();

        _defaultBook = new Product("Extreme Programming", 23.95);
        _bookCart.addItem(_defaultBook);
    }

    /**
     * Tears down the test fixture.
     *
     * Called after every test case method.
     */
    protected void tearDown() {
        _bookCart = null;
    }

    /**
     * Tests adding a product to the cart.
     */
    public void testProductAdd() {

        Product newBook = new Product("Refactoring", 53.95);
        _bookCart.addItem(newBook);

        double expectedBalance = _defaultBook.getPrice() + newBook.getPrice();

        assertEquals(expectedBalance, _bookCart.getBalance(), 0.0);

        assertEquals(2, _bookCart.getItemCount());
    }

    /**
     * Tests the emptying of the cart.
     */
    public void testEmpty() {

        _bookCart.empty();
    
        assertTrue(_bookCart.isEmpty());
    }

    /**
     * Tests removing a product from the cart.
     *
     * @throws ProductNotFoundException If the product was not in the cart.
     */
    public void testProductRemove() throws ProductNotFoundException {

        _bookCart.removeItem(_defaultBook);

        assertEquals(0, _bookCart.getItemCount());

        assertEquals(0.0, _bookCart.getBalance(), 0.0);
    }

    /**
     * Tests removing an unknown product from the cart.
     *
     * This test is successful if the
     * ProductNotFoundException is raised.
     */
    public void testProductNotFound() {

        try {

            Product book = new Product("Ender's Game", 4.95);
            _bookCart.removeItem(book);

            fail("Should raise a ProductNotFoundException");

        } catch(ProductNotFoundException success) {
            // successful test
        }
    }

    /**
     * Assembles and returns a test suite for
     * all the test methods of this test case.
     *
     * @return A non-null test suite.
     */
    public static Test suite() {

        //
        // Reflection is used here to add all
        // the testXXX() methods to the suite.
        //
        TestSuite suite = new TestSuite(ShoppingCartTest.class);

        //
        // Alternatively, but prone to error when adding more
        // test case methods...
        //
        // TestSuite suite = new TestSuite();
        // suite.addTest(new ShoppingCartTest("testEmpty"));
        // suite.addTest(new ShoppingCartTest("testProductAdd"));
        // suite.addTest(new ShoppingCartTest("testProductRemove"));
        // suite.addTest(new ShoppingCartTest("testProductNotFound"));
        //

        return suite;
    }

    /**
     * Runs the test case.
     */
    public static void main(String args[]) {
        junit.textui.TestRunner.run(suite());
    }
}

Step 2: Write A Test Suite

Next, we'll write a test suite that includes several test cases. The test suite will allow us to run all of its test cases in one fell swoop.

To write a test suite, follow these steps:

Define a subclass of TestCase.
Define a static suite() factory method that creates a TestSuite containing all the tests.
Optionally define a main() method that runs the TestSuite in batch mode.

The following is an example test suite:

Example Test Suite
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

public class EcommerceTestSuite extends TestCase {
  
    /**
     * Constructs a EcommerceTestSuite with the specified name.
     *
     * @param name Test suite name.
     */
    public EcommerceTestSuite(String name) {
        super(name);
    }

    /**
     * Assembles and returns a test suite
     * containing all known tests.
     *
     * New tests should be added here!
     *
     * @return A non-null test suite.
     */
    public static Test suite() {

        TestSuite suite = new TestSuite();
  
        //
        // The ShoppingCartTest we created above.
        //
        suite.addTest(ShoppingCartTest.suite());

        //
        // Another example test suite of tests.
        //
        suite.addTest(CreditCardTestSuite.suite());

        return suite;
    }

    /**
     * Runs the test suite.
     */
    public static void main(String args[]) {
        junit.textui.TestRunner.run(suite());
    }
}

Step 3: Run The Tests

Now that we've written a test suite containing a collection of test cases and other test suites, we can run either the test suite or any of its test cases individually. Running a TestSuite will automatically run all of its subordinate TestCase instances and TestSuite instances. Running a TestCase will automatically invoke all of its public testXXX() methods.

JUnit provides both a textual and a graphical user interface. Both user interfaces indicate how many tests were run, any errors or failures, and a simple completion status. The simplicity of the user interfaces is the key to running tests quickly. You should be able to run your tests and know the test status with a glance, much like you do with a compiler.

The textual user interface (junit.textui.TestRunner) displays "OK" if all the tests passed and failure messages if any of the tests failed.

The graphical user interface (junit.swingui.TestRunner) displays a Swing window with a green progress bar if all the tests passed or a red progress bar if any of the tests failed.

In general, TestSuite and TestCase classes should define a main() method which employs the appropriate user interface. The tests we've written so far have defined a main() method employing the textual user interface.

To run our test case using the textual user interface as defined by the main() method, use:

java ShoppingCartTest

Alternatively, the test case can be run with the textual user interface using

java junit.textui.TestRunner ShoppingCartTest

or with the Swing GUI using

java junit.swingui.TestRunner ShoppingCartTest

The EcommerceTestSuite can be run similarly.

Step 4: Organize The Tests

The last step is to decide where the tests will live within our development environment.

Here's the recommended way to organize tests:

Create test cases in the same package as the code under test. For example, the com.mydotcom.ecommerce package would contain all the application-level classes as well as the test cases for those components.
To avoid combining application and testing code in your source directories, create a mirrored directory structure aligned with the package structure that contains the test code.

For each Java package in your application, define a TestSuite class that contains all the tests for validating the code in the package.
Define similar TestSuite classes that create higher-level and lower-level test suites in the other packages (and sub-packages) of the application.
Make sure your build process includes the compilation of all tests. This helps to ensure that your tests are always up-to-date with the latest code and keeps the tests fresh.

By creating a TestSuite in each Java package, at various levels of packaging, you can run a TestSuite at any level of abstraction. For example, you can define a com.mydotcom.AllTests that runs all the tests in the system and a com.mydotcom.ecommerce.EcommerceTestSuite that runs only those tests validating the e-commerce components.

The testing hierarchy can extend to an arbitrary depth. Depending on the level of abstraction you're developing at in the system, you can run an appropriate test. Just pick a layer in the system and test it!

Here's an example testing hierarchy:

Example Testing Hierarchy
AllTests (Top-level Test Suite)
    SmokeTestSuite (Structural Integrity Tests)
        EcommerceTestSuite
            ShoppingCartTestCase
            CreditCardTestSuite
                AuthorizationTestCase
                CaptureTestCase
                VoidTestCase
            UtilityTestSuite
                MoneyTestCase
        DatabaseTestSuite
            ConnectionTestCase
            TransactionTestCase
    LoadTestSuite (Performance and Scalability Tests)
        DatabaseTestSuite
            ConnectionPoolTestCase
        ThreadPoolTestCase

Testing Idioms

Keep these things in mind when testing:

The software does well those things that the tests check.
Test a little, code a little, test a little, code a little...
Make sure all tests always run at 100%.
Run all the tests in the system at least once per day (or night).
Write tests for the areas of code with the highest probability of breakage.
Write tests that have the highest possible return on your testing investment.

If you find yourself debugging using System.out.println(), write a test to automatically check the result instead.
When a bug is reported, write a test to expose the bug.
The next time someone asks you for help debugging, help them write a test.

Write unit tests before writing the code and only write new code when a test is failing.


   Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent
Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1
客服电话 18559299278    客服信箱 714923@qq.com    客服QQ 714923