| Exploring Solution Spaces © Copyright 2003-2006, by C. Keith Ray | ||||||||||||||||||||||||
|
Archives
Subscribe |
2007.Dec.08 Sat
Jame Shore on What's the value of functional testing?
James Shore lightening talk (video) transcript quotes:
Check it out
Elisabeth Hendrickson on Why Automated Acceptance Tests are Crucial
Elisabeth Hendrickson’s lightening talk (video). 2007.Nov.26 Mon
Industrial Logic's Greatest Hits
Industrial Logic has a new home page, where you can browse and purchase eLearning products for C++, Java, and C# on the subjects of Code Smells, Refactoring, and Microtesting. The front page also lets you Take a Tour and experience the Welcome Album, which includes blooper videos. Joshua Kerievsky, Mike Hill, and Gil Broza are the authors of this self-paced training material, as well as the instructors who use the same materials in Industrial Logic's in-person workshops. 2007.Nov.25 Sun
Return of the Blog: Pairwise testing
I haven't been blogging much lately, but it's time to spill. Miscellaneous subjects follow. Via Michael Bolton, a paper (pdf) "Pairwise Testing: A Best Practice That Isn't", by James Bach and Patrick J. Schroeder Quotes: 2007.Oct.12 Fri
What's the point of tests that pass all the time?
When I'm doing Test Driven Development, my tests (almost) always fail - at least once. This is because I write a test and run it before I write the code to make the test pass. It should fail because the functionality isn't implemented yet. I could design my code without using the tests to guide me, but then I find that I spend a lot of time debugging manually, and afterwards, I don't have tests to find regressions. I define regressions as bugs for already-implemented features which can get injected into the code during refactoring or when adding new features. So the tests in TDD are serving two purposes: guiding my design, and guarding against regressions. However, I don't check into source-code-control when tests are failing, and I hope my team-mates do the same thing. So from the viewpoint of the team's continuous integration server, the programmer tests used for TDD should never fail. Sometimes they do, because we make mistakes: we forget to check in something, or the tests are poorly written -- for example, a test might be accessing a live external server that isn't running all the time, or has inconsistent time-outs or other 'flaky' behavior. The skills for TDD and writing reliable tests are more subtle than they ways of wizards, but they can be learned. [I've been watching Lord of Rings again.] 2007.Oct.05 Fri"splogs" is inspired to use 'const' more in C++ after exposure to Haskell. Quote:
An anonymous commenter responded:
Consider this bit of code:
Results aFunction(SomeClass& anObject)
{
someval = anObject.SomeMethod();
// later...
someotherval = anObject.SomeOtherMethod();
// etc.
return results;
}
Given that anObject is not const, you'd have to assume that SomeMethod and SomeOtherMethod could be modifying the state of the object. If you changed the order of calls, or called one of those methods more than once, you could break the desired behavior. The only way to know for sure would be to look at the source-code and/or documentation for SomeClass's methods. Now with const-ness:
Results aFunction(const SomeClass& anObject)
{
someval = anObject.SomeMethod();
// later...
someotherval = anObject.SomeOtherMethod();
// etc.
return results;
}
The simple act of declaring anObject to be const also implies that SomeMethod and SomeOtherMethod are const methods. (The compiler will complain if that's not true). This assumes the programmer for SomeClass was following the rules (that is, he's not 'faking' const-ness) and SomeClass isn't modifying some external state that we care about. In this case with a const reference and const methods, we could be pretty sure that we could change the order of method calls, and the number of times the methods are called, with no effect on the correct behavior. Unfortunately, even with 'const' we can't always be sure the behavior is correct, so writing micro-tests helps enforce the intended behavior. A test like...
TEST(aFunctionDoesWhatWeWant)
{
SomeClass anObject; // set up how we want
Results fResults = aFunction(anObject);
AssertEqual(expectedState, anObject.GetState());
AssertEqual(expectedResults, fResults);
}
... would help us verify that the following changes are okay, if it still passes after changing the order and number of calls.
Results aFunction(const SomeClass& anObject)
{
someval = anObject.SomeMethod();
someotherval = anObject.SomeOtherMethod();
// later...
someotherval2 = anObject.SomeOtherMethod();
someval2 = anObject.SomeMethod();
// etc.
return results;
}
Declaring a function parameter const also helps tell other programmers that the function isn't going to try to change the object's value. Tests will help document the function's desired behavior. 2007.Sep.20 Thu• Purity metric: the ratio of pure virtual methods over the total number of methods of a class. Purity of 1.0 is good for an abstract base class. Purity of 0.1 might be ok for a class implementing the Template Method Design Pattern. Purity of 0.5 is likely to be bad because the class is going to be confusing. It is likely to be an abuser of "inheritance for implementation". Note that Template Method relies on subclassing from a class with a partially-concrete implementation, and might be better replaced by the something like the Strategy Design Pattern, which has a concrete class parameterized by injection of pluggable strategy objects (whose parent class is purely abstract). • Public Interface metric: the ratio of public methods over the total number of methods of a class. A measure of 1.0 is probably good: all public methods, and no private methods (at least it's testable). A Public Interface number of 0.1 is likely to be bad - the class is consisting of mostly private or protected methods, and thus hard to test. A low number is a sign of the Iceberg Class , often indicating that the class is too large and should be broken up into several smaller, collaborating classes (after the break-up, maybe only one class is still part of the public API, but the other classes who now have public methods would be testable.) • Static Cling metric: the number of static variables in a class, including static variables in the class's implementation file, whether file-scope or function-scope. • Anti-Polymorphic metric: number of static functions and non-virtual functions in a class, plus the number of file-local functions in the class's implementation file. 2007.Sep.10 MonHere are three basic kinds of micro-tests, a.k.a. "programmer tests" such as used in test-driven design/TDD. Testing the return value of a side-effect-free function. A lot of simple examples of TDD do this. Simple return-values, scalars instead of lists, for example, are generally simpler to test. result = some_function(some_input); assert_equals(expected_result, result); Testing the state of an object after creation and/or calling one or more methods on it. In other words, testing the post-conditions of an object. The fewer methods called, the simpler the test; and, the simpler the state being checked, also the simpler the test. an_object = new some_object(maybe_some_input); an_object. some_method(maybe_some_more_input); assert_equal(expected_object_state, an_object); // or ... assert_equal(expected_object_state, an_object.get_state()); Testing the collaboration of an object with another. In other words, checking how it calls methods in another object using some kind of mock object. The fewer the objects and methods involved, the simpler the test. an_object = new some_object(); collaborator = new some_kind_of_test_double(); an_object. some_method(collaborator); assert_true(collaborator.expected_method_called_correctly()); The further away that you get from these simple basics, the harder the tests are and the more complex the code's design is. In particular, testing state gets more difficult if the state-changes are not localized to a single object under test -- global variables, modified input-variables, the file-system, networked client-server or peer-to-peer interactions, and databases are some places where non-local state changes can make things difficult. 2007.Jul.29 SunWilliam Wake described the essence of an automated test as Arrange, Act, Assert. I've added "Erase", to account for the clean-up that some tests have to do. You might ask why "Erase" added to "Arrange, Act, Assert" and not some word starting with "A". I think starting with "eh?" is close enough. :-)
In C++, with certain test frameworks, a test might be specified in a manner something like the following in C++.
TEST(TestBlurImageFilter)
{
// Arrange
string outfileName = NewTempFileName("TestImageFilter");
Image* sourceImage = new Image("lena.png");
// Act
ImageFilter* filter = new BlurImageFilter();
filter->ProcessToFile(sourceImage, outfileName);
// Assert
AssertImagesEqual("expected_lena_blurred.png", outfileName);
// Erase
DeleteTempFile(outfileName);
delete filter;
delete sourceImage;
}
Note: generally you don't want to deal with files in unit tests; working in-memory would be much faster. Also, if this is one of those frameworks that throws an exception, or otherwise aborts the test if an assertion fails, then the "Erase" portion of the test won't get executed if AssertImagesEqual failed. Let's assume that's not a problem for the moment. Let's imagine that you then write a another test like so:
TEST(TestUnblurImageFilter)
{
// Arrange
string outfileName = NewTempFileName("TestImageFilter");
Image* sourceImage = new Image("lena.png");
// Act
ImageFilter* filter = new UnblurImageFilter();
filter->ProcessToFile(sourceImage, outfileName);
// Assert
AssertImagesEqual("expected_lena_unblurred.png", outfileName);
// Erase
DeleteTempFile(outfileName);
delete filter;
delete sourceImage;
}
Now you've got duplicated "Arrange" and "Erase" sections. And duplicated logic in tests can be just as bad it would be in production code. Fortunately, most test frameworks already have support for extracting "Arrange" and "Erase" to methods in a "test fixture". The above code could be refactored to something like the following:
class ImageFilterTests : public TestFixture
{
public:
ImageFilterTests()
: sourceImage(NULL), filter(NULL)
{
}
string outfileName;
Image* sourceImage;
ImageFilter* filter;
virtual void SetUp()
{
// Arrange
outfileName = NewTempFileName("TestImageFilter");
sourceImage = new Image("lena.png");
}
virtual void TearDown()
{
// Erase
DeleteTempFile(outfileName);
delete filter;
delete sourceImage;
}
};
TEST_USING_FIXTURE(ImageFilterTests, TestBlurImageFilter)
{
// Act
filter = new BlurImageFilter();
filter->ProcessToFile(sourceImage, outfileName);
// Assert
AssertImagesEqual("expected_lena_blurred.png", outfileName);
}
TEST_USING_FIXTURE(ImageFilterTests, TestUnblurImageFilter)
{
// Act
filter = new UnblurImageFilter();
filter->ProcessToFile(sourceImage, outfileName);
// Assert
AssertImagesEqual("expected_lena_unblurred.png", outfileName);
}
Not only has this eliminated the duplicated logic, most unit test frameworks will also guarantee running the TearDown method even if the test fails, so you don't have to write your own try/catch blocks or other contortions for exception-safe "erase". You'll see that I also added a constructor to insure that the pointer variables have valid NULL values so we don't delete garbage pointers if the Image or Filter objects were not allocated successfully. (You should consider using boost::shared_ptr and/or boost::scoped_ptr if you're dealing with object pointers in C++ code and tests, by the way.) In those C++ test frameworks where the test-fixture creation and deletion is done just before and after executing the test, the SetUp and TearDown methods can (almost always) be replaced with a constructor and destructor instead. Using that and boost::scoped_ptr to insure exception-safe object deletion would allow us to write the following code:
class ImageFilterTests : public TestFixture
{
public:
ImageFilterTests()
: outfileName(NewTempFileName("TestImageFilter")),
sourceImage(new Image("lena.png"))
{
// Arrange
}
virtual ~ImageFilterTests()
{
// Erase
DeleteTempFile(outfileName);
}
string outfileName;
boost::scoped_ptr<Image> sourceImage;
boost::scoped_ptr<ImageFilter> filter;
};
TEST_USING_FIXTURE(ImageFilterTests, TestBlurImageFilter)
{
// Act
filter.reset(new BlurImageFilter());
filter->ProcessToFile(sourceImage.get(), outfileName);
// Assert
AssertImagesEqual("expected_lena_blurred.png", outfileName);
}
TEST_USING_FIXTURE(ImageFilterTests, TestUnblurImageFilter)
{
// Act
filter.reset(new UnblurImageFilter());
filter->ProcessToFile(sourceImage.get(), outfileName);
// Assert
AssertImagesEqual("expected_lena_unblurred.png", outfileName);
}
2007.Jul.17 Tue
It's easier for me to write a review of a book I'm still in the process of reading it, than it is for me to write a review after I have finished reading the book. Why is that? Once I've finished the book, I move onto the next thing. Writing a review would be "going backwards". So I'm writing this book review while I'm still reading the book. The book is Paradox of Choice by Barry Schwartz. It's got a lot of interesting stuff in it: maximizers and satisficers, sunk costs, how verbalizing the reasons for a choice sometimes causes us make worse choices, how a disappointment often leads us to consider counterfactuals that increase our regrets, and so on. A friend of mine tends to compare himself to his more successful cousin, increasing his regrets. He could compare himself to one of his less successful cousin, but doesn't. Why not? The Olympic winner of a silver medal is less happy than the winner of a bronze medal. Why? Because the silver winner compares his or her reality against the winner of the gold medal and regrets whatever choices might have preventing winning the gold, while the bronze winner compares his or her reality against not winning a medal at all, and so appreciates reality more. This may be the one of the most "practical" books available for increasing your happiness, and it's based on science. (Insert appreciative sound here.) Science may not be able to explain why we cause ourselves so much mental suffering in all cases, but it does help explain that it's not our fault for many cases. Some of our behavior is very likely built into our neurology, and some of it may come from the influence of society. But we can make an effort to overcome our natural inclinations towards thoughts that cause us suffering. 2007.Jul.09 Mon
GUI Test Driven Development in Smalltalk
Check out this tutorial of developing a graphical game application in Squeak Smalltalk, that uses test-driven development. Lots of screen-captures so you can follow along. A Development Example for Squeak 3.9. 2007.Jul.06 Fri
What is Writing Software Like?
I think writing software in small groups is very much like the writing done for TV shows, which is also often done in small groups. We complain about the failures rates of software projects, and the lack of quality, but how many TV shows are good versus bad? Or great? Isn't typings words into a script easy? TV requirements seeem pretty simple - use the actors you've got (or not, in some situations), don't write scripts that require spending a lot money on sets or special effects, don't offend the advertisers, attract and keep an audience. And "be dramatic" or "be funny", depending on the show. Some shows are wonderful but get canceled quickly because they're in the wrong time-slot, or too expensive, or the TV Executives are idiots. Some software projects are too ambitious, or too expensive, or managed by Dilbert's boss. Other TV shows, like various soap operas whose quality I cannnot comment on, survive for decades with a large and loyal audience, not unlike various makers of operating-systems and office-oriented software. We rarely write the exact same piece of software twice (bowling game exercises are the exception), and TV show writers aren't allowed to repeat themselves too obviously, either. Most of the constraints of a TV show or a piece of software are self-imposed. In the case of software, the system is usually constrained by the person(s) willing to pay for features but also constrained by the skills of the people involved. 2007.Jun.23 Sat
Standish Group 2006 (2004?) Top Ten Success Factors
Quotes from an infoq interview with Jim Johnson of the Standish Group:
and 2007.Jun.19 Tue
Fortune
Testing is an Integral Part of Development
Effective testing is not a service. Effective testing is an integral part of development. Say it, sister! 2007.May.02 WedRequirements exist, whether or not they are written down." -- Elisabeth Hendrickson 2007.Apr.09 Mon
Refactoring is like Steering a Car
Won't continual merciless refactoring slow the team down? This question is like asking, "Isn't all that messing with steering wheel going to slow your car down? Why don't you just pick a direction for your car, lock your steering wheel in place, and stick to it?" Refactoring takes care of unplanned as well planned design changes. Unplanned changes may include noticing duplicated code or other code smells. Planned changes include those changes necessary to implement a new story or feature. Similarly, in driving, we have unplanned course changes, such as avoiding accidents, slow drivers, potholes, and pedestrians, and planned course changes. Like all analogies, there's a nugget of truth here, but don't take the analogy too far. 2007.Apr.04 WedIf you don't pay down your Technical Debt as soon as it you incur it, ... it's like never paying down the balance on your credit cards. Soon you'll have so much interest and penalty debt on your credit cards that you can't pay them off at all. Technical Debt consists of refactorings and bug-fixes that don't get done (or get deferred), slowing down present and future development. When a programming team says they can't make progress with the current software and that it needs to be rewritten "from the ground up", that's declaring Technical Bankruptcy. 2007.Mar.28 Wed
How to Estimate with Story Points
Pick a very small, easy, story "User can do X". Set its story-point value to 1. Compare that to another story "User can do Y"... is that just as difficult as the previous story? two times harder? three times harder? or easier than the 1-point story? If 2-times harder then it's story point value is 2. If 3-times harder, then its story point value is 3. If it is easier, call it 1/2 a point or use the "Y" story as the new 1-point story and evaluate the "X" story against it. If the "Y" story is more than 3 times harder, find a way to split the story into two or more smaller stories. Story points are a relative measure used for estimation, and they are not based on line count. 2007.Mar.27 TueSometimes people new to Test Driven Development (Behavior Driven Design) focus on testing accessors, or testing constructors, or other details a little too much. In TDD/BDD, we're not testing, we're designing. When writing a executable example (aka "spec" or "test"), we're asking the questions: 1. What do we want X to do? 2. How do we want to tell X to do it? 3. How will we know when X has done it? In answering these questions, we use (possibly not-implemented-yet) constructors, methods, accessors, etc. We then compile and run the example to see it fail; and then write code to make the example pass. And sometimes while all examples are passing, we refactor to improve the design. When writing code to make the spec pass, we ask and answer the question: 4. How does X do it? And when refactoring we ask and answer the question: 5. Is this the best design at this time? To focus on "testing accessors" and "testing constructors" is to get a little too focused on the mechanics of "how does X do it?", which isn't even in the top 3 questions. 2007.Mar.24 Sat
Work-Arounds for C++'s Lacking Block Syntax
Doing a simple operation on each item in container... In Smalltalk, an arbitrary block of code can be passed to a method of a container. In the example below, "[" and "]" delimit a block of code, which is passed to the method named "do:" of the class List.
testDo
| alist aLocal |
alist := List new.
"...add Item instances, etc. to alist skipped."
alist do: [ | item | item doSomething ].
In Ruby, a block of code can be passed to a method using "{" and "}" to delimit the code, or "do" and "end". The method in this case is named "each" and belongs to the List class.
def testEach
alist = List.new
# add Item instances, etc. to alist skipped.
alist.each { | item | item.doSomething }
end
In C++ the are no code-blocks that can be passed around like other objects. There are several alternatives which involve writing a function or a class and then... Passing the class as a type-specialization of a template, or passing an instance of a class to have a member function invoked, or passing a function name as a specialization of a template, or passing a function pointer or a passing a pointer-to-a-member-function and a pointer to an object. The first two examples here are using a function and a functor class with the for_each template function.
#include <vector>
#include <algorithm>
class Item
{
public:
// etc...
void doSomething();
};
void doSomething(Item& anItem)
{
anItem.doSomething();
}
struct Functor
{
void operator()(Item& anItem)
{
anItem.doSomething();
}
};
void testForEachWithFunctionAndFunctor()
{
vector<Item> alist;
// add Item instances, etc. skipped.
for_each(alist.begin(), alist.end(), doSomething);
for_each(alist.begin(), alist.end(), Functor());
}
One of the drawbacks of the function or functor approach is you can't easily refer to local variables, like the Smalltalk and Ruby examples can do. (Not shown above to keep everything simple.) And the function tends to end up at an inconvenient distance away from the code that uses it do the iteration. An alternative is to use a static function declared in a class, which you can define inside your function, but accessing local variables is still inconvenient. (And for some reason, a functor class defined inside a function doesn't compile. Anyone know why?)
#include <vector>
#include <algorithm>
class Item { ... };
void testForEachWithClassStatic()
{
vector<Item> alist;
// add Item instances, etc...
class Function
{
static void doSomething(Item& anItem)
{
anItem.doSomething();
}
};
for_each(alist.begin(), alist.end(), Function::doSomething);
}
/*
void testDoesntCompile()
{
struct FunctorInternal
{
void operator()(Item& anItem)
{
anItem.doSomething();
}
};
for_each(alist.begin(), alist.end(), FunctorInternal()); // error
}
*/
So if you want to refer to local variables easily, and have the code where you expect it, there's the old for-loop:
#include <vector>
#include <algorithm>
class Item { ... };
void testForLoop()
{
vector<Item> alist;
// add Item instances, etc...
for (vector<Item>::iterator it = alist.begin();
it != alist.end(); ++it )
{
it->doSomething();
}
}
However, that can get verbose if the types and variables involved have long names. The last alternative to simulate passing a bit of code around is to get the preprocessor involved. In these examples, a macro can avoid the explicit 'for' loop as long as the action to do in the loop is simple enough. (And you can still access local variables.)
#define APPLY4(vectorType,vectorName,iteratorName,Action) \
for ( vectorType::iterator iteratorName = vectorName.begin(); \
iteratorName != vectorName.end(); ++iteratorName) \
{ \
(Action); \
}
#define APPLY3(vectorName,iteratorName,Action) \
{ \
typedef typeof(vectorName) vectorType; \
typedef vectorType::iterator itType; \
for ( itType iteratorName = vectorName.begin(); \
iteratorName != vectorName.end(); ++iteratorName) \
{ \
(Action); \
} \
}
// NOTE: "typeof" used above is non-standard/non-portable.
// (it's in the version of gcc C++ that I'm using on MacOS X.)
void testApplyMacros()
{
vector<Item> alist;
// add Item instances, etc...
APPLY4(vector<Item>, alist, each, each->doSomething);
APPLY3(alist, each, each->doSomething);
}
By the way, the APPLY macros work on more than just vectors -- any container that implements "iterator". Making a APPLY_CONST macro that works with "const_iterator" should be trivial. 2007.Mar.22 ThuThe Worse Than Failure site (formerly "The Daily WTF") usually highlights terribly buggy code. Today's example is of the Code Smell "Duplicated Code". Three different versions of a visual basic function to return the numeric part of a string — from the same source file! Check it out. 2007.Mar.06 TueTo the tune of "The Best Things in Life" from the movie musical Paint Your Wagon, set in the California gold-rush of 1848-1855: (Imagine that a self-important programmer in a dot-com startup has been lecturing that crappy, quickly-written code is the most important thing for the start-up's programmers to write.)
Words by yours, truly, C. Keith Ray. (Note: I do not recommend writing crappy code in a startup situation, because that will slow you down more than writing good code would do.) 2007.Feb.21 WedWe've compared code to spaghetti, lava flows, architecture, and a big ball of mud, but this is the worst:
I'm not saying this analogy is wrong -- all analogies are both right and wrong -- just that I'd rather focus on something positive. Kent Beck and others in describing XP, say that if a "design disagreement" goes on for more than 10 minutes, both parties should actually implement the bones of their designs and have the team compare the implementations, rather than continue to argue. By the way, check out the piano-playing cat: 2007.Feb.12 MonI like to say that there's all sorts of ways to "do design". TDD happens to be a way that leaves behind a suite of regression tests -- which you would NOT get if you did design another way. For some people that's an "aha!" idea. For others, they shrug and say "well I can write the tests after" -- but very few people do. If you *always* write the tests after, then maybe you don't need TDD. The other aspect is that TDD helps force you to pay attention to design issues - object setup and interactions, code smells, decoupling and cohesion. This is perhaps more helpful to inexperienced "junior" developers. And helps scatter-brained senior developers (like me) focus on doing one thing at time instead of trying to do too much at once. If you're solo coding (not part of a team), and working on very small projects, and/or your code is throw-away-after-one-use, then TDD may not be necessary. In that context, you may not need TDD's (1) focused test/code/design process and (2) safety-net tests that TDD provides. 2007.Feb.06 Tue
Make Test Automation a Priority If You Don't Already Have It.
"Notice that Guy assigned five of his top engieners and architects to the automated test framework. Testing in-process is hard work. If you don't have the ability to develop and maintain an automated test framework for your project, put your best people on it, as Guy did. You won't regret it."Check it out. 2007.Jan.31 Wed In most languages, when I'm using Test Driven Development to create a class, I only put into that class those methods or fields that I needed to pass a test. C++ has some exceptions to that, given how the compiler will generate aspects of a "canonical c++ class" for you. I should explain the idea of a "Canonical C++" class. Imagine that I have this code:
class Buddy
{
public:
Icon* myIcon;
std::string myName;
};
Now, I didn't write a constructor, destructor, nor an assignment-operator, but the compiler did create those for me. It's as if I really wrote the following code:
class Buddy
{
public:
Icon* myIcon;
std::string myName;
Buddy() // default constructor
: myName() // invokes std::string's default constructor
{ // myIcon is not initialized, it probably has a garbage value here.
}
Buddy(const Buddy& other) // copy constructor
: myIcon( other.myIcon ) // copy the variable's value
, myName( other.myName ) // invokes std::string's copy constructor
{
}
~Buddy() // destructor
{
} // invokes std::string's destructor for myName.
Buddy& operator=(const Buddy& other)
{ // assignment operator
myIcon = other.myIcon; // copy the variable's value
myName = other.myName; // call std::string's assignment operator
}
};
A "canonical" C++ class has default constructor (and/or other constructors), copy constructor, destructor, and assignment-operator. These may be defined by the programmer or created by the compiler. And this invisible compiler-generated code can be wrong, particularly if ownership of pointers or other resources is involved. Let's say that I test-drive a default constructor that sets up myIcon to point to a newly-created Icon object, and write the corresponding destructor code to delete the Icon object. It's hard to verify the "state" of an object after its destructor is called ('cuz it's GONE), but there are a few tricks to verify the behavior of a destructor that I won't get into here.
class Buddy
{
public:
Icon* myIcon;
std::string myName;
Buddy()
: myIcon( NULL )
, myName( "no name" )
{
myIcon = new Icon(Icon::DEFAULT_ICON);
}
~Buddy()
{
delete myIcon;
}
};
SPECIFY_(Context,BuddyHasDefaultNameAndIcon)
{
Buddy* aBud = new Buddy;
VALUE( aBud->myName ).SHOULD_EQUAL( "no name" );
VALUE( aBud->myIcon ).SHOULD_NOT_EQUAL( NULL );
delete aBud;
}
This test will pass. (by the way, I'm using "ckr_spec" here, a Behavior-Driven-Design framework I've written in C++ in my spare time. I'll publish more about ckr_spec one of these days.) However, this test doesn't exercise the compiler-created copy-constructor and assignment operators. AND THOSE ARE WRONG. Nothing (besides self-discipline) prevents anyone from writing the following (crashing) code:
void crashingCode()
{
Buddy keith;
Buddy keithClone(keith); // calls compiler-created copy constructor
Buddy anotherKeith;
anotherKeith = keith; // calls compiler-created assignment operator
// destructors are called invisibly here - crash deleting the same Icon
// object 3 times. (Also leaks an Icon object, too.)
}
The compiler-created copy constructors just copy the pointer to the Icon object. They don't create a NEW Icon object. So in "crashingCode" above, the Icon object created in the constructor of "keith" gets deleted three times, when the destructors for the "keith", "keithClone", and "anotherKeith" objects get called at the end of the function. Therefore, when I'm test-driving a C++ class, very early on, I make a decision. Is this a "value" class that is going support the copy-constructor and assignment-operator, or an "entity" class that should never be copied because the instance represents something with a persistent identity? (These are some over-simplified ideas from Domain-Driven Design.) I can change my mind later, of course. If my class is going to allow "value" semantics, then I'll need to write some tests to assure that the copy-constructor and assignment operator function correctly, whether I've written them, or the compiler has generated them. If I'm not going to allow "value" semantics, then I need to signal to the compiler and to my fellow programmer not to generate or use the copy-constructor and assignment-operator. Declaring them private and unimplemented is how to do that.
class Buddy
{
public:
Icon* myIcon;
std::string myName;
Buddy()
: myIcon( NULL )
, myName( "no name" )
{
myIcon = new Icon(Icon::DEFAULT_ICON);
}
~Buddy()
{
delete myIcon;
}
private:
Buddy(const Buddy& other);
// don't implement copy constructor
Buddy& operator=(const Buddy& other);
// don't implement assignment operator
};
// the crashingCode example will not compile now.
For entity objects, quite often I don't want to allow the default constructor either, so I would declare that private and unimplemented as well. The moral of the story is that in C++, sometimes you have to write code to prevent the compiler from writing the code for you. Just add that to your TDD/BDD development process. 2007.Jan.29 MonLate night, driving in unfamiliar territory. "It's around here somewhere; we just need to keep going in this direction." "Are you sure you didn't miss it?" In the age of GPS receivers, this conversation is going to become less and less frequent. I've been on a few software projects that had a similar feeling. One in particular was initially estimated as being a six-month project and ended up taking over a year (and it felt like two years.)
The problem with that project was a lack of measurement - we could not objectively measure how much was left to be done. Also, false measurements - we were told the code was "working" when we started... and "just" needed to be ported. Kent Beck wrote: "Testing. You have to know when you're done. The tests tell you this. If you're smart, you'll write them first so you'll know the instant you're done. Otherwise, you're stuck thinking you maybe might be done, but knowing you're probably not, but you're not sure how close you are."
Does anyone Remember Binary Coded Decimal
I learned about Binary Coded Decimal (BCD) in the 80's. (1980's!) Some CPUs had hardware support for it. The Motorola 680x0 series for sure, and I think the Z80/8080 processors. BCD represents decimal digits, one per 4 bits, like so: 978 = 0x0978. The PowerPC processor did not have it, but some versions of it supported "vector math" (aka "Altivec") which could do a kind of BCD with one decimal digit per 8 bit... 978 = 0x00090708. The only languages that supported BCD directly were COBOL, and (I think) the kitchen-sink language PL/1. People used to program in "subsets" of PL/1 kind of like many do today with C++ (that is, C++ without exceptions, or C++ without templates). Probably some versions of some databases will use BCD support if its available. But I digress. Somewhere, I read a breathless piece of prose saying that the "first ever" hardware support of decimal math was coming out soon. It's not the first ever. Of course, I don't have a link to that bit of misinformation, though I'm not sure I'd link to if I did have it. 2007.Jan.26 FriCheck out the Google Testing Blog. I particularly like the logo, which has an unhappy red light-bulb under "Debugging sucks" and a happy green light-bulb above "Testing rocks." The Google Testing Blog also introduces Testing on the Toilet. Quote:
One comment on the Testing on the Toilet entry deserves a response. Joe#302 wrote "My point is that QA needs to have a bright line between it and development. [...] But the two should remain separate. Go to any mature industry's engineering department, and ask how much of the release testing is done or defined by the actual development engineers. [...] This is a sign of the immaturity of the software development industry." There are many successful projects where the testers and developers are members of the same team. There are also many unsuccessful projects where the testers and developers are not on the same team. The auto industry is pretty mature, and yet the dominant player has this to say in one of their web pages:
The Toilet entry has a number of comments employing certain metaphors. One them is in response to Joe#302. Major Tom wrote: 2007.Jan.23 Tue Chris Hanson pointed to a nice little rant about the dangers of writing software to appeal to buyers (managers) rather than users. Groupware Bad by Jamie Zawinski. (warning: some profanity) Check it out. 2007.Jan.17 Wed I keep forgetting to go outside and look for the very bright Comet McNaught. Check out this photo gallery. Check out Jason Yip's It's Not Just Standing Up: Patterns of Daily Stand-up Meetings. Quotes: 2007.Jan.09 Tue Guy Kawasaki interviews the authors of Made to Stick: Check it out. |
|||||||||||||||||||||||