{
    "type": "tag",
    "title": "Testing",
    "canonical": "https://www.dealmobile.co.uk/writing/tag/testing",
    "path": "/writing/tag/testing",
    "url": "https://www.dealmobile.co.uk/writing/tag/testing",
    "markdown": "https://www.dealmobile.co.uk/writing/tag/testing.md",
    "json": "https://www.dealmobile.co.uk/writing/tag/testing.json",
    "feed": "https://www.dealmobile.co.uk/writing/tag/testing.rss",
    "items": [
        {
            "type": "article",
            "title": "A 30-minute test suite in five minutes",
            "url": "https://www.dealmobile.co.uk/writing/a-30-minute-test-suite-in-five",
            "markdown": "https://www.dealmobile.co.uk/writing/a-30-minute-test-suite-in-five.md",
            "json": "https://www.dealmobile.co.uk/writing/a-30-minute-test-suite-in-five.json",
            "summary": "Cutting every test off from the real world let Mullet’s tests (about 17,700 when I wrote this) run in parallel, and flushed out a real Kimi call and a fixture deleting a real file.",
            "published": "2026-10-25"
        },
        {
            "type": "article",
            "title": "Gates, not a model’s word",
            "url": "https://www.dealmobile.co.uk/writing/gates-not-a-models-word",
            "markdown": "https://www.dealmobile.co.uk/writing/gates-not-a-models-word.md",
            "json": "https://www.dealmobile.co.uk/writing/gates-not-a-models-word.json",
            "summary": "How Mullet 2 decides a coding step is done: eight gates that read the diff and the test results, and a reviewer that can block a step but never pass one.",
            "published": "2026-10-25"
        },
        {
            "type": "article",
            "title": "Mullet 2, coding jobs you can leave running",
            "url": "https://www.dealmobile.co.uk/writing/mullet-2-coding-jobs-you-can-leave-running",
            "markdown": "https://www.dealmobile.co.uk/writing/mullet-2-coding-jobs-you-can-leave-running.md",
            "json": "https://www.dealmobile.co.uk/writing/mullet-2-coding-jobs-you-can-leave-running.json",
            "summary": "Mullet 2 plans a coding job, runs it unattended in a sandboxed clone, and passes each step only on evidence. Why I rebuilt it, and how to try it.",
            "published": "2026-10-25"
        },
        {
            "type": "article",
            "title": "Testing an API client without the network",
            "url": "https://www.dealmobile.co.uk/writing/testing-an-api-client-without-the-network",
            "markdown": "https://www.dealmobile.co.uk/writing/testing-an-api-client-without-the-network.md",
            "json": "https://www.dealmobile.co.uk/writing/testing-an-api-client-without-the-network.json",
            "summary": "How the Puzzler PHP SDK signs its requests and turns HTTP errors into exceptions, and how one protected method lets the tests check both with no credentials and no connection.",
            "published": "2026-10-21"
        },
        {
            "type": "article",
            "title": "Taxing a household one earner at a time",
            "url": "https://www.dealmobile.co.uk/writing/taxing-a-household-one-earner-at-a-time",
            "markdown": "https://www.dealmobile.co.uk/writing/taxing-a-household-one-earner-at-a-time.md",
            "json": "https://www.dealmobile.co.uk/writing/taxing-a-household-one-earner-at-a-time.json",
            "summary": "How Where Should I Live taxes each earner alone, lays a country’s joint and family rules on top, and proves that one person’s figures haven’t moved at all.",
            "published": "2026-10-21"
        },
        {
            "type": "article",
            "title": "One JSON shape for RSS, Atom and RDF feeds",
            "url": "https://www.dealmobile.co.uk/writing/one-json-shape-for-rss-atom-and-rdf",
            "markdown": "https://www.dealmobile.co.uk/writing/one-json-shape-for-rss-atom-and-rdf.md",
            "json": "https://www.dealmobile.co.uk/writing/one-json-shape-for-rss-atom-and-rdf.json",
            "summary": "How RSS to JSON turns three feed formats into one predictable array, and the namespace cases that took two attempts to get right.",
            "published": "2026-10-21"
        },
        {
            "type": "article",
            "title": "Finding the dull parts of a screen recording",
            "url": "https://www.dealmobile.co.uk/writing/finding-the-dull-parts-of-a-screen-recording",
            "markdown": "https://www.dealmobile.co.uk/writing/finding-the-dull-parts-of-a-screen-recording.md",
            "json": "https://www.dealmobile.co.uk/writing/finding-the-dull-parts-of-a-screen-recording.json",
            "summary": "How Reelify scores each frame of a screen recording by how much it changed, turns the scores into cuts with a margin around them, works out a speed to fit a target length and cuts out each segment, checked against a test clip with known content.",
            "published": "2026-10-21"
        },
        {
            "type": "article",
            "title": "Writing down an upload protocol and testing servers against it",
            "url": "https://www.dealmobile.co.uk/writing/testing-servers-against-an-upload-protocol",
            "markdown": "https://www.dealmobile.co.uk/writing/testing-servers-against-an-upload-protocol.md",
            "json": "https://www.dealmobile.co.uk/writing/testing-servers-against-an-upload-protocol.json",
            "summary": "How TurboPush’s chunked upload protocol is written down in PROTOCOL.md, how a small Node suite checks any server against it over HTTP, and the two bugs it found in my own PHP endpoint.",
            "published": "2026-10-20"
        },
        {
            "type": "article",
            "title": "Fetching a feed from a URL you don’t trust",
            "url": "https://www.dealmobile.co.uk/writing/fetching-feeds-from-untrusted-urls",
            "markdown": "https://www.dealmobile.co.uk/writing/fetching-feeds-from-untrusted-urls.md",
            "json": "https://www.dealmobile.co.uk/writing/fetching-feeds-from-untrusted-urls.json",
            "summary": "How RSS to JSON stops a feed URL from reaching private addresses on the server’s own network, by checking every address a host resolves to, pinning the connection to the one it checked, and checking every redirect again.",
            "published": "2026-10-20"
        },
        {
            "type": "article",
            "title": "A live calendar feed and badge from a Cloudflare Worker",
            "url": "https://www.dealmobile.co.uk/writing/calendar-feed-and-badge-from-a-worker",
            "markdown": "https://www.dealmobile.co.uk/writing/calendar-feed-and-badge-from-a-worker.md",
            "json": "https://www.dealmobile.co.uk/writing/calendar-feed-and-badge-from-a-worker.json",
            "summary": "How the Solar Calendar serves a feed people can subscribe to in their calendar app, and a badge with today’s date, from a small Cloudflare Worker that runs the same date code as the app.",
            "published": "2026-10-20"
        },
        {
            "type": "article",
            "title": "One source of truth behind a static app",
            "url": "https://www.dealmobile.co.uk/writing/one-source-of-truth-behind-a-static-app",
            "markdown": "https://www.dealmobile.co.uk/writing/one-source-of-truth-behind-a-static-app.md",
            "json": "https://www.dealmobile.co.uk/writing/one-source-of-truth-behind-a-static-app.json",
            "summary": "How the second version of Where Should I Live keeps its data in a headless CMS, ships it as static JSON, and refuses to build if the data has drifted.",
            "published": "2026-10-19"
        }
    ]
}