Meet the Simpsons
Recap
Last time, we ended with:
"Any object that needs to know about statistics will include a local objCanonicalStatistics, which will be loaded when the object is created. It can then use that object to resolve references by name or by ID. As part of this, the objCanonicalStatistics will no longer expose a public AddStatistic method. Instead, it will load all the statistics it knows about when it's constructed. "
Steps
There are a bunch of steps in this process:
1. CanonicalStatistics object changes (make AddStatistic method private, load on start-up)
2. Character object changes (give it its own CanonicalStatistics object)
3. SetupTest changes (CreateCanonicalStatistics no longer necessary. Just New Class)
4. Test and clean up
I'm worried that these are a lot changes to make before any tests will pass, and I'm worried about the vagueness of step 4. I also feel like making changes that are not easily reverted or rolled back. There's probably a safer way to do this, but I'm going to proceed recklessly. Wish me luck.
1. CanonicalStatistics object changes (make AddStatistic method private, load on start-up)
Ok, changes made, and (from habit), I run my tests. First failure (clearly) is a call to the now private AddStatistic method. I have three choices:
(a) make the function public again (to avoid runtime errors)
(b) set the tests to treat runtime errors as failing tests.
(c) use the errors to local and eliminate the errors.
I could use a combination of (b) and (c), but I'll just do (c), as it gives me line numbers (: I will admit temptation for doing (a), but, to my credit, that temptation dissipated quickly.
Actually, ConfirmCanonicalStatisticUnicity test is no longer required. I'll nuke it. Interesting - it was the only one to use the AddStatistic method. Cool.
3. SetupTest changes (CreateCanonicalStatistics no longer necessary. Just New Class)
Did these as part of 1, to allow me to test. I will, however, remove the unnecessary function.
2. Character object changes (give it its own CanonicalStatistics object)
Phew! As it turns out, my character object already had a function named FindStatisticByName, so trying to make it call a global function of the same name would have been a disaster.
4. Test and clean up
My gosh! Other than a single copy/paste error, all my tests are passing now! A change that I approach with (justified) apprehension seems to be working.
Consider what would happen in a world without tests: I'd make the changes, but be convinced that there was a bug lurking somewhere. Now I know that, even if there is a bug left in the code, it doesn't affect anything I care about (e.g. anything that I've written a test for). Instead of worrying, or re-reading the code, or even proceeding with unfounded confidence, I can simply resume coding - knowing that my tests all pass.
Mission Accomplished
Indeed, I could even stop here and pick up again on another day (: I'm going to review my todo list and see what appeals to me. At least my code no longer smells of Global variables and test dependencies. Although, to close the loop, I should add a tear-down function.
Done. This reminds me, though, that I also need to add Deconstructors to my classes to free up arrays that are created. I've added that to my todo list, but near the bottom.
Additional parameters
Note that I've just fixed some of the tests that were named CreateFoo, but were really CreateAndTestFoo. I've done this by separating the two functions, adding Foo to the parameter list, and passing it to the testing function. I should do this for each of my basic test Characters. So far, I have:
Character without Statistics or Abilities
Character with Statistics, but no Abilities
Character with Statistics and Abilities, but low intelligence
Character with Statistics and Abilities, and high intelligence
The first two, honestly, aren't interesting. They are really edge cases, and I don't think those tests add a lot of value, so I'm going to delete them.
Actually, not having an ability but trying to use it might be a useful case. I'll leave that one for now.
So, what I'd like to do is create two characters to use as parameters. One will be minimal (low statistics in everything), and the other will have maximal statistics. I need names for these characters - how about Homer (min) and Lisa (max)? Perfect.
I'll add these characters to the setup function and then make the test functions use them (instead of creating their own characters).
Creating Homer
Now that I have this CanonicalStatistics object, I should use it to loop through all statistics to create (and test) these characters. I'll have it expose its AllStatistics array (for read only).
Function Names
One nice thing about this is that I can replace testCharacterWithLowIntCanNotReadBook with the more readable testHomerCanNotReadBook.
Simpsons
Ok, both characters are in place, and are being used by the test functions. All tests pass, the code is much less stinky now, and I think this is a good place to stop for today.
Last time, we ended with:
"Any object that needs to know about statistics will include a local objCanonicalStatistics, which will be loaded when the object is created. It can then use that object to resolve references by name or by ID. As part of this, the objCanonicalStatistics will no longer expose a public AddStatistic method. Instead, it will load all the statistics it knows about when it's constructed. "
Steps
There are a bunch of steps in this process:
1. CanonicalStatistics object changes (make AddStatistic method private, load on start-up)
2. Character object changes (give it its own CanonicalStatistics object)
3. SetupTest changes (CreateCanonicalStatistics no longer necessary. Just New Class)
4. Test and clean up
I'm worried that these are a lot changes to make before any tests will pass, and I'm worried about the vagueness of step 4. I also feel like making changes that are not easily reverted or rolled back. There's probably a safer way to do this, but I'm going to proceed recklessly. Wish me luck.
1. CanonicalStatistics object changes (make AddStatistic method private, load on start-up)
Ok, changes made, and (from habit), I run my tests. First failure (clearly) is a call to the now private AddStatistic method. I have three choices:
(a) make the function public again (to avoid runtime errors)
(b) set the tests to treat runtime errors as failing tests.
(c) use the errors to local and eliminate the errors.
I could use a combination of (b) and (c), but I'll just do (c), as it gives me line numbers (: I will admit temptation for doing (a), but, to my credit, that temptation dissipated quickly.
Actually, ConfirmCanonicalStatisticUnicity test is no longer required. I'll nuke it. Interesting - it was the only one to use the AddStatistic method. Cool.
3. SetupTest changes (CreateCanonicalStatistics no longer necessary. Just New Class)
Did these as part of 1, to allow me to test. I will, however, remove the unnecessary function.
2. Character object changes (give it its own CanonicalStatistics object)
Phew! As it turns out, my character object already had a function named FindStatisticByName, so trying to make it call a global function of the same name would have been a disaster.
4. Test and clean up
My gosh! Other than a single copy/paste error, all my tests are passing now! A change that I approach with (justified) apprehension seems to be working.
Consider what would happen in a world without tests: I'd make the changes, but be convinced that there was a bug lurking somewhere. Now I know that, even if there is a bug left in the code, it doesn't affect anything I care about (e.g. anything that I've written a test for). Instead of worrying, or re-reading the code, or even proceeding with unfounded confidence, I can simply resume coding - knowing that my tests all pass.
Mission Accomplished
Indeed, I could even stop here and pick up again on another day (: I'm going to review my todo list and see what appeals to me. At least my code no longer smells of Global variables and test dependencies. Although, to close the loop, I should add a tear-down function.
Done. This reminds me, though, that I also need to add Deconstructors to my classes to free up arrays that are created. I've added that to my todo list, but near the bottom.
Additional parameters
Note that I've just fixed some of the tests that were named CreateFoo, but were really CreateAndTestFoo. I've done this by separating the two functions, adding Foo to the parameter list, and passing it to the testing function. I should do this for each of my basic test Characters. So far, I have:
Character without Statistics or Abilities
Character with Statistics, but no Abilities
Character with Statistics and Abilities, but low intelligence
Character with Statistics and Abilities, and high intelligence
The first two, honestly, aren't interesting. They are really edge cases, and I don't think those tests add a lot of value, so I'm going to delete them.
Actually, not having an ability but trying to use it might be a useful case. I'll leave that one for now.
So, what I'd like to do is create two characters to use as parameters. One will be minimal (low statistics in everything), and the other will have maximal statistics. I need names for these characters - how about Homer (min) and Lisa (max)? Perfect.
I'll add these characters to the setup function and then make the test functions use them (instead of creating their own characters).
Creating Homer
Now that I have this CanonicalStatistics object, I should use it to loop through all statistics to create (and test) these characters. I'll have it expose its AllStatistics array (for read only).
Function Names
One nice thing about this is that I can replace testCharacterWithLowIntCanNotReadBook with the more readable testHomerCanNotReadBook.
Simpsons
Ok, both characters are in place, and are being used by the test functions. All tests pass, the code is much less stinky now, and I think this is a good place to stop for today.