Adding tests to an existing code base is not always easy. So, where do you start? In the video, I showcase how to do it using a code kata called the gilded rose. It is complicated enough that you should be able to extrapolate it to the code you are working on.
Transcription of video
It is very difficult to find real-life examples that also serve as good practice examples. Instead, we can use a code Kata as a proxy for a legacy application to show some of the difficulties with working in that context. The gilded rose is created as an example of a legacy application with complex, intertwined logic.
Our task is to implement a new feature in this code. But first, a bit of context on the gilded rose. You can find it on Github on this URL. It’s implemented in a large number of languages. I’m going to use this she shopped in-cognition for this tutorial. So, what is the gilded rose? Imagine that we were in a fantasy type game and we’re running an inn. It is selling different items and the application implements the logic to govern how the qualities of the items go down over time according to its type and its expiration date. Both the expiration date and the quality is updated every day. The application already implements logic to support a number of features, like once the sell date has passed, quality drops twice (2X) as fast. The aged brie item actually increases in quality the older it gets. Backstage passes like aged brie increases in quality as its expiration date approaches.
If we run the program, it outputs a list of item and how they’re changing the quality as the days passed. Let’s see an example of it here. Our task is to implement a support for a new type of item called “Conjured Items”. They degrade in quality twice (2X) as fast as normal items but as we look into the project, we discovered that there are no tests implemented and looking at the logic, we see a 73 lines long method that implements the core logic. It’s not at all obvious how to implement this new feature. To make sure we don’t break anything, we start by creating a test harness. The purpose of this is to make sure that we don’t break any functionality without us noticing.
So, let’s get started with the gilded rose. The first file we’ll look into is the requirements file, which is part of the Git repository. It contains all the specifications of how the program works. If we scroll a bit down in the file, we can see that it contains a bullet list. It’s different requirements, many more than I mentioned earlier. We can also see the new requirement that we should implement to conjured item and how that’s supposed to work. If we start by looking into the program, we can see that it contains a number of files through primary files that we’ll be looking at is the program to see this file, which contains the main method and then we’re going to look at the Gillett Rose file, which contains the actual logic. The first thing that’s defined in the main method is the items that is available for sale and are in contains all the names, the selling date and the quality. After that, we instantiate the gilded rose class and after that, we can use it to loop through 31 days, which is our month and every day we will output the name, the quality and the expiration date of each item, and then we’ll update the quality based on the logic. If we open the gilded rose file, we’ll see that it’s, contains a single class called the “Gilded Rose”. It’s instantiated by the list of items and then we have one single method called “Update Equality”, which contains all the logic that’s described in the requirements file and as you can see, as we scroll through it, this…, lotus nesting, it’s quite complex.
There’s no really any indication of which part of the code that is responsible for which of the requirements and it’s going to be potentially going to be quite difficult to implement only feature in this method. So, let’s try to get a the Gilded Rosa tackle get it under the test harness and make sure we can implement this new feature we have been asked to do. So, the first thing we’ll do is to compare the requirement specification we have here with the actual code to serve as a guide for what we are supposed to test.
Lucky for us, we have a requirement specification that would not always be the case if we work with legacy software. So, let’s see what we got. We have a requirement saying that once a Sell date has passed, quality degrade twice (2X) as fast. We already know that the quality is supposed to go down by one every day. So, if degrade twice (2X) as fast and we would expect the quality to degrade but 2 every day of the surveys. So, let’s ride a test for that.
So, we already have a test file which contains a quite useless test called “Foo”. So, we will use that. I usually use a convention test that is a signal to tell what I’m testing. So, test that quality degrades twice as fast when Spidey has passed. So, if we had selected to had some foo, we know that the selling date is 0 and the quality is 0. If we set the quality to 10 and we opt at the policy, we would expect the policy to be 8. So, if we run that test, we would expect to have a passing test, which we do. That’s a good thing. That’s the first of. No, we’ll copy this just to make sure that we also test the fact that quality degrades by 1 on & on, on circumstances. If we set selling date to 5, we would expect the closure of the decrease to 29. So, if we run that test, rathmann will pass. We’ll look further into the specification. See here that the quality of an item is never negative. Test for that as well. He says, quality, it does not go to negative when. When it’s 0. If we would set the quality to 0, we would expect the quality to keeping 0 and as we see, all free tests still passes.
So, let’s continue with some more tests. We also have a requirement stating that the aged brie increases in quality, the older it gets. So, if we go picture of the coat, we can create yet another test that old brie increase in quality as its ages, so going to copy the set-up from your the test and have the old brie. Now we’re going to have a quality of 10, sell data 5. So, when we update, we would expect the quality to go to 6 on the stretcher and the test. And we see that the test failed. There are actually multiple things going wrong here. First of all, the quality is 10. So, it should not be 6. It should be 11. The other thing is that I called it an old brie, and that’s actually not the name, because it’s supposed to be called aged Brie. So, let’s just fix that and see if that improves this test. Yeah. So, I have one more test pass. Those will have a requirement stating that quality of an item is never more than 50. Let’s try to read statistics for that as well. And again, with copying the set up to if we set up the quality to 50, we would expect because it’s in its brie that it will increase our requirement states that it should be no more than 50.
Let’s see if that’s true. Yeah, we have a successful test here as well. Now, we will continue to implement the rest of these tests to make sure that we have. Everything covered, but just to make sure that that we know where we are, we can use a cover to get a co coverage of the test with implemented until now to see if we actually cover all the other code inside that the gilded rose. So, the way it works is that we had this part in the left side, all he lines which I have a green mark is hit by a unit test and if some of them which agree, then not hit. Let’s see if we have any Gray lines left. It seems we have something here. So now we need to craft a test that will actually exercise this piece of code and this is actually a more Real-Life example than having the specification, because in many cases we don’t have specifications. So, we would need to read the code and try to understand what it does. So, in essence, it’s actually quite more realistic example. So, to have a test that hits that piece of code, we can see that the selling date. It must be less than 0. We can also see that it should probably be a backstage pass and then this piece of code would be exercised. I’m not quite sure what it would actually do. So, I’ll start by just creating a test and call it foo and then I’ll rename it after kind of rison it around what the code actually does to copy our set up again. So, here’s a backstage pass our selling date. So, this one’s one of quality of 10 and then it is just, we don’t know what to expect. I’ll just write 100. Definitely the quality will not go to 100 when we exercise this code. So, the test fails and let’s just see if we run this test with the coverage, then we should be able to see if we actually hit the expected part of the code. So, if we go here, I can see that the test failed and it covers this pop.
What this code actually does in a very convoluted manner is that it takes the quality of the item minus the quality of the item which will always, always return 0. So that actually matches with the requirement that quality dropped to 0 after the concert. If we can to a coverage, pass you can. This is something that usually happens when you create craft tests for these specific examples. You need to continue creating tests until you believe that you have sufficient coverage to feel confident that changes will not, well, explode or create other issues when they’re introduced. We still have this part of the code. So, this little statement should be matched by. Let’s see. So, if it if the Sell date indeed is negative and if it’s not an aged brie and the quality is less than 50 quality or if it is aged brie, the quality should increase by 1. Lets see if we already had a test for something like that.
So, if you look at this one. Oh, that’s part is positive selling date. So, let’s just copy this test. So, if we said this to zero, we would expect it to increase twice (2X) as fast. Let’s see if that works what is expected and that’s just from the coverage, again, to make sure that we have all our parts covered. It seems like we have green everywhere. This is a method that here contain many different paths. So, to actually create units test that covers every combination of path through all of this method is, I would say, a very daunting task. So, we’re not going to do that. You just have to continue adding tests until you find that you’re confident enough to introduce this and this new feature.
Now we’re kind of ready to start implementing our new feature. So, the requirements says that we have recently signed a supplier of contract items. This requires an update to our system, conjured items degrade in quality twice (2X) as fast as normal items. So, with that specification in mind, we would need to start with implementing a test for that. So, we’re going to put in one more test that conjured items degrade in quality twice as fast and then we’re going to KBR set up sort of two type so much. Conjured. He had a sell date in 10 days. Quality of 10 and we expect the quality to drop to 8, let’s try to run this test. Of course, we haven’t implemented anything for conjured items that we would expect it to fail as it does here. So, it’s actually 9, which is to expected for a normal item but we would like to be 8 because we have this new requirement. So now we are ready to try to implement this until all of this code. So first of all, we need to find an insertion point to actually put in this new feature.
So, let’s see, a conjured item is definitely not an aged brie and it’s not a backstage pass. We expect the quality to be more than 0 and it’s not. So first hand of Ragnaros. So, we’ll put it here. So, I had since name contains conjured. If it does that, we want to decrease the quality by 1 but .. So lets try to run our test again. So now we have success. So usually what happens here is that now that we have a test harness covering all of the all of the code, we can use that as a safety net to actually refect to this because when we look down on this code, doesn’t really feel like very nice. Well, it’s that easy to put in stuff in the 100 percent sure that it actually works. So, I’m not right now 100 percent sure that this will actually always work but we can use it to actually re-facture but we look at that in it’s a not some other time.