blob: b34f334b33b1c3ac4436d5d667ca07b3830ea276 [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?>
<org.eclipse.epf.uma:ContentDescription xmi:version="2.0"
xmlns:xmi="http://www.omg.org/XMI" xmlns:org.eclipse.epf.uma="http://www.eclipse.org/epf/uma/1.0.4/uma.ecore"
xmlns:epf="http://www.eclipse.org/epf" epf:version="1.2.0" xmi:id="-3AbfvnHrCOIQS63sEjrOew"
name="test-first_design,6.556259235358794E-306" guid="-3AbfvnHrCOIQS63sEjrOew"
changeDate="2006-11-13T18:58:35.617-0500" version="1.0.0">
<mainDescription>&lt;a id=&quot;XE_test__developer_testing__test-first_design&quot; name=&quot;XE_test__developer_testing__test-first_design&quot;>&lt;/a>&lt;a id=&quot;XE_design__test-first_design&quot; name=&quot;XE_design__test-first_design&quot;>&lt;/a> &#xD;
&lt;h3>&#xD;
&lt;a id=&quot;Introduction&quot; name=&quot;Introduction&quot;>Introduction&lt;/a>&#xD;
&lt;/h3>&#xD;
&lt;p>&#xD;
Test designs are created using information from a variety of artifacts, including design artifacts such as use case&#xD;
realizations, design models, or classifier interfaces. Tests are executed after components are created. It's typical to&#xD;
create the test designs just before the tests are to be executed - well after the software design artifacts are&#xD;
created. Figure 1, following, shows an example. Here, test design begins sometime toward the end of implementation. It&#xD;
draws on the results of component design. The arrow from Implementation to Test Execution indicates that the tests&#xD;
can't be executed until the implementation is complete.&#xD;
&lt;/p>&#xD;
&lt;p align=&quot;center&quot;>&#xD;
&lt;img height=&quot;159&quot; alt=&quot;&quot; src=&quot;resources/tstfrsdsg-img1.gif&quot; width=&quot;614&quot; />&#xD;
&lt;/p>&#xD;
&lt;p class=&quot;picturetext&quot;>&#xD;
Fig1: Traditionally, Test Design is performed later in the life-cycle&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
However, it doesn't have to be this way. Although test execution has to wait until the component has been implemented,&#xD;
test design can be done earlier. It could be done just after the design artifact is completed. It could even be done in&#xD;
parallel with component design, as shown here:&#xD;
&lt;/p>&#xD;
&lt;p align=&quot;center&quot;>&#xD;
&lt;img height=&quot;158&quot; alt=&quot;&quot; src=&quot;resources/tstfrsdsg-img2.gif&quot; width=&quot;610&quot; />&#xD;
&lt;/p>&#xD;
&lt;p class=&quot;picturetext&quot;>&#xD;
Fig2: Test-first Design brings test design chronologically in-line with software design&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
Moving the test effort &quot;upstream&quot; in this way is commonly called &quot;test-first design&quot;. What are its advantages?&#xD;
&lt;/p>&#xD;
&lt;ol>&#xD;
&lt;li>&#xD;
No matter how carefully you design software, you'll make mistakes. You might be missing a relevant fact. Or you&#xD;
might have particular habits of thought that make it hard for you to see certain alternatives. Or you might just be&#xD;
tired and overlook something. Having other people review your design artifacts helps. They might have the facts you&#xD;
miss, or they might see what you overlooked. It's best if these people have a different perspective than you do; by&#xD;
looking at the design differently, they'll see things you missed.&lt;br />&#xD;
&lt;br />&#xD;
Experience has shown that the testing perspective is an effective one. It's relentlessly concrete. During software&#xD;
design, it's easy to think of a particular field as &quot;displaying the title of the current customer&quot; and move on&#xD;
without really thinking about it. During test design, you must decide &lt;i>specifically&lt;/i> what that field will show&#xD;
when a customer who retired from the Navy and then obtained a law degree insists on referring to himself as&#xD;
&quot;Lieutenant Morton H. Throckbottle (Ret.), Esq.&quot; Is his title &quot;Lieutenant&quot; or &quot;Esquire&quot;?&lt;br />&#xD;
&lt;br />&#xD;
If test design is deferred until just before test execution, as in Figure 1, you'll probably waste money. A&#xD;
mistake in your software design will remain uncaught until test design, when some tester says, &quot;You know, I knew&#xD;
this guy from the Navy...&quot;, creates the &quot;Morton&quot; test, and discovers the problem. Now a partially or fully complete&#xD;
implementation has to be rewritten and a design artifact has to be updated. It would be cheaper to catch the&#xD;
problem before implementation begins.&lt;br />&#xD;
&lt;/li>&#xD;
&lt;li>&#xD;
Some mistakes might be caught before test design. Instead, they'll be caught by the Implementer. That's still bad.&#xD;
Implementation must grind to a halt while the focus switches from how to implement the design to what that design&#xD;
should be. That's disruptive even when the Implementer and Designer roles are filled by the same person; it's much&#xD;
more disruptive when they're different people. Preventing this disruption is another way in which test-first design&#xD;
helps improve efficiency.&lt;br />&#xD;
&lt;/li>&#xD;
&lt;li>&#xD;
Test designs help Implementers in another way-by clarifying design. If there's a question in the Implementer's mind&#xD;
about what the design meant, the test design might serve as a specific example of the desired behavior. That will&#xD;
lead to fewer bugs due to Implementer misunderstanding.&lt;br />&#xD;
&lt;/li>&#xD;
&lt;li>&#xD;
There are fewer bugs even if the question &lt;i>wasn't&lt;/i> in the Implementer's mind - but should have been. For&#xD;
example, there might have been an ambiguity that the Designer unconsciously interpreted one way and the Implementer&#xD;
another. If the Implementer is working from both the design and also from specific instructions about what the&#xD;
component is supposed to do - from test cases - the component is more likely to actually do what is required.&#xD;
&lt;/li>&#xD;
&lt;/ol>&#xD;
&lt;h3>&#xD;
&lt;a id=&quot;Examples&quot; name=&quot;Examples&quot;>Examples&lt;/a>&#xD;
&lt;/h3>&#xD;
&lt;p>&#xD;
Here are some examples to give you the flavor of test-first design.&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
Suppose you're creating a system to replace the old &quot;ask the secretary&quot; method of assigning meeting rooms. One of the&#xD;
methods of the &lt;font size=&quot;+0&quot;>MeetingDatabase&lt;/font> class is called &lt;font size=&quot;+0&quot;>getMeeting&lt;/font>, which has this&#xD;
signature:&#xD;
&lt;/p>&#xD;
&lt;blockquote>&#xD;
&lt;pre>&#xD;
Meeting getMeeting(Person, Time);&#xD;
&lt;/pre>&#xD;
&lt;/blockquote>&#xD;
&lt;p>&#xD;
Given a person and a time, &lt;font size=&quot;+0&quot;>getMeeting&lt;/font> returns the meeting that person is scheduled to be in at&#xD;
that time. If the person isn't scheduled for anything, it returns the special &lt;font size=&quot;+0&quot;>Meeting&lt;/font> object&#xD;
&lt;font size=&quot;+0&quot;>unscheduled&lt;/font>. There are some straightforward test cases:&#xD;
&lt;/p>&#xD;
&lt;ul>&#xD;
&lt;li>&#xD;
The person isn't in any meeting at the given time. Is the &lt;font size=&quot;+0&quot;>unscheduled&lt;/font> meeting returned?&#xD;
&lt;/li>&#xD;
&lt;li>&#xD;
The person is in a meeting at that time. Does the method return the correct meeting?&#xD;
&lt;/li>&#xD;
&lt;/ul>&#xD;
&lt;p>&#xD;
These test cases are unexciting, but they need to be tried eventually. They might as well be created now, by writing&#xD;
the actual test code that will someday be run. Java code for the first test might look like this:&#xD;
&lt;/p>&#xD;
&lt;pre>&#xD;
// if not in a meeting at given time,&#xD;
// expect to be unscheduled.&#xD;
public void testWhenAvailable() {&#xD;
Person fred = new Person(&quot;fred&quot;);&#xD;
Time now = Time.now();&#xD;
MeetingDatabase db = new MeetingDatabase();&#xD;
expect(db.getMeeting(fred, now) == Meeting.unscheduled);&#xD;
}&#xD;
&lt;/pre>&#xD;
&lt;p>&#xD;
But there are more interesting test ideas. For example, this method searches for a match. Whenever a method searches,&#xD;
it's a good idea to ask what should happen if the search finds more than one match. In this case, that means asking&#xD;
&quot;Can a person be in two meetings at once?&quot; Seems impossible, but asking the secretary about that case might reveal&#xD;
something surprising. It turns out that some executives are quite often scheduled into two meetings at once. Their role&#xD;
is to pop into a meeting, &quot;rally the troops&quot; for some short amount of time, and then move on. A system that didn't&#xD;
accommodate that behavior would go at least partially unused.&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
This is an example of test-first design done at the implementation level catching an analysis problem. There are a few&#xD;
things to note about that:&#xD;
&lt;/p>&#xD;
&lt;ol>&#xD;
&lt;li>&#xD;
You would hope that good use-case specification and analysis would have already discovered this requirement. In&#xD;
that case, the problem would have been avoided &quot;upstream&quot; and &lt;font size=&quot;+0&quot;>getMeeting&lt;/font> would have been&#xD;
designed differently. (It couldn't return a meeting; it would have to return a set of meetings.) But analysis&#xD;
always misses some problems, and it's better for them to be discovered during implementation than after&#xD;
deployment.&lt;br />&#xD;
&lt;/li>&#xD;
&lt;li>&#xD;
In many cases, Designers and Implementers won't have the domain knowledge to catch such problems - they won't have&#xD;
the opportunity or time to quiz the secretary. In that case, the person designing tests for &lt;font size=&quot;+0&quot;>getMeeting&lt;/font> would ask, &quot;is there a case in which two meetings should be returned?&quot;, think for a&#xD;
while, and conclude that there wasn't. So test-first design doesn't catch every problem, but the mere fact of&#xD;
asking the right kinds of questions increases the chance a problem will be found.&lt;br />&#xD;
&lt;/li>&#xD;
&lt;li>&#xD;
Some of the same testing techniques that apply during implementation also apply to analysis. Test-first design can&#xD;
be done by analysts as well, but that's not the topic of this page.&#xD;
&lt;/li>&#xD;
&lt;/ol>&#xD;
&lt;p>&#xD;
The second of the three examples is a statechart model for a heating system.&#xD;
&lt;/p>&#xD;
&lt;p align=&quot;center&quot;>&#xD;
&lt;img height=&quot;253&quot; alt=&quot;&quot; src=&quot;resources/tstfrsdsg-img3.gif&quot; width=&quot;567&quot; />&#xD;
&lt;/p>&#xD;
&lt;p class=&quot;picturetext&quot;>&#xD;
Fig3: HVAC Statechart&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
A set of tests would traverse all the arcs in the statechart. One test might begin with an idle system, inject a Too&#xD;
Hot event, fail the system during the Cooling/Running state, clear the failure, inject another Too Hot event, then run&#xD;
the system back to the Idle state. Since that does not exercise all the arcs, more tests are needed. These kinds of&#xD;
tests look for various kinds of implementation problems. For example, by traversing every arc, they check whether the&#xD;
implementation has left one out. By using sequences of events that have failure paths followed by paths that should&#xD;
successfully complete, they check whether error-handling code fails to clean up partial results that might affect later&#xD;
computation. (For more about testing statecharts, see &lt;a class=&quot;elementLinkWithUserText&quot; href=&quot;./../../../xp/guidances/guidelines/test_ideas_for_statechart_and_flow_diagrams.html&quot; guid=&quot;1.0347051690476123E-305&quot;>Guideline: Test Ideas for Statechart and Activity Diagrams&lt;/a>.)&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
The final example uses part of a design model. There's an association between a creditor and an invoice, where any&#xD;
given creditor can have more than one invoice outstanding.&#xD;
&lt;/p>&#xD;
&lt;p align=&quot;center&quot;>&#xD;
&lt;img height=&quot;45&quot; alt=&quot;&quot; src=&quot;resources/tstfrsdsg-img4.gif&quot; width=&quot;186&quot; />&#xD;
&lt;/p>&#xD;
&lt;p class=&quot;picturetext&quot;>&#xD;
Fig4: Association between Creditor and Invoice Classes&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
Tests based on this model would exercise the system when a creditor has no invoices, one invoice, and a large number of&#xD;
invoices. A tester would also ask whether there are situations in which an invoice might need to be associated with&#xD;
more than one creditor, or where an invoice has no creditor. (Perhaps the people who currently run the paper-based&#xD;
system the computer system is to replace use creditor-less invoices as a way to keep track of pending work). If so,&#xD;
that would be another problem that should have been caught in Analysis.&#xD;
&lt;/p>&#xD;
&lt;h3>&#xD;
&lt;a id=&quot;WhoDoesTest-FirstDesign&quot; name=&quot;WhoDoesTest-FirstDesign&quot;>Who does test-first design?&lt;/a>&#xD;
&lt;/h3>&#xD;
&lt;p>&#xD;
Test-first design can be done by either the author of the design or by someone else. It's common for the author to do&#xD;
it. The advantage is that it reduces communication overhead. The Designer and Test Designer don't have to explain&#xD;
things to each other. Further, a separate Test Designer would have to spend time learning the design well, whereas the&#xD;
original Designer already knows it. Finally, many of these questions - like &quot;what happens if the compressor fails in&#xD;
state X?&quot; - are natural questions to ask during both software artifact design and test design, so you might as well&#xD;
have the same person ask them exactly once and write the answers down in the form of tests.&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
There are disadvantages, though. The first is that the Designer is, to some extent, blind to his or her own mistakes.&#xD;
The test design process will reveal some of that blindness, but probably not as much as a different person would find.&#xD;
How much of a problem this is seems to vary widely from person to person and is often related to the amount of&#xD;
experience the Designer has.&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
Another disadvantage of having the same person do both software design and test design is that there's no parallelism.&#xD;
Whereas allocating the roles to separate people will take more total effort, it will probably result in less elapsed&#xD;
calendar time. If people are itching to move out of design and into implementation, taking time for test design can be&#xD;
frustrating. More importantly, there's a tendency to skimp on the work in order to move on.&#xD;
&lt;/p>&#xD;
&lt;h3>&#xD;
&lt;a id=&quot;CanAllTestDesignBeDoneAtComponentDesignTime&quot; name=&quot;CanAllTestDesignBeDoneAtComponentDesignTime&quot;>Can all test&#xD;
design be done at component design time?&lt;/a>&#xD;
&lt;/h3>&#xD;
&lt;p>&#xD;
No. The reason is that not all decisions are made at design time. Decisions made during implementation won't be&#xD;
well-tested by tests created from the design. The classic example of this is a routine to sort arrays. There are many&#xD;
different sorting algorithms with different tradeoffs. Quicksort is usually faster than an insertion sort on large&#xD;
arrays, but often slower on small arrays. So a sorting algorithm might be implemented to use Quicksort for arrays with&#xD;
more than 15 elements, but insertion sort otherwise. That division of labor might be invisible from design artifacts.&#xD;
You &lt;i>could&lt;/i> represent it in a design artifact, but the Designer might have decided that the benefit of making such&#xD;
explicit decisions wasn't worthwhile. Since the size of the array plays no role in the design, the test design might&#xD;
inadvertently use only small arrays, meaning that none of the Quicksort code would be tested at all.&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
As another example, consider this fraction of a sequence diagram. It shows a &lt;font size=&quot;+0&quot;>SecurityManager&lt;/font>&#xD;
calling the &lt;font size=&quot;+0&quot;>log()&lt;/font> method of &lt;font size=&quot;+0&quot;>StableStore&lt;/font>. In this case, though, the &lt;font size=&quot;+0&quot;>log()&lt;/font> returns a failure, which causes &lt;font size=&quot;+0&quot;>SecurityManager&lt;/font> to call &lt;font size=&quot;+0&quot;>Connection.close()&lt;/font>.&#xD;
&lt;/p>&#xD;
&lt;p align=&quot;center&quot;>&#xD;
&lt;img height=&quot;161&quot; alt=&quot;&quot; src=&quot;resources/tstfrsdsg-img5.gif&quot; width=&quot;303&quot; />&#xD;
&lt;/p>&#xD;
&lt;p class=&quot;picturetext&quot;>&#xD;
Fig5: SecurityManager sequence diagram instance&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
This is a good reminder to the Implementer. Whenever &lt;font size=&quot;+0&quot;>log()&lt;/font> fails, the connection must be closed.&#xD;
The question for testing to answer is whether the Implementer really did it-and did it correctly-in &lt;i>all&lt;/i> cases or&#xD;
just in &lt;i>some&lt;/i>. To answer the question, the Test Designer must find all the calls to &lt;font size=&quot;+0&quot;>StableStore.log()&lt;/font> and make sure each of those call points is given a failure to handle.&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
It might seem odd to run such a test, given that you've just looked at all the code that calls &lt;font size=&quot;+0&quot;>StableStore.log()&lt;/font>. Can't you just check to see if it handles failure correctly?&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
Perhaps inspection might be enough. But error-handling code is notoriously error-prone because it often implicitly&#xD;
depends on assumptions that the existence of the error has violated. The classic example of this is code that handles&#xD;
allocation failures. Here's an example:&#xD;
&lt;/p>&#xD;
&lt;blockquote>&#xD;
&lt;pre>&#xD;
while (true) { // top level event loop&#xD;
try {&#xD;
XEvent xe = getEvent();&#xD;
... // main body of program&#xD;
} catch (OutOfMemoryError e) {&#xD;
emergencyRestart();&#xD;
}&#xD;
}&#xD;
&lt;/pre>&#xD;
&lt;/blockquote>&#xD;
&lt;p>&#xD;
This code attempts to recover from out of memory errors by cleaning up (thus making memory available) and then&#xD;
continuing to process events. Let's suppose that's an acceptable design. &lt;font size=&quot;+0&quot;>emergencyRestart&lt;/font> takes&#xD;
great care not to allocate memory. The problem is that &lt;font size=&quot;+0&quot;>emergencyRestart&lt;/font> calls some utility&#xD;
routine, which calls some other utility routine, which calls some other utility routine-which allocates a new object.&#xD;
Except that there's no memory, so the whole program fails. These kinds of problems are hard to find through inspection.&#xD;
&lt;/p>&#xD;
&lt;h3>&#xD;
&lt;a id=&quot;Test-FirstDesignAndRUPPhases&quot; name=&quot;Test-FirstDesignAndRUPPhases&quot;>Test-first design and the phases&#xD;
of&amp;nbsp;Unified&lt;/a> Process&#xD;
&lt;/h3>&#xD;
&lt;p>&#xD;
Up to this point, we've implicitly assumed that you'd do as much test design as possible as early as possible. That is,&#xD;
you'd derive all the tests you could from the design artifact, later adding only tests based on implementation&#xD;
internals. That may not be appropriate in the Elaboration phase, because such complete testing may not be aligned with&#xD;
an iteration's objectives.&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
Suppose an architectural prototype is being built to demonstrate product feasibility to investors. It might be based on&#xD;
a few key use-case instances. Code should be tested to see that it supports them. But is there any harm if further&#xD;
tests are created? For example, it might be obvious that the prototype ignores important error cases. Why not document&#xD;
the need for that error handling by writing test cases that will exercise it?&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
But what if the prototype does its job and reveals that the architectural approach won't work? Then the architecture&#xD;
will be thrown away - along with all those tests for error-handling. In that case, the effort of designing the tests&#xD;
will have yielded no value. It would have been better to have waited, and only designed those tests needed to check&#xD;
whether this proof-of-concept prototype really proves the concept.&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
This may seem a minor point, but there are strong psychological effects in play. The Elaboration phase is about&#xD;
addressing major risks. The whole project team should be focused on those risks. Having people concentrating on minor&#xD;
issues drains focus and energy from the team.&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
So where might test-first design be used successfully in the Elaboration phase? It can play an important role in&#xD;
adequately exploring architectural risks. Considering how, precisely, the team will know if a risk has been realized or&#xD;
avoided will add clarity to the design process and may well result in a better architecture being built the first time.&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
During the Construction phase, design artifacts are put into their final form. All the required use-case realizations&#xD;
are implemented, as are the interfaces for all classes. Because the phase objective is completeness, complete&#xD;
test-first design is appropriate. Later events should invalidate few, if any, tests.&#xD;
&lt;/p>&#xD;
&lt;p>&#xD;
The Inception and Transition phases typically have less focus on design activities for which testing is appropriate.&#xD;
When it is, test-first design is applicable. For example, it could be used with candidate proof-of-concept work in&#xD;
Inception. As with Construction and Elaboration phase testing, it should be aligned with iteration objectives.&#xD;
&lt;/p></mainDescription>
</org.eclipse.epf.uma:ContentDescription>