How to Re-factor or Change Behavior using TDD
We often hear about how TDD (Test Driven Development) is making it
easier to change code and ensure that it is still behaving as it should
be and that it is not affecting behavior in other parts of the code
base. And I am completely in agreement with this, it is making changes
into a no-fear-issue.
Without TDD making a change is often scary, developers are afraid of what else might be affected by their change. And more importantly when will they discover this? During the testing procedures or perhaps when it is already in production?
When using TDD you are ensuring yourself that all your code is properly tested. Of course a even bigger benefit is that you are actually need to think upfront about the design and the created code is more loosely coupled, but that is for an other post. Proper code coverage ensures that when you make a change in the code you will be notified the first possible moment what the impact of that change is. So there is much less fear that something you do will break something unexpected. You will still break stuff, but now you know that you broke it!
But one of the things I don’t hear so much about (perhaps I don’t listen well enough) is how to do these re-factorings or how to change functionality in combination with TDD.
Now lets first talk about the difference between re-factoring and changing behavior, because this is often a little bit misunderstood. Re-factoring code means that you will optimize (let’s assume that that is what you want) existing code without changing the purpose of that piece of code. For example changing a search algorithm from a list search to a informed search to enhance performance. Both implementations should return the same result. Now when you are changing behavior you will actually change what the purpose of the code is if we take the same example as above but now we want the search algorithm to be more precise so maybe instead of using an informed search you want to use fuzzy logic, then you are not re-factoring anymore.
Now what I really wanted to talk about is how to make changes to your code base, it doesn’t really matter whether this is a re-factoring or a change in behavior, both are just changes to the code base. I just grabbed the opportunity to put a light on the difference between the two.
As I see it there are two possible ways of doing these type of changes to the code base while still keeping the benefit of TDD. You could of course make the changes in the code and adjust the tests accordingly, but then you would not be test driving your development are you? So first logical option is to figure out what needs to be changed, change the existing tests accordingly. This means that you’ll end up with some failing tests, after that you make the changes in the code until the tests pass again, and you are done. The downside with this approach is that it is really simple to miss tests to change or even worse not test certain parts of your changes.
So the second option is to start writing new tests for the new behavior that you want, completely ignoring the existing tests that are already there. Doing so you will ensure that all the new behavior is properly tested, as with any new behavior. Just because you are changing behavior/code in an existing code base does not mean you cannot write a test first. Some tests will pass straight away others needs to be implemented first. But everything is confirmed by a test. This will also mean that some of the existing tests will start failing, some won’t even compile anymore this is ok, just let them fail (and comment out the code in the ones that don’t compile and add an Assert.Fail()). Because this gives you a really good overview of what you have actually changed.
After you have completely implemented the new story you start thinking about the failing tests, just remove the ones that make no sense anymore. Now some of the ‘old’ code will still work as specified by the tests, but is not used at all anymore. So what to do with that? I say that you should remove it from the code base and remove the tests for that as well. This will only complicate things further down the line.
Well it may be clear from the text already, but I want to suggest to follow the second option, I believe this will result in better thought of code and ensures that you are actually changing what you intended to change.
I would really love to get your feedback on this.
Without TDD making a change is often scary, developers are afraid of what else might be affected by their change. And more importantly when will they discover this? During the testing procedures or perhaps when it is already in production?
When using TDD you are ensuring yourself that all your code is properly tested. Of course a even bigger benefit is that you are actually need to think upfront about the design and the created code is more loosely coupled, but that is for an other post. Proper code coverage ensures that when you make a change in the code you will be notified the first possible moment what the impact of that change is. So there is much less fear that something you do will break something unexpected. You will still break stuff, but now you know that you broke it!
But one of the things I don’t hear so much about (perhaps I don’t listen well enough) is how to do these re-factorings or how to change functionality in combination with TDD.
Now lets first talk about the difference between re-factoring and changing behavior, because this is often a little bit misunderstood. Re-factoring code means that you will optimize (let’s assume that that is what you want) existing code without changing the purpose of that piece of code. For example changing a search algorithm from a list search to a informed search to enhance performance. Both implementations should return the same result. Now when you are changing behavior you will actually change what the purpose of the code is if we take the same example as above but now we want the search algorithm to be more precise so maybe instead of using an informed search you want to use fuzzy logic, then you are not re-factoring anymore.
Now what I really wanted to talk about is how to make changes to your code base, it doesn’t really matter whether this is a re-factoring or a change in behavior, both are just changes to the code base. I just grabbed the opportunity to put a light on the difference between the two.
As I see it there are two possible ways of doing these type of changes to the code base while still keeping the benefit of TDD. You could of course make the changes in the code and adjust the tests accordingly, but then you would not be test driving your development are you? So first logical option is to figure out what needs to be changed, change the existing tests accordingly. This means that you’ll end up with some failing tests, after that you make the changes in the code until the tests pass again, and you are done. The downside with this approach is that it is really simple to miss tests to change or even worse not test certain parts of your changes.
So the second option is to start writing new tests for the new behavior that you want, completely ignoring the existing tests that are already there. Doing so you will ensure that all the new behavior is properly tested, as with any new behavior. Just because you are changing behavior/code in an existing code base does not mean you cannot write a test first. Some tests will pass straight away others needs to be implemented first. But everything is confirmed by a test. This will also mean that some of the existing tests will start failing, some won’t even compile anymore this is ok, just let them fail (and comment out the code in the ones that don’t compile and add an Assert.Fail()). Because this gives you a really good overview of what you have actually changed.
After you have completely implemented the new story you start thinking about the failing tests, just remove the ones that make no sense anymore. Now some of the ‘old’ code will still work as specified by the tests, but is not used at all anymore. So what to do with that? I say that you should remove it from the code base and remove the tests for that as well. This will only complicate things further down the line.
Well it may be clear from the text already, but I want to suggest to follow the second option, I believe this will result in better thought of code and ensures that you are actually changing what you intended to change.
I would really love to get your feedback on this.
Recent blog posts
- Follow me @ Elegant Code
- CQRS à la Greg Young
- CQRS à la Greg Young example code
- My Kindle DX
- My book: Are You Better Than Yesterday?
- Running with Scissors
- Applying Domain-Driven Design and Patterns
- Hey Developer, YAGNI I tell you
- Hey Developer, the product you create is your code
- NDC videos are published







No comments yet!