<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>HanLHo. - Fractional Architect &amp; Software Product Engineer - testing-dsls</title>
    <link rel="self" type="application/atom+xml" href="https://hanlho.com/tags/testing-dsls/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://hanlho.com"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-02-04T00:00:00+00:00</updated>
    <id>https://hanlho.com/tags/testing-dsls/atom.xml</id>
    <entry xml:lang="en">
        <title>AI’s Opportunity: Pacing Control Loops with Development</title>
        <published>2026-02-04T00:00:00+00:00</published>
        <updated>2026-02-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hanlho.com/p/ais-opportunity-pacing-control-loops-with-development/"/>
        <id>https://hanlho.com/p/ais-opportunity-pacing-control-loops-with-development/</id>
        
        <content type="html" xml:base="https://hanlho.com/p/ais-opportunity-pacing-control-loops-with-development/">&lt;p&gt;What caught my attention in the book &lt;em&gt;Vibe Coding&lt;&#x2F;em&gt; by Gene Kim and Steve Yegge is the idea that, as LLMs and coding agents change how we build software, control loops—tests, reviews, and other signals that tell you whether a change behaves as expected—should be faster and more integrated into development feedback loops than before. My intuition says this makes perfect sense.&lt;&#x2F;p&gt;
&lt;p&gt;For example, when there is a dedicated test stage or a QA role that tests after the fact, that role inevitably struggles to keep up with the speed of development. Over time, this makes it increasingly difficult to sustain a &#x27;testing after the fact&#x27; organisation of quality.&lt;&#x2F;p&gt;
&lt;p&gt;So how do we solve this?&lt;&#x2F;p&gt;
&lt;p&gt;Some may think that introducing AI by implementing it at the test level after the fact, could be the solution. However, at the rate of development I see and read about, this approach will be hard to keep up with. One either has to accept not fully taking advantage of what AI can help development with, or rethink how testing is integrated into the development process.&lt;&#x2F;p&gt;
&lt;p&gt;Put bluntly: if AI lets you produce a feature in hours but the first meaningful acceptance signal only arrives days later in a separate stage, quality assurance become the bottleneck.&lt;&#x2F;p&gt;
&lt;p&gt;To me, the logical consequence is a stronger shift towards automated quality controls, including acceptance tests and code reviews at the least. I refer to acceptance tests here as writing executable specifications of expected behaviour (in domain language) before or alongside the code. This implies that testing &lt;em&gt;has&lt;&#x2F;em&gt; to move earlier in the development chain &lt;em&gt;because&lt;&#x2F;em&gt; of AI.&lt;&#x2F;p&gt;
&lt;p&gt;AI is an opportunity to start writing acceptance tests if you have not yet. It pushes us to invest time in strategic test design, testing against stable contracts, testing from a behavioural point of view, and isolating test descriptions from the actual implementation.&lt;&#x2F;p&gt;
&lt;p&gt;Put differently, the shift in development practices that LLMs are causing should inspire &lt;em&gt;more&lt;&#x2F;em&gt; adherence to testing best practices, not less. That is, if you want to keep on adding new features, fix and prevent bugs, and keep up the pace of development.&lt;&#x2F;p&gt;
&lt;p&gt;More broadly, to keep benefiting from AI over time, we should shift towards tightly coupled feedback loops embedded in everyday development. This is not limited to testing but also applies to, for example, reviews. In that sense, AI doesn’t remove quality practices; it raises the stakes if you don’t have them.&lt;&#x2F;p&gt;
&lt;p&gt;If testing &#x27;shifts left&#x27;, team structures must change as well. This evolution points towards smaller, more autonomous teams where testing, development, and feedback are inseparable rather than sequential.&lt;&#x2F;p&gt;
&lt;p&gt;AI presents us with an opportunity: not faster quality control after the fact, but to design systems, processes, and teams that make quality the fastest path forward, so control loops can keep up with the increasing pace of development loops.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Implementing an Urgent Feature with Opencode, Claude, and Zed</title>
        <published>2025-12-18T00:00:00+00:00</published>
        <updated>2025-12-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hanlho.com/p/implementing-an-urgent-feature-with-llms-and-zed/"/>
        <id>https://hanlho.com/p/implementing-an-urgent-feature-with-llms-and-zed/</id>
        
        <content type="html" xml:base="https://hanlho.com/p/implementing-an-urgent-feature-with-llms-and-zed/">&lt;p&gt;This is a short post to share a positive experience I had using an LLM agent to quickly add a feature to an existing personal CLI &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;lhohan&#x2F;simple-time-tracker&quot;&gt;time-tracking application&lt;&#x2F;a&gt;. Below, I describe how I added it using Zed, Opencode and Claude.&lt;&#x2F;p&gt;
&lt;p&gt;To start, I wasn&#x27;t even sure the feature I needed existed in a text-based time-tracking application I use day-to-day to keep track of what I work on. My application &lt;em&gt;has&lt;&#x2F;em&gt; a way of getting the information I needed out, but the feature that should make this easy was missing details on &lt;em&gt;what&lt;&#x2F;em&gt; I had actually worked on.&lt;&#x2F;p&gt;
&lt;p&gt;So, should I invest the time and expand the feature, or accept that it was missing for now and spend a lot more time on manual work? I could try an LLM agent and see if it could help me implement the change quickly. Each choice had downsides: spending &lt;em&gt;more&lt;&#x2F;em&gt; time digging through time-tracking information to fill in timesheets is not very appealing, and implementing the feature (with or without agents) could become a time sink. I also had lots of other work planned for the day.&lt;&#x2F;p&gt;
&lt;p&gt;I decided to implement it, telling myself I’d stop if it didn’t look like I had a clear path to finish it within one hour.&lt;&#x2F;p&gt;
&lt;p&gt;Because I think it is relevant to the (spoiler) successful implementation, let me share a little about this project. It is a Rust codebase that I use to test development practices, and I think it is structured and implemented fairly well. The CLI, the main part of the application, has over 95% coverage using behaviour-driven, DSL-style acceptance tests. This setup gives the LLM models both structure and plenty of examples to follow when adding tests. I will not go into the details here, but I have added a brief example at the end. Also noteworthy: this is a small project, which definitely makes a difference.&lt;&#x2F;p&gt;
&lt;p&gt;For this implementation, I used Zed with its Opencode integration. Lately, I have been on the command line building smaller apps prompt-driven, without worrying much about the fine details. But for this project the actual implementation mattered to me, so I wanted to track changes more closely in an IDE. Opencode taps into my Claude subscription; I can use Opus for planning and Haiku for implementation.&lt;&#x2F;p&gt;
&lt;p&gt;Honestly, I was very pleased with how smoothly this feature was implemented. What contributed to this was the plan-first approach before implementing anything. For anything non-trivial, always plan first!&lt;&#x2F;p&gt;
&lt;p&gt;Here is a high-level overview of my interaction with Opencode in Zed:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Investigate
&lt;ul&gt;
&lt;li&gt;I started with Claude Opus and asked whether the feature I needed already existed, rather than looking it up myself because I was under time pressure. It didn&#x27;t.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Plan
&lt;ul&gt;
&lt;li&gt;I asked Claude to plan the feature and use a test-driven approach. It broke the work into nine tests, and I asked it to pause after each one for me to review.&lt;&#x2F;li&gt;
&lt;li&gt;Before starting any implementation, I asked it to write the plan to a Markdown file in the backlog.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;Then I reviewed the plan.&lt;&#x2F;em&gt; That sounds more superficial than what I did, but I cannot say much more than that it simply looked good.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Implementation
&lt;ul&gt;
&lt;li&gt;I switched to Claude Haiku for implementation.&lt;&#x2F;li&gt;
&lt;li&gt;It started off well and asked for feedback after each test cycle, and I asked for a refactor to remove duplication.&lt;&#x2F;li&gt;
&lt;li&gt;While it was implementing, I discovered I wanted a different kind of description for the tasks, so I told it to change that in the plan. I did not switch models for this.&lt;&#x2F;li&gt;
&lt;li&gt;The plan was updated in all the correct places.&lt;&#x2F;li&gt;
&lt;li&gt;After this, the workflow changed: it stopped asking me for feedback after each step, and before I realised it, seven of nine tests were running. Not the TDD flow I asked for, but it worked.&lt;&#x2F;li&gt;
&lt;li&gt;Instead of asking it to redo anything, I reviewed the implementation (it was not a lot of code) and continued.&lt;&#x2F;li&gt;
&lt;li&gt;I ran the program against my own data and everything worked as intended. Aside from the one refactoring to remove duplication, I did not change the code.&lt;&#x2F;li&gt;
&lt;li&gt;One loose end I had to remind it of: update the documentation.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I have shared the full session here: https:&#x2F;&#x2F;opncd.ai&#x2F;share&#x2F;aBYozahW.&lt;&#x2F;p&gt;
&lt;p&gt;There were some pitfalls of using LLMs that I ran into, and I admittedly leaned into them. Speed beating accuracy is a real risk. The feature works and the code looks good, but if I were coding hands-on I probably would have reviewed more thoroughly. It is hard to tell if the end result would have been &lt;em&gt;drastically&lt;&#x2F;em&gt; better. It requires discipline to not start running along with the agent and to not start accepting everything if the outcome is as expected. The LLM gave me what I needed and any follow-up changes should be small, but I still see little things I would have done differently if I done it manually (which other developers may disagree with too, to be fair). For example, some of the tests could do with fewer assertions. The current code organisation makes that easy to address later. &lt;em&gt;If&lt;&#x2F;em&gt; any tech debt was added, it is very small and under control, so I stopped, generated my reports and filled in my timesheets.&lt;&#x2F;p&gt;
&lt;p&gt;Overall, working in Zed made it easy to review the code, and combining Opencode&#x27;s plan phases kept things organised. The existing, structured DSL-based test approach with plenty of examples also helped.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;extra-how-an-llm-can-work-better-with-a-well-structured-dsl&quot;&gt;Extra: How an LLM can work better with a well-structured DSL&lt;&#x2F;h2&gt;
&lt;p&gt;To give some context, what the application needed was to combine two existing flags: &lt;code&gt;breakdown&lt;&#x2F;code&gt; and &lt;code&gt;details&lt;&#x2F;code&gt;. The time &lt;code&gt;breakdown&lt;&#x2F;code&gt; reports were already implemented but were only reporting time spent per day, week, month or year. What I needed were details of the projects I had worked on. The application already had a &lt;code&gt;details&lt;&#x2F;code&gt; flag but it was not implemented for this view.&lt;&#x2F;p&gt;
&lt;p&gt;In the test DSL, the flags are set by calling methods in the &lt;code&gt;given&lt;&#x2F;code&gt; setup phase: &lt;code&gt;breakdown_flag(...)&lt;&#x2F;code&gt; and &lt;code&gt;details_flag()&lt;&#x2F;code&gt;. The breakdown feature did not implement the &lt;code&gt;details&lt;&#x2F;code&gt; flag, so it was not used in the tests for this feature. What is nice (and I credit this way of testing for it) is that the LLM was able to figure out the &lt;code&gt;details_flag&lt;&#x2F;code&gt; was already present and decided to re-use it: &lt;code&gt;Cmd::given().details_flag()....&lt;&#x2F;code&gt;. Here is an example of such a DSL test:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#eff1f5;color:#4f5b66;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;breakdown_day_with_details_should_show_tasks_per_day&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; some_content = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;r&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;## TT 2020-01-01
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;- #project-a 1h Task A
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;- #project-b 2h Task B&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    Cmd::given()
&lt;&#x2F;span&gt;&lt;mark style=&quot;background-color:#a7adba30;&quot;&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;details_flag&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;&#x2F;mark&gt;&lt;mark style=&quot;background-color:#a7adba30;&quot;&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;breakdown_flag&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;day&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;&#x2F;mark&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;tags_filter&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;project-a&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;project-b&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;])
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;at_date&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;2020-01-01&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;a_file_with_content&lt;&#x2F;span&gt;&lt;span&gt;(some_content)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;when_run&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;should_succeed&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect_task_with_duration&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;project-a&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1h 00m&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect_task_with_duration&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;project-b&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;2h 00m&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Thank you for reading,&lt;&#x2F;p&gt;
&lt;p&gt;Hans&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
