Summary "Expert one-on-one J2EE design and development"
Chapter3: Testing J2EE applications
Testing should occur throughout the development lifecycle. Testing should never be an afterthought. Integrating testing into the development process brings many benefits.
What can testing achieve
Although it cannot uncover e.g. multi-threading problems, code that hasn’t been tested, does not exist (slogan of Test Driven Development (TDD)).
While in this chapter we’ll focus on testing code, it’s important to remember that a sound QA strategy is needed from requirements analysis onwards.
The book defines:
- Unit tests
- Test coverage
- Black-box testing
- White-box testing, also known as glass-box testing
- Regression tests
- Boundary-value tests
- Acceptance tests, also known as functional tests
- Load tests (are non-functional tests, should be part of business requirements)
- Stress tests
The XP approach to testing
Since XP strongly advocates TDD (test driven development, writing tests before code), we can benifit from the ideas of XP in this respect. TDD offers many advantages.
Unit testing will only provide a secure basis for refactoring and bug fixing if we have a comprehensive set of unit tests. A half-hearted approach to unit testing will deliver limited benefit.
Write unit tests before writing code, and update them throughout the project lifecycle. Bug reports and new functionality should first prompt the writing and execution of failing tests demonstrating the mismatch between what the application does and what it should do.
Test-first development is the best way to guarantee comprehensive test coverage, as it is much harder to write comprehensive tests for existing code.
Remember to use test failures to improve error message and handling. If a test fails, and it wasn’t immediately obvious what went wrong, try first to make the problem obvious (through improved error handling and messages) and then to fix it.
There shouldn’t be an artificial division between development and testing roles. Developers should be encouraged to value the writing of good test cases as an important skill.
Writing test cases
- Test the most common execution paths
- Test what happens on unexpected arguments
- Test what happens if components under test receive errors from components they use
Introduction unit test using JUnit, see the book for a lively example or JUnit Test Infected: Programmers Love Writing Tests
- Write tests to interfaces
- Donâ€™t bother testing Java bean properties
- Maximize test coverage
- Donâ€™t rely on the ordering of test cases
- Avoid side effects
- Read test data from the classpath, not the file system
- Avoid code duplication in test cases
Inheritance and testing
It’s good design practice to subclass a more general JUnit test case to add new tests for a subclass of an object or a particular implementation of an interface.
Should testing strategy affect how we write code?
- Helps classes to have limited responsibility, i.e. that they don’t become too complex
- It assesses that instance variables are only accessed locally
- It encourages stricter encapsulation with respect to inheritance
- Occasionally it prompts us to add methods purely intended to facilitate testing
Testing business objects
Testing business objects implemented without using EJB
- Simply use JUnit, container services may be mocked
- Write a test that is a remote client of the EJB container (suitable for EJBs with remote interfaces)
- Write and deploy a test that executes within the application server (requires additional test framework + complicates deployment)
- Work with stub objects (only feasible when EJBs have simple requirements of the EJB container)
When we use EJBs with remote interfaces, we can write ordinary JUnit test cases that test them from a remote JVM.
When we use EJBs with local interfaces, we will usually need to test them within the target application server.
Cactus is an open source test framework that allows in-container testing of EJBs, servlets, JSP pages and servlet filters.
Testing database interaction
- Using mock objects
- Using JDBC helper classes (to set/verify/undo the results in the database)
- [added by ZWH], using DBunit
Testing web interfaces
Testing web-tier components
Acceptance testing web interfaces
HttpUnit allows us to write acceptance tests that run outside of the container.
In my experience [Rod Johnson's, ZWH], acceptance testing is more important than unit testing where web interfaces are concerned, as a web interface should be a thin layer on top of the application’s business interfaces. The implementation of the application’s use cases should be tested against the implementations of the business interfaces.
The HttpUnt class library provides an excellent, intuitive way of implementing web-tier acceptance tests using JUnit.
Where possible, design applications so that their central functionality can be tested agains ordinary Java classes, not J2EE components such as EJBs, servlets, or JSPpages.
Testing performance and scalability
Load and stress testing should not be left until an application is substantially complete. We’ve already considered the importance of tackling risks early. Inadequate performance and scalability is a major risk in enterprise applications, and especially web applications.
It’s good practice to implement a proof of concept or “vertical slice” of an application’s functionality early in the project life cycle and run load tests against it. Thus load testing should continue throughout the project life cycle. Application performance tuning is closely tied to load testing, throughout the project life cycle.
Load testing EJBs and other business objects
Any load testing tool should offer
- Execute user-defined operations in multiple threads
- Use random sleeps between operations
- Read data from configuration files
- Repeat operations as required
- Produce readable reports
Load testing web interfaces
Complementary approaches to testing
J2SE 1.4 introduces major enhancements, of wich the new assertion mechanism is one of the most important. If using Java 1.4, take advantage of assertions — they’re now integrated into the language and offer a performance-neutral way of heading off bugs. Learn to use assertions effectively: this is a skill that takes a little practice to acquire.