SlideShare una empresa de Scribd logo
1 de 5
Descargar para leer sin conexión
P
REVIOUS COLUMNS HAVE FREQUENTLY
emphasised the benefits of travelling
light and keeping your code fit. This
advice applies both to the general guidelines
to follow in structuring code1, 2
and to the breadth
of a type’s interface3
. Many problems can be
tackled with simpler solutions than you might first
think—solutions that are sufficient and surprisingly
versatile. A good example is the lightweight regular
expression matcher presented in the previous
column4
.
In the case of that regex code, the algorithm was
taken from some C code in The Practice of
Programming5
. I refactored the code into C++, and
not just for cosmetic reasons—the code was
adapted and generalised to use iterators. The
result was a generic algorithm that worked on any
kind of forward iterator range for any type of
character; the original was constrained to work only
with null-terminated arrays of char. Last time
we looked at the code and this time I want to look
at the process. If you want to travel light, you’d better
have a process for doing it. Refactoring is part of
such a process.
Personal hygiene for code
Refactoring6, 7
has grabbed a lot of programmer mind
share. However, it is often still used as a front for
rabid hacking. Refactoring is not just taking a piece
of code that you don’t like the look of and then
playing around with it for a week until it satisfies
your own preferred indentation, naming or design
style, tossing in a raft of extra features while you’re
at it. Refactoring is actually bound by quite a
precise definition that automatically disqualifies
most of the programming practice that takes its name
in vain6
:
Refactoring (noun): a change made to the internal
structure of software to make it easier to understand
and cheaper to modify without changing its
observable behavior.
Refactor (verb): to restructure software by applying
a series of refactorings without changing the
observable behavior of the software.
Far from random code-cutting and splicing,
you can see that refactoring is quite a precise and
rigorous discipline. A number of projects use the
term as a more fashionable and less management-
upsetting alternative to the word ‘rewrite’. There
is certainly an element of rewriting in refactoring,
but it is not the same as tearing down and
reworking the whole architecture so that nothing
builds successfully for a week, a month or longer.
While refactoring, you should ensure that the
code can be built at any time at the drop of a hat
or function key. The code base should be as stable
and buildable as possible, which means taking many
small steps where you might otherwise be tempted
to leap. And the code should never offer less
functionality.
Refactoring clearly requires a more methodical
q Refactoring involves careful changes
to code that modify its structure but
do not affect its functionality.
q Successful refactoring is best carried
out with one or any combination of
uninterrupted work,pair
programming and unit testing.
q Unit testing should be automated and
frequent,not manual and rare.
q Designing for testability reduces
interface bloat and coupling in a
system.
q The minimum requirement for
productive testing is the humble
assert.
FACTS AT A GLANCE
C++ WORKSHOP
Refactoring is only refactoring when combined with rigorous unit testing.
Kevlin Henney explains how to take the taskwork out of testing in C++
Put to the test
38 APPLICATION DEVELOPMENT ADVISOR q www.appdevadvisor.co.uk
Many problems can be
tackled with simpler
solutions than you might
first think—solutions that
are sufficient and
surprisingly versatile
NOVEMBER–DECEMBER 2002 39
and double-checked approach than is often misguidedly suggested.
So how do you refactor with balance, rather than with bugs?There
are three ways that you can change code with confidence:
q Make changes with a clear state of mind. This means having
continuous blocks of uninterrupted time when you are feeling
alert. So, that excludes afternoons for most people—especially
Friday—and suggests that Monday morning and any ‘morning
after the night before’ is also probably inappropriate. It also
means that unpartitioned open-plan offices are unsuitable
environments. If you cannot reach the zone of total concentration,
you will miss too many tricks to carry out refactoring effectively.
q Make changes with a colleague. Two pairs of eyes are better
than one when it comes to spotting careless mistakes and
suggesting alternative designs. This kind of pair programming
is effective in many different office environments and most times
of day. One person’s attention low may coincide with the other’s
high, flattening out the peaks and troughs. Apparently some
managers regard any form of pair programming as a waste of
resources. Many such managers indulge in long and frequent
meetings that are, however, apparently another matter and should
not be judged by the same productivity criteria. Pots. Kettles.
‘Nuff said.
q Make changes against a set of unit tests. You break it, you find
out very quickly. Footfall is surer because the feedback loop
between change and effect is tightened. Change, compile, test.
And then, depending on the outcome, you either move on to
the next step or fix the bug just introduced with the change.
Or you can use any workable combination of the three. Each
approach also has benefits above and beyond refactoring. For instance,
working without interruption is always more of a pleasure than
working with distractions. You are likely to write code more clearly
and cleanly, introducing fewer bugs in the process, tackling
design problems more easily and so on.
Pairing is a practice that can address many different development
activities. Two pairs of eyes are better than one for writing tests,
debugging code, deciphering existing code and training or
familiarising someone else with the existing system. Pairing also
serves as a form of active code review that is superior to the slow
cycle of organising review meetings, printing out the code,
involving many people who make vague suggestions or get
caught up in the wrong level of detail—and then finally making
the changes. Depending on the social skills of the code reviewing
peers, a meeting can also be hard on the code author’s self
esteem. Code reviews are a good idea, but they are better when
they are more interactive and intimate.
Unit tests make you think about the design of the code you are
testing and, in particular, the interface to classes and functions.
Unit tests can be seen as part of a design-driven approach8
that
reduces the coupling of your system and sharpens the interfaces.
Highly coupled code is hard to test because the unit of test
must be that much larger: loosely coupled code has more easily
separable and testable chunks. You also find out what you can and
can’t test easily and what it’s like to use your classes and functions.
This experience feeds back positively. You write fewer of those lame
‘setter’ and ‘getter’ functions that plague so much code that
masquerades as object-oriented and you cut out functions that
aren’t used. You also instantiate all of the template code that you've
written, which can potentially hide basic compilation errors for
a long time after it was first produced.
The joy of testing
So how do you go about testing? Perhaps the best advice is also
the most succinct9
: Test early. Test often. Test automatically.
This advice goes against the test approach adopted by many
organisations (even the thorough and methodical ones). Let’s get
one thing straight: writing code is a lot more fun than most of
the other activities in software development, and testing has to
be one of the activities traditionally associated with the least amount
of fun. Many projects that explicitly include a testing activity often
include it as a test phase, which is a sure way of guaranteeing the
absence of fun. You’ve done all the interesting stuff and now you’ve
got to test it all. For a very small project, this may not be a major
obstacle. But for anything larger, it is quite a serious demotivator.
And you may find out late in the day that you have written something
that is quite hard to test. An interface may be broader than is strictly
useful and more highly coupled than is strictly necessary.
Additionally, if you leave testing until late and then discover that
the project is late, guess what gets squeezed? Test early. Test
often.
The significance of automated testing is often overlooked, but
is perhaps one of the most important pieces of the puzzle. Many
systems are tested by manual inspection. Someone sits staring at
a screen, either pumping data in or pushing buttons, waiting for
the right result. This is neither engaging nor effective. It is,
however, time consuming, which means that you are never in a
hurry to do it early and often. It is also not really unit testing, unless
you consider the whole application to be the unit. This approach
to testing makes expensive monkeys out of bright programmers.
If writing code is considered more fun than testing, then
make testing a matter of writing code. If you write code to test
code, you can rerun it automatically. Regression testing is no longer
a chore and you can be more selective about what parts of your
system you include in a test.
So what tools do you need for this? #include <cassert> (or
#include <assert.h> if you want the original C header) is not
a bad place to start. Yes, there are expensive tools on the market
that will do all kinds of fabulous things for you, but the
minimum toolset is assert—a macro in C and C++, a built-in
facility in Python and Java (as of SDK 1.4) and an easily
provided feature in most other languages. This is a very low entry
level indeed, but one that gets you into testing straight away without
any fuss or ceremony.
Once you have played around with assert-based testing for a
while, you will have a better idea of what you want from your test
harness and you can move on to something more powerful.
Many people refactor such a harness out of their test code to keep
total control over it. Others adopt one of the open source xUnit
family of test harnesses (most famously, JUnit for Java10
), which
are simple but effective testing frameworks. If you want a level
of static checking, automatic wrapping of library calls, memory
C++ WORKSHOP
Perhaps the best advice is also the
most succinct:Test early.Test
often.Test automatically
40 APPLICATION DEVELOPMENT ADVISOR q www.appdevadvisor.co.uk
leak detection, coverage statistics and so on, a commercial tool
is probably the right choice. However, none of these change the
basic need for a testing mindset and knowing what you need for
your own application. Go to product vendors with clear empirical
requirements, rather than letting them suggest their requirements
to you.
Testing the ins and outs
The presentation layer—whether console, browser or GUI—is
often held up as an obstacle to automated testing. In part, this
is a valid objection, but only in part. A GUI test should test the
appearance, rather than the logic.That means separating the model
logic from all the window presentation logic. The problem is not
so much that GUIs are hard to test, but that many programs are
not designed to be tested. When you design for testability, you
put into practice the common design recommendation to
decouple the actual presentation details from all the event
handling and core logic. The manual element of testing is then
reduced to testing the appearance and usability rather than the
underlying logic. The real challenge here is not the technology,
but the quality of the design presented by so many books,
courses and code samples for windowing APIs and frameworks.
Developers model their code after these examples, but the aims
of the two sorts of code are incompatible: the samples show sufficient
code to demonstrate how the API is used, not how to build a large,
testable commercial product.
One presentation facility that can be tested automatically is I/O
streams. Instead of hardwiring the input and output streams (e.g.
to std::cin and std::cout), pass in std::istream and std::ostream
parameters. In a test harness, these can be wired up to
std::stringstream objects or std::fstream objects opened onto
files prepared in advance with test data and correct responses.
If such substitution is not an option, you can get in under the
wire and change the internal plumbing. Consider the toy
function in listing 1. How would you test the decimal to hex
conversion? You could have a main call it and script up a test
externally in something like Perl or another scripting language.
This would be fine, but given that this is a C++ column, let’s look
at a C++-only solution that doesn’t involve running multiple
processes.
Listing 2 shows a main function that runs the function through
a single test case without changing any code in dec2hex.The technique
is to substitute string buffers of type std::stringbuf in place of
the normal console-connected streambufs used by std::cin and
std::cout. You prime the input with a value and then collect and
compare the output. Remember to restore the standard I/O
streams to their former state. This is more than just politeness:
if you don’t, the streams will attempt to delete the local stringbuf
variables on program exit, as well as leak their own original
buffers. The catastrophic result of attempting to delete an
object that was not created with new somewhat dwarfs the
memory leak problem. The object ownership model in I/O
streams is easy to trip over, in part because it fails to follow the
recommendation to make acquisition and release symmetric1
. If
you wish, you can use objects to automate the setting and
resetting of the buffers2
.
You can scale this approach up to many test cases by surrounding
the whole lot with a loop and popping a predefined sequence of
string values to test. You can use a little string manipulation to
exclude the prompt text from the test condition. Although this
is a very simple example, it demonstrates the anatomy of an
automated I/O test.
Refactoring match
To return to where we started out, how can testing work in support
of refactoring? I refactored the regex_match functions in the
last article4
against a set of tests.
The original code was written in C5
, so that was where I
started. The functions were copied verbatim. Compilation
provided the basic reality check that the code was copied correctly
and that the C was clean enough to be used as C++. I then wrote
out 10 or so assertions that seemed briefly to exercise the range
of its functionality. I felt that only a few tests were required because
the code was published by authors of note. The whole chapter
devoted to testing in the same book also did a lot to inspire
confidence.
With the raw materials in place, I changed the names to be closer
to the ones that I intended to use in the final code, which
required some slight modifications. I then tightened up the
types used, e.g. using const to qualify char * and bool instead of
int, which did not require any test changes. Because I was
aiming for an STL-styled algorithm, the next step was to switch
the argument order around, which clearly necessitated changing
test code. I then replaced some of the pointer-based expressions
by expressions that would work for STL forward iterators, which
did not require any test changes. Extra arguments were added so
that iterator ranges were used instead of assuming null-terminated
strings—the tests changed to accommodate the interface change,
but no new tests were added.
I followed through with some more work ensuring that only
forward iterator operations were used. I then introduced
templating, which did not require any changes to the tests, and
then new tests were added to check that pattern matching
worked for embedded nulls and wide-character strings. In
between each sentence of this paragraph, the tests were compiled
and run.
The resulting interface for pattern matching is declared in listing
3 and the final tests are shown in listing 4. It is certainly possible
to add more tests, and it is certainly possible to refactor the test
code. However, for the immediate problem, the tests as structured
were sufficient to give the right level of confidence. If I were to
go back and extend the capabilities of the regular expression matching
and therefore add to the number of tests, I would almost certainly
refactor main before doing anything else. Don’t refactor the test
harness and the test subject in the coding spree between
compilations. Driving the code from a table that held sample values
and expected results would be the best way to capture the
commonality in this case.
Most developers do not work with such small, insular pieces
of code. However, the principles and practices do scale. The trick
is to make code sufficiently modular and independent that it becomes
testable. Testing doesn’t have to be tedious and it certainly isn’t
rocket science. It is a matter of documenting what you might
otherwise have thought about testing by hand or would have looked
for in a code inspection. However, instead of wasting time
documenting the tests with a word processor, document these cases
in code. That way you never actually have to do the tests yourself:
you just compile and run the documentation. With a body of tests
to lean on, you are much freer to modify the code to improve or
extend it. s
C++ WORKSHOP
NOVEMBER–DECEMBER 2002 41
References
1. Kevlin Henney, “Six of the Best”, Application Development
Advisor, May 2002, www.appdevadvisor.co.uk.
2. Kevlin Henney, “The Rest of the Best”, Application
Development Advisor, June 2002,
www.appdevadvisor.co.uk.
3. Kevlin Henney, “Stringing Things Along”, Application
Development Advisor, July-August 2002,
www.appdevadvisor.co.uk.
4. Kevlin Henney, “The Next Best String”, Application
Development Advisor, October 2002,
www.appdevadvisor.co.uk.
5. Brian W Kernighan and Rob Pike, The Practice of
Programming, Addison-Wesley, 1999.
6. Martin Fowler, Refactoring: Improving the Design of
Existing Code, Addison-Wesley, 1999.
7. Refactoring home page, www.refactoring.com.
8. Kent Beck, “Aim, Fire”, IEEE Software, 2001,
http://computer.org/software/homepage/2001/05Design
/index.htm.
9. Andrew Hunt and David Thomas, The Pragmatic
Programmer, Addison-Wesley, 2000.
10. JUnit home page, www.junit.org.
Kevlin Henney is an independent software development consultant
and trainer. He can be reached at www.curbralan.com.
C++ WORKSHOP
Listing 1: How would you test this function?
void dec2hex()
{
std::cout << "decimal? ";
int value = 0;
std::cin >> value;
std::cout << "hex: " << std::hex << value << std::endl;
}
Listing 2: Running listing 1 through a single test case
int main()
{
std::stringbuf out, in("42");
std::streambuf *old_cout = std::cout.rdbuf(&out);
std::streambuf *old_cin = std::cin.rdbuf(&in);
dec2hex();
std::cout.rdbuf(old_cout);
std::cin.rdbuf(old_cin);
assert(out.str() == "decimal? hex: 2an");
std::cout << "OK" << std::endl;
return 0;
}
Listing 3: An interface for pattern matching in the refactored regex_match function
template<
typename text_iterator,
typename regex_iterator>
bool regex_match(
text_iterator text_begin, text_iterator text_end,
regex_iterator regex_begin, regex_iterator regex_end);
template<
typename text_iterator,
42 APPLICATION DEVELOPMENT ADVISOR q www.appdevadvisor.co.uk
typename regex_char>
bool regex_match(
text_iterator text_begin, text_iterator text_end,
const regex_char *regex);
Listing 4:The final tests
int main()
{
// test three-agument regex_match
const std::string text = "the cat sat on the mat";
assert(regex_match(text.begin(), text.end(), "the"));
assert(!regex_match(text.begin(), text.end(), "foo"));
assert(regex_match(text.begin(), text.end(), "c.t"));
assert(!regex_match(text.begin(), text.end(), "t.t"));
assert(regex_match(text.begin(), text.end(), "^t"));
assert(!regex_match(text.begin(), text.end(), "^c"));
assert(regex_match(text.begin(), text.end(), "^t.*t"));
assert(!regex_match(text.begin(), text.end(), "^t.*f"));
assert(regex_match(text.begin(), text.end(), "t$"));
assert(!regex_match(text.begin(), text.end(), "a$"));
assert(regex_match(text.begin(), text.end(), "^t.*t$"));
assert(!regex_match(text.begin(), text.end(), "^t.*f$"));
assert(regex_match(
text.begin(), text.end(), "t.*cat.*o"));
assert(!regex_match(
text.begin(), text.end(), "t.*rat.*o"));
// test four-argument regex_match
const std::string pattern = "s.*o";
assert(regex_match(
text.begin(), text.end(),
pattern.begin(), pattern.end()));
// test regex_match across embedded null
std::string next = "abcd";
next += '0';
next += "efgh";
assert(regex_match(next.begin(), next.end(), "fg"));
assert(regex_match(next.begin(), next.end(), "d.e"));
// test regex_match with wide-character strings
const std::wstring wtext = L"the cat sat on the mat";
assert(regex_match(
wtext.begin(), wtext.end(), L"^t.*t$"));
assert(!regex_match(
wtext.begin(), wtext.end(), L"^t.*f$"));
assert(regex_match(
wtext.begin(), wtext.end(), "t.*cat.*o"));
assert(!regex_match(
wtext.begin(), wtext.end(), "t.*rat.*o"));
std::cout << "OK" << std::endl;
return 0;
}
C++ WORKSHOP

Más contenido relacionado

La actualidad más candente

TDD CrashCourse Part2: TDD
TDD CrashCourse Part2: TDDTDD CrashCourse Part2: TDD
TDD CrashCourse Part2: TDDDavid Rodenas
 
TDD - Test Driven Development
TDD - Test Driven DevelopmentTDD - Test Driven Development
TDD - Test Driven DevelopmentLim Chanmann
 
Introduction to Test Driven Development
Introduction to Test Driven DevelopmentIntroduction to Test Driven Development
Introduction to Test Driven DevelopmentMichael Denomy
 
Agile Test Driven Development
Agile Test Driven DevelopmentAgile Test Driven Development
Agile Test Driven DevelopmentViraf Karai
 
Pair Programming - a pratical guide
Pair Programming - a pratical guidePair Programming - a pratical guide
Pair Programming - a pratical guideGiuseppe Sorrentino
 
Test driven development vs Behavior driven development
Test driven development vs Behavior driven developmentTest driven development vs Behavior driven development
Test driven development vs Behavior driven developmentGallop Solutions
 
Tdd practices
Tdd practicesTdd practices
Tdd practicesaxykim00
 
Test driven development
Test driven developmentTest driven development
Test driven developmentHarry Potter
 
Test driven development - Zombie proof your code
Test driven development - Zombie proof your codeTest driven development - Zombie proof your code
Test driven development - Zombie proof your codePascal Larocque
 
Test Driven Development
Test Driven DevelopmentTest Driven Development
Test Driven Developmentguestc8093a6
 

La actualidad más candente (16)

TDD CrashCourse Part2: TDD
TDD CrashCourse Part2: TDDTDD CrashCourse Part2: TDD
TDD CrashCourse Part2: TDD
 
TDD - Test Driven Development
TDD - Test Driven DevelopmentTDD - Test Driven Development
TDD - Test Driven Development
 
Introduction to Test Driven Development
Introduction to Test Driven DevelopmentIntroduction to Test Driven Development
Introduction to Test Driven Development
 
TDD with RSpec
TDD with RSpecTDD with RSpec
TDD with RSpec
 
TDD = bra design?
TDD = bra design?TDD = bra design?
TDD = bra design?
 
Tdd
TddTdd
Tdd
 
Agile Test Driven Development
Agile Test Driven DevelopmentAgile Test Driven Development
Agile Test Driven Development
 
Pair Programming - a pratical guide
Pair Programming - a pratical guidePair Programming - a pratical guide
Pair Programming - a pratical guide
 
Test driven development vs Behavior driven development
Test driven development vs Behavior driven developmentTest driven development vs Behavior driven development
Test driven development vs Behavior driven development
 
Tdd practices
Tdd practicesTdd practices
Tdd practices
 
Test driven development
Test driven developmentTest driven development
Test driven development
 
Test driven development - Zombie proof your code
Test driven development - Zombie proof your codeTest driven development - Zombie proof your code
Test driven development - Zombie proof your code
 
Tdd com Java
Tdd com JavaTdd com Java
Tdd com Java
 
Test drive on driven development process
Test drive on driven development processTest drive on driven development process
Test drive on driven development process
 
Test Driven Development
Test Driven DevelopmentTest Driven Development
Test Driven Development
 
Tdd
TddTdd
Tdd
 

Destacado

Collections for States
Collections for StatesCollections for States
Collections for StatesKevlin Henney
 
Stringing Things Along
Stringing Things AlongStringing Things Along
Stringing Things AlongKevlin Henney
 
Worse Is Better, for Better or for Worse
Worse Is Better, for Better or for WorseWorse Is Better, for Better or for Worse
Worse Is Better, for Better or for WorseKevlin Henney
 
Promoting Polymorphism
Promoting PolymorphismPromoting Polymorphism
Promoting PolymorphismKevlin Henney
 
Making Steaks from Sacred Cows
Making Steaks from Sacred CowsMaking Steaks from Sacred Cows
Making Steaks from Sacred CowsKevlin Henney
 
Source-to-Pay: Advancing from Pure Cost Optimization to Value Generation
Source-to-Pay: Advancing from Pure Cost Optimization to Value GenerationSource-to-Pay: Advancing from Pure Cost Optimization to Value Generation
Source-to-Pay: Advancing from Pure Cost Optimization to Value GenerationCognizant
 
Social Media Strategies for Small to Medium Businesses
Social Media Strategies for Small to Medium BusinessesSocial Media Strategies for Small to Medium Businesses
Social Media Strategies for Small to Medium BusinessesDayn Wilberding
 
Автономания
АвтономанияАвтономания
АвтономанияDibar_agency
 
Camila herramientas para crear carteles
Camila herramientas para crear cartelesCamila herramientas para crear carteles
Camila herramientas para crear cartelesAlexander Guzman
 
резюме_Юрий Федоров
резюме_Юрий Федороврезюме_Юрий Федоров
резюме_Юрий ФедоровRS7987198
 

Destacado (20)

Collections for States
Collections for StatesCollections for States
Collections for States
 
One Careful Owner
One Careful OwnerOne Careful Owner
One Careful Owner
 
Stringing Things Along
Stringing Things AlongStringing Things Along
Stringing Things Along
 
Worse Is Better, for Better or for Worse
Worse Is Better, for Better or for WorseWorse Is Better, for Better or for Worse
Worse Is Better, for Better or for Worse
 
Promoting Polymorphism
Promoting PolymorphismPromoting Polymorphism
Promoting Polymorphism
 
The Perfect Couple
The Perfect CoupleThe Perfect Couple
The Perfect Couple
 
Inside Requirements
Inside RequirementsInside Requirements
Inside Requirements
 
Learning Curve
Learning CurveLearning Curve
Learning Curve
 
Substitutability
SubstitutabilitySubstitutability
Substitutability
 
Bound and Checked
Bound and CheckedBound and Checked
Bound and Checked
 
Exceptional Naming
Exceptional NamingExceptional Naming
Exceptional Naming
 
Making Steaks from Sacred Cows
Making Steaks from Sacred CowsMaking Steaks from Sacred Cows
Making Steaks from Sacred Cows
 
Flag Waiving
Flag WaivingFlag Waiving
Flag Waiving
 
Source-to-Pay: Advancing from Pure Cost Optimization to Value Generation
Source-to-Pay: Advancing from Pure Cost Optimization to Value GenerationSource-to-Pay: Advancing from Pure Cost Optimization to Value Generation
Source-to-Pay: Advancing from Pure Cost Optimization to Value Generation
 
Diagnostico de centro edu - flujograma
Diagnostico de centro edu - flujogramaDiagnostico de centro edu - flujograma
Diagnostico de centro edu - flujograma
 
Social Media Strategies for Small to Medium Businesses
Social Media Strategies for Small to Medium BusinessesSocial Media Strategies for Small to Medium Businesses
Social Media Strategies for Small to Medium Businesses
 
Автономания
АвтономанияАвтономания
Автономания
 
Camila herramientas para crear carteles
Camila herramientas para crear cartelesCamila herramientas para crear carteles
Camila herramientas para crear carteles
 
Deltemoralpoder
DeltemoralpoderDeltemoralpoder
Deltemoralpoder
 
резюме_Юрий Федоров
резюме_Юрий Федороврезюме_Юрий Федоров
резюме_Юрий Федоров
 

Similar a Put to the Test

Test-Driven Developments are Inefficient; Behavior-Driven Developments are a ...
Test-Driven Developments are Inefficient; Behavior-Driven Developments are a ...Test-Driven Developments are Inefficient; Behavior-Driven Developments are a ...
Test-Driven Developments are Inefficient; Behavior-Driven Developments are a ...Abdelkrim Boujraf
 
Software Development Standard Operating Procedure
Software Development Standard Operating Procedure Software Development Standard Operating Procedure
Software Development Standard Operating Procedure rupeshchanchal
 
Agile Methodologies And Extreme Programming - Svetlin Nakov
Agile Methodologies And Extreme Programming - Svetlin NakovAgile Methodologies And Extreme Programming - Svetlin Nakov
Agile Methodologies And Extreme Programming - Svetlin NakovSvetlin Nakov
 
WordCamp Nashville: Clean Code for WordPress
WordCamp Nashville: Clean Code for WordPressWordCamp Nashville: Clean Code for WordPress
WordCamp Nashville: Clean Code for WordPressmtoppa
 
Agile Methodologies And Extreme Programming
Agile Methodologies And Extreme ProgrammingAgile Methodologies And Extreme Programming
Agile Methodologies And Extreme ProgrammingUtkarsh Khare
 
Code Review
Code ReviewCode Review
Code ReviewRavi Raj
 
Agile development with Ruby
Agile development with RubyAgile development with Ruby
Agile development with Rubykhelll
 
Code Craftsmanship Checklist
Code Craftsmanship ChecklistCode Craftsmanship Checklist
Code Craftsmanship ChecklistRyan Polk
 
Clean Code Software Engineering
Clean Code Software Engineering Clean Code Software Engineering
Clean Code Software Engineering Inocentshuja Ahmad
 
The "Evils" of Optimization
The "Evils" of OptimizationThe "Evils" of Optimization
The "Evils" of OptimizationBlackRabbitCoder
 
Problems of testing 64-bit applications
Problems of testing 64-bit applicationsProblems of testing 64-bit applications
Problems of testing 64-bit applicationsPVS-Studio
 
Software design.edited (1)
Software design.edited (1)Software design.edited (1)
Software design.edited (1)FarjanaAhmed3
 
Software engineering
Software engineeringSoftware engineering
Software engineeringsweetysweety8
 
The Essentials Of Test Driven Development
The Essentials Of Test Driven Development The Essentials Of Test Driven Development
The Essentials Of Test Driven Development Rock Interview
 
Cinci ug-january2011-anti-patterns
Cinci ug-january2011-anti-patternsCinci ug-january2011-anti-patterns
Cinci ug-january2011-anti-patternsSteven Smith
 

Similar a Put to the Test (20)

Test-Driven Developments are Inefficient; Behavior-Driven Developments are a ...
Test-Driven Developments are Inefficient; Behavior-Driven Developments are a ...Test-Driven Developments are Inefficient; Behavior-Driven Developments are a ...
Test-Driven Developments are Inefficient; Behavior-Driven Developments are a ...
 
Software Development Standard Operating Procedure
Software Development Standard Operating Procedure Software Development Standard Operating Procedure
Software Development Standard Operating Procedure
 
Agile Methodologies And Extreme Programming - Svetlin Nakov
Agile Methodologies And Extreme Programming - Svetlin NakovAgile Methodologies And Extreme Programming - Svetlin Nakov
Agile Methodologies And Extreme Programming - Svetlin Nakov
 
WordCamp Nashville: Clean Code for WordPress
WordCamp Nashville: Clean Code for WordPressWordCamp Nashville: Clean Code for WordPress
WordCamp Nashville: Clean Code for WordPress
 
Agile Methodologies And Extreme Programming
Agile Methodologies And Extreme ProgrammingAgile Methodologies And Extreme Programming
Agile Methodologies And Extreme Programming
 
Quick Intro to Clean Coding
Quick Intro to Clean CodingQuick Intro to Clean Coding
Quick Intro to Clean Coding
 
Quality Software Development
Quality Software DevelopmentQuality Software Development
Quality Software Development
 
Code quality
Code quality Code quality
Code quality
 
Code Review
Code ReviewCode Review
Code Review
 
Agile development with Ruby
Agile development with RubyAgile development with Ruby
Agile development with Ruby
 
Code Craftsmanship Checklist
Code Craftsmanship ChecklistCode Craftsmanship Checklist
Code Craftsmanship Checklist
 
Clean Code Software Engineering
Clean Code Software Engineering Clean Code Software Engineering
Clean Code Software Engineering
 
The "Evils" of Optimization
The "Evils" of OptimizationThe "Evils" of Optimization
The "Evils" of Optimization
 
Problems of testing 64-bit applications
Problems of testing 64-bit applicationsProblems of testing 64-bit applications
Problems of testing 64-bit applications
 
TestDrivenDeveloment
TestDrivenDevelomentTestDrivenDeveloment
TestDrivenDeveloment
 
Software design.edited (1)
Software design.edited (1)Software design.edited (1)
Software design.edited (1)
 
Software engineering
Software engineeringSoftware engineering
Software engineering
 
The Essentials Of Test Driven Development
The Essentials Of Test Driven Development The Essentials Of Test Driven Development
The Essentials Of Test Driven Development
 
Refactoring 2 The Max
Refactoring 2 The MaxRefactoring 2 The Max
Refactoring 2 The Max
 
Cinci ug-january2011-anti-patterns
Cinci ug-january2011-anti-patternsCinci ug-january2011-anti-patterns
Cinci ug-january2011-anti-patterns
 

Más de Kevlin Henney

The Case for Technical Excellence
The Case for Technical ExcellenceThe Case for Technical Excellence
The Case for Technical ExcellenceKevlin Henney
 
Empirical Development
Empirical DevelopmentEmpirical Development
Empirical DevelopmentKevlin Henney
 
Lambda? You Keep Using that Letter
Lambda? You Keep Using that LetterLambda? You Keep Using that Letter
Lambda? You Keep Using that LetterKevlin Henney
 
Lambda? You Keep Using that Letter
Lambda? You Keep Using that LetterLambda? You Keep Using that Letter
Lambda? You Keep Using that LetterKevlin Henney
 
Solid Deconstruction
Solid DeconstructionSolid Deconstruction
Solid DeconstructionKevlin Henney
 
Procedural Programming: It’s Back? It Never Went Away
Procedural Programming: It’s Back? It Never Went AwayProcedural Programming: It’s Back? It Never Went Away
Procedural Programming: It’s Back? It Never Went AwayKevlin Henney
 
Structure and Interpretation of Test Cases
Structure and Interpretation of Test CasesStructure and Interpretation of Test Cases
Structure and Interpretation of Test CasesKevlin Henney
 
Refactoring to Immutability
Refactoring to ImmutabilityRefactoring to Immutability
Refactoring to ImmutabilityKevlin Henney
 
Turning Development Outside-In
Turning Development Outside-InTurning Development Outside-In
Turning Development Outside-InKevlin Henney
 
Giving Code a Good Name
Giving Code a Good NameGiving Code a Good Name
Giving Code a Good NameKevlin Henney
 
Clean Coders Hate What Happens To Your Code When You Use These Enterprise Pro...
Clean Coders Hate What Happens To Your Code When You Use These Enterprise Pro...Clean Coders Hate What Happens To Your Code When You Use These Enterprise Pro...
Clean Coders Hate What Happens To Your Code When You Use These Enterprise Pro...Kevlin Henney
 
Thinking Outside the Synchronisation Quadrant
Thinking Outside the Synchronisation QuadrantThinking Outside the Synchronisation Quadrant
Thinking Outside the Synchronisation QuadrantKevlin Henney
 

Más de Kevlin Henney (20)

Program with GUTs
Program with GUTsProgram with GUTs
Program with GUTs
 
The Case for Technical Excellence
The Case for Technical ExcellenceThe Case for Technical Excellence
The Case for Technical Excellence
 
Empirical Development
Empirical DevelopmentEmpirical Development
Empirical Development
 
Lambda? You Keep Using that Letter
Lambda? You Keep Using that LetterLambda? You Keep Using that Letter
Lambda? You Keep Using that Letter
 
Lambda? You Keep Using that Letter
Lambda? You Keep Using that LetterLambda? You Keep Using that Letter
Lambda? You Keep Using that Letter
 
Solid Deconstruction
Solid DeconstructionSolid Deconstruction
Solid Deconstruction
 
Get Kata
Get KataGet Kata
Get Kata
 
Procedural Programming: It’s Back? It Never Went Away
Procedural Programming: It’s Back? It Never Went AwayProcedural Programming: It’s Back? It Never Went Away
Procedural Programming: It’s Back? It Never Went Away
 
Structure and Interpretation of Test Cases
Structure and Interpretation of Test CasesStructure and Interpretation of Test Cases
Structure and Interpretation of Test Cases
 
Agility ≠ Speed
Agility ≠ SpeedAgility ≠ Speed
Agility ≠ Speed
 
Refactoring to Immutability
Refactoring to ImmutabilityRefactoring to Immutability
Refactoring to Immutability
 
Old Is the New New
Old Is the New NewOld Is the New New
Old Is the New New
 
Turning Development Outside-In
Turning Development Outside-InTurning Development Outside-In
Turning Development Outside-In
 
Giving Code a Good Name
Giving Code a Good NameGiving Code a Good Name
Giving Code a Good Name
 
Clean Coders Hate What Happens To Your Code When You Use These Enterprise Pro...
Clean Coders Hate What Happens To Your Code When You Use These Enterprise Pro...Clean Coders Hate What Happens To Your Code When You Use These Enterprise Pro...
Clean Coders Hate What Happens To Your Code When You Use These Enterprise Pro...
 
Thinking Outside the Synchronisation Quadrant
Thinking Outside the Synchronisation QuadrantThinking Outside the Synchronisation Quadrant
Thinking Outside the Synchronisation Quadrant
 
Code as Risk
Code as RiskCode as Risk
Code as Risk
 
Software Is Details
Software Is DetailsSoftware Is Details
Software Is Details
 
Game of Sprints
Game of SprintsGame of Sprints
Game of Sprints
 
Good Code
Good CodeGood Code
Good Code
 

Último

英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作qr0udbr0
 
Best Web Development Agency- Idiosys USA.pdf
Best Web Development Agency- Idiosys USA.pdfBest Web Development Agency- Idiosys USA.pdf
Best Web Development Agency- Idiosys USA.pdfIdiosysTechnologies1
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...OnePlan Solutions
 
What are the key points to focus on before starting to learn ETL Development....
What are the key points to focus on before starting to learn ETL Development....What are the key points to focus on before starting to learn ETL Development....
What are the key points to focus on before starting to learn ETL Development....kzayra69
 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesPhilip Schwarz
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmSujith Sukumaran
 
What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...Technogeeks
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxTier1 app
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEEVICTOR MAESTRE RAMIREZ
 
Recruitment Management Software Benefits (Infographic)
Recruitment Management Software Benefits (Infographic)Recruitment Management Software Benefits (Infographic)
Recruitment Management Software Benefits (Infographic)Hr365.us smith
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio, Inc.
 
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfGOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfAlina Yurenko
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtimeandrehoraa
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...OnePlan Solutions
 
Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Andreas Granig
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Velvetech LLC
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWave PLM
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesŁukasz Chruściel
 
Implementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureImplementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureDinusha Kumarasiri
 

Último (20)

英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作
 
Best Web Development Agency- Idiosys USA.pdf
Best Web Development Agency- Idiosys USA.pdfBest Web Development Agency- Idiosys USA.pdf
Best Web Development Agency- Idiosys USA.pdf
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
 
What are the key points to focus on before starting to learn ETL Development....
What are the key points to focus on before starting to learn ETL Development....What are the key points to focus on before starting to learn ETL Development....
What are the key points to focus on before starting to learn ETL Development....
 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a series
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalm
 
What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEE
 
2.pdf Ejercicios de programación competitiva
2.pdf Ejercicios de programación competitiva2.pdf Ejercicios de programación competitiva
2.pdf Ejercicios de programación competitiva
 
Recruitment Management Software Benefits (Infographic)
Recruitment Management Software Benefits (Infographic)Recruitment Management Software Benefits (Infographic)
Recruitment Management Software Benefits (Infographic)
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
 
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfGOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtime
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
 
Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need It
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New Features
 
Implementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureImplementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with Azure
 

Put to the Test

  • 1. P REVIOUS COLUMNS HAVE FREQUENTLY emphasised the benefits of travelling light and keeping your code fit. This advice applies both to the general guidelines to follow in structuring code1, 2 and to the breadth of a type’s interface3 . Many problems can be tackled with simpler solutions than you might first think—solutions that are sufficient and surprisingly versatile. A good example is the lightweight regular expression matcher presented in the previous column4 . In the case of that regex code, the algorithm was taken from some C code in The Practice of Programming5 . I refactored the code into C++, and not just for cosmetic reasons—the code was adapted and generalised to use iterators. The result was a generic algorithm that worked on any kind of forward iterator range for any type of character; the original was constrained to work only with null-terminated arrays of char. Last time we looked at the code and this time I want to look at the process. If you want to travel light, you’d better have a process for doing it. Refactoring is part of such a process. Personal hygiene for code Refactoring6, 7 has grabbed a lot of programmer mind share. However, it is often still used as a front for rabid hacking. Refactoring is not just taking a piece of code that you don’t like the look of and then playing around with it for a week until it satisfies your own preferred indentation, naming or design style, tossing in a raft of extra features while you’re at it. Refactoring is actually bound by quite a precise definition that automatically disqualifies most of the programming practice that takes its name in vain6 : Refactoring (noun): a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior. Refactor (verb): to restructure software by applying a series of refactorings without changing the observable behavior of the software. Far from random code-cutting and splicing, you can see that refactoring is quite a precise and rigorous discipline. A number of projects use the term as a more fashionable and less management- upsetting alternative to the word ‘rewrite’. There is certainly an element of rewriting in refactoring, but it is not the same as tearing down and reworking the whole architecture so that nothing builds successfully for a week, a month or longer. While refactoring, you should ensure that the code can be built at any time at the drop of a hat or function key. The code base should be as stable and buildable as possible, which means taking many small steps where you might otherwise be tempted to leap. And the code should never offer less functionality. Refactoring clearly requires a more methodical q Refactoring involves careful changes to code that modify its structure but do not affect its functionality. q Successful refactoring is best carried out with one or any combination of uninterrupted work,pair programming and unit testing. q Unit testing should be automated and frequent,not manual and rare. q Designing for testability reduces interface bloat and coupling in a system. q The minimum requirement for productive testing is the humble assert. FACTS AT A GLANCE C++ WORKSHOP Refactoring is only refactoring when combined with rigorous unit testing. Kevlin Henney explains how to take the taskwork out of testing in C++ Put to the test 38 APPLICATION DEVELOPMENT ADVISOR q www.appdevadvisor.co.uk Many problems can be tackled with simpler solutions than you might first think—solutions that are sufficient and surprisingly versatile
  • 2. NOVEMBER–DECEMBER 2002 39 and double-checked approach than is often misguidedly suggested. So how do you refactor with balance, rather than with bugs?There are three ways that you can change code with confidence: q Make changes with a clear state of mind. This means having continuous blocks of uninterrupted time when you are feeling alert. So, that excludes afternoons for most people—especially Friday—and suggests that Monday morning and any ‘morning after the night before’ is also probably inappropriate. It also means that unpartitioned open-plan offices are unsuitable environments. If you cannot reach the zone of total concentration, you will miss too many tricks to carry out refactoring effectively. q Make changes with a colleague. Two pairs of eyes are better than one when it comes to spotting careless mistakes and suggesting alternative designs. This kind of pair programming is effective in many different office environments and most times of day. One person’s attention low may coincide with the other’s high, flattening out the peaks and troughs. Apparently some managers regard any form of pair programming as a waste of resources. Many such managers indulge in long and frequent meetings that are, however, apparently another matter and should not be judged by the same productivity criteria. Pots. Kettles. ‘Nuff said. q Make changes against a set of unit tests. You break it, you find out very quickly. Footfall is surer because the feedback loop between change and effect is tightened. Change, compile, test. And then, depending on the outcome, you either move on to the next step or fix the bug just introduced with the change. Or you can use any workable combination of the three. Each approach also has benefits above and beyond refactoring. For instance, working without interruption is always more of a pleasure than working with distractions. You are likely to write code more clearly and cleanly, introducing fewer bugs in the process, tackling design problems more easily and so on. Pairing is a practice that can address many different development activities. Two pairs of eyes are better than one for writing tests, debugging code, deciphering existing code and training or familiarising someone else with the existing system. Pairing also serves as a form of active code review that is superior to the slow cycle of organising review meetings, printing out the code, involving many people who make vague suggestions or get caught up in the wrong level of detail—and then finally making the changes. Depending on the social skills of the code reviewing peers, a meeting can also be hard on the code author’s self esteem. Code reviews are a good idea, but they are better when they are more interactive and intimate. Unit tests make you think about the design of the code you are testing and, in particular, the interface to classes and functions. Unit tests can be seen as part of a design-driven approach8 that reduces the coupling of your system and sharpens the interfaces. Highly coupled code is hard to test because the unit of test must be that much larger: loosely coupled code has more easily separable and testable chunks. You also find out what you can and can’t test easily and what it’s like to use your classes and functions. This experience feeds back positively. You write fewer of those lame ‘setter’ and ‘getter’ functions that plague so much code that masquerades as object-oriented and you cut out functions that aren’t used. You also instantiate all of the template code that you've written, which can potentially hide basic compilation errors for a long time after it was first produced. The joy of testing So how do you go about testing? Perhaps the best advice is also the most succinct9 : Test early. Test often. Test automatically. This advice goes against the test approach adopted by many organisations (even the thorough and methodical ones). Let’s get one thing straight: writing code is a lot more fun than most of the other activities in software development, and testing has to be one of the activities traditionally associated with the least amount of fun. Many projects that explicitly include a testing activity often include it as a test phase, which is a sure way of guaranteeing the absence of fun. You’ve done all the interesting stuff and now you’ve got to test it all. For a very small project, this may not be a major obstacle. But for anything larger, it is quite a serious demotivator. And you may find out late in the day that you have written something that is quite hard to test. An interface may be broader than is strictly useful and more highly coupled than is strictly necessary. Additionally, if you leave testing until late and then discover that the project is late, guess what gets squeezed? Test early. Test often. The significance of automated testing is often overlooked, but is perhaps one of the most important pieces of the puzzle. Many systems are tested by manual inspection. Someone sits staring at a screen, either pumping data in or pushing buttons, waiting for the right result. This is neither engaging nor effective. It is, however, time consuming, which means that you are never in a hurry to do it early and often. It is also not really unit testing, unless you consider the whole application to be the unit. This approach to testing makes expensive monkeys out of bright programmers. If writing code is considered more fun than testing, then make testing a matter of writing code. If you write code to test code, you can rerun it automatically. Regression testing is no longer a chore and you can be more selective about what parts of your system you include in a test. So what tools do you need for this? #include <cassert> (or #include <assert.h> if you want the original C header) is not a bad place to start. Yes, there are expensive tools on the market that will do all kinds of fabulous things for you, but the minimum toolset is assert—a macro in C and C++, a built-in facility in Python and Java (as of SDK 1.4) and an easily provided feature in most other languages. This is a very low entry level indeed, but one that gets you into testing straight away without any fuss or ceremony. Once you have played around with assert-based testing for a while, you will have a better idea of what you want from your test harness and you can move on to something more powerful. Many people refactor such a harness out of their test code to keep total control over it. Others adopt one of the open source xUnit family of test harnesses (most famously, JUnit for Java10 ), which are simple but effective testing frameworks. If you want a level of static checking, automatic wrapping of library calls, memory C++ WORKSHOP Perhaps the best advice is also the most succinct:Test early.Test often.Test automatically
  • 3. 40 APPLICATION DEVELOPMENT ADVISOR q www.appdevadvisor.co.uk leak detection, coverage statistics and so on, a commercial tool is probably the right choice. However, none of these change the basic need for a testing mindset and knowing what you need for your own application. Go to product vendors with clear empirical requirements, rather than letting them suggest their requirements to you. Testing the ins and outs The presentation layer—whether console, browser or GUI—is often held up as an obstacle to automated testing. In part, this is a valid objection, but only in part. A GUI test should test the appearance, rather than the logic.That means separating the model logic from all the window presentation logic. The problem is not so much that GUIs are hard to test, but that many programs are not designed to be tested. When you design for testability, you put into practice the common design recommendation to decouple the actual presentation details from all the event handling and core logic. The manual element of testing is then reduced to testing the appearance and usability rather than the underlying logic. The real challenge here is not the technology, but the quality of the design presented by so many books, courses and code samples for windowing APIs and frameworks. Developers model their code after these examples, but the aims of the two sorts of code are incompatible: the samples show sufficient code to demonstrate how the API is used, not how to build a large, testable commercial product. One presentation facility that can be tested automatically is I/O streams. Instead of hardwiring the input and output streams (e.g. to std::cin and std::cout), pass in std::istream and std::ostream parameters. In a test harness, these can be wired up to std::stringstream objects or std::fstream objects opened onto files prepared in advance with test data and correct responses. If such substitution is not an option, you can get in under the wire and change the internal plumbing. Consider the toy function in listing 1. How would you test the decimal to hex conversion? You could have a main call it and script up a test externally in something like Perl or another scripting language. This would be fine, but given that this is a C++ column, let’s look at a C++-only solution that doesn’t involve running multiple processes. Listing 2 shows a main function that runs the function through a single test case without changing any code in dec2hex.The technique is to substitute string buffers of type std::stringbuf in place of the normal console-connected streambufs used by std::cin and std::cout. You prime the input with a value and then collect and compare the output. Remember to restore the standard I/O streams to their former state. This is more than just politeness: if you don’t, the streams will attempt to delete the local stringbuf variables on program exit, as well as leak their own original buffers. The catastrophic result of attempting to delete an object that was not created with new somewhat dwarfs the memory leak problem. The object ownership model in I/O streams is easy to trip over, in part because it fails to follow the recommendation to make acquisition and release symmetric1 . If you wish, you can use objects to automate the setting and resetting of the buffers2 . You can scale this approach up to many test cases by surrounding the whole lot with a loop and popping a predefined sequence of string values to test. You can use a little string manipulation to exclude the prompt text from the test condition. Although this is a very simple example, it demonstrates the anatomy of an automated I/O test. Refactoring match To return to where we started out, how can testing work in support of refactoring? I refactored the regex_match functions in the last article4 against a set of tests. The original code was written in C5 , so that was where I started. The functions were copied verbatim. Compilation provided the basic reality check that the code was copied correctly and that the C was clean enough to be used as C++. I then wrote out 10 or so assertions that seemed briefly to exercise the range of its functionality. I felt that only a few tests were required because the code was published by authors of note. The whole chapter devoted to testing in the same book also did a lot to inspire confidence. With the raw materials in place, I changed the names to be closer to the ones that I intended to use in the final code, which required some slight modifications. I then tightened up the types used, e.g. using const to qualify char * and bool instead of int, which did not require any test changes. Because I was aiming for an STL-styled algorithm, the next step was to switch the argument order around, which clearly necessitated changing test code. I then replaced some of the pointer-based expressions by expressions that would work for STL forward iterators, which did not require any test changes. Extra arguments were added so that iterator ranges were used instead of assuming null-terminated strings—the tests changed to accommodate the interface change, but no new tests were added. I followed through with some more work ensuring that only forward iterator operations were used. I then introduced templating, which did not require any changes to the tests, and then new tests were added to check that pattern matching worked for embedded nulls and wide-character strings. In between each sentence of this paragraph, the tests were compiled and run. The resulting interface for pattern matching is declared in listing 3 and the final tests are shown in listing 4. It is certainly possible to add more tests, and it is certainly possible to refactor the test code. However, for the immediate problem, the tests as structured were sufficient to give the right level of confidence. If I were to go back and extend the capabilities of the regular expression matching and therefore add to the number of tests, I would almost certainly refactor main before doing anything else. Don’t refactor the test harness and the test subject in the coding spree between compilations. Driving the code from a table that held sample values and expected results would be the best way to capture the commonality in this case. Most developers do not work with such small, insular pieces of code. However, the principles and practices do scale. The trick is to make code sufficiently modular and independent that it becomes testable. Testing doesn’t have to be tedious and it certainly isn’t rocket science. It is a matter of documenting what you might otherwise have thought about testing by hand or would have looked for in a code inspection. However, instead of wasting time documenting the tests with a word processor, document these cases in code. That way you never actually have to do the tests yourself: you just compile and run the documentation. With a body of tests to lean on, you are much freer to modify the code to improve or extend it. s C++ WORKSHOP
  • 4. NOVEMBER–DECEMBER 2002 41 References 1. Kevlin Henney, “Six of the Best”, Application Development Advisor, May 2002, www.appdevadvisor.co.uk. 2. Kevlin Henney, “The Rest of the Best”, Application Development Advisor, June 2002, www.appdevadvisor.co.uk. 3. Kevlin Henney, “Stringing Things Along”, Application Development Advisor, July-August 2002, www.appdevadvisor.co.uk. 4. Kevlin Henney, “The Next Best String”, Application Development Advisor, October 2002, www.appdevadvisor.co.uk. 5. Brian W Kernighan and Rob Pike, The Practice of Programming, Addison-Wesley, 1999. 6. Martin Fowler, Refactoring: Improving the Design of Existing Code, Addison-Wesley, 1999. 7. Refactoring home page, www.refactoring.com. 8. Kent Beck, “Aim, Fire”, IEEE Software, 2001, http://computer.org/software/homepage/2001/05Design /index.htm. 9. Andrew Hunt and David Thomas, The Pragmatic Programmer, Addison-Wesley, 2000. 10. JUnit home page, www.junit.org. Kevlin Henney is an independent software development consultant and trainer. He can be reached at www.curbralan.com. C++ WORKSHOP Listing 1: How would you test this function? void dec2hex() { std::cout << "decimal? "; int value = 0; std::cin >> value; std::cout << "hex: " << std::hex << value << std::endl; } Listing 2: Running listing 1 through a single test case int main() { std::stringbuf out, in("42"); std::streambuf *old_cout = std::cout.rdbuf(&out); std::streambuf *old_cin = std::cin.rdbuf(&in); dec2hex(); std::cout.rdbuf(old_cout); std::cin.rdbuf(old_cin); assert(out.str() == "decimal? hex: 2an"); std::cout << "OK" << std::endl; return 0; } Listing 3: An interface for pattern matching in the refactored regex_match function template< typename text_iterator, typename regex_iterator> bool regex_match( text_iterator text_begin, text_iterator text_end, regex_iterator regex_begin, regex_iterator regex_end); template< typename text_iterator,
  • 5. 42 APPLICATION DEVELOPMENT ADVISOR q www.appdevadvisor.co.uk typename regex_char> bool regex_match( text_iterator text_begin, text_iterator text_end, const regex_char *regex); Listing 4:The final tests int main() { // test three-agument regex_match const std::string text = "the cat sat on the mat"; assert(regex_match(text.begin(), text.end(), "the")); assert(!regex_match(text.begin(), text.end(), "foo")); assert(regex_match(text.begin(), text.end(), "c.t")); assert(!regex_match(text.begin(), text.end(), "t.t")); assert(regex_match(text.begin(), text.end(), "^t")); assert(!regex_match(text.begin(), text.end(), "^c")); assert(regex_match(text.begin(), text.end(), "^t.*t")); assert(!regex_match(text.begin(), text.end(), "^t.*f")); assert(regex_match(text.begin(), text.end(), "t$")); assert(!regex_match(text.begin(), text.end(), "a$")); assert(regex_match(text.begin(), text.end(), "^t.*t$")); assert(!regex_match(text.begin(), text.end(), "^t.*f$")); assert(regex_match( text.begin(), text.end(), "t.*cat.*o")); assert(!regex_match( text.begin(), text.end(), "t.*rat.*o")); // test four-argument regex_match const std::string pattern = "s.*o"; assert(regex_match( text.begin(), text.end(), pattern.begin(), pattern.end())); // test regex_match across embedded null std::string next = "abcd"; next += '0'; next += "efgh"; assert(regex_match(next.begin(), next.end(), "fg")); assert(regex_match(next.begin(), next.end(), "d.e")); // test regex_match with wide-character strings const std::wstring wtext = L"the cat sat on the mat"; assert(regex_match( wtext.begin(), wtext.end(), L"^t.*t$")); assert(!regex_match( wtext.begin(), wtext.end(), L"^t.*f$")); assert(regex_match( wtext.begin(), wtext.end(), "t.*cat.*o")); assert(!regex_match( wtext.begin(), wtext.end(), "t.*rat.*o")); std::cout << "OK" << std::endl; return 0; } C++ WORKSHOP