Test-driving Tetris from the outside in (1)

Sep 9, 2019 tdd tetris rust | Next

To hone my Rust and TDD skills, I recently developed a simplified version of Tetris.

The tests in this project tend to look like this:

use crate::game::brick_factory::*;
use crate::_tests::helpers::testable_game::TestableGame;

#[test]
fn after_brick_reaches_bottom_it_stays_there() {
    let mut game = TestableGame::init()
        .with_drop_interval(100)
        .with_brick_sequence(vec![i_block(), i_block()])
        .with_field_height(3)
        .build();

    game.verify_exact_frame_after(100, vec![
        "..........",
        ".####.....",
        "..........",
    ]);

    game.verify_exact_frame_after(200, vec![
        "..........",
        "..........",
        ".####....."
    ]);

    game.verify_exact_frame_after(300, vec![
        ".####.....",
        "..........",
        ".####....."
    ]);
}

What are we looking at here?
This is what is sometimes called a “feature test”. As opposed to a unit test, it tends to spin up the whole system and make assertions against its output, so:

Under the hood, it also tells the game to draw itself to a special renderer that will allow us to assert against its contents in string-form.

All this enables us to verify that, after N milliseconds, the frame looks a certain way, using only code and no external files.

And it enables us to write the assertion in a way that makes visual sense, as it were. No magic numbers needed. No asking about which function is called with what parameter. Heck, if we play our cards right, we might even get the IDE to display a nice diff for us.

The method to the madness is inspired by what some call “Outside-In TDD”, as opposed to the traditional “Inside-Out” approach.

Briefly, Outside-In means you write a test that covers the whole system first, then make it pass. You proceed with more tests guided by the next piece of indended high-level behavior.

Inside-Out means you write a test that covers a small part of the system, then make it pass. You proceed guided by what you think the next test should be in order to evolve the system.

You can find the code on github.

You will need a good grasp on Rust and TDD to make sense of it without getting grumpy. If you have a better grasp on Rust and/or TDD than I do, you might get grumpy too. In that case, please let me know.