UseAbility
Light Reading
There are some books which inspire me to write code. Reading them makes me want to run to the nearest keyboard and try out some ideas. The first book that had this affect on me was Code Complete by Steve McConnell. The second book, which I'm reading now, is Refactoring by Martin Fowler.
One of the things that I read recently is that the automated tests for refactoring focus on Unit tests, not Functional tests. The test that I've written so far are, I think Unit tests, but I haven't focused on the distinction between the two. Perhaps that one of the reasons I've been struggling with things like CreateBasicCharacter - I may be trying to implement a Functional test instead of a Unit test.
Not to imply that I shouldn't have any Functional tests, but perhaps the acknowledging the difference will help me approach those tests more clearly.
Checked email. Adjusted keyboard height. Symphony Fantastique (Berlioz) is playing. I'm ready to code ... something.
Abilities
I'd like to implement Character abilities. Each ability should have a name, a way of determining if the attempted action was successful, and a set of successful/unsuccessful effects. We'll leave the last part for later.
A simple example ability to start with is "Read scroll." This will require a successful check against Intelligence, say the character must have an Intelligence attribute of at least 11. My first test will be to generate a character at random, and have the character attempt this action. Based on the character's intelligence, the test should be able to predict (and check for) the result.
Premature Generalization?
Easiest thing to do would be to call objCharacter.ReadBoook(). Well, calling objCharacter.UseAbility("ReadBook") is pretty close. This function should return success or failure. Of course, just like the chrematistics, they will have a strName as well as a lngID.
I think that the function should return an output parameter for success or failure, and return a status code for "did you encounter any errors along the way". This is a poor man's try/catch in a language that doesn't support that syntax.
Stats
Incidentally, the server statistics show a massive amount of activity as I debug (mostly because I'm going back to test so often). This is slighly annoying (as it obscures the other data I'm trying to gather like real visits), but it's also a good sign.
Test First
I did it. I remember to write a failing test. Of course, the failure is because I haven't implemented UseAbility yet. Actually, I'll also need "AddAbility". My first test, though should have the character fail because the ability wasn't found.
I'm copying/pasting some code for the array handling. No smell there, though.
Exciting Failure!
Hey! For the first time, a test other than the one I was working on failed. I got an unexpected failure that, without the help of these tests, would have usually been an undetected bug.
I'm sold on test-driven development.
In fact, this bug shows that I missed a test - I should have tested a character without any statistics. Now, as it happens, that's not a very likely case. But a chacter without any abilities (e.g. just starting out) is much more likely. I can fix the code for both to work smoothly.
Now, back to implementing the "UseAbility" function. For which, first, I'll need a FindAbility function.
Copying/Pasting again. Now it's beginning to get a little whiffy. I think that the solution may be to extract a subclass of Abilities, and delegate the addition/finding/testing to that class. The main warning sign is that ABILITY_NOT_FOUND doesn't feel right as part of the Character class. But it's not necessary just yet. Or, I'm just postponing the inevitable.
In fact, I may want to generalize the add/find functions of the collection. Or, possibly, use a Scripting.Dictionary or similar object. Not yet, though.
Test Order
I'll also need a test to ensure that two abilities with the same name can't be added. Actually, that test would pass now (even before the UseAbility one I was trying to implement). I'll back up and add the test now.
Ability as object
Well, that didn't take long. I need to create an Ability object (just like a statistic object) in order to include the relevant Statistic that needs to be tested. So, I'll be undoing some of the changes I made to the code I copied and pasted to support objects (instead of strings).
While I'm at it, I suppose I really do want to have a CanonicalAbilities collection, too. Perhaps these Canonical objects will no longer be necessary, once I hook up to a database.
File level copy/paste, find/replace, and I've got a CanonicalAbilities object. Quick test - everything looks okay.
First thing, though, is to tie this new ability class to the Character class (which is still using "disconnected" strings instead of objects).
New music - Phish - nah, Clapton.
Ability attributes
Ok, so each ability has a name (I feel like I'm not writing enough tests), but it also needs a StatisticID to go along with it. It does seem a little odd that a Statistic wouldn't know its own StatisticID. Hmm... I may want to do database stuff next to see if that clears up some confusion.
Set up/Tear Down
This is no good. I've changed one of my tests to use a global variable, moved that test to the top of the line with comment "set-up/test", and added a "tear down" comment where I free up that global object. Phew. I'll add creating real methods to my list of things to do.
I need a test to prove that I can't add the same statistic twice to the same character.
Ok, while doing that test, I found a more basic problem - I can't find a statistic once I've added it. I create a more simple test to isolate just this problem. Now that this test is failing, I can investigate.
Found it
I found the problem. VBScript Classes don't support (amoung other things) class members that are also constants. In this case the STATISTIC_NOT_FOUND was using the default 0 (which is also the position of the first object in the array) instead of -1.
I get around this by exposing a public member, and setting the value once in the initialize statement. I should make the member private and then just expose an accessor, but I like the intellisense that the first approach provides. I can always clean it up later...
As part of this debugging, I had to disable other tests. I should incorporate that into the program and expose it in the UI (something like a checkbox for each test or something). This would go nicely with putting all my separate test calls into a loop.
Finally ...
I'm ready to implement the UseAbility Function.
Yuck. That was much more difficult than it should have been. I could, if I didn't want to check for any errors, simply code it in 3 lines:
UseAbility(strAbilityName)
Set objAbility = m_Abilities(FindAbilityByName(strAbilityName))
Set objStatistic = m_Statistics( FindStatisticByName( FindStatisticByIndex( objAbility.StatisticID ) ) )
blnSuccess = (objAbility.SuccessThreshold <= objStatistic.Value)
But I'm not comfortable with that. I want to make sure that each intermediate function returns a valid answer. Perhaps I'm too defensive. Well, let's see if it works.
Yup, I ended up adding some tests for intermediate functions, which end up producing different return codes for failure:
Const RETURN_CODE_ABILITY_NOT_FOUND = -3
Const RETURN_CODE_ABILITY_STATISTIC_NOT_FOUND = -2
Const RETURN_CODE_ABILITY_STATISTIC_NAME_NOT_FOUND = -1
I've also added 4 tests:
"testCharacterWithoutStatsCanNotReadBook"
"testCharacterWithoutAbilitiesCanNotReadBook"
"testCharacterWithLowIntCanNotReadBook"
"testCharacterWithHighIntCanReadBook"
Well, that took longer than expected, but I'm pretty happy with the results. I've got a long list of things to start on next time, to, including:
Create setup/teardown functions
SaveTo/LoadFrom database
See you next time.
There are some books which inspire me to write code. Reading them makes me want to run to the nearest keyboard and try out some ideas. The first book that had this affect on me was Code Complete by Steve McConnell. The second book, which I'm reading now, is Refactoring by Martin Fowler.
One of the things that I read recently is that the automated tests for refactoring focus on Unit tests, not Functional tests. The test that I've written so far are, I think Unit tests, but I haven't focused on the distinction between the two. Perhaps that one of the reasons I've been struggling with things like CreateBasicCharacter - I may be trying to implement a Functional test instead of a Unit test.
Not to imply that I shouldn't have any Functional tests, but perhaps the acknowledging the difference will help me approach those tests more clearly.
Checked email. Adjusted keyboard height. Symphony Fantastique (Berlioz) is playing. I'm ready to code ... something.
Abilities
I'd like to implement Character abilities. Each ability should have a name, a way of determining if the attempted action was successful, and a set of successful/unsuccessful effects. We'll leave the last part for later.
A simple example ability to start with is "Read scroll." This will require a successful check against Intelligence, say the character must have an Intelligence attribute of at least 11. My first test will be to generate a character at random, and have the character attempt this action. Based on the character's intelligence, the test should be able to predict (and check for) the result.
Premature Generalization?
Easiest thing to do would be to call objCharacter.ReadBoook(). Well, calling objCharacter.UseAbility("ReadBook") is pretty close. This function should return success or failure. Of course, just like the chrematistics, they will have a strName as well as a lngID.
I think that the function should return an output parameter for success or failure, and return a status code for "did you encounter any errors along the way". This is a poor man's try/catch in a language that doesn't support that syntax.
Stats
Incidentally, the server statistics show a massive amount of activity as I debug (mostly because I'm going back to test so often). This is slighly annoying (as it obscures the other data I'm trying to gather like real visits), but it's also a good sign.
Test First
I did it. I remember to write a failing test. Of course, the failure is because I haven't implemented UseAbility yet. Actually, I'll also need "AddAbility". My first test, though should have the character fail because the ability wasn't found.
I'm copying/pasting some code for the array handling. No smell there, though.
Exciting Failure!
Hey! For the first time, a test other than the one I was working on failed. I got an unexpected failure that, without the help of these tests, would have usually been an undetected bug.
I'm sold on test-driven development.
In fact, this bug shows that I missed a test - I should have tested a character without any statistics. Now, as it happens, that's not a very likely case. But a chacter without any abilities (e.g. just starting out) is much more likely. I can fix the code for both to work smoothly.
Now, back to implementing the "UseAbility" function. For which, first, I'll need a FindAbility function.
Copying/Pasting again. Now it's beginning to get a little whiffy. I think that the solution may be to extract a subclass of Abilities, and delegate the addition/finding/testing to that class. The main warning sign is that ABILITY_NOT_FOUND doesn't feel right as part of the Character class. But it's not necessary just yet. Or, I'm just postponing the inevitable.
In fact, I may want to generalize the add/find functions of the collection. Or, possibly, use a Scripting.Dictionary or similar object. Not yet, though.
Test Order
I'll also need a test to ensure that two abilities with the same name can't be added. Actually, that test would pass now (even before the UseAbility one I was trying to implement). I'll back up and add the test now.
Ability as object
Well, that didn't take long. I need to create an Ability object (just like a statistic object) in order to include the relevant Statistic that needs to be tested. So, I'll be undoing some of the changes I made to the code I copied and pasted to support objects (instead of strings).
While I'm at it, I suppose I really do want to have a CanonicalAbilities collection, too. Perhaps these Canonical objects will no longer be necessary, once I hook up to a database.
File level copy/paste, find/replace, and I've got a CanonicalAbilities object. Quick test - everything looks okay.
First thing, though, is to tie this new ability class to the Character class (which is still using "disconnected" strings instead of objects).
New music - Phish - nah, Clapton.
Ability attributes
Ok, so each ability has a name (I feel like I'm not writing enough tests), but it also needs a StatisticID to go along with it. It does seem a little odd that a Statistic wouldn't know its own StatisticID. Hmm... I may want to do database stuff next to see if that clears up some confusion.
Set up/Tear Down
This is no good. I've changed one of my tests to use a global variable, moved that test to the top of the line with comment "set-up/test", and added a "tear down" comment where I free up that global object. Phew. I'll add creating real methods to my list of things to do.
I need a test to prove that I can't add the same statistic twice to the same character.
Ok, while doing that test, I found a more basic problem - I can't find a statistic once I've added it. I create a more simple test to isolate just this problem. Now that this test is failing, I can investigate.
Found it
I found the problem. VBScript Classes don't support (amoung other things) class members that are also constants. In this case the STATISTIC_NOT_FOUND was using the default 0 (which is also the position of the first object in the array) instead of -1.
I get around this by exposing a public member, and setting the value once in the initialize statement. I should make the member private and then just expose an accessor, but I like the intellisense that the first approach provides. I can always clean it up later...
As part of this debugging, I had to disable other tests. I should incorporate that into the program and expose it in the UI (something like a checkbox for each test or something). This would go nicely with putting all my separate test calls into a loop.
Finally ...
I'm ready to implement the UseAbility Function.
Yuck. That was much more difficult than it should have been. I could, if I didn't want to check for any errors, simply code it in 3 lines:
UseAbility(strAbilityName)
Set objAbility = m_Abilities(FindAbilityByName(strAbilityName))
Set objStatistic = m_Statistics( FindStatisticByName( FindStatisticByIndex( objAbility.StatisticID ) ) )
blnSuccess = (objAbility.SuccessThreshold <= objStatistic.Value)
But I'm not comfortable with that. I want to make sure that each intermediate function returns a valid answer. Perhaps I'm too defensive. Well, let's see if it works.
Yup, I ended up adding some tests for intermediate functions, which end up producing different return codes for failure:
Const RETURN_CODE_ABILITY_NOT_FOUND = -3
Const RETURN_CODE_ABILITY_STATISTIC_NOT_FOUND = -2
Const RETURN_CODE_ABILITY_STATISTIC_NAME_NOT_FOUND = -1
I've also added 4 tests:
"testCharacterWithoutStatsCanNotReadBook"
"testCharacterWithoutAbilitiesCanNotReadBook"
"testCharacterWithLowIntCanNotReadBook"
"testCharacterWithHighIntCanReadBook"
Well, that took longer than expected, but I'm pretty happy with the results. I've got a long list of things to start on next time, to, including:
Create setup/teardown functions
SaveTo/LoadFrom database
See you next time.
0 Comments:
Post a Comment
<< Home