Book · Companion code

Red, Green, Copperbars

Growing an AmigaBASIC-to-PHP transpiler with PHPUnit, PHPStan, and Infection

Test-driven development, learnt on a project that consists almost entirely of the boundaries that are usually written off as untestable: timing, terminal, signals, pixels, and sound. The book grows a transpiler, a graphics stack, a four-voice synthesiser, and a demo engine test-first. It introduces PHPUnit, PHPStan, and Infection from zero along the way.

  • Coming in 2027
  • Ebook in English, as PDF and EPUB
  • With the complete source code
  • No prior knowledge of PHPUnit, PHPStan, or Infection needed

A toy example teaches the tool, not the discipline

For most of the years since he created PHPUnit, Sebastian Bergmann wanted to write a book about test-driven development and refused to, because every example he could think of was a toy. A calculator, a shopping cart, or a bank account can teach the mechanics of a tool. The discipline only shows itself under load: when there is timing, or sound, or a screen full of pixels, and someone at the back of the room says that you cannot test that.

The example he had refused to invent turned out to be a project of his own. It translates programs written in AmigaBASIC, the BASIC that Microsoft wrote for the Commodore Amiga and that shipped with it in 1985, into PHP, renders demoscene effects in a terminal, and sequences effects, text, and four-voice music into one show. Its graphics are pure integer arithmetic, its music is rendered offline and identical down to the byte, its timing sits behind seams, and the whole of it is guarded by a quality gate. The project existed before the book did, and none of its code was written to make an example work.

Nobody reaches for PHP to draw graphics effects or to sequence music, and the book chooses such a project on purpose. In a web application, the excuse is a familiar one: tests are fine for the domain logic, but at the boundaries of the system (database, HTTP layer, queues, the clock) testing is supposedly too hard. This project consists almost entirely of boundaries: timing, terminal, signals, sound. If the discipline holds here, it holds in your application, where the boundaries are tamer.

Cover of Red, Green, Copperbars by Sebastian Bergmann: on a near-black ground, the words Red, Green, and Copperbars in large serif type, each in its own colour on a glowing horizontal bar of red, green, and copper, above them the line “A book by the creator of PHPUnit”, below them the subtitle, the author, and the imprint thePHP.cc Press

Everything in these pictures was drawn in a terminal

The first chapter opens with a show that runs in an ordinary terminal window. Each frame is a grid of the Unicode character ▄, whose background colour paints one pixel and whose foreground colour paints the one below it: two 24-bit pixels in every character cell. Every effect is written in AmigaBASIC and was verified with the original interpreter on an emulated Amiga.

Three horizontal bars in green, red, and copper, each fading out towards its edges, on a black background
Copperbars
A wireframe cube drawn in magenta lines on a black background
Wireframe cube
A red and white chequered ball on a black background
Boing ball
A fractal mountain range in shades of green below a strip of sea and a pale blue sky
Fractal landscape
Blocky flames shading from pale yellow at the bottom through orange and red into black at the top
Fire
The Mandelbrot set in black, surrounded by bands of yellow, orange, red, and magenta
Mandelbrot set
A plasma of soft blobs in green, blue, magenta, and orange flowing into each other
Plasma
Concentric rings in the colours of the rainbow, converging on a point to the right of the centre
Tunnel

Proofs, then examples, then an audit of the examples

The book introduces three tools from zero, each with the one question it answers. Together they form the gate that every change to the project has to pass.

PHPUnit

Does the code behave correctly when it runs?

Unit tests for the smallest pieces, integration tests that compile every effect against known-good output, and end-to-end tests that drive the command-line tools the way a user does. The configuration is strict enough to fail on stray output, warnings, and notices.

PHPStan

Can the code be proven free of certain wrong behaviours without running it?

Level 10, the maximum, with the strictest optional rules switched on, across the source, the tests, and the command-line scripts. Every parameter, return value, property, and constant declares its type.

Infection

Would the tests notice if the behaviour changed?

A required mutation score of 100 %: every change Infection knows how to make, anywhere in the code, makes at least one test fail. The tests do not merely execute the code, they pin it down.

These numbers were not reached by testing harder after the fact. They follow from how the system was built: in small steps, each one forced by a failing test, with every piece of I/O, timing, and randomness behind a seam from the day it was written.

On most codebases, numbers like these are out of reach, and the reasons why are the actual subject of the book. Its chapter on PHPStan also describes the way there for a codebase that was not built this strictly: one level at a time, with a baseline at each step, and a ratchet that only turns one way.

Each chapter introduces one idea, at the moment the example needs it

The project grows in the order a transpiler grows, from a single PRINT statement to a show of eight effects with music, and the testing skills grow in the order a practitioner needs them. From Chapter 6 on, every chapter starts with a test list, works through red, green, and refactor in small steps with real code, and ends with the gate and with exercises that extend the project. What each chapter adds to your testing:

Part I: Getting ready

  1. A demo, a transpiler, and a discipline Test-driven development as a way of making decisions
  2. The toolbox Three tools, pinned and wired into a gate before there is anything to check
  3. Your first test Never trust a test you have not seen fail
  4. Types are tests you do not have to write Static analysis covers what lies between your examples
  5. Who tests the tests? Mutation testing asks whether the tests would notice a change

Part II: The transpiler

  1. Hello, Amiga An acceptance test that stays red until Chapter 9
  2. From characters to tokens Change the representation until the strongest assertion is also the most readable one
  3. From tokens to a tree Dump the tree to text and compare it in one assertion
  4. From tree to PHP The acceptance test goes green without being touched
  5. Rejecting broken programs Error messages are user interface
  6. Expressions Data providers, and when a table is not one
  7. Variables and sigils Triangulation, and the test suite as specification
  8. Control flow Keeping a growing test class habitable
  9. Subroutines and jumps Testing a step that the public API never exposes
  10. DATA, READ, DIM Running exactly the tests you need, fifty times an hour

Part III: The runtime

  1. A screen made of numbers Value equality: stated by the type, pinned by a test, or both
  2. Drawing under test Mutation testing as a method: 82 mutants, 82 killed
  3. Two pixels per character Golden masters, and where to keep them
  4. Taming time Five kinds of test doubles, and a design that needs none of them
  5. Deterministic noise Exact assertions on audio, without a single tolerance
  6. Four voices and a facade Fakes that ship as production code and work as measuring instruments

Part IV: The demo engine

  1. A show in JSON Parse, do not validate
  2. One frame at a time Testing a protocol around a Fiber
  3. Dissolving between worlds Boundary-value analysis: derive the interesting cases instead of guessing them
  4. The whole show Test sizes, test suites, and end-to-end tests that run the tools as a user would

Part V: The quality gate

  1. Coverage that means something Covered is not tested
  2. One hundred per cent mutants Every mutant must die, and how to afford that
  3. Level 10 Analyse the tests as strictly as the code
  4. The gate, the loop, and what comes next One command, and a way of deciding what to build next
  5. Epilogue: Never just nice to have The same gate, read again with coding agents at the keyboard

Appendices

  1. An AmigaBASIC crash course The supported subset of the language in one place
  2. Machine setup Linux, macOS, Windows with WSL2, and a container
  3. Architecture decision records The decisions behind the project, and how to write your own
  4. Dhrystones per second One benchmark, an Amiga 500, and a machine from today

Modern PHP is the only prerequisite

What you need to know

You need to read and write modern PHP comfortably: classes, interfaces, enumerations, closures, namespaces. You do not need to know

  • PHPUnit, PHPStan, or Infection: Chapter 3 writes your first test assuming you have never seen one
  • compilers: lexer, parser, and code generator are as formal as the theory gets
  • AmigaBASIC: the dialect is introduced statement by statement, and Appendix A collects it in one place
  • the demoscene, and you do not need an Amiga

What your machine needs

PHP 8.5 with the pcntl and posix extensions, and a terminal emulator that supports 24-bit colour.

  • Linux or macOS, or WSL2 on Windows
  • ffplay or aplay for the sound; without them, the demo runs silently
  • alternatively, a Containerfile from which Podman or Docker builds an image with everything the demo needs

The book comes with a source code archive of the project: every class, test, and configuration file shown in its pages, at the exact revision the book describes. The versions of PHPUnit, PHPStan, and Infection are pinned in it, so the output printed in the book is the output your terminal shows. When your version of a class drifts from the book’s, you diff it against the real thing.

The gate never asked who wrote the code

The book ends with an epilogue that reads the whole gate again with coding agents at the keyboard. None of the questions the gate asks contains the word “who”: the tests pass or fail in the same way whether the change they judge was typed by you, by a colleague, or by a machine.

Nobody can read code as fast as an agent writes it, so the checking has to be mechanical or it does not happen at all. What stays with you is what was always hard: deciding whether a problem needs code at all, writing the failing test that states the requirement, the design, and the judgement of when to slow down and read. An agent is never better than the specification that constrains it.

Coming in 2027

The book cannot be ordered or pre-ordered yet. Until it is out, many of its topics are covered in the articles and presentations on this site.

Sebastian Bergmann is the creator of PHPUnit, the de facto standard for testing PHP code, and writes its manual. Since 2001 he has been helping teams build maintainable and well-tested software as a consultant and trainer, since 2009 as a co-founder of thePHP.cc. He started programming in AmigaBASIC on an Amiga 500; the article From AmigaBASIC to AI tells where that led.

Also coming in 2027: PHPUnit Best Practices, which collects the recommendations that the PHPUnit manual states in passing, each followed by the reason behind it.

PHPUnit Best Practices

Black-and-white portrait of Sebastian Bergmann outdoors in front of blurred foliage
Photo: Helge Strauss