Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
1

On my new ‘Valuable feedback, fast’ course

from On Test Automation [alt+shift+b] in programming

A couple of weeks ago, I published the training page for a brand-new course that I’m looking to run at least a couple of times in 2026. The course is called ‘Valuable feedback, fast’, and in this blog post, I’d like to share a little more about why I created this course, why it is designed in the way I have in mind and what it will look like. Why did I create this course in the first place? I’ve been running workshops and training courses for close to a decade now, and I really, really enjoy it. There’s just something very rewarding in sharing your knowledge and experience and helping others learn something new. Until now, the most important part of most of the courses I have been offering so far has been the ‘hands-on’ part: learning how to use a specific tool, or how to start with implementing a new technique or approach. Next to the how, I always try and teach participants in my course what to do and what not to do with that tool or technique, and why they should or should not use a tool or technique in a specific context or situation. In other words: I try to go beyond ‘teaching people tricks with tools’, and I think I’m doing a pretty good job in that regard. You should really ask the participants themselves if they’d agree, though. I plan to keep offering this type of workshop or training course in the future. It’s useful, fun, there’s demand for it, and it pays the bills, so I don’t see a reason to stop. However, I think there’s a need for a different kind of course in the area of test automation, too. A course that does not focus on a specific tool, technique or practice, but one that focuses on ‘the bigger picture’ of test automation. One that teaches teams and organizations how to be successful with test automation. How test automation fits into their organization, their tech stack and their way of working. What common pitfalls one encounters on the road to test automation success, and how to navigate those pitfalls. In other words, a course that teaches...
7th Jan 2026

Stay updated

Get a weekly newsletter with the top 5 articles worth reading every week.

More from On Test Automation

Should testers write unit tests?

This post was previously published through my newsletter on May 25, 2026. From time to time, I will republish newsletter issues on my blog here if I think people (and search engines) might benefit from it. If you want to read everything I’ve written once it is posted, I recommend signing up for the newsletter. A few months ago, I had the pleasure of delivering a keynote at an internal developer conference for one of the largest banks in the Netherlands. While I still don’t really see myself as a keynote speaker - I enjoy doing practical, hands-on sessions much more - I had a great time talking about challenges of E2E testing, breaking down E2E tests and the test automation quadrant model that I use in my thinking, speaking and teaching about test automation these days. However, the keynote or the contents of it are not what I want to talk about this week. Instead, it was a question that came up during the Q&A after the talk that triggered me to write this post. That question was “Do you think that testers should be writing unit tests?” It’s not the first time I heard that question. In fact, I’ve seen and heard whether or not testers should be involved in unit testing being discussed regularly in the past, with arguments for and against the various standpoints. However, I was under the impression that we, collectively, had found some sort of answer to the question and moved past this point by now. Guess I was mistaken. I tried to give the person asking the question an answer as well as I could, but given that there is quite a bit to unpack around the topic, I’m not sure if I gave them the entire story. So, that’s what I’ll try and do here. Who knows they might even read it… So, should testers be writing unit tests? Well, my answer is either a ‘yes’ or a ‘no’, depending on how you interpret the question. Before we explore these various interpretations, what is a unit test anyway? Well, by now, I don’t really know anymore, as there are so many definitions floating around, some of them contradicting each other. This is one of the reasons I came up with the test automation quadrant model as an alternative to the well-known automation pyramid model, but again, that’s not the topic of this post. For the sake of the argument, let’s define a unit test as a test that verifies a very small piece of behaviour of our product, without relying on external interfaces like APIs, databases or file systems, for example. With that definition in place, let’s look at two different interpretations of the question of ‘should testers be writing unit tests?’. Interpretation 1: Testers should be responsible for unit testing Well, no, I don’t think they should, no matter how good of a tester they are, and no matter what their coding skills are. Writing unit tests is an activity that should be performed in support of and lockstep with software development. Often, especially in practices like test-driven development, tests are written first, and they drive the design and development of the product. Leaving the writing of these tests to testers, especially if it is done after the product code itself is written, is both inefficient and a potential source of problems. Inefficient, because the product has already been written, yet we only know whether its behaviour matches expectations once the tests are written and run. Also, because there’s a handoff happening between ‘development’ and ‘testing’, which takes up valuable time as the developer will switch to a different task while the tester writes the unit tests. If there’s a problem with the product that is discovered during unit testing, the developer needs to make a context switch back to the original task, and the more context switching you do during the day, the less efficiently you will work and the less you will get done. A potential source of problems, because when the developer only focuses on shipping a potentially working product, they will likely not spend too much time thinking about what to test for, or how to make the product (the code, in this case) easily testable in the first place. Also, the handoff from ‘development’ to ‘test’ I described before leaves open room for different interpretations of what the software should do, leading to potential bugs slipping through and the resulting back-and-forth discussions after the fact. So, no, I don’t think we should leave unit testing to testers. I still sometimes hear about developer who don’t want to or do not know how to write (decent) unit tests, and I think that’s a problem that needs to be fixed at the source, instead of trying to patch it up by someone else writing the unit tests for the already-created product. Interpretation 2: testers should be involved in unit testing If we interpret the question this way, I think the answer should be a resounding ‘yes’, testers should be involved in unit testing. I mean, there’s the word ‘testing’ in the name, why shouldn’t we involve the people who specialize in testing in the process? What that involvement looks like, exactly, depends on the context, of course. Again, ‘being involved’ and ‘being responsible’ are two entirely different things. While I believe that writing unit tests is a development activity, there are a couple of ways in which testers can add value, too: They can review the unit tests to learn about what has been covered already, so that they do not repeat that testing later on They can review the unit tests to identify what has not yet been covered, and either give that back as feedback to a developer, or add the missing tests themselves They can suggest and use techniques like mutation testing to test the tests and find out whether the tests that were written before are actually able to catch meaningful problems I’m sure there are a few more benefits, but these alone should, in my opinion, be enough for any team to not exclude testers from the process of writing unit tests from here on. And yes, that will require some additional skills from both testers and developers. This is where the power of collaboration comes in. I’m a big fan of pair programming and testing, and I’ve seen a lot of good things come from testers and developers pairing up to write, review and improve unit tests. The developer improves their testing skills, the tester learns more about both the product they’re testing and the development process, and in the end, both the entire team and the product itself reaps the benefits. That’s a long answer to a simple question, and I’m sure there are some nuances that I didn’t yet unpack, but generally speaking, these are my views on the question of whether testers should be writing unit tests. Oh, and before you ask ‘but what about integration / end-to-end / performance / security / … tests’? That’s easy. Simply replace ‘unit tests’ and ‘unit testing’ with ‘integration / end-to-end / performance / security / … tests’ and ‘… testing’, and you’ll have my answer. I’ve always found it a little strange that unit tests have traditionally been seen as ‘different’ from other types of tests, as if they’re some kind of special artefact that only developers know how to write. If that’s what you think, too, let me let you in on a little secret: they aren’t special. Unit tests are simply tests that test a small piece of the behaviour of the product that we write, written against and invoking a specific interface of the product: the source code. Other types of tests do exactly the same thing: verify parts of our product behaviour by invoking one or more specific interfaces (APIs, the UI, a database, a queue, …). They just have a different scope, and with that, they verify behaviour at a different scope. That’s all. This, again, is the reason where I think the traditional test automation pyramid model lacks: I really don’t care that much about what is a unit / integration / end-to-end test. All I really care about is tests that produce valuable information about the state and the behaviour of our product in as efficient a way as possible. Unit tests really aren’t any different.

20th Aug 2026 1 votes
I’m (re-)starting a newsletter

Just a quick update to let those of you who bookmarked this blog or who have subscribed to my RSS feed know that I have (re-)started a newsletter. Why a newsletter? As you might know (or not), while I’ve been pretty active on LinkedIn over the years, I do have a love-hate (or rather an appreciate-hate) relationship with that platform. Lately, I’ve been noticing that the pendulum is swinging in the ‘hate’ direction more often, mainly because the ever-changing algorithm used by LinkedIn makes it incredibly hard to predict if people are even going to see what I write. I’d rather publish my thoughts, ideas and other ramblings via a platform that I do control, and that platform will be a newsletter. I’ve had a newsletter in the past, but that only lived for about three months. This time, I intend to keep writing and publishing a new issue every week. The first edition goes out a few hours after I’m writing this, and a new issue will be sent to subscribers every Monday morning around 11 AM CET. But what about the blog? I’ll still publish to the blog, too, but that will be on a much less regular basis. Just like it has been for a while, really. The idea is to post the more ‘technical’ posts, that is, the ones including code, directly to my blog, whereas the ‘text-and-images-only’ posts go through my blog post first. My priority is with the newsletter, though. How to subscribe That’s easy, just go to the subscription page, leave your email address, click the button on the confirmation email and you’re in. I promise I won’t use the newsletter or your email to spam or sell to you. Ever.

11th May 2026 1 votes
The ‘valuable’ in valuable feedback, fast

When I talk about the goals and the purpose of test automation, I often use the phrase ‘valuable feedback, fast’: we use tools to support our testing to help us get valuable information about the state of our product in the most efficient manner possible. The ‘fast’ part of ‘valuable feedback, fast’ is pretty self-explanatory for most people: as build and release cycles are becoming shorter, teams want to be informed timely about any unexpected changes in behaviour of their product, often after every change they make to that product. Tools can help them achieve that by running quick, focused tests automatically when a change is made or committed to version control. Of course, it takes plenty of hard work to write those tests to be fast, but that’s not what I wanted to talk about here. The ‘valuable’ in ‘valuable feedback, fast’ is a much more ambiguous term, and one that deserves some more explanation. To me, there are multiple dimensions to what makes a test valuable, and in this post, I want to unpack and address them one by one. Valuable = important to someone who matters Borrowing from the classic definition of ‘quality’ as defined by Jerry Weinberg and further refined by James Bach and Michael Bolton, this is where it all starts. The information presented by a test should be important to someone who matters. That someone could be a member of the development team, a stakeholder such as a product owner or business analyst, the end user of the product, or a combination of those. Without that importance, a test is meaningless, dead weight. It could be the most reliable, best-written test ever, but if the information that is provided by it is not important to someone who matters in the context of the product, why bother writing, running and maintaining the test? Valuable = covering what matters Test coverage is a tricky subject, and I want to steer clear of the discussion on what ‘coverage’ means exactly in this blog post. The only realistic answer is ‘it depends’, anyway, as there are so many ways to define coverage (line, branch, requirements, mutation, …). Having said that, for the information provided by our tests to be valuable, teams should invest time in making sure that the tests cover the parts of the product behaviour that are deemed ‘important enough’ in a sufficient manner. What exactly constitutes ‘sufficient’ here depends on, you guessed it, the context. Some products require deeper, more thorough coverage than others. The same applies to individual parts of the same product. It all depends on the acceptable amount of risk a team is willing to take before putting a product in the hands of their users. Teams would do well to have a continual discussion about these risks and the extent to which they are covered by the tests that accompany and scrutinize the product. Valuable = trustworthy The higher the degree of automation in the build and delivery process of a product, and that includes testing, the more teams will rely (and have to rely) on the results of the execution of that automation. Concerning tests, that means that teams need to be able to rely on the information presented by the tests, because they will make decisions based on that information. The nature of that decision might vary from anywhere between ‘this build seems sufficiently stable to warrant deeper testing’ to ‘this change is ready to be put in the hands of our users’. No matter what the specific decision is, if teams make it based on the results of your test automation, even in part, they can only confidently do so if the information provided by the tests is trustworthy. In practice, that means that when a test emits a signal indicating a problem with the product, the team can safely conclude that there is a problem with the product, not with the test, the data it uses or the environment it runs in (no false positives). It also means that when a test does not emit such a signal, the team can trust that the particular piece of behaviour exercised by the test is working according to expectations expressed in the test (no false negatives). Valuable = actionable Another dimension of the value of the feedback provided by a test is that it should be actionable. This applies specifically to those situations where a test ‘fails’, i.e., it indicates a problem with the product I have put ‘fails’ between quotes here, because the test didn’t fail, the product failed the test. There’s a difference. Anyway, when a test result indicates a (potential) problem with the product, teams need to able to act on that information as soon as possible, spending as little time digging deeper into the product or into the test as possible to identify the root cause of the problem. Some practices that might help here are: Making your test scope as small as possible - the fewer moving parts your test has, the easier it will be to identify which of those parts made a move that was unexpected Have good test names - A descriptive test name that tells you what part of the behaviour your product verifies and what the expected behaviour is helps in finding out where exactly the problem might be found Use custom assertion messages - Many test frameworks allow you to specify custom, descriptive error messages in case of assertion failures (something RestAssured.Net supports as of version 5.0.0, too) So, is this a complete and final definition of what ‘valuable’ means to me when I talk about ‘valuable feedback, fast’ as the goal of test automation? I don’t think so. I don’t know if it is complete, but it definitely is a good reflection of my current thoughts on ‘value’ in test automation right now. Those thoughts are definitely not ‘final’, and I would appreciate your takes on what I wrote here.

1st Apr 2026 1 votes
Writing tests with Claude Code - part 1 - initial results

In a recent post, I wrote about how I used Claude Code to analyze the code for RestAssured.Net and then perform a refactoring action, using hand-written tests as the safety net. In that post, I wrote that I didn’t want Claude to touch the tests themselves, and why. I was still curious, though, to find out for myself what Claude was capable of in terms of writing tests. In this blog post, I’ll share with you some first steps in doing exactly that, and you’ll read about my thoughts and my thought process along the way. You’ll see how I create an initial suite of tests for a small Spring Boot-based API that I wrote for use in my workshops, and how I think about and assess the results. In a follow-up blog post, I’ll show you how I improved the test suite based on my findings, again using Claude Code. The starting point As a starting point, I created a new repository containing the code for the API I use in my mutation testing workshop. I removed the existing tests, as we’re going to ask Claude to generate these for us. I also removed the README and the GitHub Actions build pipeline definition, as I want Claude to write tests based only on the product code itself, without being primed by other artifacts in the codebase. The only thing I left in are the dependencies used to write and run the tests, in this case REST Assured and JUnit. After installing and initializing Claude, I gave it a first prompt: “Add acceptance tests for the endpoints exposed by the AccountController to this project. Cover all the logic in the AccountService class. Use REST Assured as the tool to interact with the API. Use JUnit 5 as the test runner. Both libraries are already part of the project, see the pom.xml. Assert status codes and relevant response body elements as part of the tests. Extract common request properties into a RequestSpecification.” After some deliberation, Claude added a new test file to the project, containing 23 tests, all of them passing. You can see these tests here. What you’re seeing in this file is the raw output from the above prompt, I haven’t changed anything in there. It took Claude only a minute or two to write these tests, which definitely is a lot faster than what I could have done myself. But how good are they, really? A first look at the tests Let’s look at the quality of the code first. I’m seeing people argue that code quality is not really all that important anymore once AI will write most of our code, but I beg to differ, especially when it concerns our tests. Tests are documentation of the intended behaviour of our code, and I would say that being able to read that documentation as a human being, without too much effort, remains very important. So, is our code easy to read? There’s a @BeforeEach hook creating the RequestSpecification (an object in REST Assured containing shared HTTP request properties). There’s a helper method to create a new account passing in the AccountType and a predefined balance. There’s the aforementioned 23 tests that, especially at first glance, seem to verify things that are valuable. What Claude did not do, probably because I didn’t explicitly ask for it, is add an abstraction layer to make the code easier to read, such as the one described here. We’ll see how Claude does in this area in the next blog post, as I want to stick to assessing the quality of the initial output from Claude in this one. And I have to say, all in all, for a first try, I’m not unhappy with what I’m seeing. Yes, there’s room for improvement, but I have seen humans do far worse than this. The tests seem to cover all endpoints defined in the API controller, and most paths in the business logic defined in the service layer. I should note here that I was able to fairly quickly come to this conclusion only because: I wrote the code for the API, so I have knowledge of the inner workings and the intent of the API, and I have plenty of experience writing tests for APIs and writing tests in REST Assured, so I’d like to think I know what ‘good’ looks like If you don’t have that prior knowledge and experience, it will be harder to draw meaningful conclusions from just looking at what Claude coughs up. And there’s a significant risk there: the risk of saying ‘looks good to me’ without actually understanding what you’re approving, and then ending up with a safety net of tests that is riddled with holes. Testing the generated tests with mutation testing To further increase our understanding of the value of the tests that were generated for me, let’s see if these tests can fail. If they can’t, the fact that we have generated 23 passing tests in two minutes flat is nothing more than an example of productivity theater. To check if our tests can actually fail, let’s use a mutation testing tool to scrutinize our tests a little more. In this case, because we’re working with Java code, I’ll use PITest as my mutation testing tool of choice. I configured the tool to mutate all the code in the project and run all the tests, to get a complete overview of the quality of the test suite generated. Note that in a real life-sized project, you probably want to start by mutating only part of the code base and run part of the tests to get mutation testing feedback within a reasonable amount of time. After about a minute, PITest reports back that the initial test suite achieves 95% line coverage. This looks impressive, but it doesn’t really tell me anything. The much more valuable metric here is the number of mutants that were killed by the test suite. PITest reports that this is 91%, which, again is pretty good. In absolute numbers, out of 55 mutants generated by PITest, 50 were detected by the initial test suite. Two follow-up questions arise immediately: Which mutants were missed by the tests, and what is the impact of that? Could we have achieved the same amount of (line and mutation) coverage with fewer tests? In other words, do we have tests that are dead weight? Looking at the surviving mutants First, let’s have a look at the mutants that survived, i.e., changes in the API code that were not detected by any of the tests. To start, in the CustomizedResponseEntityExceptionHandler, the HTTP 500 path isn’t covered in any of the tests, and that causes a surviving mutant. By design, the API returns an HTTP 500 when an Exception occurs that isn’t a ResourceNotFoundException (returning an HTTP 404) or a BadRequestException (returning an HTTP 400). This looks like a useful path to cover in a test. Second, the API returns an HTTP 204 in response to a GET call to /accounts when there are no accounts in the database. That path isn’t covered in the tests. This, too, seems like a useful path to test, because it is intentional API behaviour. Finally, the tests that were written do not properly cover some of the boundary values, both in the logic that implements the business rule of ‘you cannot overdraw on a savings account’ and in the interest calculation logic. Once more, I would like to have these situations covered by tests. Coincidentally (or maybe not?), these are all cases that I cover in my mutation testing workshop, too. This, to me, indicates that mutation testing is a powerful way to assess what is tested and what isn’t, no matter if you wrote the tests or you had them write by an LLM. I’m also happy to see that I’m probably covering the right things in my workshop. Note: I can confidently and quickly perform this analysis of the signals produced by PITest, and of the quality of my tests, because I know that mutation testing as a technique exists, and because I know how it works. Most importantly, I’m motivated / I feel like I am morally obliged to do so, because I deeply value writing tests that test meaningful things and that are actually able to detect changes in product behaviour. If all I cared about was having some tests to cover the API and declared, for example, 90% line coverage as ‘good enough’, I would be done by now. However, I don’t. In the next blog post, I want to return this feedback to Claude and see how well it does in updating the existing test suite based on my observations. I also want to see if I can add mutation testing to the test generation loop, and have Claude achieve better mutation coverage without my interfering. For now, I’ll conclude that when I ask Claude to generate tests in the way I have done, it produces pretty good results in terms of both line and mutation coverage, but that it missed certain key paths in my application code. Identifying dead weight in our test suite As a next step, I want to find out if the test suite that was generated by Claude contains dead weight, that is, do we have any tests that do not uniquely contribute to either line or mutation coverage? To do so, I asked PITest to generate a report in XML format next to the HTML report, as (for some reason) only the XML report contains information about which test killed a specific mutant. Performing this analysis required a bit of elbow grease, as I had to manually search the XML test report for occurrences of the test name for every test in the test suite. This, too, is probably a process that can be automated, but for now, I’m OK with doing this the manual way, since there’s only 23 tests in the suite anyway. This search tells me that four tests that were generated by Claude did were not mentioned as a test killing a mutant in the results file. In all four cases, the reason behind this is that the exact same code path is exercised in another test. For example, one of the tests performs a withdrawal on a checking account and verifies that the balance is updated accordingly: @Test void withdraw_positiveAmount_fromCheckingAccount_updatesBalance() { long id = createAccount(AccountType.CHECKING, 500.0); given(requestSpec) .post("/{id}/withdraw/{amount}", id, 200.0) .then() .statusCode(200) .body("balance", equalTo(300.0f)); } The next test in the suite, however, does the exact same thing for a savings account: @Test void withdraw_positiveAmount_fromSavingsAccount_withSufficientFunds_updatesBalance() { long id = createAccount(AccountType.SAVINGS, 500.0); given(requestSpec) .post("/{id}/withdraw/{amount}", id, 200.0) .then() .statusCode(200) .body("balance", equalTo(300.0f)); } After removing these four tests from the suite and running mutation testing again, as expected, I can see that the impact on both line and mutation coverage is 0, meaning that these four tests can indeed be classified as ‘dead weight’. Conclusions So, after completing the analysis of the results of asking Claude Code to generate tests for a new code base, what do I think? Well, while I am impressed, I think a couple of words of warning are in order. I am positively surprised by the quality and the coverage of the initial test suite. 95% line coverage and 91% mutation coverage are good numbers, and all that coverage was generated in a few minutes, definitely a lot less time than it would have taken me to write these tests myself. There is some room for improvement in terms of readability of the tests, but that can probably be resolved by being more specific in my prompt and / or using dedicated Claude Code skills. I’ll explore and write about that soon. While Claude achieved a pretty decent mutation coverage, it did oversee a few critical paths in the code. Maybe I was simply ‘unlucky’, and another attempt with the same prompt would have given better results. I don’t know, but it does tell me not to simply accept what Claude gives me at face value. The same applies to the tests that Claude did generate. 4 out of the 23 tests generated were dead weight, which equates to 17% of the test suite. Now, n = 1, and this is a small codebase and test suite, so the numbers might be skewed, but again, if you want your test suite to be as efficient and effective as possible, these are numbers that you probably don’t want to ignore. Finally, there are of course many things that Claude did not do, mainly because I didn’t ask it to. An example of that would be telling me that since we’re working with a banking API, it probably would be a good idea to add some form of authentication to the endpoints. There’s a lot more to unpack about what Claude does and does not do, and I will probably write about that in more detail in another blog post, but not here. First, in a follow-up blog post, I’ll document the process of improving the existing test suite that Claude generated, both in terms of coverage and of coding style. I will once again be using Claude and mutation testing to do that. The code for the API that was used in this blog post, as well as the initial suite of tests generated by Claude, can be found here.

9th Mar 2026 1 votes
My LinkedIn break - six weeks in

About six weeks ago, I decided to take some time away from LinkedIn. I won’t go into the reasons behind this decision again, you can read all about that in the post I just linked to, but I do want to take some time and look back on the past six weeks and the things that not spending so much time on LinkedIn have brought me. First of all, moving away from spending an hour or two on LinkedIn wasn’t as easy as I thought. Especially early on, thoughts of ‘am I missing something?’ were in my head pretty much all the time, and yes, that led to me logging in and checking to see whether there was something I needed to address - DMs to answer, invites to accept - a few times. After a few weeks, though, and after seeing that there wasn’t much of importance, or really anything at all, that I missed, that feeling slowly faded. It’s still there, sometimes, but I don’t feel the ‘need’ (it’s more of a ‘want’, really) to log in as often as I did in the beginning. When I started my break, I was hoping for several positive side effects. It’s still early, too early to draw conclusions, but so far, things are looking pretty good: The contents for my brand new ‘Valuable Feedback, Fast’ course is coming together slowly but surely, and I’ve got 3-4 companies interested in booking me to deliver this course in 2026, with one of them confirmed. My target is to deliver it at least three times in 2026, so things are looking good. My training business is off to a good start, too, with 5 full days of training already delivered in January. I was off to a much slower start in 2025, so this makes me happy. I’m also working on different ways to bring my training offerings to the attention of potential clients. One thing I’m thinking about is starting a YouTube channel with instructional videos, to give people an idea of my teaching style and the type of content they can expect when they book me to teach a course. I’ve already published 4 blog posts, including this one, where I only wrote 13 in all of 2025. I really hope to keep up this pace. I’ve definitely been reading more, too. Mostly fiction, including the fantastic Winter’s Bone by Daniel Woodrell, but I’m also slowly working my way through Taking Testing Seriously by James Back and Michael Bolton. Outside of work, I’m definitely taking my cycling much more seriously. Even though January wasn’t the best cycling month, because of snow, the flu and some other things that got in the way, I have a training plan in place, I have set some ambitious but not-too-crazy goals, and I hope those will lead to my first century as well as my first 200k and even a 300k before the end of the year. Because of that last bullet point, and especially the time that training for long-distance cycling events takes, I have decided to pretty much entirely focus on my training business in 2026, as that is simply more flexible than consulting. This doesn’t mean I will not take on any consulting gigs at all, but the ones that I do will be of a very part-time nature. I don’t want to do all the cycling I want to do in the evenings and weekends only, if only because there’s no better feeling than going for a long bike ride at a time you know most other people are in the office ;) Needless to say, I’ll remain absent from LinkedIn for the foreseeable future. Yes, I might log in once every other week to quickly check DMs and invites. You might see a very occasional post from me promoting my training services, but that will be once a month, tops, and probably even less than that. I have zero interest at the moment in getting back to regular posting, commenting, sharing and liking. My brain relishes the quiet, the absence of that background noise that was present when I was spending a lot of time on LinkedIn. Plus, it has given me the time and attention it needs to do more valuable things, and I’m not ready to give that up again. Don’t expect me to write an update like this every month, either. As the months go by, I’ll probably think about LinkedIn even less, especially now that I’ve seen that I’m not really missing out on a lot. As I said before, email is a much better way to get or stay in touch, so if you have a question, something to share, or you just want to catch up, email me at [email protected], and I’ll happily talk to you. I’ve had some great conversations already, and I’m looking for many more of those.

1st Feb 2026 1 votes

More in programming

Mommy bloggers react

After a write-up in the New York Times, Mommy Bloggers had two options. Either lean in, or step back. Given how popular it became after that, it's not hard to guess which option they chose. The post Mommy bloggers react appeared first on The History of the Web.

15 hours ago 1 votes
Haunt 0.4.0 released

I'm quite a bit late on this one, but Haunt version 0.4.0 was released released back in July. I haven't had much time for blogging, but I'm catching up now! This release contains a small set of improvements and bug fixes since the 0.3.0 release in 2024. About Haunt Haunt is a static site generator that uses the Guile Scheme as its configuration language. It aims to be simple, functional, and extensible. Features include: Easy blog and Atom/RSS feed generation Markdown post support Simple development server for viewing edits before publishing Purely functional build process User extensibility Notable changes Added support for HTML in Markdown documents. This was a long time coming because guile-markdown did not support it and the library was abandoned by the original maintainer. As part of my work at Spritely, we forked it, implemented the relevant portions of the CommonMark specification, and released it. Spritely's guile-commonmark fork is now considered to be the official upstream by Guix and others. A further consequence of this is that guile-lib is now a required dependency for building Haunt as we need the (htmlprag) module to parse Markdown documents with embedded HTML. html->shtml from guile-lib's (htmlprag) module is now used instead of xml->sxml in the HTML reader. It was silly of me to use xml->sxml for this purpose years ago, but at the time I wanted guile-lib to be an optional dependency. Added haunt new subcommand for creating a new site. Added default directory, template, and prefix arguments to flat-pages procedure. Added support for index metadata flag to flat pages for pretty URLs. Flat pages now receive all page metadata, not just the page title. This is a breaking change from 0.3.0. Added .scm as an additional extension for sxml-reader. make-file-extension-matcher now supports multiple extensions. Fixed emission of <script> and <style> elements. Fixed handling of no available reader in flat pages builder. Fixed unreachable error handling clause when a reader is not found for a post. Fixed default blog theme template missing an <html> tag. Fixed overloaded -h option in haunt serve. Deprecated post in Skribe reader in favor of document. Download Haunt 0.4.0 is already available in Guix: guix pull guix install haunt See the Haunt project page for information on how to build from source. Thank you to Camilo Rodrigues, Noé Lopez, jgart, Jakob L. Kreuze, and Daniel Meißner for their contributions to this release! Happy haunting!

17 hours ago 1 votes
On reading books

How books have coloured my life

19 hours ago 1 votes
I Bought A Scanner (No, Really This Time)

This is a transcript from a talk I gave at the German Perl Workshop earlier this year. If you'd prefer to watch the video recording, you can find it here. I have lots of photographic projects on the go. Lots of these being on film, as some of these I started shooting a long time ago. I don’t have any particular loyalty or attraction to film, it’s just that I started shooting many of these projects before affordable medium format digital was available. Since I mostly shoot medium/large format film I never really jumped to digital until recently, so film has continued to feature heavily in my workflow. That said, it’s a pain in the arse to shoot film now given the spiraling costs, limited availability, and issues around traveling with it: modern airport CT scanners, being rolled out across many airports, are much more convenient but will fog film. Asking for a hand inspection often comes down to arbitrary timing - how busy the security is, how experienced the operator is, or if you’re lucky/unlucky. I’ve had film forced to be scanned (and fogged) and politely argued with security on more than one occasion. I don’t want to deal with that so don’t travel with film anymore, thus I am shooting less of it and have mostly moved to digital. I still have a tonne of film I need to scan and process however. Here’s just some of the binders and files of film. I don’t plan to scan all of this, but I do plan to scan the ones I need to. Probably in the region of a couple of thousand frames. I want to scan to the highest possible quality (within reason) for archiving, book projects, and large prints. If you’re wondering how large I print, it can be up to 160x60cm panoramics for selling. This is restricted by the size of my printer (that’s another story). Three Years Ago Three years ago I almost bought a scanner. I ended up blogging about it and the post got a bit of traction on Hacker News (HN). I’m never quite sure which posts I submit will pique the interest of the users. I’ll spend months chipping away at a draft and when I post it it tanks. Or I’ll cobble something together in twenty minutes, like the linked one above, and it gets 440 points and over 300 comments… The thread had some useful suggestions and some not so useful ones, the not so useful ones being effectively “buy an Epson”: I’ve had one for fifteen years and it’s not good enough for large prints or archiving. It’s passable for web stuff and smaller prints, but for my recent use cases? Not even close. Ten years ago I had negatives scanned with a high resolution scanner for the first time and recently, wanting to scan my archives for various projects, I decided I should invest in one of those scanners. The Original Plan The plan, back in 2023, was simple: Buy scanner (at significantly reduced rate) Scan all my film Sell scanner Profit! And I mean profit - the scanner that I almost bought was being offered to me at about 2/3rd of the price they usually sell. And they’re becoming harder to find in working order so the prices are going up. Or profit in not having to pay > 25.- CHF per frame to have someone else do this. You can see the pricing from The Film Lab. You can read the original blog post to find out more about the scanner in question, so I won’t repeat it here. Other than the parts being relevant to the rest of this post, namely that the scanner was showing hard and soft problems. The software that drives the scanner was last updated in 2012, it’s proprietary and closed source, requiring 32bit architecture and no third party drivers or software exist. So you are stuck using old software/computers to run it. Or maybe you could use emulation / virtualisation? The problem there is that the interface is firewire, or SCSI on the even older models, and firewire is known to be problematic on these scanners as the controllers start to go bad after a decade of continued use. That’s a risk, and the scanner was very much EOL as the firewire controller was dying: both ports were bad that suggests controller, not ports. The scanner would have been €5,000 to purchase and then €3,000 (ish) to repair. Or, as HN suggested - just open it up and use a soldering iron. I’m not going to drop 5k on something and then start poking it with a soldering iron. I’ll pass on that thanks. Camera Scanning In the meantime I’ve been camera scanning, which you can read about in another blog post. But how does that compare cost wise? It’s expensive because you’ll need a high resolution camera, a macro lens, copy stand, negative carrier/holder, and quality light source. You’ll look to spend anything from three to five thousand Euros on everything. Camera scanning does actually work well, in that it’s close to a high resolution dedicated scanner. But you have to setup the entire thing every time you want to use it, including ensuring everything is straight and parallel. It also suffers from the same weakness as most other scanning methods. What do you think that is? Film Flatness Or lack thereof: Film is rarely flat, especially so with 35mm. These are pretty mild examples of curl. It tends to be flatter in the larger formats but then you get into flatness issues due to it sagging. The smallest difference in the film plane can cause major issues in sharpness due to focus fall off (film scanning is essentially macro photography). Any workflow or solution that does not take this into account is significantly compromised. And the workflow is only as good as its weakest part. This is the biggest problem in scanning film - all other considerations are more than adequate these days: resolution, dynamic range, etc. However, most negative carriers don’t keep the film perfectly flat. This has always been a problem - this is from a book called “Edge of Darkness” which is about traditional analog photography and printing, and summarises the problems of negative carriers thusly: “if you use a glassless negative carrier, you might as well just buy the cheapest enlarging lens you can find. You are simply throwing away the money and sharpness you paid for it in your enlarging lens, and also in your fine camera and the expensive lenses you bought for it… No film will lie flat in a glassless carrier. That’s right, none… There is no avoiding this issue. Use glass.” So you have to use (anti-newton ring) glass, which introduces other issues - you’ve now got extra glass in the transmission path, and dust (which isn’t a massive problem, but a pain nonetheless). You could use drum scanning, which is absurdly impractical from a cost and operating point of view. Or you could use a Flextight, the scanner I almost bought three years ago. Interim Solution I stuck with camera scanning, but wasn’t happy though, because of film flatness and the setup faff. So of course I started looking for another scanner. I was idly browsing near the end of 2025 and came across this one. It’s exactly the same spec as the one I tried three years ago, except SCSI not Firewire so less prone to failure. It just predates Hasselblad buying Imacon (so is pre the rebranding, etc). It was in Switzerland so I could inspect and pick it up. It was also significantly cheaper than the previous one I had looked at, so worth a punt even if I needed to take a soldering iron to it. We went to St Gallen for a weekend and I picked it up. Here’s the software interface back in my studio. Look at that marvelous interface! None of that liquid glass bollocks. The first scans were promising, but I had the sense things needed some TLC. The first thing was calibrating the focus, which the software can do in combination with a focus slide. I was lucky that the focus slide was included with the scanner and I’m not sure what I would have done otherwise. Probably paid a fortune for a replacement? Possibly a lot of manual trial and error with the software? After doing that I scanned images of the 1951 USAF resolution test chart (taken on ultra high resolution 35mm film): That’s what the resulting scan looked like. Notice that it’s sharp from edge to edge, corner to corner. At 100% crop we can resolve around 110 to 123 line pairs per mm, which equates to about 5,600 to 6,300 DPI. This is beyond the limit of most 35mm lenses, but importantly - exactly to spec for this scanner. So I was happy the focus was calibrated. If you’re curious this is the same target with the camera scanning setup. It’s close, but we’ve got another variable in the workflow, several even, and that impacts the results. It’s not as sharp, and the extra glass in the transmission path causes aberrations. Another thing that needed attention was the power supply. The seller mentioned that “sometimes it takes five minutes to warm up”. Sometimes it was more than five minutes, and the power supply would click click click away. So that needed fixing and it was easy enough to find a compatible new replacement, however it cost 200 Euros. Expensive! The third problem I noticed was that some of the scans were coming out stretched. Often about 10% too wide/long, sometimes more than that. My panoramics looked panoooooooramic. I did some research and someone suggested this might be a “buffering issue”, which I thought was nonsense. Doing some testing I heard slipping sounds when the scanner was pulling the film into the body. After more research I stumbled on a post that suggested the belts need replacing. I opened the scanner up, and sure enough: A ha! You can’t quite see that the one on the back is even worse. I replaced those with compatible belts: 535 synchroflex t 2.5/245. Problem solved. The fourth problem was that the film holders were old and/or had been mishandled. They were falling apart and held together with electrical tape or glue, which didn’t seem optimal. Replacements cost 350 Euros in total for the four I needed. They’re now available cheaper from China, since the patents have expired. Or, you know, China. They used to cost about 200 Euros each from Hasselblad. The fifth problem, which is a potential one and hasn’t manifested yet, is that the lamps may eventually need replacing. I picked up a couple for 25 Euros. That seemed like a reasonable thing to do while they’re still available. Success? Let’s add up the costs of acquiring this scanner and renovating it: Scanner: 1,750.- CHF Power Supply: 175.- CHF Belts: 25.- CHF Film Holders: 350.- CHF Lamps: 25.- CHF Total: 2,325.- CHF (c. 2,500 EUR) In the last year (since acquiring the scanner) I have scanned: c. 250 panoramics frames (~ 6,000 CHF) c. 2,500 medium format frames (~ 80,000 CHF) c. 200 large format frames (~ 9,000 CHF) The figures in parentheses are what it would have cost me to have that number of frames scanned by a third party. That is, er, quite a saving. Also quite a lucrative business model perhaps? I think I can argue the cost of the scanner was a very good investment, and I haven’t finished using it yet. Even if it were to stop working tomorrow, it has already paid for itself many times over. Could it stop working tomorrow? Yes, because of other issues that will be harder to solve. The Bigger Issue(s)? A Power Mac G4 (discontinued in 2004). This came with the scanner, the necessary hardware and software to drive it, and is almost certainly living on borrowed time. Spinning metal is never good in the long-term. I’ll maybe purchase a backup soon, as these can still be found for a couple of hundred Euros. The key thing though, is that this very expensive, very high quality scanner, will at some point be rendered useless by the upgrade treadmill because the software required to run it will be increasingly difficult to run. A scanner that is still used by businesses, educational institutions, and individuals like me. A scanner that originally cost tens of thousands of Euros less than a decade ago. The upgrade treadmill is constantly whirring away. This is from the top of the Seattle Space Needle. “Do not upgrade anything on computer”. Clearly that notice speaks of someone being bitten by an upgrade at some point. I wonder is anyone else feeling the fatigue? Security updates, sure I can understand. But feature creep and trivialities? No! What tangible benefits have the last ten, fifteen, or even twenty years of OS updates brought? Other than security, and compatibility with newer hardware? New hardware is great, really, but by association forced deprecation of older hardware. No! It feels like the upgrade treadmill gets faster and steeper every year. Add to that subscription lock-in and dead endpoints: “I couldn’t vacuum my house because an SSL cert had expired” is what someone told me earlier this year. Fortunately this person is a software engineer so ended up man-in-the-middling the network traffic to get the vacuum cleaner to work again (no SSL-pinning it seems). “GoPro is announcing the end of life of the GoPro Quik app for macOS, effective at the end of 2024”. They discontinued the former in favour of their mobile app, which requires an account, login, subscription, and so on. I just want to transfer the videos from the hardware, I don’t need any of this crap (I don’t need any of that crap, it turns out GoPro haven’t locked the device down enough to prevent using third party apps to access the files. Yet). And, of course, software has to be in everything. These days the scanner would/could have an embedded Raspberry PI? Just a keyboard and mouse input, monitor and USB output would reduce the surface area, connectivity issues, and software dependency. Or software is never done? Because: externalities. I guess software is “done” when it’s no longer supported? Marciano Planque has a good piece on this: When hardware products reach end-of-life (EOL), companies should be forced to open-source the software. I think that’s a fair thing to say. I suspect Hasselblad/Imacon never open-sourced the software due to licensing issues. Or they just lost the source. Or they just don’t care, I don’t know. Maybe some combination of the three. And, inevitably, discontinued hardware like this scanner. Or, that is to say, discontinued parts? What about regulation changes? The panoramics I shoot are with a camera that was discontinued in 2004 because EU regulation banned lead solder in circuit boards. The company decided redesigning the parts wasn’t worth it. Old hardware has new exciting ways to fail. As time goes on components will fail or loosen - components that were expected to last decades. Then that results in tribal knowledge, or worse link rot and QR code rot. A lot of this stuff is hidden in walled gardens. There’s a Facebook Imacon group, for example. Why in the ever-loving fuck is a group for technical people, by technical people, on Facebook? Then there’s misleading AI. “My flextight scans are coming out stretched, what might the problem be?” LLM’s have gobbled up all the right information, and all the wrong information. Or information that is massively out of date. Nowhere in the suggestions here does it mention the belts might need replacing, which, according to my own research, is the most common reason these days. Legacy Software A decade ago I wrote an essay that also hit the front page of HN: All Software is Legacy. I think it is still relevant today, some parts not so much given we are now in The Age of Prompt, but mostly it’s still true. Nicholas always said “legacy software is the ugly stuff that makes you money”, which I think is true. But now it’s the stuff that surrounds us, like when I want to withdraw cash (guess what software most cash machines are still running?). Or when I want to take a train - when I gave this talk in Germany I had to get from the airport to the city centre. The ticket machines were disabled with a sign saying “no longer in use, download the app”. Then register. Then buy the ticket. I just want to give you money. Or when I wanted to pay for parking while stopping off at some random town in the UK - the same situation as with the ticket machines. “Download the app, register, pay”. Fuck that, I went and parked somewhere else. I just want to park, I don’t want to fight with software. Or if I want to hire a bike (not pictured: the half dozen apps on my phone to hire a bike). And when I want to buy stuff from a shop… One of the self-checkouts crashed recently in the coop, rebooting into a version of SUSE Linux from well over a decade ago. We’re collectively creating more and more of this everyday, letting it out into the world where it becomes a future liability for someone or the death knell for something. A pile of bikes, an unplugged ticket machine, a top of the line but no longer driveable scanner. References Imacon Users Group (the non-Facebook group) The state of Hasselblad Flextight scanners (2019) 1951 USAF resolution test chart Vlads Test Target Printer Story Original Scanner Blog Responses to HN Camera Scanning All Software is Legacy Repair Cafe

yesterday 1 votes
Attention is all you have

The Tetris effect is one of psychology’s most easy to reproduce experiments. Simply spend a bit of time playing the eponymous game every day for a few weeks. After a little while, you’ll start recognizing familiar Tetromino shapes in clouds, buildings, and everyday objects. You might even see them appear before your eyes when you start falling asleep. Tom Tang Attention hijacking There’s one lesson the Tetris effect teaches us: whatever you focus on long enough will end up shaping your thoughts. This can be a good thing since it’s how we learn new skills and discover new ideas. Sadly, less and less of our attention is focused intentionally. Instead of picking what we want to see we let other people decide what is supposed to be good for us. Do you want to watch a video? YouTube knows you like cooking and art streams. But why not also recommend a few clips about the stock market bubble, global warming, and the war in Iran. Doomscrolling will make you stay longer and click on a few more ads. Do you want to listen to music? Just open a Spotify playlist and let the algorithm figure out what you like. Please ignore the AI slop they will insert in between real songs to avoid paying royalties to real artists. Do you want to know how your colleagues are doing? Too bad, LinkedIn will bury any relevant career news between the opinion of complete strangers. It is surely just a coincidence that those strangers happen to be shilling whatever Microsoft is invested in at the moment. Do you want the opinion of strangers on a product? Well those Redditors you wanted to ask are probably just a bunch of LLMs talking to a bunch of Russian trolls now. I hope you didn’t value their opinion too much. If, like me and most people, you spend the major part of your day focused on your device, there’s no doubt it’s affecting you. And when you let someone else dictate what appears on your screen, it’s the same as giving them the key to your brain. New York Said Back to an intentional internet The internet wasn’t always like that. Before recommendation algorithms where a thing, you had to decide what you would be doing on the computer. You didn’t really have one big app that you could open and order it to entertain you. Instead, you had a few dozen of bookmarks to websites, each with a specific idea in mind. A site for video game news, that one website with lots of tutorials, a blog about anime that didn’t update often enough, a wiki about a TV show from the 90s… Of course awful things existed on the web. We had Encyclopedia Dramatica and Rotten.com, but you actually had to put the effort to go there if you wanted. Nobody was going to put pictures of dead kids and far-right propaganda as a suggestion after a pancake recipe or a cat video. The good thing is that this intentional internet is still around. It has just been a bit buried below the corporate web, but it’s not very hard to find. After all you’re on this blog, so you probably already have a good idea about it. The main difference between this time and now is you. When you want to get back to reading blogs, RSS feeds, and finish that tutorial instead of doomscrolling shorts, you have to get used to a slower internet. One where content is not infinite and doesn’t get updated every click. But like every habit, the only thing you have to do is to keep at it. And if you pay enough attention to it, something will click in your brain.

2 days ago 2 votes
📚 BoredReading

You seem to be enjoying this.

Join free to unlock everything.

Create free account

Already have an account? Sign in