ArticleS
.
MichaelFeathers
.
TheNewGuy
Edit Page:
!title The New Guy ''1'' [[2][http://www.butunclebob.com/ArticleS.MichaelFeathers.RamonWalksHome]] [[3][http://www.butunclebob.com/ArticleS.MichaelFeathers.SallysExploration]] [[4][http://www.butunclebob.com/ArticleS.MichaelFeathers.MullingNull]] ''Ramon came into the lab and took a look around. There wasn't much going on. The team had just finished a milestone and people were mulling around, catching up on some things that they'd put off, doing their timesheets, and cleaning up odd bits of work. Sally is sitting at a table flipping through a book.'' "What are you reading?", Ramon said. "Oh, it's this book about [[working with legacy code][http://www.amazon.com/exec/obidos/tg/detail/-/0131177052]]", said Sally. Ramon bursts out laughing, spraying Sprite all over his shirt. "Well, that was impressive", Sally said. "No no, it's just that.... well, that's what it's come to? We spent five years working on this system and you're trying to get hints from a book on COBOL? "You should read it, Ramon. It's not about COBOL at all. In fact, most of the examples are in Java. This guy is saying that there is a lot of code out there that feels like legacy code. He goes a bit further too. He says that code without tests is legacy code." "Right. Sounds like hype." "Well, I'm gonna try some of this stuff, want to pair?" "Sure. Want me to change my shirt? I have my running shirt in my office." "No way. I'll take the Sprite. Sit down." Sally browses to the bug database and clinks into the third link on the page. "Remember this one? I figured out how to fix it. We have to go to the !-TradeColumnHeader-! class and set the alignment on refresh." "Oh," said Ramon, "who did that?" "I don't know. Probably Randy, but anyway, you know this class, you know how to fix it, right?" "Yeah." "Well, let's write a test for it." "Huh?" "Yes, we can write a test for the correct behavior and show that it fails because there is a bug. Then when we fix the bug, the test will pass." Ramon looks like he's bitten into a lemon. "What do you want to do THAT for?! It's a one line code change?!" "Relax, Ramon. There are some very good reasons. For one thing, when we write the test we'll have some real feedback that we've solved the problem. How would you verify that we solved the problem without the test?" "It's just an obvious fix, Sally. We'll see when the weekly reports are run." "Uh huh. I'll bet Randy thought that he understood the code too." "Okay, you've gotta point. Any other reasons?" Sally smiles, "Well, we can find out whether we can get rid of our legacy code or not." "You've been corrupted. How do we start? Sally takes the keyboard and starts to write a junit test case. Ramon, wheels his chair around and drops his chin into palms. "So, what are you going to test?" "That's not the first step. The first step is to get the class to run in the test harness." "Shouldn't be a problem, Sally. We had a review for this class a few weeks ago. It's good work." "Okay," said Sally, "we'll see." Sally starts to type in a test case in her IDE. "So, Ramon, what do you think of the new guy?" "I don't know, he's pretty quiet." "He is, isn't he? I don't think he's right for this job. You should've seen how he was writing equals methods before I stopped him. The fool lowered the resolutions on two of the monitors in the lab last night also." "Seyed hired him. He should know something." "You'd think, but you're right; he's so quiet. Hard to tell. Here. Here's our first test..." {{{ import junit.framework.*; public class TradeColumnHeaderTest extends TestCase { public void testCreate() { new TradeColumnHeader(); } } }}} "That's it?" said Ramon, "it doesn't test anything." "Well, it does in a way. If we can compile and run this thing, we know that we can write more tests, including the one we want to write, the one that shows the bug." "So, you'll be using those techniques" said Ramon as he started to shuffle papers around on the desk. "Yeah" Sally hits the run button and gets an error message. There is no parameter-less constructor for the !-TradeColumnHeader-! class. "So, the moral of the story is, look at the class to see what constructors it has, right?" said Ramon. "Well, sometimes you get lucky," said Sally smiling. Sally opens the class declaration in a browser pane and looks at the constructors. There are five of them and the simplest of them accepts a single parameter: a Configuration object. "What does this one do, Ramon?" "Not much. It just holds that configuration internally. If methods need to use it, they use it." "What about the refresh method? Does it use the configuration?" "I'm not sure", said Ramon. "Let's see", said Sally. Sally changes the test: {{{ public class TradeColumnHeaderTest extends TestCase { public void testAlignmentOnRefresh() { TradeColumnHeader header = new TradeColumnHeader(null); header.refresh(); } } }}} "Passing null?! You're passing null into the constructor! What the hell are you doing that for?!" said Ramon. "It's just a quick way to see if we really need to create Configuration." "Of course, we have to create a Configuration, Sally. That's what the class needs." "In production, yes. But we're just writing a test here." "What does it test, Sally?" "Just the fact that we can run refresh by itself. The next thing we are going to have to do is see what we can do to sense the bug." Sally changes the code: {{{ public class TradeColumnHeaderTest extends TestCase { public void testCreate() { TradeColumnHeader header = new TradeColumnHeader(null); header.refresh(); assertEquals(TradeColumn.VERTICAL, header.getAlignment()); } } }}} "See, now we have a real test." said Sally. Sally runs the test. "See, it fails." "Okay, so how are you going to fix it?", said Ramon. "It looks like we can change the bias factor like this, to 5.0 and it aligns.", said Sally as she typed in the code change. Ramon gets up and walks to the window. "So, does that make sense? Passing in a null like that?" "It's one of the techniques in the book, Ramon." "But it's wrong! If you pass null into methods all over the place you turn your system into a mess. We'll have null pointer exceptions all over the place!" Sally turns her chair to face Ramon. "Yes, if you pass null in production code. We are NOT passing null in production code, we are passing it in tests so that we don't have to create a Configuration." Just then the new guy, Aiden, walks into the room. "What's up?" Aiden sits down as Sally and Ramon battle it out, not even noticing him. "But you're going to need a Configuration eventually, aren't you?", said Ramon. "Most of the other methods on that class do use the configuration." Just then, Frank, the Product Manager walks in, followed by the rest of the team. "Yes, Ramon, but we can get to that when we need to. By passing null we were able to write an important test without confronting all of the dependencies all at once; you can make some progress. "I'm not convinced yet. You're not even really testing the class if it doesn't have a Configuration." said Ramon. Just then Frank spoke up. "Okay, team meeting. I doubt this is a surprise to you all, but Marco was let go this morning. As of today, Aiden is your new manager. I had him join your team as a programmer last week just to get to know you all while we were making the transition. If you have any questions, come by my office. Have a nice weekend." Frank walks out the door, leaving the team in an uneasy silence. ''Click [[here][RamonWalksHome]] to visit with Ramon as he walks home.'' ''1'' [[2][http://www.butunclebob.com/ArticleS.MichaelFeathers.RamonWalksHome]] [[3][http://www.butunclebob.com/ArticleS.MichaelFeathers.SallysExploration]] [[4][http://www.butunclebob.com/ArticleS.MichaelFeathers.MullingNull]] ---- Append comments below: * Nov 22, 2004, KelleyHarris: I want to hear the next part of the story! :-) Fun and instructive article. Could be a series for a mag... - * Nov 23, 2004, AnthonyMoralez: I find stories like this motivate me to practice programming. They are also enjoyable to read. * MichaelFeathers: thanks to all. I'm going to be dealing not only with programming issues but also team issues.. that is the reason for all of the set up that is going on now. * Nov 23, 2004, Uncle Bob: ''This is so cool.'' The fact that they could pass ''null'' into the constructor and still meaningfully call methods implies that they should create a default constructor to be used for test purposes. I wish Java had ''friends'' so that you could restrict visibility of the default constructor to just the test cases. Of course the default constructor could be put at package scope, so that the tests could access it. * MichaelFeathers: Yeah, I've refrained from doing that just because the constructor could lead people to believe that they have another valid way of producing an object. I can hear people say "Hey, all we have to do is make this constructor public. Nulls are sort of explicit. I've had some interesting remarks on my [[Artima blog][http://www.artima.com/weblogs/viewpost.jsp?thread=81895]] about whether using nulls in tests is deceptive. * Nov 25, 2004, Great stuff Michael, looking forward to the next installment. My favorite character is the cat. ;-) --DaveHoover * MichaelFeathers: Why? He's just a cat, a furry reptile. Oh, your name :-) Thanks, I'll try to work in an important plot point with him. * JeffreyFredrick: why do I have the impression this class is going to be refactored into two, one that needs the Configuration and one that doesn't? hmm... * JeffGonzalez: I think passing nulls in a test could be just as useful because it allows you to test for that possiibility also. What happens if there is a null in production. Don't unit tests usually include tests with bad data? At work we try to test APIs by sending bad data (e.g. a ctor accepts an int so we pass it a string just for kicks) * MichaelFeathers: Hmm.. Do you check for null arguments in ''all'' of your methods? I advise people against that unless all their methods are part of an external API. It leads to a lot of technical clutter that can be dealt at the team level, for the most part. If you have a team writing code and the rule is that you don't pass null all over the place, you shouldn't have to check for it. On the other hand, if people are going to call your code and you can't be reasonably sure that they won't do something that silly, it makes sense to check at only at those interfaces, to build an isolation layer so that you don't have to check lower in your code.
Hints:
Use alt+s (Windows) or control+s (Mac OS X) to save your changes. Or, tab from the text area to the "Save" button!
Grab the lower-right corner of the text area to increase its size (works with some browsers).