One of the things we struggled with in our Tic-Tac-Toe game was using GridLayout
to make the board. A GridLayout
is a little too sophisticated and slightly wrong for a tic-tac-toe game, as we saw. Or any game, really: We needed to, for example, draw on it, but since it's a layout container, it wanted to adjust whenever a new component was added. That's when we discovered the joy of "wrapping things in other things"—basically putting whatever was giving us trouble into another container and dealing with the outer container. It bailed us out quite a bit.
We made it work, sure, but it was a very simple scenario and a fair amount of trouble.
So we vowed to use a grid that was more suited to our needs as (very) amateur game designers. Other things we vowed to do to learn from our chaotic first run: Create some tests, derive the visual representation from the game state—from the get go this time, and more reactively, and eat our vegetables.
I describe the game we're building here or you can just buy the inspiration here . We're going to be doing a...oh, let's call it a "family friendly" version.
Now, on to the grid component.
True Grid
There is a JavaFX game grid at github:
It's a fine piece of work, too. Initially, I dismissed it as a possibility because it only allowed squares to be in a specific, enumerated space. Now, imagine building a maze using only a traditional enumerated type:
possible_states = { canGoNorth, canGoSouth, canGoEast, canGoWest, canGoNorthAndSouth, canGoNorthAndEast, canGoNorthAndWest, canGoSouthAndWest, canGoNorthSouthAndEast, canGoNorthSouthAndWest, canGoSouthAndEast, canGoSouthAndWest, canGoSouthEastAndWest, canGoEastAndWest, canGoNorthSouthEastAndWest, canGoNowhere}
(I'll leave it as an exercise for the reader to determine if I actually hit all the possibilities.)
Obviously, you'd never do that. Actually, Pascal has a kind of neat primitive type called a set, which would work something like:
directions = set of (north, south, east, west);
And your set object—which would take up a mere byte's worth of space, back when that mattered—would be something you could do simple set operations with. The advantage of this over enumerating, besides the obvious, is that if you added a new possible element to the set, it was very easy:
directions = set of (north, south, east, west, up, down);
And you could do something like:
all_possible_things = set of (north, south, east, west, stairs, monster, treasure);
Even further back than that, you'd just have a byte and do the bit math yourself (which is really what's going on behind the scenes with Pascal's set type):
I := I or 8
And you'd just have to remember that the number 8 meant "east".
Now, I thought I saw, somewhere in the github for the Grid project, the assertion that you no longer need to use an enum. Even so, I think we should build our own instead, for a few reasons:
"Grid" (the existing project) is severely out-of-date, which would be fine, except that it uses a lot of Gradle stuff that's also severely out-of-date. I made it work but I don't know that it's a particularly helpful project to explain the process, and I don't want anyone getting lost.
"Grid" is oriented around this concept of state as one entry in a finite, enumerable list, and I'd like to have our grid have more of a "Here's a place any number or combination of things can be put" feel.
Goldilocks syndrome: "Grid" does not necessarily do everything want and also does things we don't want. This is very common with off-the-shelf components.
It's good experience. Or at least, it's experience. We won't know if it's good or bad till we've had it.
I'm not crazy about stuff like this, either:
private Optional<Consumer<Cell<State>>>
I suppose generics are the tribute that static typing systems pay to dynamic ones. Perhaps, however, we can define a nice little interface to serve our needs, rather than nesting generics.
Nonetheless, there's a lot of good to be gleaned from "Grid". The three basic classes it has are: Cell, GridView and GridModel. Cell has the main feature we wanted from our tic-tac-toe adventure: The ability to detect a mouse over and also to know where it was in its parent.
GridModel reflects the state of the underlying game board, which you may recall is not unlike what we ended up with in tic-tac-toe. "Grid"'s version of this is probably more dynamic than what we need: I think we're going to (for starters, anyway) have our grid's dimensions set up front.
GridView extending StackPane and being composed of layers (one pane for the root, where the cells will go, and one for guidelines) seems like a good starting place, even if we only have one layer.
So let's build!
Basic Glitches
How simple can we make our grid component? As we say in the business world, what's our MVP? I initially had the idea of making the grid contain a bunch of panes that would control the game objects but—do we need that? Is a grid really just...a state of mind?
That's probably going too far, but let's say we take a pane and do this:
package fxgames;
import javafx.scene.layout.StackPane;
public class Grid extends StackPane {
private int colCount;
private int rowCount;
public Grid(int columns, int rows) {
colCount = columns;
rowCount = rows;
}
}
Well, we haven't really done anything yet, right? Not anything useful, anyway. To be useful, we need some methods like:
public Coord getCoord(double x, double y) ...
public Rect getArea(Coord c)...
public void addObjectAt(Coord c, Node n)...
public void colorCell(Coord c)...
public void onResize()...
Coord is from the first series and is just:
package fxgames;
public class Coord {
public int x;
public int y;
public Coord(int aX, int aY) {
x = aX;
y = aY;
}
}
Rect would be something similar, only also containing a height and width property, and it would be doubles rather than integers, since it reflects a screen coordinate. It'd actually be a lot like the JavaFX Rectangle object, except that it wouldn't be an actual Node descendent. Just a dumb record. But maybe returning an actual Rectangle shape would be helpful—we'll have to ponder that one.
The addObjectAt would allow us to place objects at specific grid coordinates, something like:
addObjectAt(new Coord(2, 2), anObject);
And we'll have some logic for placing the object, let's say dead-center of the grid. We'll quickly move away from this to interacting with some kind of GridModel, but this will help us get the show on the road.
Same with the colorCell method. The idea there is that we can color the grid so that we know where Grid thinks its cells are. This is going to be very helpful for debugging, I suspect especially when we start talking grid borders and cell borders.
And resizing! Resizing is gonna be critical, and I frankly have no idea how it's going to work. The resizing is done all the way up at the stage level, so it may be the only way we can hook into it is to keep a reference to the stage inside Grid
itself.
So, let's see if we can pull this off!
Baby Steps
First the getCoord method. We could just about get away with this:
public Coord getCoord(double x, double y) {
return new Coord(x % colWidth, y % rowHeight);
}
Except we have no colWidth or rowHeight properties, nor any guarantee that x and y will be in range at all. They could be passing us -100,100 for a grid that's 50x50 pixels. Let's remedy the colWidth and rowHeight issue by making functions:
public double colWidth() {
return widthProperty().doubleValue() / colCount;
}
public double rowHeight() {
return heightProperty().doubleValue() / rowCount;
}
I want to use functions here rather than a pre-initialized object property because I'm still fuzzy on how we're going to work with the dimensions of the object and the resizing action.
For keeping the coordinate legal, here are some dubious, but probably correct functions:
public int boundCol(double v) {
return (v < 0) ? 0 : (v > colCount) ? colCount : (int) v;
}
public int boundRow(double v) {
return (v < 0) ? 0 : (v > rowCount) ? rowCount : (int) v;
}
So, if it's less than zero, we return zero; if it's greater than the number of rows or columns, we return the number of rows or columns; otherwise it's legal and we'll return the actual coordinate.
Put it all together and it looks like:
public Coord getCoord(double x, double y) {
return new Coord(boundCol(x % colWidth()), boundRow(y % rowHeight()));
}
Which is not bad.
If you recall back in series one, I "joked" about being dyslexic, regarding my tendency to mix up columns and rows, exes and whys, and in fact, the tic-tac-toe game is still internally inconsistent—look, it works, okay?—even after I switched things back-and-forth several times.
But let's try to avoid any such shenanigans on this time through by actually having some tests for our grid control. I'm actually a fan, conceptually, of red-green-blue test-driven-development (TDD) which goes something like this:
I want my code to be able to do something, so I write a test that will pass when the code can do said thing. The test fails, of course, because I haven't written the code. This is the RED.
I write the code to pass the test, which turns the RED into GREEN.
I refactor the code written in step 2, as appropriate, and this is BLUE.
This is a very soothing approach to development which, in my experience, has some limitations, and can be especially challenging with GUIs. While there are test suites for JavaFX, we can test out my x/y "dyslexia" with something more basic.
I'm using IntelliJ, so I just create a "test" package as a sibling to the "src" package and use the context menu "mark as test root" option. Then I can go back to the Grid component, pull down the action menu, and build tests for the Grid class. If I select all the methods in the class, I'll get this:
package fxgames;
import static org.junit.jupiter.api.Assertions.*;
class GridTest {
@org.junit.jupiter.api.Test
void boundCol() {
}
@org.junit.jupiter.api.Test
void boundRow() {
}
@org.junit.jupiter.api.Test
void colWidth() {
}
@org.junit.jupiter.api.Test
void rowHeight() {
}
@org.junit.jupiter.api.Test
void getCoord() {
}
}
I have to add jupiter to the input, but again IntelliJ walks me through that. Let's get to writing those tests.
We shouldn't have any "red" if we write our tests because we have already written the code, but let's see. Maybe we made a mistake! Testing the bounds calls is super easy:
class GridTest {
Grid g = new Grid(3, 3);
@org.junit.jupiter.api.Test
void boundCol() {
assertEquals(0, g.boundCol(-1), "Column cannot be less than 0.");
assertEquals(3, g.boundCol(100), "Column should max out at 3.");
}
@org.junit.jupiter.api.Test
void boundRow() {
assertEquals(0, g.boundRow(-1), "Row cannot be less than 0.");
assertEquals(3, g.boundRow(100), "Row should max out at 3.");
}
IntelliJ conveniently allows you to quickly run the tests with a Ctrl+Shift+F10, and we can see these both pass. Particularly in situations where I've written the code first, I like to put in values that I know won't pass, because it is possible to write tests that will never fail and are therefore useless, so I tested these tests with values they should never return, like -1.
Now, colWidth and rowHeight. Well, a grid should have width. And colWidth should be 1/3rd of whatever it is. So, too, with rowHeight and the grid's height. So, something like this:
@org.junit.jupiter.api.Test
void colWidth() {
assertEquals(g.widthProperty().doubleValue() / 3, g.colWidth(),"Column width should be one third of grid width.");
}
This passes. Yet I am suspicious. Let's change the value to something that should never pass:
@org.junit.jupiter.api.Test
void colWidth() {
assertEquals(g.widthProperty().doubleValue() / 10, g.colWidth(),"Column width should be one third of grid width.");
}
The column, which should be 1/3rd of the grid width, and passes the test for being 1/3rd of the grid width, also passes the test for being 1/10th of the grid width. How can it be?
It can be if the grid has a width of zero, which makes sense and is sorta what I expected. We've never set it to anything, it isn't in a parent control that might tell it what to do.
New Dimensions
I don't think there's much we can do about this in terms of just saying, arbitrarily, "Now, Grid, you are 99 pixels wide!" We can set the preferred dimensions, we can set the minimum and maximum dimensions, but literally nothing happens without the object being put into play, somehow.
But we can't just create a scene or stage to put our grid in. Why? Because life is pain, princess, and anyone who says differently is selling something. Less philosophically, it's because JavaFX has to be initialized, and JavaFX objects can only be created on a specific thread which is not the thread tests are created on, apparently.
Searching for "JavaFX Testing" is a dismal undertaking from which one can glean that, to get around this problem: One can paste in a helper class; one can use JemmyFX; one can use TestFX; or something else.
The helper class isn't the worst idea, I suppose. JemmyFX is based off a NetBeans(?) test suite called Jemmy that seems to have peaked 7-8 years ago. TestFX has been updated as recently as a year ago but it's lacking in documentation. It may be the best choice.
However, after struggling and trying a few things, I decided to look at the actual JavaFX code, and what I found was they use a system based on JUnit. Well, IntelliJ set us up with Jupiter (yet another testing framework). Perhaps we can use both.
We need a CountDownLatch, apparently—this syncs up the JavaFX and testing threads—and we need to call a method to initialize FX:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
class GridTest {
static CountDownLatch startupLatch;
static Grid g = new Grid(3, 3);
public static void main(String[] args) throws Exception {
initFX();
}
@Test
void boundCol() {
Assertions.assertEquals(0, g.boundCol(-1), "Column cannot be less than 0.");
Also note that I had to change assertEquals
to Assertions.assertEquals
, which I don't quite understand but presume has to do with the fact that both JUnit4 and Jupiter have assertEquals
calls?
Well, if I arrange my imports like so:
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertEquals;
class GridTest {
I found I didn't need to add Assertions
so, be aware of this possibly confusing thing.
Anyway, the bulk of the code I found at the bottom of the JFX source code looked something like this:
public static class TestApp extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
// Scene scene = new Scene(new Group(), 99, 99);
. . .
}
}
@BeforeClass
public static void initFX() throws Exception {
startupLatch = new CountDownLatch(1);
new Thread(() -> Application.launch(TestApp.class, (String[])null)).start();
Assertions.assertTrue(
startupLatch.await(15, TimeUnit.SECONDS), "Timeout waiting for FX runtime to start");
}
@AfterClass
public static void tearDown() {
Platform.exit();
}
Indeed, this will all work, except for the line commented out initializing scene
. When that's included, we get the dreaded:
Error: JavaFX runtime components are missing, and are required to run this application
But! What? How?! If you were scarred by your first attempts to use JavaFX because the code had been removed, this could be quite discouraging. Remember, though, all the way back in the beginning, we had to tell IntelliJ where to find the JavaFX libraries.
In fact, we had to tell it twice! Once for developing, then again for being able to run. Well, I think we can guess that we need to tell it again for testing. All these three situations (testing, development, execution) have different environments, which is a good thing, even if it's annoying occasionally.
So, in our test environment we have to modify the VM options (which are, once again, somewhat hidden—currently Alt+V will reveal them) to include the JavaFX options. Now we can run our test, so let's make sure our existing tests work, then try to set a width.
public static class TestApp extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(new Group(), 99, 99);
scene.setRoot(g);
primaryStage.setScene(scene);
g.setMinHeight(99);
g.setMaxHeight(99);
g.setPrefHeight(99);
System.out.println(g.heightProperty().doubleValue());
}
}
OK, if we run this, we'll see the 99 appear in the console, which is good. And if we go test the dimensions of the column width:
@org.junit.jupiter.api.Test
void colWidth() {
assertEquals(g.widthProperty().doubleValue()/3, g.colWidth(), "Column width should be one third of grid width.");
}
We'll discover that this passes. But—and here's the value of red-green testing—what if we do something silly like:
@org.junit.jupiter.api.Test
void colWidth() {
assertEquals(g.widthProperty().doubleValue()/100, g.colWidth(), "Column width should be one third of grid width.");
}
It will still pass! Even though we're saying the column width should be one one-hundredth of the width! What's going on there is pretty easy to figure out: the width of g is zero.
So, even though we're adding g to a scene, and we are setting its width and height—and verifying this fact!—that value isn't set when these tests are running and failing. Well, what if instead of initializing g at the top, we initialize it in the constructor:
public static class TestApp extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(new Group(), 99, 99);
g = new Grid(3, 3);
g.setMinHeight(99);
Now if we run it...mmm...okay, a bunch of "g is null" errors, which means that this constructor isn't being run prior to the rest of our tests, which is of course precisely the issue. Well, wait, this makes sense because the tests are outside of the TestApp class. What if we move the methods inside?
OK, that doesn't seem to work either. I wonder if this is because we've mixed Jupiter in with the other tests. Sometimes the help IntelliJ gives you can work against you.
I've scanned all the JavaFX books at O'Reilly from the last five years and can't find one that covers testing. So, back to the source code. Here's a test from the library for Accordion controls, which does sorta what we're doing:
public class AccordionTitlePaneLeakTest {
static private CountDownLatch startupLatch;
static private Accordion accordion;
static private StackPane root;
static private Stage stage;
So, it's got the boilerplate elements startupLatch
, root
and stage
, as well as the thing actually being tested, accordion
, and they're all static and private. We don't need a StackPane
, so we'll just have:
private static CountDownLatch startupLatch;
private static Grid g;
private static Stage stage;
Next, they create their scene, stage and root:
public static class TestApp extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
stage = primaryStage;
accordion = new Accordion();
root = new StackPane(accordion);
stage.setScene(new Scene(root));
stage.setOnShown(l -> {
Platform.runLater(() -> startupLatch.countDown());
});
stage.show();
}
}
So, let's take our TestApp and extend it with our stuff the same way:
public static class TestApp extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
stage = primaryStage;
g = new Grid(3, 3);
g.setMinHeight(99);
g.setMaxHeight(99);
g.setPrefHeight(99);
stage.setScene(new Scene(g));
System.out.println(g.heightProperty().doubleValue());
stage.setOnShown(l -> {
Platform.runLater(() -> startupLatch.countDown());
});
stage.show();
}
}
Hmmm. We didn't have the stage.setOnShown
business before. I didn't think it mattered whether or not the stage was actually shown. The BeforeClass
call looks familiar:
@BeforeClass
public static void initFX() throws Exception {
startupLatch = new CountDownLatch(1);
new Thread(() -> Application.launch(TestApp.class, (String[])null)).start();
Assert.assertTrue("Timeout waiting for FX runtime to start", startupLatch.await(15, TimeUnit.SECONDS));
}
IntelliJ is rather insistent we should convert the Assert to a Jupiter call. Man, I hate introducing variables like that, but we trusted IJ with the whole using Jupiter in the first place. If it doesn't work out, we may just have to go back to vanilla.
Assertions.assertTrue(startupLatch.await(15, TimeUnit.SECONDS), "Timeout waiting for FX runtime to start");
The AfterClass
is different, but we don't really care, we'll just paste it in directly:
@AfterClass
public static void teardownOnce() {
Platform.runLater(() -> {
stage.hide();
Platform.exit();
});
}
Finally—good Lord, Java excels in boilerplate above all else, doesn't it?—there's the test:
@Test
public void testForTitledPaneLeak() throws Exception {
TitledPane pane = new TitledPane();
accordion.getPanes().add(pane);
WeakReference<TitledPane> weakRefToPane = new WeakReference<>(pane);
pane = null;
accordion.getPanes().clear();
for (int i = 0; i < 10; i++) {
System.gc();
System.runFinalization();
if (weakRefToPane.get() == null) {
break;
}
Util.sleep(500);
}
// Ensure accordion's skin no longer hold a ref to titled pane.
Assert.assertNull("Couldn't collect TitledPane", weakRefToPane.get());
}
OK, let's try to shoehorn our tests into that format. Once again, IJ will insist on these being Jupiter tests:
@org.junit.jupiter.api.Test
void boundCol() {
assertEquals(0, g.boundCol(-1), "Column cannot be less than 0.");
assertEquals(3, g.boundCol(100), "Column should max out at 3.");
}
If we run these, we'll discover the exact same problem. g
is still null. Suspecting Jupiter as the issue, let's change our tests to just be @Test.
@Test
public void boundCol() {
assertEquals(0, g.boundCol(-1), "Column cannot be less than 0.");
assertEquals(3, g.boundCol(100), "Column should max out at 3.");
}
Run the tests and—holy cats! We're in business. The tests are passing again, or at least some are. All but gridDims
, in fact:
org.opentest4j.AssertionFailedError: Dimensions are 99x99. ==>
Expected :120.0
Actual :99.0
But we don't actually need gridDims
! In fact, we can not bother with the (attempted) setting of the Grid component's dimensions at all:
public static class TestApp extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
stage = primaryStage;
g = new Grid(3, 3);
stage.setScene(new Scene(g));
System.out.println(g.heightProperty().doubleValue());
stage.setOnShown(l -> {
Platform.runLater(() -> startupLatch.countDown());
});
stage.show();
}
}
Now, when I run this, the grid dimension comes out as 1,424, which seems a little wide, but do we really care? No, we do not.
JavaFX has an odd and idiosyncratic way of managing the screen. The whole question of who's in charge of what size things will be is by far the most challenging thing we've dealt with to date, and at some point we will have to face the music and really figure out what's going on.
But Today Is Not That Day!
We've raised a number of questions. Like, we're still using Jupiter-based assertions though we're not generally using Jupiter, I guess. I'm also getting this troubling error:
Jul 17, 2021 4:10:13 PM com.sun.javafx.application.PlatformImpl startup
WARNING: Unsupported JavaFX configuration: classes were loaded from 'unnamed module @43f4d51'
But from what I can tell, it doesn't really affect us. We may have to come back to it later when we want to jar everything up. In the meantime, let's finish up the first of our damn tests!
Onward
Let's finish up our basic tests:
@Test
public void rowHeight() {
assertEquals(g.heightProperty().doubleValue()/3, g.rowHeight(), "Row height should be one third of grid height.");
}
This works. (And I broke it in a couple of ways to make it fail to make sure it's testing what I think it's testing. All is well. I'm tellin' ya, there's something to red-green-blue.)
Now we just need to test the coordinates.
@Test
public void getCoord() {
Coord c = g.getCoord(0, 0);
assertEquals(c.x, 0);
assertEquals(c.y, 0);
}
This also passes but if you've been paying attention, you surely realize the hazards of testing something using all 0s like this. There are many, many ways our grid could be wrong and return 0.
How much testing one does is a serious aspect of TDD. Because tests are also code, and must be maintained and can easily consume all the time in the world for this maintenance. They can end up making design weirdly brittle because one can hesitate to make changes just considering all the tests that might have to be updated. And then the tests can end up neglected and commented out.
We're, of course, nowhere near this point. Let's add some more tests to getCoord.
double h = g.heightProperty().doubleValue();
double w = g.widthProperty().doubleValue();
Coord d = g.getCoord(h, w);
assertEquals(c.x, 3);
assertEquals(c.y, 3);
In this case, we're looking for the bottom-right corner of the grid, which should also be the maximum coordinate the grid can return. This fails because c.x
is 0
instead of 3
. Wait, why would c.y
work and not c.x
?
And here's something even more unfortunate: The actual highest coordinate should be 2
, not 3
. One of the most annoying things about finding two issues at once is figuring out which one to fix, especially when one may impact the other.
Well, let's go back and max out the coordinates correctly first, since that's pretty fundamental to every other test. boundCol
and boundRow
should be:
public int boundCol(double v) {
return (v < 0) ? 0 : (v >= colCount) ? colCount - 1 : (int) v;
}
public int boundRow(double v) {
return (v < 0) ? 0 : (v > rowCount) ? rowCount - 1 : (int) v;
}
This, of course, will break the other tests, but they are just as easily remedied:
@Test
public void boundCol() {
assertEquals(0, g.boundCol(-1), "Column cannot be less than 0.");
assertEquals(2, g.boundCol(100), "Column should max out at 2.");
}
@Test
public void boundRow() {
assertEquals(0, g.boundRow(-1), "Row cannot be less than 0.");
assertEquals(2, g.boundRow(100), "Row should max out at 2.");
}
And now we're back to where we were, with just one test failing:
org.opentest4j.AssertionFailedError: expected: <0> but was: <2>
Expected :0
Actual :2
And on this line:
assertEquals(c.x, 2);
Oh, but wait, now it seems obvious, doesn't it? We're still checking against c
instead of our newly declared d
coordinate.
Generally, resolving a fundamental error helps in untangling errors that occur over it, even though the temptation can be to try to live with a "minor" problem at the base and untangle the ones over it.
It's not necessarily obvious but the reason the c.x
test fails and the c.y
test does not is because the instant the test runner hits an assertion in a test that fails, it stops and fails the whole test. So even if the next test is:
assertEquals(c.y, 20);
you'll never see an error for that. It might be a sign to put the tests all in their own routines, but for now let's just fix:
Coord d = g.getCoord(h, w);
assertEquals(d.x, 2);
assertEquals(d.y, 2);
Now we're back passing again.
The question when writing tests is: What proves that the code works? Mathematically, the answer is nothing. You cannot prove that any code works without writing more code than you're trying to test the validity of. And then you cannot prove that your test code works without writing the more code than your test code. And so on.
So, we have to decide a practical "end". For purely mathematical formulae, you can't really do much other than sanity check. I mean, if you've got a function that does sums, you can't run all the numbers through it and check against their answers.
When your code branches in some fashion, you test the branches, which is what we've done here. The bound routines look like this:
return (v < 0) ? 0 : (v >= colCount) ? colCount - 1 : (int) v;
We could test a negative coordinate, I suppose.
When your code changes state, you check the state. We don't have any of that yet.
You're also, in a very real way, expressing your understanding of what your code is literally meant to do. That's why you some time see people advocating a style of TDD that works like this:
assertEquals(sum(2, 2), 4); // 2+2=4
then writing a sum method that works like this:
int sum(int op1, int op2) {
return 4
}
In other words, you write exactly what the test tests and nothing more so that you're forced to be more explicit about what is expected.
assertEquals(sum(i, j), i+j); // i+j = sum(i, j);
A tautological example, but you get the point: Specifications are either always sloppy or inadequate. In many magical cases, they are both.
Test-driven development is a kind of self-defense that establishes a baseline of agreed upon behavior. Keep in mind, however, as Rich Hickey points out, they're like guardrails on a highway: They can keep you from going off the road completely but they don't provide any protection for going the wrong way entirely.
The "dunslip" branch of the fxgames github is available here .