In Progress
Unit 1, Lesson 21
In Progress

TDD is easy! – with Michal Taszycki

Have you ever heard someone say that TDD is just “too hard” to get started with on some platform? Watch Michal Taszycki put this fear to bed once and for all.

Video transcript & code

(Don't miss the Special Offer below)

Oh boy have I got a treat for you today. And you know what? I'm not going to spoil it for you. I'll just say this: if you've ever heard someone say that test-driven development is just too hard to be workable in a certain technical stack... well, today's guest chef Michal Taszycki has a definitive answer to that objection ;-)

Who is Michal? He's a game programmer and web developer who got his start hacking on the Commodore 64. He has his own series of screencasts at 64bites.com, and you're about to get a taste of just how good he is at that job. Enjoy!


TDD is Easy

Have you ever encountered a situation where you wanted to convince someone that Test-Driven-Development can be easy and even fun?

If you did, you probably heard some of those excuses: - "It takes too much time." - "I would need to write so much code..." - "There are no testing frameworks in my programming language."

Or my favorite: - "There are too many of them, and it's hard to choose."

What if, instead of arguing, you could show them in a couple of minutes that you can practice TDD, and you don't even need a framework.

In fact, you can write one yourself in just a few lines of code in any programming language.

Let's say an assembly.

And not just any assembly, the one used in Commodore 64 thirty years ago.

Let me show you.

First, we need to decide where to place our program in the memory so that it doesn't overwrite anything important. Let's pick a round number for the address. Two-thousand-sixty-four will do.


* = 2064

This will be the entry point, and we can mark it with a label called main.


main:

To see that the program is doing something, let's change the border color.

First, we need to load the color index to the accumulator register. We could put a number here, but the compiler has handy built-in constants for all 16 colors available on Commodore 64.


lda #RED

With the accumulator holding the color value, we can store it into the address 53280, which is hardwired to the border color register of the graphical chip.


sta 53280

Finally, we can return to the operating system using the rts instruction.


rts

When we compile the program and load it in the emulator, it doesn't start automatically, even though it does execute the RUN command.

Commodore 64 starts in a BASIC interpreter and expects BASIC commands and programs by default.

To jump into an assembly routine, we need to execute a SYS command with the address of the entry point of our program.


sys 2064

If we prepend this command with a number, it won't be executed automatically but stored as a basic program in the memory.


10 sys 2064

Now, the RUN command will behave as expected.

It will start the BASIC program that uses the SYS command to start the assembly program. Which, in turn, changes the border color to red and returns to the BASIC interpreter.

Doing that manually is tedious. So we can use a built-in macro that injects the same basic program into the memory and places the entry point at the correct place.


:BasicUpstart2(main)
  main:

Now when we compile the program and pass it to the emulator, it will start everything automatically with the RUN command.

Meanwhile, we have already implemented half of our testing framework. The border changes to red and this will signify that our tests fail.

Let's mark the address of that small routine with an appropriate label.


tests_fail: lda #RED

Let's also mark the address of the rts command at the end of the program with the end label.

To finish the testing framework we also need to mark the success state. So let's make a similar routine that will change the border color to green.

Let's mark it with a label named: tests_pass, load the green color index into the accumulator and store it into the border color address.


tests_pass: lda #GREEN sta 53280

We also need to skip over the failure state, so the green color is not overwritten. To do that we will jump unconditionally to the end of the program.


jmp end

And that's it!

Just look at the code.

We have a functional testing framework written in just ten lines of assembly.

Now it's time to start our TDD cycle.

We want to write a macro that adds two numbers together.

.macro add(x, y) {

} 

Before we write a test case, let's explain it first with a comment.

We expect that adding one and two will result in three.


// adding 1 and 2 results in 3

So we call the macro with one and two as arguments.


:add(1, 2)

Compare the result with three.


cmp #3

And use a branch instruction to jump to the tests_fail subroutine only if it was not equal.


bne tests_fail

If that happens, we will change the border color to red right before exiting.

Otherwise, we will continue executing further assertions and if none of them fail the border will end up green.

At the moment the macro is empty, so our tests fail.

Let's add a proper implementation.

We need to load the first argument into the accumulator.


lda #x

Then we will clear the carry flag, which needs to be done before the addition.


clc

And then we will add a second argument to the accumulator, which will now store the result.


adc #y

If we run the program, the border color changes to green and proves that our macro passes the test.

Now would be a time to add more test cases, but let's stop it here and appreciate how little code we need to start doing Test Driven Development in an extremely low-level language.

That's great... But one might argue that this is just an overly simplistic case.

That was my concern as well, I was curious if it was possible to write a proper testing framework for Commodore 64 in assembly.

So I wrote it.

The result is 64spec.

It's heavily inspired by the RSpec framework that might be somewhat familiar to you.

You can find it on GitHub, and it can be used in any project simply by importing one file.


import "64spec.asm"

To start using it, we just need two lines of boilerplate code at the beginning and the end of the program.


sfspec: :init_spec()

:finish_spec() 

We are going to test drive the same macro as in the previous example.


.macro add(x, y) {

} 

Again, before adding any assertions, we are going to describe them in the source code.


:describe("add")

But instead of using comments, we can describe each test case with a string.

:it("returns 3 when given 1 and 2")
:it("returns 5 when given 4 and 1")

The framework provides various assertions. In this case, we need to confirm that the accumulator register contains the result after executing the macro.


  :add(1,2) 
  :assert_a_equal #3

...
  :add(4,1)
  :assert_a_equal #5

After we run the program, we can see that it not only changes the border color. It also reports which test cases failed.

We can confirm that by deliberately making only one of them pass.

So let's load the accumulator with three in the macro.


lda #3

Now, only the first test is marked as green.

But if we implement the macro in the same way as before and add two arguments together,


lda #x
clc
adc #y

the border and both descriptions become green, and the 64spec reports that all our tests passed.

This feature works exactly as in Rspec. It creates a living documentation out of test descriptions.

It seems that, it's not only possible to write a very simple testing framework in what? Ten lines of assembly code?

We can even have a sophisticated one running on the same thirty-years-old computer.

If that won't convince someone that TDD is easy and fun, I don't know what will.

By the way, you might be wondering how could we test the 64spec itself. As any testing framework it required a considerable test suite that unfortunately cannot fit in the 64 kilobytes of RAM. Let's just say that solving that challenge was quite interesting. It involved writing a command line runner in the language I didn't know and having an emulator, running inside of a virtual machine, crash on purpose.

But, that's a story for another time.

Happy hacking!

[su_box title="Special Offer at 64bites" box_color="#283037"]Avdi says: "Having watched a few episodes, I reiterate my original position: sooooooo coool. And Michal has really done a top-notch job on writing, production, audio, everything."  Now Michal has made a special offer for RubyTapas subscribers at https://64bites.com/ruby-tapas/[/su_box]

Responses