Chapter 7 Technology-Facing Tests that Support the Team



We use the Agile Testing Quadrants as a guide to help us cover all the types of testing we need and to help us make sure we have the right resources to succeed at each type. In this chapter, we look at tests in the first quadrant, technology-facing tests that support the team, and at tools to support this testing. The activities in this quadrant form the core of agile development.


An Agile Testing Foundation

We discuss Quadrant 1 first because the technology-facing tests that support the team form the foundation of agile development and testing. See Figure 7-1 for a reminder of the Agile Testing Quadrants with this quadrant highlighted. Quadrant 1 is about much more than testing. The unit and component tests we talk about in Quadrant 1 aren’t the first tests written for each story, but they help guide design and development. Without a foundation of test-driven design, automated unit and component tests, and a continuous integration process to run the tests, it’s hard to deliver value in a timely manner. All of the testing in the other quadrants can’t make up for inadequacies in this one. We’ll talk about the other quadrants in the next few chapters and explain how they all fit together.

Figure 7-1 The Agile Testing Quadrants, highlighting Quadrant 1

Teams need the right tools and processes to create and execute technology-facing tests that guide development. We’ll give some examples of the types of tools needed in the last section of this chapter.


The Purpose of Quadrant 1 Tests

Unit tests and component tests ensure quality by helping the programmer understand exactly what the code needs to do, and by providing guidance in the right design. They help the team to focus on the story that’s being delivered and to take the simplest approach that will work. Unit tests verify the behavior of parts as small as a single object or method [Meszaros, 2007]. Component tests help solidify the overall design of a deployable part of the system by testing the interaction between classes or methods.

Developing unit tests can be an essential design tool when using TDD. When an agile programmer starts a coding task, she writes a test that captures the behavior of a tiny bit of code and then works on the code until the test passes. By building the code in small test-code-test increments, the programmer has a chance to think through the functionality that the customer needs. As questions come up, she can ask the customer. She can pair with a tester to help make sure all aspects of that piece of code, and its communication with other units, are tested.

The term test-driven development misleads practitioners who don’t understand that it’s more about design than testing. Code developed test-first is naturally designed for testability. Quadrant 1 activities are all aimed at producing software with the highest possible internal quality.

When teams practice TDD, they minimize the number of bugs that have to be caught later on. Most unit-level bugs are prevented by writing the test before the code. Thinking through the design by writing the unit test means the system is more likely to meet customer requirements. When post-development testing time is occupied with finding and fixing bugs that could have been detected by programmer tests, there’s no time to find the serious issues that might adversely affect the business. The more bugs that leak out of our coding process, the slower our delivery will be, and in the end, it is the quality that will suffer. That’s why the programmer tests in Quadrant 1 are so critical. While every team should adopt practices that work for its situation, a team without these core agile practices is unlikely to benefit much from agile values and principles.


Supporting Infrastructure

Solid source code control, configuration management, and continuous integration are essential to getting value from programmer tests that guide development. They enable the team to always know exactly what’s being tested. Continuous integration gives us a way to run tests every time new code is checked in. When a test fails, we know who checked in the change that caused the failure, and that person can quickly fix the problem. Continuous integration saves time and motivates each programmer to run the tests before checking in the new code. A continuous integration and build process delivers a deployable package of code for us to test.

Agile projects that lack these core agile practices tend to turn into “mini-waterfalls.” The development cycles are shorter, but code is still being thrown “over the wall” to testers who run out of time to test because the code is of poor quality. The term waterfall isn’t necessarily derogatory. We’ve worked on successful “waterfall” projects where the programmers stringently automate unit tests, practice continuous integration, and use automated builds to run tests. These successful “waterfall” projects also involve customers and testers throughout the development cycle. When we code without appropriate practices and tools, regardless of what we call the process, we’re not going to deliver high-quality code in a timely manner.


Why Write and Execute These Tests?

We’re not going into any details here about how to do TDD, or the best ways to write unit and component tests. There are several excellent books on those subjects. Our goal is to explain why these activities are important to agile testers. Let’s explore some reasons to use technology-facing tests that support the team.


Lets Us Go Faster and Do More

Speed should never be the end goal of an agile development team. Trying to do things fast and meet tight deadlines without thinking about the quality causes us to cut corners and revert to old, bad habits. If we cut corners, we’ll build up more technical debt, and probably miss the deadline anyway. Happily, though, speed is a long-term side effect of producing code with the highest possible internal quality. Continuous builds running unit tests notify the team of failure within a few minutes of the problem check-in, and the mistake can be found and fixed quickly. A safety net of automated unit and code integration tests enables the programmers to refactor frequently. This keeps the code at a reasonable standard of maintainability and delivers the best value for the time invested. Technical debt is kept as low as possible.

If you’ve worked as a tester on a project where unit testing was neglected, you know how easy it is to spend all of your time finding unit-level defects. You might find so many bugs while testing the “happy path” that you never have time to test more complex scenarios and edge cases. The release deadline is pushed back as the “find and fix” cycle drags on, or testing is just stopped and a buggy product is foisted off on unsuspecting customers.

Our years on agile teams have been Utopian in contrast to this scenario. Driving coding practices with tests means that the programmers probably understood the story’s requirements reasonably well. They’ve talked extensively with the customers and testers to clarify the desired behaviors. All parties understand the changes being made. By the time the team has completed all of the task cards for coding a story, or a thin, testable slice of one, the feature has been well covered by unit and component tests. Usually the programmers have made sure at least one path through the story works end to end.

This means that we, as testers, waste little time finding low-level bugs. We’re likely to try scenarios the programmers hadn’t thought of and to spend our time on higher-level business functionality. Well-designed code is usually robust and testable. If we find a defect, we show it to the programmer, who writes a unit test to reproduce the bug and then fixes it quickly. We actually have time to focus on exploratory testing and the other types of in-depth tests to give the code a good workout and learn more about how it should work. Often, the only “bugs” we find are requirements that everyone on our team missed or misunderstood. Even those are found quickly if the customer is involved and has regular demos and test opportunities. After a development team has mastered TDD, the focus for improvement shifts from bug prevention to figuring out better ways to elicit and capture requirements before coding.

Test-First Development vs. Test-Driven Development

Gerard Meszaros [Meszaros 2007, pp. 813–814] offers the following description of how test-first development differs from test-driven development:

“Unlike test-driven development, test-first development merely says that the tests are written before the production code; it does not imply that the production code is made to work one test at a time (emergent design). Test-first development can be applied at the unit test or customer test level, depending on which tests we have chosen to automate.”

Erik Bos [2008] observes that test-first development involves both test-first programming and test-first design, but there’s a subtle difference:

“With test-first design, the design follows the tests, whereas you can do test-first programming of a design that you first write down on a whiteboard. On larger projects, we tend to do more design via whiteboard discussions; the team discusses the architecture around a whiteboard, and codes test-first based on this design. On smaller projects, we do practice test-driven design.”


There are several different philosophies about when to write tests and for what purpose. It’s up to each team to agree on the approach that helps it achieve its quality objectives, although there is common agreement in the agile community that TDD definitely helps a team achieve better-quality software. This is an important way that programmer tests support the team. Let’s look at some more ways.


Making Testers’ Jobs Easier

The core practices related to programmer tests make lots of testing activities easier to accomplish. Programmers work in their own sandboxes, where they can test new code without affecting anyone else’s work. They don’t check in code until it has passed a suite of regression tests in their sandbox.

The team thinks about test environments and what to use for test data. Unit tests usually work with fake or mock objects instead of actual databases for speed, but programmers still need to test against realistic data. Testers can help them identify good test data. If the unit tests represent real-life data, fewer issues will be found later.

Lisa’s Story

Here’s a small example. When my current team first adopted agile development, we didn’t have any automated tests. We had no way to produce a deployable code package, and we had no rudimentary test environments or test databases. I didn’t have any means to produce a build myself, either. We decided to start writing code test-first and committed to automating tests at all levels where appropriate, but we needed some infrastructure first.

Our first priority was to implement a continuous build process, which was done in a couple of days. Each build sent an email with a list of checked-in files and comments about the updates. I could now choose which build to deploy and test. The next priority was to provide independent test environments so that tests run by one person would not interfere with other tests. The new database expert created new schemas to meet testing needs and a “seed” database of canonical, production-like data. These schemas could be refreshed on demand quickly with a clean set of data. Each team member, including me, got a unique and independent test environment.

Even before the team mastered TDD, the adopted infrastructure was in place to support executing tests. This infrastructure enabled the team to start testing much more effectively. Another aspect of trying to automate testing was dealing with a legacy application that was difficult to test. The decisions that were made to enable TDD also helped with customer-facing tests. We decided to start rewriting the system in a new architecture that facilitated testing and test automation, not only at the unit level but at all levels.

—Lisa

Writing tests and writing code with those tests in mind means programmers are always consciously making code testable. All of these good infrastructure-related qualities spill over to business-facing tests and tests that critique the product. The whole team is continually thinking of ways to improve design and make testing easier.


Designing with Testing in Mind

One advantage of driving development with tests is that code is written with the express intention of making the tests pass. The team has to think, right from the beginning, about how it will execute and automate tests for every story it codes. Test-driven development means that programmers will write each test before they write the code to make it pass.

Writing “testable code” is a simple concept, but it’s not an easy task, especially if you’re working on old code that has no automated tests and isn’t designed for testability. Legacy systems often have business logic, I/O, database, and user interface layers intertwined. There’s no easy way to hook in to automate a test below the GUI or at the unit level.

A common approach in designing a testable architecture is to separate the different layers that perform different functions in the application. Ideally, you would want to access each layer directly with a test fixture and test algorithms with different inputs. To do this, you isolate the business logic into its own layer, using fake objects instead of trying to access other applications or the actual database. If the presentation layer can be separated from underlying business logic and database access, you can quickly test input validation without testing underlying logic.

Layered Architectures and Testability

Lisa’s team took the “strangler application” approach to creating a testable system where tests could be use to drive coding. Mike Thomas, the team’s senior architect, explains how their new layered architecture enabled a testable design.

A layered architecture divides a code base into horizontal slices that contain similar functionality, often related to a technology. The slices at the highest level are the most specific and depend upon the slices below, which are more general. For example, many layered code bases have slices such as the following: UI, business logic, and data access.

Horizontal layering is just one way to organize a code base: Another is domain-oriented slices (such as payroll or order entry), which are generally thought of as “vertical.” These layering approaches can be combined, of course, and all can be used to enhance testability.

Layering has advantages for testing, but only if the mechanism for “connecting” the slices provides flexibility. If a code base has tightly coupled slices via such mechanisms as direct concrete class dependencies and static methods, it is difficult to isolate a unit for testing, despite the layering. This makes most automated tests into integration tests, which can be complicated and can run slowly. In many cases, testing can only be accomplished by running the entire system.

Contrast this with a code base where the layers are separated by interfaces. Each slice depends only upon interfaces defined in the slice beneath it rather than on specific classes. Dependencies on such interfaces are easy to satisfy with test doubles at test time: mocks, stubs, and so on. Unit testing is thus simplified because each unit can truly be isolated. For example, the UI can be tested against mock business layer objects, and the business layer can be tested against mock data access objects, avoiding live database access.

The layered approach has allowed Lisa’s team to succeed in automating tests at all levels and drive development with both technology-facing and business-facing tests.

Another example of an approach to testable design is Alistair Cockburn’s Ports and Adapters pattern [Cockburn, 2005]. This pattern’s intent is to “create your application to work without either a UI or a database so you can run automated regression tests against the application, work when the database becomes unavailable, and link applications together without any user involvement.” Ports accept outside events, and a technology-specific adapter converts it into a message that can be understood by the application. In turn, the application sends output via a port to an adapter, which creates the signals needed by the receiving human or automated users. Applications designed using this pattern can be driven by automated test scripts as easily as by actual users.

See the bibliography for more information on Alastair Cockburn’s Ports and Adapters pattern.

It’s more obvious how to code test-first on a greenfield project. Legacy systems, which aren’t covered by automated unit tests, present a huge challenge. It’s hard to write unit tests for code that isn’t designed for testability, and it’s hard to change code that isn’t safeguarded with unit tests. Many teams have followed the “legacy code rescue” techniques explained by Michael Feathers in Working Effectively with Legacy Code [Feathers, 2005]. Other teams, such as Lisa’s, aim to “strangle” their legacy code. This strategy stems from Martin Fowler’s “strangler application” [Fowler, 2004]. New stories were coded test-first in a new architecture while the old system was still maintained. Over time, much of the system has been converted to the new architecture, with the goal of eventually doing away with the old system.

The bibliography has links to more articles about “rescue” and “strangler” approaches to legacy code.

Agile testing in a legacy mainframe type of environment presents particular challenges, not the least of which is the lack of availability of publications and information about how to do it successfully. COBOL, mainframes, and their ilk are still widely used. Let agile principles and values guide your team as you look for ways to enable automated testing in your application. You might have to adapt some techniques; for example, maybe you can’t write code test-first, but you can test soon after writing the code. When it’s the team’s problem to solve, and not just the testers’ problem, you’ll find a way to write tests.

Testing Legacy Systems

John Voris, a developer with Crown Cork and Seal, works in the RPG language, a cousin of COBOL, which runs on the operating system previously known as AS 400 and now known as System i. John was tasked with merging new code with a vendor code base. He applied tenets of Agile, Lean, and IBM-recommended coding practices to come up with an approach he calls “ADEPT” for “AS400 Displays for External Prototyping and Testing.” While he isn’t coding test-first, he’s testing “Minutes Afterward.” Here’s how he summed up his approach:

• Write small, single-purpose modules (not monolithic programs), and refactor existing programs into modules. Use a Presenter First development approach (similar to the Model View Presenter or Model View Controller pattern).

For more information about Presenter First development, see the bibliography.

• Define parameter interfaces for the testing harness based on screen formats and screen fields. The only drawback here is numbers are defined as zoned decimals rather than packed hexadecimal, but this is offset by the gain in productivity.

• “Minutes after” coding each production module, create a testing program using the screen format to test via the UI. The UI interface for the test is created prior to the production program, because the UI testing interface is the referenced interface for the production module. The impetus for running a test looms large for the programmer, because most of the coding for the test is already done.

• Use standard test data sets, which are unchanging, canonical test data, to drive the tests.

• This approach, in which the test programs are almost auto-generated, lends itself to automation with a record/playback tool that would capture data inputs and outputs, with tests run in a continuous build, using RPGUnit.


For more about RPGUnit, see www.RPGUnit.org.

Your team can find an approach to designing for testability that works for you. The secret is the whole-team commitment to testing and quality. When a team is constantly working to write tests and make them pass, it finds a way to get it done. Teams should take time to consider how they can create an architecture that will make automated tests easy to create, inexpensive to maintain, and long-lived. Don’t be afraid to revisit the architecture if automated tests don’t return enough value for the investment in them.


Timely Feedback

The biggest value of unit tests is in the speed of their feedback. In our opinion, a continuous integration and build process that runs the unit tests should finish within ten minutes. If each programmer checks code in several times a day, a longer build and test process will cause changes to start stacking up. As a tester, it can be frustrating to have to wait a long time for new functionality or a bug fix. If there’s a compile error or unit test failure, the delay gets even worse, especially if it’s almost time to go home!

A build and test process that runs tests above the unit level, such as functional API tests or GUI tests, is going to take longer. Have at least one build process that runs quickly, and a second that runs the slower tests. There should be at least one daily “build” that runs all of the slower functional tests. However, even that can be unwieldy. When a test fails and the problem is fixed, how long will it take to know for sure that the build passes again?

If your build and test process takes too long, ask your team to analyze the cause of the slowdown and take steps to speed up the build. Here are a few examples.

Database access usually consumes lots of time, so consider using fake objects, where possible, to replace the database, especially at the unit level.

Move longer-running integration and database-access tests to the secondary build and test process.

See if tests can run in parallel so that they finish faster.

Run the minimum tests needed for regression testing your system.

Distribute tasks across multiple build machines.

Upgrade the hardware and software that run the build.

Find the area that takes the most time and take incremental steps to speed it up.

Lisa’s Story

Early in my current team’s agile evolution, we had few unit tests, so we included a few GUI smoke tests in our continual build, which kicked off on every check-in to the source code control system. When we had enough unit tests to feel good about knowing when code was broken, we moved the GUI tests and the FitNesse functional tests into a separate build and test process that ran at night, on the same machine as our continual build.

Our continual ongoing build started out taking less than 10 minutes, but soon was taking more than 15 minutes to complete. We wrote task cards to diagnose and fix the problem. The unit tests that the programmers had written early on weren’t well designed, because nobody was sure of the best way to write unit tests. Time was budgeted to refactor the unit tests, use mock data access objects instead of the real database, and redesign tests for speed. This got the build to around eight minutes. Every time it has started to creep up, we’ve addressed the problem with refactoring, removing unnecessary tests, upgrading the hardware, and choosing different software that helped the build run faster.

As our functional tests covered more code, the nightly build broke more often. Because the nightly build ran on the same machine as the continual ongoing one, the only way to verify that the build was “green” again was to stop the ongoing build, which removed our fast feedback. This started to waste everyone’s time. We bought and set up another build machine for the longer build, which now also runs continuously. This was much less expensive than spending so much time keeping two builds running on the same machine, and now we get quick feedback from our functional tests as well.

—Lisa

Wow, multiple continuous build and test processes providing constant feedback—it sounds like a dream to a lot of testers. Regression bugs will be caught early, when they’re cheapest to fix. This is a great reason for writing technology-facing tests. Can we get too carried away with them, though? Let’s look at the line between technology-facing tests and business-facing tests.


Where Do Technology-Facing Tests Stop?

We often hear people worry that the customer-facing tests will overlap so much with the technology-facing tests that the team will waste time. We know that business-facing tests might cover a bit of the same ground as unit or code integration tests, but they have such different purposes that waste isn’t a worry.

For example, we have a story to calculate a loan amortization schedule and display it to a user who’s in the process of requesting a loan. A unit test for this story would likely test for illegal arguments, such as an annual payment frequency if the business doesn’t allow it. There might be a unit test to figure the anticipated loan payment start date given some definition of amount, interest rate, start date, and frequency. Unit-level tests could cover different combinations of payment frequency, amount, interest date, term, and start date in order to prove that the amortization calculation is correct. They could cover scenarios such as leap years. When these tests pass, the programmer feels confident about the code.

Each unit test is independent and tests one dimension at a time. This means that when a unit test fails, the programmer can identify the problem quickly and solve the issue just as quickly. The business-facing tests very seldom cover only one dimension, because they are tackled from a business point of view.

The business-facing tests for this story would define more details for the business rules, the presentation in the user interface, and error handling. They would verify that payment details, such as the principal and interest applied, display correctly in the user interface. They would test validations for each field on the user interface, and specify error handling for situations such as insufficient balance or ineligibility. They could test a scenario where an administrator processes two loan payments on the same day, which might be harder to simulate at the unit level.

The business-facing tests cover more complex user scenarios and verify that the end user will have a good experience. Push tests to lower levels whenever possible; if you identify a test case that can be automated at the unit level, that’s almost always a better return on investment.

Chapter 13, “Why We Want to Automate Tests and What Holds Us Back,” talks more about the ROI of the different types of tests.

If multiple areas or layers of the application are involved, it might not be possible to automate at the unit level. Both technology-facing and business-facing levels might have tests around the date of the first loan payment, but they check for different reasons. The unit test would check the calculation of the date, and the business-facing test would verify that it displays correctly in the borrower’s loan report.

Learning to write Quadrant 1 tests is hard. Many teams making the transition to agile development start out with no automated unit tests, not even a continuous integration and build process. In the next section, we suggest actions agile testers can take if their teams don’t tackle Quadrant 1 tests.


What If the Team Doesn’t Do These Tests?

Many an organization has decided to try agile development, or at least stated that intention, without understanding how to make a successful transition. When we’re in a tester role, what can we do to help the development team implement TDD, continuous integration, and other practices that are key to successful development?

Our experience over the years has been that if we aren’t programmers ourselves, we don’t necessarily have much credibility when we urge the programmers to adopt practices such as TDD. If we could sit down and show them how to code test-first, that would be persuasive, but many of us testers don’t have that kind of experience. We’ve also found that evangelizing doesn’t work. It’s not that hard to convince someone conceptually that TDD is a good idea. It’s much trickier to help them get traction actually coding test-first.


What Can Testers Do?

If you’re a tester on a so-called “agile” team that isn’t even automating unit tests or producing continuous builds—or at a minimum, doing builds on a daily basis—you’re going to get frustrated pretty quickly. Don’t give up; keep brainstorming for a way to get traction on a positive transition. Try using social time or other relaxing activity to take some quality time to see what new ideas you can generate to get all team members on board.

One trap to avoid is having testers write the unit tests. Because TDD is reallymore of a design activity, it’s essential that the person writing the code also write the tests, before writing the code. Programmers also need the immediate feedback that automated unit tests give. Unit tests written by someone else after the code is written might still guard against regression defects, but they won’t have the most valuable benefits of tests written by the programmer.

Lisa’s Story

Whenever I’ve wanted to effect change, I’ve turned to the patterns in Fearless Change by Mary Lynn Manns and Linda Rising [2004]. After working on two XP teams, I joined a team that professed a desire to become agile but wasn’t making strides toward solid development practices. I found several patterns in Fearless Change to try to move the team toward agile practices.

“Ask for Help” was one pattern that helped me. This pattern says, in part: “Since the task of introducing a new idea into an organization is a big job, look for people and resources to help your efforts” [Manns and Rising, 2004]. When I wanted my team to start using FitNesse, I identified the programmer who was most sympathetic to my cause and asked him to pair with me to write FitNesse tests for the story he was working on. He told the other programmers about the benefits he derived from the FitNesse tests, which encouraged them to try it too. Most people want to help, and agile is all about the team working together, so there’s no reason to go it alone.

“Brown Bag” is another change pattern that my teams have put to good use. For example, my current team held several brown bag sessions where they wrote unit tests together. “Guru on Your Side” is a productive pattern in which you enlist the help of a well-respected team member who might understand what you’re trying to achieve. A previous team I was on was not motivated to write unit tests. The most experienced programmer on the team agreed with me that test-driven development was a good idea, and he set an example for the rest of the team.

We think you’ll find that there’s always someone on an agile team who’s sympathetic to your cause. Enlist that person’s support, especially if the team perceives him or her as a senior-level guru.

—Lisa

As a tester on an agile team, there’s a lot you can do to act as a change agent, but your potential impact is limited. In some cases, strong management support is the key to driving the team to engage in Quadrant 1 activities.


What Can Managers Do?

If you’re managing a development team, you can do a lot to encourage test-driven development and unit test automation. Work with the product owner to make quality your goal, and communicate the quality criteria to the team. Encourage the programmers to take time to do their best work instead of worrying about meeting a deadline. If a delivery date is in jeopardy, push to reduce the scope, not the quality. Your job is to explain to the business managers how making quality a priority will ensure that they get optimum business value.

Give the team time to learn, and provide expert, hands-on training. Bring in an experienced agile development coach or hire someone with experience in using these practices who can transfer those skills to the rest of the team. Budget time for major refactoring, for brainstorming about the best approach to writing unit and code integration tests, and for evaluating, installing, and upgrading tools. Test managers should work with development managers to encourage practices that enhance testability and allow testers to write executable tests. Test managers can also make sure testers have time to learn how to use the automation tools and frameworks that the team decides to implement.


It’s a Team Problem

While you can find ways to be an effective change agent, the best thing to do is involve the whole team in solving the problems. If you aren’t already doing retrospectives after every iteration, propose trying this practice or some other type of process improvement. At the retrospective, raise issues that are hampering successful delivery. For example, “We aren’t finishing testing tasks before the end of the iteration” is a problem for the whole team to address. If one reason for not finishing is the high number of unit-level bugs, suggest experimenting with TDD, but allow programmers to propose their own ways to address the problem. Encourage the team to try a new approach for a few iterations and see how it works.

More about retrospectives and process improvement in Chapter 19, “Wrap Up the Iteration.”

Technology-facing tests that support the team’s development process are an important foundation for all of the testing that needs to happen. If the team isn’t doing an adequate job with the tests in this quadrant, the other types of testing will be much more difficult. This doesn’t mean you can’t get value from the other quadrants on their own—it just means it will be harder to do so because the team’s code will lack internal quality and everything will take longer.

Technology-facing tests can’t be done without the right tools and infrastructure. In the next section, we look at examples of the types of tools a team needs to be effective with Quadrant 1 tests.


Toolkit

There’s no magical tool that will ensure success. However, tools can help good people do their best work. Building up the right infrastructure to support technology-facing tests is critical. There’s a huge selection of excellent tools available, and they improve all the time. Your team must find the tools that work best for your situation.


Source Code Control

Source code control is known by other names too, such as version control or revision control. It’s certainly not new, or unique to agile development, but no software development team can succeed without it. That’s why we’re discussing it here. Without source code control, you’ll never be sure what you’re testing. Did the programmer change only the module he said he changed, or did he forget changes he made to other modules? You can’t back out unwanted or erroneous changes without some kind of versioning system. Source code control keeps different programmers from walking on each other’s changes to the same modules. Without versioning, you can’t be sure what code to release to production.

Software Configuration Management Patterns: Effective Teamwork, Practical Integrations [2003], by Stephen Berczuk and Brad Appleton, is a good resource to use to learn how and why to use source code control. Source code control is essential to any style of software development.

Use source code control for automated test scripts, too. It’s important to tie the automated tests with the corresponding code version that they tested in case you need to rerun tests against that version in the future. When you label or tag a build, make sure you label or tag the test code too, even if it doesn’t get released to production.

Teams can organize their code hierarchy to provide a repository for production code, corresponding unit tests, and higher-level test scripts. Doing this might require some brainstorming and experimenting in order to get the right structure.

There are many terrific options to choose from. Open source systems such as CVS and Subversion (SVN) are easy to implement, integrate with a continuous build process and IDEs, and are robust. Vendor tools such as IBM Rational ClearCase and Perforce might add features that compensate for the increased overhead they often bring.

Source code control is tightly integrated with development environments. Let’s look at some IDEs used by agile teams.


IDEs

A good IDE (integrated development environment) can be helpful for programmers and testers on an agile team. The IDE integrates with the source code control system to help prevent problems with versioning and changes walking on each other. The editors inside an IDE are specific to the programming language and flag errors even as you write the code. Most importantly, IDEs provide support for refactoring.

Programmers who use an IDE tend to have strong personal preferences. However, sometimes an organization decrees that all programmers must use a specific IDE. This might be because of licensing, or it might be intended to encourage open pair programming. It is easier to pair with another programmer if the other person uses the same IDE, but it’s generally not essential for the same one to be used. Most tools work similarly, so it’s not hard to change from one IDE to another in order to meet new needs or take advantage of new features. Some diehards still prefer to use tried-and-true technology such as vi, vim, or emacs with make files rather than an IDE.

Open source IDEs such as Eclipse and NetBeans are widely used by agile teams, along with proprietary systems such as Visual Studio and IntelliJ IDEA. IDEs have plug-ins to support different languages and tools. They work as well with test scripts as they do with production code.

Lisa’s Story

On my current team, some programmers were using IntelliJ IDEA, while others used Eclipse. Environmental differences in rare cases caused issues, such as tests passing in the IDE but not the full build, or check-ins via the IDE causing havoc in the source code control system. Generally, though, use of different IDEs caused no problems. Interestingly, over time most of the Eclipse users switched. Pairing with the IntelliJ users led them to prefer it.

I use Eclipse to work with the automated test scripts as well as to research issues with the production code. The Ruby plug-in helps us with our Ruby and Watir scripts, and the XML editor helps with our Canoo WebTest scripts. We can run unit tests and do builds through the IDE. Programmers on the team helped me set up and start using Eclipse, and it has saved huge amounts of time. Maintaining the automated tests is much easier, and the IDE’s “synchronize” view helps me remember to check in all of the modules I’ve changed.

Test tools are starting to come out with their own IDEs or plug-ins to work with existing IDEs such as Eclipse. Take advantage of these powerful, time-saving, quality-promoting tools.

—Lisa

Testers who aren’t automating tests through an IDE, but who want to be able to look at changed snippets of code, can use tools such as FishEye that enable the testers to get access to the code through the automated build.

As of this writing, IDEs have added support for dynamic languages such as Ruby, Groovy, and Python. Programmers who use dynamic languages may prefer lighter-weight tools, but they still need good tools that support good coding practices, such as TDD and refactoring.

Regardless of the development environment and tools being used, agile teams need a framework that will integrate code changes from different programmers, run the unit tests to verify no regression bugs have occurred, and provide the code in a deployable format.


Build Tools

Your team needs some way to build the software and create a deployable jar, war, or other type of file. This can be done with shell-based tools such as make, but those tools have limitations, such as the platforms where they work. Agile teams that we know use tools such as ant, Nant, and Maven to build their projects. These tools not only manage the build but also provide easy ways to report and document build results, and they integrate easily with build automation and test tools. They also integrate with IDEs.


Build Automation Tools

Continuous integration is a core practice for agile teams. You need a way to not only build the project but also run automated tests on each build to make sure nothing broke. A fully automated and reproducible build that runs many times a day is a key success factor for agile teams. Automated build tools provide features such as email notification of build results, and they integrate with build and source code control tools.

Commonly used tools as of the writing of this book include the open source tools CruiseControl, CruiseControl.net, CruiseControl.rb, and Hudson. Other open source and proprietary tools available at publication time are AnthillPro, Bamboo, BuildBeat, CI Factory, Team City, and Pulse, just to name a few.

Without an automated build process you’ll have a hard time deploying code for testing as well as releasing. Build management and build automation tools are easy to implement and absolutely necessary for successful agile projects. Make sure you get your build process going early, even before you start coding. Experiment with different tools when you find you need more features than your current process provides.


Unit Test Tools

Unit test tools are specific to the language in which you’re coding. “xUnit” tools are commonly used by agile teams, and there’s a flavor for many different languages, including JUnit for Java, NUnit for .NET, Test::Unit for Perl and Ruby, and PyUnit for Python.

Behavior-driven development is another flavor of test-driven development, spelling out expected behavior to drive tests with tools such as RSpec and easyb.

See Chapter 9, “Toolkit for Business-Facing Tests that Support the Team,” for more information on behavior-driven development tools.

GUI code can and should be developed test-first as well. Some tools for rich-client unit testing are TestNG, Abbot, and SWTBot.

Tools such as EasyMock and Ruby/Mock help with implementing mock objects and test stubs, an integral part of well-designed unit tests.

See the bibliography for links and books to help your team search for the right unit test tools.

The tools programmers use to write technology-facing tests can also be used for business-facing tests. Whether they are suited for that purpose in your project depends on the needs of your team and your customers.


Summary

In this chapter, we explained the purpose of technology-facing tests that support the team, and we talked about what teams need to use them effectively.

Technology-facing tests that support programming let the team produce the highest quality code possible; they form the foundation for all other types of testing.

The benefits of this quadrant’s tests include going faster and doing more, but speed and quantity should never be the ultimate goal.

Programmers write technology-facing tests that support the team and provide great value to testers by enhancing the internal quality and testability of the system.

Teams that fail to implement the core practices related to agile development are likely to struggle.

Legacy systems usually present the biggest obstacles to test-driven development, but these problems can be overcome with incremental approaches.

If your team doesn’t now do these tests, you can help them get started by engaging other team members and getting support from management.

There can be some overlap between technology-facing tests and business-facing tests that support the team. However, when faced with a choice, push tests to the lowest level in order to maximize ROI.

Teams should set up continuous integration, build, and test processes in order to provide feedback as quickly as possible.

Agile teams require tools for tasks such as source code control, test automation, IDEs, and build management in order to facilitate technology-facing tests that support the team.


Загрузка...