Part 0: TCR Ray Tracer Challenge— Intro

Thomas Deniffel
5 min readNov 15, 2018

--

This blog post is the first of a potentially a long series of test && commit || revert (TCR) applied. As a case study, I picked my recently bought book “The Ray Tracer Challenge,” which is a beautiful book, I didn’t read so far. The challenge is — you guessed it — to build a Ray Tracer. Special: The Book only explains the background and provides Tests. We have to implement the code yourself.

Note: If you are want to try TCR along with me, buy a copy of “The Ray Tracer Challenge”!

Note: Not quite sure, what TCR is and what is different to TDD? Give this article a look. It provides an overview.

The Basic Building Blocks

A tuple

First of all, we need a tuple, to represent a 3D-Point. In real-world code, we should use some library, but in this tutorial, we build everything from scratch. Start with a test.

@Test
public void tupleConstructsCorrectly() {
Tuple t = new Tuple(4.3, -4.2, 3.1, 1.0);
assertEquals(4.3, t.x, 0.0001);
assertEquals(-4.2, t.y, 0.0001);
assertEquals(3.1, t.z, 0.0001);
assertEquals(1.0, t.w, 0.0001);
}

This test will not compile, because we don’t have a Tuple class yet. Let’s run the tests anyway:

$ ./test

The tests script triggers our TCR-Behaviour that we defined here. Let’s revisit it without explanation:

$ cat test
./scripts/buildIt && (./scripts/runTests && ./scripts/commit || ./scripts/revert)
$ cat scripts/buildIt
./gradlew build -x test
$ cat scripts/runTests
./gradlew test
$ cat scripts/commit
git commit -am working
$ cat scripts/revert
git checkout HEAD -- src/main/

The test claims (as expected) that Java was not able to compile. Let’s fix it.

public class Tuple {    public final double x;
public final double y;
public final double z;
public final double w;
public Tuple(double x, double y, double z, double w) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
}

It was harder to fake than to implement. Therefore I code it directly. We are green! If the fourth component ‘w’ is zero, the Tuple is a vector, if its one, it is a point. But first, we need a factory method instead of a constructor for our first example, because, we want to return subclasses as well. Refactor:

@Test
public void tupleConstructsCorrectly() {
Tuple t = Tuple.of(4.3, -4.2, 3.1, 1.0);
// ...
}
$ ./test

And make it compile:

public class Tuple {    public final double x;
public final double y;
public final double z;
public final double w;
Tuple(double x, double y, double z, double w) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
public static Tuple of(double x, double y, double z, double w) {
return new Tuple(x, y, z, w);
}
}

Green again! Let’s move on with the next test:

@Test
public void tupleWithW1_isAPoint() {
Tuple t = Tuple.of(4.3, -4.2, 3.1, 1.0);
assertTrue(t instanceof Point);
}

We create the class Point for making compile:

public class Point extends Tuple {
private Point(double x, double y, double z) {
super(x, y, z, 1.);
}
}
$ ./test

Red! TCR kicks in and brings us to the last green state. Try again

public class Point extends Tuple {
private Point(double x, double y, double z) {
super(x, y, z, 1.);
}
}
-------------
public static Tuple of(double x, double y, double z, double w) {
if(w == 1.)
return new Point(x, y, z);
return new Tuple(x, y, z, w);
}
$ ./test

This reversion is not lovely with TCR! We have to do two things at once. We have to find a solution to this… Anyway, we are green.

The next step. If ‘w’ is zero, it is a vector:

@Test
public void tupleWith0_aVector() {
Tuple t = Tuple.of(4.3, -4.2, 3.1, 0.0);
assertTrue(t instanceof Vector);
}
$ ./test # fail

And implement it:

public class Vector extends Tuple {
Vector(double x, double y, double z) {
super(x, y, z, 0.);
}
}
--------------------
public static Tuple of(double x, double y, double z, double w) {
if(w == 0.)
return new Vector(x, y, z);
if(w == 1.)
return new Point(x, y, z);
return new Tuple(x, y, z, w);
}
$ ./test # green

We have our tuple, vector, and point. Let’s continue with the operations on them.

Tuples Operations

Let’s start with the plus operator. But, we want to test it, we need an equals operator first:

@Test
public void tupleEquality() {
Tuple t = Tuple.of(1,1,1,1);
assertEquals(t, Tuple.of(1,1,1,1)); assertNotEquals(t, Tuple.of(2,1,1,1));
assertNotEquals(t, Tuple.of(1,2,1,1));
assertNotEquals(t, Tuple.of(1,1,2,1));
assertNotEquals(t, Tuple.of(1,1,1,2));
}
$ ./test # fail

What we can make a pass with a real implementation instead of faking, because it is just too easy (in fact, IntelliJ generated it for me):

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tuple tuple = (Tuple) o;
return Double.compare(tuple.x, x) == 0 &&
Double.compare(tuple.y, y) == 0 &&
Double.compare(tuple.z, z) == 0 &&
Double.compare(tuple.w, w) == 0;
}
$ ./test # green

I didn’t even look at the generated code, because my tests pass. If it had had an unexpected behavior, TCR would have reverted it.

Tuple Plus Operator / Tuple Minus Operator

@Test
public void adding() {
Tuple one = Tuple.of(3, -2, 5, 1);
Tuple two = Tuple.of(-2, 3, 1, 0);
Tuple result = one.plus(two);
assertEquals(Tuple.of(1, 1, 6, 1), result);
}
$ ./test # no compiling

Fake it, til making it:

// in class Tuple
public Tuple plus(Tuple other) {
return Tuple.of(1, 1, 6, 1);
}
$ ./test # green

A tuple is a value object. Therefore plus should create a new instance:

@Test
public void addingImmutable() {
Tuple one = Tuple.of(3, -2, 5, 1);
Tuple two = Tuple.of(-2, 3, 1, 0);
one.plus(two);
assertEquals(Tuple.of(3, -2, 5, 1), one);
assertEquals(Tuple.of(-2, 3, 1, 0), two);
}
$ ./test # green

I call this an experiment: I create a test about a fact to see if it already works. I write regular tests with the intention to implement code and experiments with the aim to document or to learn something about my existing program.

Let’s remove the faked behavior:

public Tuple plus(Tuple other) {
return Tuple.of(
x + other.x,
y + other.y,
z + other.z,
w + other.w);
}
$ ./test # green

I will not test the null-case, because this is too defensive for me!

I copy&paste my code for minus:

@Test
public void subtracting() {
// ...
}
@Test
public void minusImmutable() {
// ...
}
-----------
public Tuple minus(Tuple other) {
// ...
}
$ ./test # Red!!!

Aaarg. Copy&Paste. My code got reverted because I didn’t adjust the test cases. TCR kicked in. Try again! I like this kind of punishment.

$ ./test # green now

In the next post, I will continue with Negation, Multiplication, and Division. I expect it will be quite interesting.

--

--

Thomas Deniffel

Programmer, CTO at Skytala GmbH, Software Craftsman, DDD, Passion for Technology