Skip to main content
GameDev.net gamedev.net
🔒 Locked

Test Driven Development - Logging module

Started by rick_appleton Mar 10, 2008 at 4:49 AM 8 replies 1.7k views
Original Post
rick_appleton
rick_appleton
I'm interested in learning more about Test Driven Development, and have read a bit about it. It seems like something that would fit my style of programming quite nicely. However, I have no clue on were to even start applying it to my own work. So I figured I'd pop up my user case here, and hope somebody could suggest some starting points, or some initial tests to create. I need to update my logging class. It still works okay'ish, but as I'm moving to different platforms, it won't always be able to work the way it currently is. So these are the requirements I have for the logging: - easy to use (naturally) - able to remove it entirely from a build - able to group different outputs together - able to specify an optional importance level (note this is perpendicular from the different output groups) - output to file, to console, or to a place in memory (related to the different platforms) - able to add formatting before the text is actually output (ex. adding html colours)
Rydinare
Rydinare
Quote:
Original post by rick_appleton
I'm interested in learning more about Test Driven Development, and have read a bit about it. It seems like something that would fit my style of programming quite nicely. However, I have no clue on were to even start applying it to my own work. So I figured I'd pop up my user case here, and hope somebody could suggest some starting points, or some initial tests to create.

I need to update my logging class. It still works okay'ish, but as I'm moving to different platforms, it won't always be able to work the way it currently is. So these are the requirements I have for the logging:

- easy to use (naturally)
- able to remove it entirely from a build
- able to group different outputs together
- able to specify an optional importance level (note this is perpendicular from the different output groups)
- output to file, to console, or to a place in memory (related to the different platforms)
- able to add formatting before the text is actually output (ex. adding html colours)


If you're using a TDD methodology, ideally, the first thing you'd start out with is the test. If you were starting out with a test, going by the steps you've listed, you would make a simple test that does each of the above, then add the "minimum" amount of code to satisfy that condition and move onto the step, each time making your test more and more robust.

So your process might look like this:
- Make basic test, it might just construct the logger and make a few log calls, then check the results to make sure they were as expected (Initially test will fail).
- Add logger class with basic logging functionality to satisfy test.
- Add test to show that you can remove it from build (if possible).
- Add code to logger class (if necessary) to prove it can be removed from the build.
- Add test to verify you can group outputs together (initially, will fail).
- Add code to make test pass.
- etc...

If you use this process, by the time you're done, you'll have a very verbose test that you can continually run as you develop to make sure that the logger class doesn't regress in any way.

You can still use a similar process if you have existing code, but the one difference I find is that you have to be careful to write a verbose enough starting test, because it's easy to miss things or skimp out since you "know" it works today.
CipherCraft
CipherCraft
In short:
* Write a failing test and make it succeed. [wink]

A little longer:
* Write a test (that may not compile)
* Make the test compile
* Run the entire test suite and watch it fail(!)
* Add minimal code to make the test succeed. This may include only returning the value you expect in your test...

Rinse and repeat.

When you find that you've had to return a test expected value, add another test that forces you to add more logic behind it. Do this, until the general solution is formed. You'll now have simple and general test cases alike.

Need to change behaviour of your class? Change the current tests first, or write a failing test, and then make them succeed. All other behaviour is guaranteed to be working still, since you're testing it all.

Choosing where to start is done by asking the question: which test can I write quickly or fail easily? And then work from there.

hth,
CipherCraft
dave
dave
I'm sure he knows how TDD works, hell the principle is trivial. The real difficulty is how you actually test. In my limited experience, testing input and output such as this is non-trivial.

Rick, how far have you got? Have you got a framework set up with your project?
Rydinare
Rydinare
Quote:
Original post by Dave
I'm sure he knows how TDD works, hell the principle is trivial. The real difficulty is how you actually test. In my limited experience, testing input and output such as this is non-trivial.

Rick, how far have you got? Have you got a framework set up with your project?


I'm written a test for similar things before. One trick I used was having the logger configurable as to where it writes. As such I could tell it to write to a std::ostringstream during the test and then test the contents of the stream to make sure it did what I expected. That seemed to work reasonably well.
rick_appleton
rick_appleton
Thank you for the replies. I do indeed understand how TDD works, but am looking for info on how specifically to apply it to this case.

I have an existing framework for my codebase yes, but there is currently nothing setup for the testing yet.
dave
dave
Rick,

For this specific scenario i fail to see any other approach than to simply log in all possible combinations, read the data back and compare. The issues i have with this scenario is that the code to test it is going to be large enough that it could be problematic in itself.
rick_appleton
rick_appleton
Quote:
Original post by Dave
Rick,

For this specific scenario i fail to see any other approach than to simply log in all possible combinations, read the data back and compare. The issues i have with this scenario is that the code to test it is going to be large enough that it could be problematic in itself.


Hmm, that's fair enough I guess. Maybe you could suggest some other fairly low-level module that might be a more suitable introduction to TDD? (I've just started porting code to a new platform, hence I don't have much low-level stuff at all yet on that platform). This is for a fairly common 2D with 3D tech top-down shooting game.
Cycles
Cycles
I believe you should be testing 2 things. If you split the Logger into a main Logging class and a ILogStream interface of some sort you can first test that your logger is interacting with an ILogStream correctly (mock objects). When you have settled on an interface for Logger and ILogStream, then you can begin test driving specific ILogStream implementations. These would be FileLogger, ConsoleLogger, PhoneMeUpAt2AMLogger - etc ;)

By seperating these 2 parts you're getting a cleaner more extendable design, and it's much more cohesive.
Oliver Charles (aka aCiD2) [ Cycles Blog ]
dave
dave
Quote:
Original post by rick_appleton
Quote:
Original post by Dave
Rick,

For this specific scenario i fail to see any other approach than to simply log in all possible combinations, read the data back and compare. The issues i have with this scenario is that the code to test it is going to be large enough that it could be problematic in itself.


Hmm, that's fair enough I guess. Maybe you could suggest some other fairly low-level module that might be a more suitable introduction to TDD? (I've just started porting code to a new platform, hence I don't have much low-level stuff at all yet on that platform). This is for a fairly common 2D with 3D tech top-down shooting game.


An example of something i wrote recently is a class that takes a path and parses it such that you end up with:

- A list of folders
- Filename
- Extension
- Drive

You also get string substitution, for example this is a path with a symbol:

#root_drive#/windows/system32

and you can specify that #root_drive# gets replaced with say "C:". This is thoroughly unit testable. Firstly you assume that the getters and setters work, since they are trivial, so here the parsing is what you really want to test.

Give it a few paths that test the different functionalities, such as a path with and without filenames, extensions, drives, empty path etc. Then test that the entire state of the PathParser class is exactly as it should be.

I also strongly recommend that you have one unit test per function-that-does-testing. This allows you to break down the testing more easily and also track down a failed test.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.