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!