Our mission today, and we have decided to accept it, is to work on creating a set of controls for each ship, so that we can drive them around. We begin by reviewing our tests, which presently look like this:

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")
    
    button:touched(insideEndTouch)
    assertEqual(button.capturedID, nil, "captured id should still be nil")
    
    button:touched(insideBeganTouch)
    assertEqual(button.capturedID, 3, "captured id should be 3")
    
    button:touched(outsideBeganTouch)
    assertEqual(button.capturedID, 3, "id should still be 3")    
end

Note that so far we have tested the insideCircle function, and tested whether the button has captured the touch at the right moments, but we have not tested our pressed member variable. We “discussed” whether we needed more tests. I claim that the following code is entirely clear and that we do not:

function Button:touched(touch) 
    self.capturedID = self:getCapture(touch)
    self.pressed = self:getPressed(touch)
    if self.ship and self.pressed then self.ship:perform(self.action) end
end

function Button:getCapture(touch) 
    if touch.state == BEGAN and self:insideCircle(touch) and not self.capturedID then
        return touch.id 
    elseif touch.state == ENDED and self.capturedID == touch.id then
        return nil
    else
        return self.capturedID
    end
end

function Button:getPressed(touch)
    if not self.capturedID then
        return false
    else 
        return self.capturedID == touch.id and self:insideCircle(touch)
    end
end

When Tozier threatened to abstain from the vote, I agreed that if any member of the pair wants more testing, we should do more testing. I was kind of hoping to get in more trouble but I’m sure that trouble will be provided. So now we want some tests for whether the button is or is not pressed, and we’ll probably want to add some tests for the MOVING state, which is not checked at all. (Because, as the code clearly shows, it is not needed. :) ) Here goes:

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")
    
    button:touched(outsideBeganTouch)
    assertEqual(button.capturedID, 3, "id should still be 3")
    assertFalse(button.pressed, "outside touch erroneously captured a press")
    
    button:touched(insideBeganTouch)
    assertEqual(button.capturedID, 3, "captured id should be 3")
    assertTrue(button.pressed, "now it is pressed")
end

We’ve just added a check for the pressed state into our existing tests, and reordered them to put the only pressed one last, for further testing.

Yes, well, the tests go red. We had forgotten that we had two “BEGAN” tests in a row, the second of which should not have captured an ID, because the first one did. Since we are using the same Button throughout, the Button has memory and is carrying state forward from touch to touch, as intended. However, our tests are temporally coupled. That was our plan, but note that even so we managed to confuse ourselves. Now we are easily confused, but this shows how dangerous temporal coupling is in tests: one comes to expect otherwise. Now that we’re alerted, we’ll proceed.

    -- 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
    assertTrue(button.pressed, "moving inside should remain pressed")
    movingFinger.x = 200
    assertEqual(button.capturedID, 5, "moving outside does not lose capture")
    assertFalse(button.pressed, "moving outside loses press")

We are writing the above sequence test. It is our intention that when a touch moves outside the Button, but has not ended, the button is not pressed. We also intend that if you move back in it will be pressed. However the final test above fails, that is, pressed is true when we are outside.

Note that my assertion that this code clearly works was not as true as it might have been. There is a lesson here and I hope that you and Tozier learn it.

The bug is that we have not sent our movingFinger Button a new touched event, so it retains its old state. We make that change, and our tests now run:

    -- 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")

Our tests are helping us, but they’re also confusing us. This is due, in part, to the necessity of testing a changing touch event, which requires our test above to be temporally coupled. But we’ve been confused here two or three times in this many lines of text, so let’s think about whether we could express this testing better.

One possibility is to build a test support function that accepts a button, a touch, and a list of tests or run. Inside the support function, we have to remember to touch the button … But we only have to remember it once. This may help our feeble brains in the future. The cost, however is in passing in a table of assert functions. The result looks like this. The two blocks shown here do the same thing:

    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 blah") end,
         function() assertFalse(button.pressed, "FFF moving outside loses press") end
        }
    )

Supported, of course by our new function `testTouch():

function testTouch(b, touch, tests)
    b:touched(touch)
    for _,test in ipairs(tests) do
        test()
    end
end

Now, I am a bear of very simple brain, and passing around these function closures is a pretty complex hammer. However, in Lua, functions are first-class objects, and everyone is passing around closures these days, so it seems to be a good idea to become familiar with the idea.

Why are those closures, you ask? Because the value button, referred to in the two function assert... bits, picks up the local variable button that we’re using in all our tests, referring to it. Note that in the testTouch function we just call test() on each element of our tests table. We do not pass it our button: it has the button already.

We think we’ll do things this way. Once done the code is just about as clear as the in-line code and it saves our aging brains from forgetting to call touch(). We’ll refactor all our tests to work this way. Next time, because it is time to stop. However … A story goes with this.

In trying to write our testTouch function, our first plan was to pass in the touch, and a change table. It would have looked like this:

testTouch(button, touch, changes, tests)
    for k,v in pairs(changes) do
        touch.k = v
    end

… and so on. We’d call it with something like testTouch(button, movingFinger, {x=200}, ...) and it would all be good. Inside the changes loop we’d find our x=100 value, set k to “x” and v to 200 and plunk it into the input table. Nothing to it, really.

Except that it’s wrong. touch.k = v doesn’t set touch.x when k = “x”. It sends the value of touch.k. That is, it sets touch[“k”] to v. To get it to change the x value we would have to write touch[k] = v. This is listed in the Lua manual as one of the most common errors in using tables. We fell into that trap and it took us around a half hour to get out of it, after tracing values all around. Finally, while Tozier stepped away, I wrote a tiny test program that made it clear what was going on:

function setup()
    local touch = {x=100, y=100, state=ENDED}
    update(touch, {x=200} )
    for k,v in pairs(touch) do
        print(k,v)
    end
end

function update(tab, adj) 
    for k,v in pairs(adj) do
        tab.k = v
    end
end

This little gem prints:

y 100
state 2
k 200
x 100

As soon as the k 200 printed out, I screamed an obscenity that nearly got us ejected from the coffee shop. My exact words were something like “it’s REDACTED setting k!!!”

We had, in fact, read the part about this being a common mistake before I wrote this little sample code. Perhaps you saw the problem right away. Had you been here, you would have saved us lots of trouble, but we have perhaps learned the lesson better in your absence. We then went ahead and changed the version that did the update, made it work, then decided that the code looks better with the update to movingFinger done explicitly, and so we deleted our change bit, resulting in the version you see above.

We’ll do the rest of the refactoring next time.

What have we learned on the show tonight, Craig? We learned that we should always test when anyone in the pair thinks we should. We learned that the argument that it is your iPad and you can do it your way is not as valid as it seems. And we learned a bit about passing function closures as arguments, and that it seems to make the code only a bit less clear, and in this case, it makes our tests more reliable.

And we learned that touch.k means touch["k"] because it’s just syntactic sugar for that, not for touch[whatever's in k].