"Extreme Programming" testing in BlitzMax

BlitzMax Forums/BlitzMax Tutorials/"Extreme Programming" testing in BlitzMax

WendellM(Posted 2005) [#1]
I'm still a novice at Extreme Programming, abbreviated "XP" (which has nothing to do with "Windows XP"), but I started playing with it a few months ago and have found it interesting. There are several aspects of it that I've found useful, the main one being its test-driven development. The examples of testing frameworks that I've seen elsewhere are written in C++ or Java, so this tutorial (which is doubtless less than ideal, but is hopefully better than nothing <g>) shows one possible approach using BlitzMax along with a quick introduction to Extreme Programming's designing via testing.

Maybe you're like me... before XP, whenever I'd write a new function or method, I'd write a little throw-away code to test it out. Once I verified that it was working, I'd delete (or sometimes comment out) the test code and move on. With XP, you're just a little more formal in your testing and keep the tests around in easy-to-use, automated form. That way, you can easily test everything each time that you add or change something, to make sure that nothing has been broken.

In fact, in XP you write the test before you write the code that's to be tested. That lets you know when you're done: if the test is passed, you've written enough. It seems a little weird at first (OK, maybe a lot weird <g>), but the aim here is to create just enough functionality to pass the test that you create beforehand.

Other coding methods emphasize careful planning and building expandable methods and functions that can grow to do anything that might be needed later on. XP believes that this isn't the best approach; rather you should write only enough to do what's needed today, confident that you can add more functionality tomorrow if that additional functionality is needed. And if it turns out that it isn't, then you were better off not wasting time making your code expansion-ready.

So, for example, to create a game like Tic Tac Toe (Noughts & Crosses), you'd start by creating a board. But first, you need a framework for your tests:
Strict

Type Test

	Function Create:Test()
		Local t:Test = New Test
		Return t
	End Function

	Method Suite()
	End Method

End Type

Test.Create().Suite
This is just the skeleton on which future tests will be built. The one line, "Test.Create().Suite", will live just long enough to run the Suite method, which will be de-allocated with the next FlushMem.

OK, so now you're ready to write a test for the board, see why it fails, and then write just enough code to keep that test from failing. First, you pretend that there's already a board type prepped and waiting for you so you create an instance the way that you'd like for it to work and call it by adding this code after "End Method":
	Field Board:BoardType

	Method BoardCreate()
		Board = BoardType.Create( 3, 3 )
		Assert Board.Width = 3
		Assert Board.Height = 3
	End Method
This will attempt to create a 3x3 gameboard (and check it with Asserts), but it will fail with "Identifier 'BoardType' not found" - telling you that it's time to create a Board Type. So, add this before "Type Test":
Type BoardType
EndType
Note that this is just enough to fix the error message. Now you get a new error message: "Identifier 'Create' not found" so you know that you need to make a Create method or function for your BoardType. Once you add that, your code might look like:
Strict

Type BoardType

	Function Create:BoardType()
		Local b:BoardType = New BoardType
		Return b
	End Function

End Type

Type Test

	Function Create:Test()
		Local t:Test = New Test
		Return t
	End Function

	Method Suite()
	End Method

	Field Board:BoardType

	Method BoardCreate()
		Board = BoardType.Create( 3, 3 )
		Assert Board.Width = 3
		Assert Board.Height = 3
	End Method

End Type

Test.Create().Suite
Again, this is just barely enough to get past the current error message and get to a new one: "Too many function parameters", so you know to expand the BoardType to handle two dimensions as used by your test code with something like:
Type BoardType

	Field Width
	Field Height
	Field Cell[,]

	Function Create:BoardType( w, h )
		Local b:BoardType = New BoardType
		b.Width = w
		b.Height = h
		b.Cell = New Int[ w, h ]
		Return b
	End Function

End Type
This time when you run your test, it works with no errors, so you know that you can pass the BoardCreate test. You can then add it to Suite so that you can call it from now on:
Strict

Type BoardType

	Field Width
	Field Height
	Field Cell[,]

	Function Create:BoardType( w, h )
		Local b:BoardType = New BoardType
		b.Width = w
		b.Height = h
		b.Cell = New Int[ w, h ]
		Return b
	End Function

End Type


Type Test

	Function Create:Test()
		Local t:Test = New Test
		Return t
	End Function

	Method Suite()
		BoardCreate
	End Method

	Field Board:BoardType

	Method BoardCreate()
		Board = BoardType.Create( 3, 3 )
		Assert Board.Width = 3
		Assert Board.Height = 3
	End Method

End Type

Test.Create().Suite
You're now ready to move on to the next step... it might be nice to be able to display the board that you just created. So you write a test of how you'd like that to work and try it out:
	Method BoardDisplay()
		Board.Display
	End Method
When it fails to run with "Identifier 'Display' not found', you know that you need to add that method to BoardType... and so on, and so on.

Now, in reality, you'd probably be bright enough to anticipate the errors shown above and write the needed BoardType capability before running the test. That's what I usually do, too (at least a simple framework), but the point is that by writing the test first, "error messages" become guideposts pointing out what you need to add.

I usually prefer using the entire suite each time, but it's possible to call the tests individually if you like (though you'll want to Null out the instance required to do this when you're done with it so that it will be freed). Here's a final example showing all of this:
Strict

Type BoardType ' An example type used in a game (in this case, like Tic-Tac-Toe)

	Field Width
	Field Height
	Field Cell[,]

	Function Create:BoardType( w, h )
		Local b:BoardType = New BoardType
		b.Width = w
		b.Height = h
		b.Cell = New Int[ w, h ]
		Return b
	End Function
	
	Method Display()
		Print "W: " + Width
		Print "H: " + Height
		Local a$ = ""
		Print "Board:"
		For Local y = 0 To Height - 1
			For Local x = 0 To Width - 1
				a$ :+ Cell[ x, y ]
			Next
			Print " " + a$
			a$ = ""
		Next
	End Method

	Method Assign( x, y, v )
		Cell[ x, y ] = v
	End Method

	Method Clear()
		For Local x = 0 To Width - 1
			For Local y = 0 To Height - 1
				Cell[ x, y ] = 0
			Next
		Next
	End Method

End Type



Type Test ' used for XP-style testing

	Function Create:Test()
		Local t:Test = New Test
		Return t
	End Function

	Field Board:BoardType

	Method BoardCreate()
		Board = BoardType.Create( 3, 3 )
		Assert Board.Width = 3
		Assert Board.Height = 3
	End Method

	Method BoardDisplay()
		Board.Display
	End Method

	Method BoardAssign()
		Board.Assign( 0, 1, 1)
		Assert Board.Cell[ 0, 1 ] = 1
		Board.Assign( 2, 1, 2)
		Assert Board.Cell[ 2, 1 ] = 2
	End Method

	Method BoardClear()
		Board.Clear
	End Method

	Method Suite()
		BoardCreate
		BoardDisplay
		BoardAssign
		BoardDisplay
		BoardClear
		BoardDisplay
	End Method

End Type


FlushMem; Print "MemAlloced = " + MemAlloced()

Print "AUTOMATED:"
Test.Create().Suite ' one-line automated testing (that's auto-freed since there's no handle)

FlushMem; Print "MemAlloced = " + MemAlloced()

' If you'd prefer more control, create a Test instance and call any method(s) as needed
Print "MANUAL:"
Local MyTest:Test = Test.Create()
MyTest.BoardCreate
MyTest.BoardDisplay
MyTest = Null

FlushMem; Print "MemAlloced = " + MemAlloced()
Now that you've created the needed Board type along with its function and methods, and you know that it's reliable since you wrote the tests first, you can move on to actually creating a board instance in your game and using it.


deps(Posted 2005) [#2]
A most interesting little article, thanks a lot!


Chris C(Posted 2005) [#3]
jeeze an theres me thinking that XP was coding with a laptop on a white water raft :))


deps(Posted 2005) [#4]
jeeze an theres me thinking that XP was coding with a laptop on a white water raft :))

While fending off sharks with a stick, standing on one hand and juggling chainsaws with the feets.

You don't see that kind of thing on TV. :/


michak(Posted 2005) [#5]
Yes very interesting. Related to XP there is also the technique of unit tests. Unit tests are small applications which allow to test either at the smallest scale (test a function or a class) or medium scale (a related set of features like drawing functions, ...) or even a complete feature of which will ater be part of an application (eg finding and reading configuration files at startup)

BR
mitch


ozak(Posted 2005) [#6]
Actually it's quite cool to do the other XP style with two coders per machine although it's a bit expensive payment wise :)

But yeah. Somewhere in between hacking away and planning everything out on paper is the best :)