Spacewar! 14 - Some refactoring of the tests.
Today, for something to do, I’ll refactor our existing tests into the new format where we pass in the assert functions. the idea behind this scheme is that sending ~touched~ to the button is inside the standard call, so that it cannot be forgotten. Here’s what we have now:
function testButton()
local button = Button(nil, 100, 100, "nothing")
local insideEndTouch = { id=1, state=ENDED, x=100, y=100 }
local outsideEndTouch = { id=2, state=ENDED, x=151, y=100 }
local insideBeganTouch = { id=3, state=BEGAN, x=100, y=100 }
local outsideBeganTouch = { id=4, state=BEGAN, x=151, y=100 }
assertTrue(button:insideCircle(insideEndTouch), "should be inside")
assertFalse(button:insideCircle(outsideEndTouch), "should be outside")
assertEqual(button.capturedID, nil, "initial captured id should be nil")
assertFalse(button.pressed, "button better not be pressed")
button:touched(insideEndTouch)
assertEqual(button.capturedID, nil, "captured id should still be nil")
assertFalse(button.pressed, "button still not pressed")
-- test that we do not lose a capture without an ENDED
button:touched(insideBeganTouch)
assertEqual(button.capturedID, 3, "should capture the id")
button:touched(outsideBeganTouch)
assertEqual(button.capturedID, 3, "id should be 3")
assertFalse(button.pressed, "outside touch erroneously captured a press")
-- sequence test
local closeThree = { id=3, state=ENDED, x=200, y=200 }
local movingFinger = { id=5, state=BEGAN, x = 100, y=100}
button:touched(closeThree)
assertEqual(button.capturedID, nil, "should have lost captured value")
button:touched(movingFinger)
assertEqual(button.capturedID, 5, "captured id should be 5")
assertTrue(button.pressed, "now it is pressed")
movingFinger.state = MOVING
button:touched(movingFinger)
assertTrue(button.pressed, "moving inside should remain pressed")
movingFinger.x = 200
button:touched(movingFinger)
assertEqual(button.capturedID, 5, "moving outside does not lose capture")
assertFalse(button.pressed, "moving outside loses press")
movingFinger.x = 200
testTouch(button, movingFinger,
{function() assertEqual(button.capturedID, 5, "moving outside does not lose capture") end,
function() assertFalse(button.pressed, "moving outside loses press") end
}
)
print(testResult)
end
The last two tests are the same: the last form is the one we want to go to. (We’ll talk about the ~print(testResult)~ later.) So first, I refactor the sequence test above, to look like this:
-- sequence test
local closeThree = { id=3, state=ENDED, x=200, y=200 }
local movingFinger = { id=5, state=BEGAN, x = 100, y=100}
testTouch(button, closeThree,
{function() assertEqual(button.capturedID, nil, "should have lost captured value") end} )
testTouch(button, movingFinger,
{function() assertEqual(button.capturedID, 5, "captured id should be 5") end,
function() assertTrue(button.pressed, "now it is pressed") end } )
movingFinger.state = MOVING
testTouch(button, movingFinger,
{ function() assertTrue(button.pressed, "moving inside should remain pressed") end } )
movingFinger.x = 200
testTouch(button, movingFinger,
{function() assertEqual(button.capturedID, 5, "moving outside does not lose capture") end,
function() assertFalse(button.pressed, "moving outside loses press") end
}
)
That runs OK, so I’ll carry on with refactoring the simpler tests up above. I’ll report back when I encounter something interesting … And here we are with all the button-touching tests refactored to the new form:
function testButton()
local button = Button(nil, 100, 100, "nothing")
local insideEndTouch = { id=1, state=ENDED, x=100, y=100 }
local outsideEndTouch = { id=2, state=ENDED, x=151, y=100 }
assertTrue(button:insideCircle(insideEndTouch), "should be inside")
assertFalse(button:insideCircle(outsideEndTouch), "should be outside")
assertEqual(button.capturedID, nil, "initial captured id should be nil")
assertFalse(button.pressed, "button better not be pressed")
testTouch(button, insideEndTouch,
{function() assertEqual(button.capturedID, nil, "captured id should still be nil") end,
function() assertFalse(button.pressed, "button still not pressed") end } )
-- test that we do not lose a capture without an ENDED
local insideBeganTouch = { id=3, state=BEGAN, x=100, y=100 }
local outsideBeganTouch = { id=4, state=BEGAN, x=151, y=100 }
testTouch(button, insideBeganTouch,
{function() assertEqual(button.capturedID, 3, "should capture the id") end } )
testTouch(button, outsideBeganTouch,
{function() assertEqual(button.capturedID, 3, "id should be 3") end ,
function() assertFalse(button.pressed, "outside touch erroneously captured a press") end } )
-- sequence test
local closeThree = { id=3, state=ENDED, x=200, y=200 }
local movingFinger = { id=5, state=BEGAN, x = 100, y=100}
testTouch(button, closeThree,
{function() assertEqual(button.capturedID, nil, "should have lost captured value") end} )
testTouch(button, movingFinger,
{function() assertEqual(button.capturedID, 5, "captured id should be 5") end,
function() assertTrue(button.pressed, "now it is pressed") end } )
movingFinger.state = MOVING
testTouch(button, movingFinger,
{ function() assertTrue(button.pressed, "moving inside should remain pressed") end } )
movingFinger.x = 200
testTouch(button, movingFinger,
{function() assertEqual(button.capturedID, 5, "moving outside does not lose capture") end,
function() assertFalse(button.pressed, "moving outside loses press") end
}
)
print(testResult)
end
Now my assessment of this is that this new scheme does “guarantee” that we’ll trigger the touched
event on the button, which was the mistake we were trying to avoid. The code is a bit more arcane, which I don’t like, but it’s not bad enough to make me want to go back to the more temporally coupled version.
What we see now are a couple of things. First, there are some tests of insideCircle
that are testing that low-level function, so that bit of testing is at a different level of abstraction than the button tests. We could consider that those are scaffolding tests and that their function is subsumed by the subsequent button tests. We might need to add a couple more button tests to be sure: I’d have to look and think about it.
Second, we see several smaller tests wrapped up in our one big testButton
function. I was taught, and have come to strongly believe, that a function that does several things should use the “Composed Function” pattern (see “Composed Method”), and turned into a function that only calls subfunctions, each one doing one of the things.
This latter bit is more significant: this testButton
is going to get more and more hard to understand as time goes on, and certainly if we start testing other things something will have to be done.
A “real testing framework” would allow us to just write all the little test functions, give them some kind of name or attribute, and the framework would collect them all up and call them. Maybe it’s time to start growing a bit more framework. I think I’ll leave that for the next time I have Tozier to pair with, because he’s interested and he’ll help keep me honest. See you then!