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:
- It starts the game, just like in real life, but it wraps it in a small blanket of test helpers
- It tweaks the game configuration for our own nefarious purposes, mainly:
- It gives it a “field height” of 3, so this particular instance of the game is only three rows high
- It says to drop a brick every 100 milliseconds
- It says to spawn two i-blocks in a row
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.