<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mohamed Afiq </title>
    <description>The latest articles on DEV Community by Mohamed Afiq  (@mafiqqq).</description>
    <link>https://dev.to/mafiqqq</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F119363%2Fd3005e6b-9da6-4ae7-9855-c5102e2387d5.png</url>
      <title>DEV Community: Mohamed Afiq </title>
      <link>https://dev.to/mafiqqq</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mafiqqq"/>
    <language>en</language>
    <item>
      <title>Building ResuMatch AI with TDD and AI-Assisted Development (Claude)</title>
      <dc:creator>Mohamed Afiq </dc:creator>
      <pubDate>Fri, 15 May 2026 08:29:10 +0000</pubDate>
      <link>https://dev.to/mafiqqq/building-resumatch-ai-with-tdd-and-ai-assisted-development-claude-24hd</link>
      <guid>https://dev.to/mafiqqq/building-resumatch-ai-with-tdd-and-ai-assisted-development-claude-24hd</guid>
      <description>&lt;h3&gt;
  
  
  🎯 Why I Started Experimenting with This
&lt;/h3&gt;

&lt;p&gt;While building ResuMatch AI, I ran into a problem I didn’t expect:&lt;/p&gt;

&lt;p&gt;AI could generate code extremely fast… but it could also confidently generate the wrong implementation.&lt;/p&gt;

&lt;p&gt;At first, I was treating AI like an autopilot.. &lt;em&gt;blindly accepting all the changes ..&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Eventually I realized something important:&lt;/p&gt;

&lt;p&gt;If I couldn’t clearly define the expected behavior first, I couldn’t properly review the AI’s output either.&lt;/p&gt;

&lt;p&gt;That pushed me into learning Test-Driven Development (TDD) more seriously while building actual features in my project.&lt;/p&gt;

&lt;p&gt;This article isn’t a guide on “the best way” to build AI systems. It’s mostly a reflection on what I learned while combining TDD, ASP.NET Core, and AI-assisted development in a real application.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 The Mental Model That Changed Everything
&lt;/h3&gt;

&lt;p&gt;One idea from my mentorship sessions really stuck with me:&lt;/p&gt;

&lt;p&gt;You are the architect of intent.&lt;br&gt;
The AI is the implementation engine.&lt;/p&gt;

&lt;p&gt;That completely changed how I worked with AI.&lt;/p&gt;

&lt;p&gt;Instead of asking AI to “build the feature,” I started:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Defining the expected behavior first&lt;/li&gt;
&lt;li&gt;Writing failing tests&lt;/li&gt;
&lt;li&gt;Letting AI implement against those tests&lt;/li&gt;
&lt;li&gt;Reviewing whether the implementation actually satisfied the contract&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tests became the control mechanism... not the AI.&lt;/p&gt;
&lt;h3&gt;
  
  
  🏗️ The Feature I Used to Practice TDD
&lt;/h3&gt;

&lt;p&gt;One of the first features I implemented this way in ResuMatch AI was a daily generation limit system.&lt;/p&gt;

&lt;p&gt;The idea was simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free users can only generate 3 tailored applications per day&lt;/li&gt;
&lt;li&gt;Usage resets daily&lt;/li&gt;
&lt;li&gt;Backend should block requests once the limit is reached&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of jumping straight into implementation, I started with test scenarios first.&lt;/p&gt;

&lt;p&gt;🔴 Red → 🟢 Green → 🔵 Refactor&lt;/p&gt;

&lt;p&gt;I followed the classic TDD cycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write failing test
↓
Run tests (RED)
↓
Implement minimum code
↓
Run tests again (GREEN)
↓
Clean up implementation (REFACTOR)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What surprised me was how useful this became when working with AI-generated code.&lt;/p&gt;

&lt;p&gt;Without tests, it was easy to accept code that “looked correct.”&lt;/p&gt;

&lt;p&gt;With tests, incorrect assumptions surfaced immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✍️ Writing the Behaviors First
&lt;/h3&gt;

&lt;p&gt;Before implementation, I wrote the feature scenarios as test method names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;CreateApplication_WhenUserHasThreeGenerationsToday_ShouldThrowDailyLimitExceededException&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;CreateApplication_WhenUserHadThreeGenerationsYesterday_ShouldSucceed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;CreateApplication_WhenNoUsageRowExists_ShouldCreateUsageRowWithCountOne&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was probably the biggest learning moment for me.&lt;/p&gt;

&lt;p&gt;The test names themselves became executable requirements.&lt;/p&gt;

&lt;p&gt;If I couldn’t clearly name the scenario, I usually didn’t fully understand the business rule yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤖 Where AI Actually Helped
&lt;/h3&gt;

&lt;p&gt;Once the test structure was clear, AI became much more useful.&lt;/p&gt;

&lt;p&gt;I used it to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fill in repetitive Arrange/Act/Assert sections
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// File: Unit/Services/ApplicationServiceTests.cs&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;ResuMatch.Tests.Unit.Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationServiceTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// SCENARIO 1: Happy path — user is under the limit&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;CreateApplication_WhenUserHasZeroGenerationsToday_ShouldSucceed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// YOU write this comment structure:&lt;/span&gt;
        &lt;span class="c1"&gt;// Arrange: user exists, no UserUsage row for today&lt;/span&gt;
        &lt;span class="c1"&gt;// Act: call CreateApplicationAsync&lt;/span&gt;
        &lt;span class="c1"&gt;// Assert: returns valid Guid, no exception&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// SCENARIO 2: Edge case — exactly at limit (2 out of 3 used)&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;CreateApplication_WhenUserHasTwoGenerationsToday_ShouldSucceed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Arrange: UserUsage row exists with GenerationCount = 2&lt;/span&gt;
        &lt;span class="c1"&gt;// Act: call CreateApplicationAsync&lt;/span&gt;
        &lt;span class="c1"&gt;// Assert: succeeds, GenerationCount becomes 3&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Many more scenarios&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Generate boilerplate EF Core setup&lt;/li&gt;
&lt;li&gt;Implement DTOs and exception classes&lt;/li&gt;
&lt;li&gt;Suggest minimal production code changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, after defining the expected behavior, I could prompt the AI with very targeted instructions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modify ApplicationService to make these tests pass.&lt;/li&gt;
&lt;li&gt;Do not change method names.&lt;/li&gt;
&lt;li&gt;Do not modify unrelated logic.&lt;/li&gt;
&lt;li&gt;Use DateOnly.FromDateTime(DateTime.UtcNow).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That produced far better results than vague prompts like:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Build a rate limiting feature for this application and only allow 3 attempts”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In conclusion, TDD doesn't make AI deterministic. It makes your integration reliable, your refactoring safe, and your debugging sane. If you're building with AI-Assisted development, don't skip the tests. Your future self will thank you.&lt;/p&gt;

&lt;h3&gt;
  
  
  💬 Let's Connect!
&lt;/h3&gt;

&lt;p&gt;Have you tried TDD with AI agents? What challenges did you face?&lt;br&gt;
Drop a comment below or connect with me:&lt;/p&gt;

&lt;p&gt;GitHub: [&lt;a href="https://github.com/mafiqqq" rel="noopener noreferrer"&gt;https://github.com/mafiqqq&lt;/a&gt;]&lt;br&gt;
LinkedIn: [&lt;a href="https://www.linkedin.com/in/afiqqqx/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/afiqqqx/&lt;/a&gt;]&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>softwareengineering</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
