• Stars
    star
    392
  • Rank 106,098 (Top 3 %)
  • Language
    Java
  • Created about 15 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Exercise for learning Test-Driven Development with the help of predefined tests

TDD Tetris Tutorial

In this tutorial you will be implementing a Tetris game using Test-Driven Development (TDD). Some 30 of the first tests have been provided, so that you just need to write code to pass them. The purpose of working with these pre-written test cases is to get accustomed to the TDD cycle, and to get some ideas on what kind of tests to write. After doing that for some while, it will be easier when it's time to begin writing your own tests towards the end of this tutorial.

Update 2021: There now exists a newer JavaScript-based successor of this tutorial. The course material has also been renewed: https://tdd.mooc.fi

For information about Test-Driven Development, here are some links. It is recommendable to read them before doing this tutorial, so that you would know what TDD is about.

General information about TDD:

TDD is more about specifying behaviour than about testing:

Summary:

This tutorial has been used in the TDD programming technique and designing code course in the University of Helsinki. The lecture slides and exercises of that course can also be helpful (the material is in English, but for the rest of the site you can use Google Translate).

The Steps of the Tutorial

Use the tests in the src/test/java/tetris directory to write a Tetris game. Implement code to pass the tests, one file and one test at a time, in the same order as they are listed below, starting with FallingBlocksTest.

When you first run the tests, you should see the first test (A_new_board.is_empty) failing. Fix the code (a one-line change) and run the tests to see it pass. Then uncomment the following test (A_new_board.has_no_falling_blocks). When that test passes, uncomment the next one (When_a_block_is_dropped.the_block_is_falling) and make it pass, until finally you have written code which passes all tests in that class. Then open the next test class and keep on continuing in the same fashion.

Reference implementations for the steps of this tutorial have been tagged in its Git repository. There are also Let's Code screencasts of implementing the take3 branch. Also the beyond and take2 branches have reference implementations. It might be helpful to have a look at them after you have yourself implemented this tutorial that far or you get stuck with your design.

  1. FallingBlocksTest

    In Tetris, the most important feature is that there are blocks which fall down, so that is the first behaviour which we will specify by writing tests. It is good to start the writing of a program with a very trivial test. In this case, we will first make sure that we have an empty game board.

    We'll use the Simplification strategy and first deal with just falling 1x1 blocks. We can expand that later to handle more complex multi-block pieces. It's best to avoid taking too big steps.

    Design Hint: In line with YAGNI, avoid introducing new classes or patterns until there is a demonstrated need for them. For example, don't introduce class when just a char is enough. Don't introduce a two-dimensional array when just a scalar field is enough. Later when the real need for them arises, you will have learned more about the code's needs and you'll be able to make a better design without having to undo your old designs.

  2. RotatingPiecesOfBlocksTest

    Rotating the pieces in Tetris is also very important, so let's code that next. You might need pen and paper when figuring out how the shape coordinates change when the shape is rotated.

    I decided to go for a generic algorithm for rotating any shapes. Another option would have been to hard-code the rotations of each shape. Both have their pros and cons. But even if this decision would prove to be bad when the game evolves furher, good test coverage and decoupled code will make it possible to change it afterwards.

  3. RotatingTetrominoesTest

    Tetrominoes can have 1, 2 or 4 different orientations when they are rotated. Now we can take advantage of the shape rotation code which we wrote just a moment ago.

    Notice that the first test specifies the Tetromino objects to be immutable. Check the Wikipedia article about immutable objects if that concept is new to you. Defaulting to immutability is a good thing.

    Also notice that the I shape has only two possible orientations and the O shape has only one orientation. The tests are the way they are by design. Did you know that Tetris has many alternative rotation systems?

    Design Hint: If you are thinking of making the Tetromino class extend the Piece class, first read about the Liskov Substitution Principle to know when it's right for a class to inherit another. In general, it's best to favor composition over inheritance.

  4. FallingPiecesTest

    Next we will connect the falling blocks and the rotatable pieces. In order to make the first test pass, you will probably need to refactor your code quite much (for me it took 1½ hours). You will need to build suitable abstractions, so that single-block pieces and multi-block pieces can be handled the same way (changing the test code should not be necessary). When refactoring, you must keep the tests passing between every small change, or you will end up in refactoring hell. If more than five minutes have passed since the last time all tests passed, it's best to revert your code from source control to the last version where all tests passed.

    The difficulty of this refactoring depends on how well factored the code is. You could even say that the difficulty of this refactoring is inversely proportional to the length of the Board.tick() method (if most of the logic is in that one method, instead of using Composed Method, then you're in trouble). If you get stuck, there are refactoring examples in this PDF presentation and this YouTube video.

  5. MovingAFallingPieceTest

    Now it's your turn to write the tests. I've provided some TODO items which should hint you on what tests to write.

    Feel free to refactor the test code and change any of the previosly used class or method names to be more descriptive, because there are no more predefined tests which might become incompatible with your code. Both production code and test code need to be taken care of, so you should regularly see if there is something to improve and then refactor it.

  6. RotatingAFallingPieceTest

    Keep on writing your own tests. You're getting the hang of it!

  7. And beyond...

    Next you should implement the following features in suitable order: removing full rows, counting removed rows, counting score, game over, choosing the next piece by random (using a shuffle bag). For counting the removed rows, you could launch an event (call a listener's method) when a row is removed - Mockito might come in handly for testing that.

    Also change the game to use the rotation rules of TGM. In order to do that, it would be good to replace the earlier generic algorithmic shape rotation (which was done in step 2) with one where each orientation of a shape is hardcoded, because that will probably simplify the code considerably. When you have made the change, evaluate that which of the implementations is better, and remove all code that relates to the worse implementation - deleting code is a good thing.

    Soon after that you should be able to create a user interface which is only a thin wrapper over the already implemented functionality. Automated testing of user interfaces is generally hard to do, but by separating the UI model from its view it is possible, by minimizing the code which is not tested automatically (see these articles).

What Next?

After you have completed this tutorial, you should have a rough understanding of how to use TDD - you should be at the Practicing/Shu/Advanced Beginner level. You will probably still struggle with things like always writing a test first, using small enough steps, writing self-documenting tests, keeping the code clean etc. Also most of the tests in this tutorial are perhaps confusingly similar (they verify all game state using toString()) so you should practice writing tests for different kinds of situations. Likewise, this tutorial uses a bottom-up approach to TDD, which has the risk of producing something that is not needed, so learning also a top-down approach would be beneficial.

You should continue practicing by writing lots of small applications using TDD, until TDD becomes second nature to you and you can use it "when you have to get it done". Learning that will take many months. It's also recommendable to read the books Clean Code: A Handbook of Agile Software Craftsmanship and Growing Object-Oriented Software, Guided by Tests. The former book teaches how to write good code. The latter book teaches how to use TDD to drive the design.

License

Copyright © 2008-2015 Esko Luontola <http://www.orfjackal.net>
You may use and modify this material freely for personal non-commercial use.
This material may NOT be used as course material without prior written agreement.

More Repositories

1

retrolambda

Backport of Java 8's lambda expressions to Java 7, 6 and 5
Java
3,537
star
2

idea-sbt-plugin

IntelliJ IDEA plugin for integration with SBT (Simple Build Tool) in order to compile Scala projects easily and quickly [UNMAINTAINED]
Java
178
star
3

gospec

Testing framework for Go. Allows writing self-documenting tests/specifications, and executes them concurrently and safely isolated. [UNMAINTAINED]
Go
113
star
4

jumi-actors

Actor library for Java to support concurrency and asynchronous event-driven programming.
Java
79
star
5

jumi

Test runner for the JVM. Natively supports running tests in parallel. Full stack from UI to class loading. Overcomes a bunch of limitations in JUnit's test runner, IDEs and build tools. [PRE-ALPHA]
Java
72
star
6

cqrs-hotel

Example application about CQRS and Event Sourcing #NoFrameworks
Java
69
star
7

dimdwarf

Distributed application server for Java. You write single-threaded, event-driven POJO code - the server makes it multi-threaded, persisted and transactional. Designed for the needs of online games. Compatible with Project Darkstar (now RedDwarf). [INACTIVE]
Java
41
star
8

native-clojure-lambda

Example project of Clojure + GraalVM Native Image + AWS Lambda container images
HCL
28
star
9

specsy

BDD-style unit-level testing framework for Java/Scala/Groovy. Safely isolates mutable state. Unlimited nesting.
Java
20
star
10

tdd-mooc-tetris

Exercise for learning Test-Driven Development with the help of predefined tests
JavaScript
9
star
11

facebook-history-purge

Userscript for Greasemonkey/Tampermonkey. Deletes from your Facebook wall all activity and posts older than the selected post. [OBSOLETE]
JavaScript
8
star
12

tdd-mooc

Massive open online course for learning Test-Driven Development
JavaScript
8
star
13

sgs-server

Project Darkstar Server Core. Git mirror of sgs-server's SVN repository, trunk only. GPL 2.0 License.
Java
7
star
14

extformatter

External Code Formatter, an IntelliJ IDEA plugin. [UNMAINTAINED]
Java
7
star
15

varjocafe

Mashup of UniCafe.fi's daily lunch menus [UNMAINTAINED]
Clojure
5
star
16

visualvm4idea

VisualVM Profiler, an IntelliJ IDEA plugin. [UNMAINTAINED]
Java
5
star
17

web-intro

Introduction to Making Web Applications
CSS
5
star
18

clojure-native-image-agent

Helps GraalVM's native-image-agent to be more useful for Clojure applications.
Java
4
star
19

classmembersorter

Sorts Java's Method and Class objects according to their actual source code line numbers.
Java
4
star
20

puzzle-warrior

Hyper Puzzle Warrior XVII Ultra - a "Super Puzzle Fighter II Turbo" clone. [DRAFT]
Java
4
star
21

gobbler

Build tool for Go. With it you can build and test advanced multi-package projects with near-zero configuration. [DRAFT]
Go
4
star
22

text-adventure

Small example application, a typical homework assignment. Coding this app has been screencasted - see the below web site.
Java
4
star
23

tdd-mooc-small-steps

Refactoring exercise to practise small, safe steps.
JavaScript
3
star
24

idea-config

My configs for IntelliJ IDEA (~/.IntelliJIdeaXX/config)
Java
3
star
25

maven-central-bundler

Takes a POM, a binary JAR, a sources JAR, an optional javadoc JAR, and generates from them a bundle which can be uploaded to Maven Central.
Ruby
3
star
26

pa-mentor

Planetary Annihilation mod showing how your in-game stats compare to other players [UNMAINTAINED]
JavaScript
3
star
27

weenyconsole

Command line interface which executes methods on a POJO.
Java
2
star
28

gcp-dynamic-dns

Syncs the current IP address to Google Cloud DNS records
Go
2
star
29

misc-tools

Chaotic pile of small utility classes and experiments.
Java
2
star
30

web-intro-project

Introduction to Making Web Applications - Example Solution
HTML
2
star
31

cygwin-deployer

Automatic installer for my Cygwin configuration
Ruby
2
star
32

thematrixtime

Slows down or speeds up the system clock. [UNMAINTAINED]
Java
2
star
33

tdd-mooc-untestable-code

Exercise about unit testing legacy code
JavaScript
2
star
34

territory-bro

Tool for managing congregation territory cards
Clojure
2
star
35

pommac

Utility for generating Maven POM files. [DRAFT]
Java
2
star
36

rust-game-of-life

Rust
1
star
37

clojure-game-of-life

Conway's Game of Life, including RLE file import and export
Clojure
1
star
38

tdd-mooc-new-js-project

An empty project with JavaScript testing tools preconfigured.
JavaScript
1
star
39

learning-cobol

COBOL
1
star
40

darkstar-contrib

User contributed code to Project Darkstar (SGS) [UNMAINTAINED]
Java
1
star
41

reddwarf-server

Git mirror of reddwarf/sgs-server
Java
1
star
42

sgs-java-client

Project Darkstar Java Client. Original SVN repository converted into Git.
Java
1
star
43

rimworld-love-puddle

Solves the problem of who to put in bed with who, without needing a spreadsheet.
Clojure
1
star
44

buildtest

Utilites for asserting about build artifacts
Java
1
star
45

new-clojure-project

Something to get quickly started with Clojure for a code kata or similar
Clojure
1
star
46

longcat

Longcat is looooooooooooooong. Command line tool for generating as long longcats as you wish.
Java
1
star