Skip to main content

Test Driven Development - An agentic way to develop software

 Test Driven Development is my favorite kind of Software Development Life Cycle. 

TDD - Test Driven Development is a Software Development Life Cycle, that leads the development of a feature by writing the test first. 

The loop goes as, Write failing unit test -> Write the actual implementation -> Run the test so it passes

Test -> Develop -> Pass : Repeat

Now this verifies the implemented functionality is doing what is supposed to do. Lets look at a simple example -I think for the last couple of articles we are only looking at theory without any actual code or working lets fix that.

Say you are working on a feature where you need to give certain features only to Premium users, for that, first you will write unit test as

fun `verify user have premimum membership`(){
    //given
    setupUserProfile(loggedInViaEmail = true)
   
    //when
    val result = sut.checkIsUserHaveActiveSubscription()
   
    //then
    assert.(result).equals(true)
}

I'm sure at this point you know what the above code does, but just to rewire our brain, let me give you a quick recap. 

The above code uses the classic testing patter, give-when-then. 

Given - the data initialization

When - a condition is met

Then - assert the expected result

So we first setup  the profile in the with a setting loggedInViaEmail to true, this will initialize the data and prepare the SUT, which is System Under Test , and once the data is ready, we call the real function from them implementation checkIsUserhaveActiveSubscription(), so this will call the actual code and run it, against the data that we passed at the given step and will produce a result, for which later we assert the result.

This pretty much sums up what is happening in the above code. But this is an actual test code that you used to write once you have a function named `checkIsUserhaveActiveSubscription` whereas in TDD, you write this test before even that, well, adding a function name without declaration might throw compilation error, so lets not be that brutal. Add a function like this

fun checkIsUserhaveActiveSubscription():Boolean {
    TODO("Not yet implemented")
}

now, at least you wont get a compilation error, now your code will build but the test will fail. That is the core principle of failing test and then now you write code to pass the test, some might feel this doesn't feel right because you are not writing code to actually satisfy the use case, rather you are writing it just to pass the test case, well that is the beauty of TDD, because these tests define the actual use cases, and the more granular and detailed a test case is the better it improve the code quality.

But Why TDD?

This is the general question that will be asked everywhere, because, believe it or not, Test Driven Development requires a near perfect initial codebase setup and simply adding or deleting a functionality involves always touching the test cases first so it requires more time as well, and time is something more valuable than anything we often overlook TDD.

Time being one of constraint in todays fast paced development environment, we only identify an issue on production, and that is where Test Driven Development to the rescue.

Test Driven Development not only, identifies and covers test cases during the development itself, it will enforce a coding standard, which proportionally improves the code quality, lets take an example from the Android stack, because I'm much familiar with that, and there is a peculiar thing about Android's Unit test, those are, in Android, we cannot reference Android classes in an unit test as it requires the access to Android runtime,  and the most famous one of all is the `Context` which is an Android runtime library, which cannot and should not reside in the unit test as it might break the app's functionality under certain conditions.

So, In Android, it is generally advised to have anything related to `Context` at view level, not only with TDD, even if we are writing unit test cases, it will enforce you to not have any reference to Android classes, but Test Driven Development, will go on step further and will identify this kinds of code smells, before writing the actual timplemenation.

By practicing Test Driven Development, you identify these code smells, early in the implementation, and that gives the advantage to have the perfect architecture.

How TDD helps LLMs write better code?

The whole idea is based on the underlying technology behind how a large language model words, basically they are token prediction black box, and from whatever knowledge I have gathered, the a Token in large language model is either a small word of a part of a word, for example, the word "check" can be a single token,  and the word "Subscription" can be broken down to multiple tokens like "Subs", "crip and "tion" based on the tokenizer.

And this is what that helps these LLMs to be "smart" so in our example, to complete the function name from our example, the LLM will predict the next token sequentially, so when it sees the word "Subs" it predicts the next word based on the previous context as "crip".

This way, when it see a test cases, based on the context it will try to predict the next tokens as the code which will make the test cases pass.

Lets get a bit more detailed, so our test cases verifies the user's active subscription, for that we setup the profile data, and then we call the actual code, `checkIsUserHaveActiveSubscription`, and this function returns a Boolean which later we assert whether it is true or not.

What we are doing here is instead of asking the Agentic IDE to write a fucntion to check the user's active subscription, here we are defining the test cases first on what are the ways a active subscription can be available, and then we write code for that.

When Vibe coding, you should first describe the requirement and ask the AI to write test cases for it, include, keywords like, include edge cases , then when you ask the AI to write the actual implementation you don't just describe the requirement alone, you have to mention the failing test cases, so it follows the pattern strictly to pass the test cases.

How or Why This Works?

As the name describes, they are Large Language Models, so they basically have seen, more test and actual implementation during their training then we ever will.

This makes the prediction much more solid and grounded, because LLMs because Agentic by the way they are post-trained on, even though post training holds the generation pattern, the actual pattern identification happens during pre-training. So they can easily connect the dots.

Along with TDD, you should also be aware of the Scripting and Templating when vibe coding, as those holds a much useful precedence as TDD

With this I conclude the series of articles that touch based on how we can improve the code of Vibe coding LLM generated codes. All these articles might feel written aimlessly, and I would like to change and improve that, from now, we will see some hands on development strategies with actual production code.

Stay tuned for more! 

Comments

Popular posts from this blog

Unit Tests can help AI Assisted Development from code breaks

Code Breaks are the unspoken enemy of the Software Development Cycle. When you are writing code for a new feature there is a chance you might break an existing working functionality. Well, this can often come from code smells, remember Formatting and Linting, in that article we briefly touch based about about code smells. The irony here is, code breaks can sometimes introduced because of fixing code smells, lets look at an example, say, you want to ask the user for feedback after they tried a feature for certain number of times. For that you may put a number in constant as "ASK_FEEDBACK_AFTER_COUNT" so here this constant means, you have to ask the user about feedback after X count, the count is tracked as either as an action the user performs or the page transition. Now, you add a new feature, and here you can the user's feedback after a different count, lets say after 10 action, so instead of introducing a new variable, you edit the previous one. This is where the code b...

Templates gives a guidance to AI Assisted Development tools when generating code - Vibe Coding

Templates are a useful pattern holders that guide Agentic IDEs on how to structure the output. If I know one thing, that, LLMs, AIs are getting increasingly good at following instruction or pattern. Say, you give it a passage or a paragraph, with some extra spacing and indention, and ask the AI agent to follow the same spacing and indention, and create a new paragraph, at most it can maintain the same, after all that is how they learn about everything. Now, look at this from programming perspective, there are number of programming languages available, and each one of the has its own unique syntax, like a line in Java must end with a semicolon whereas in Kotlin we don't have to worry about that.  The key idea here is, Large Language Models, at the time of training were fed enormous amount of data to learn the pattern, and they identify them, in the above case, every time they see a Java code they "learn" there is a semicolon at the end of the line, but when they see a simi...

Formatting or Linting - Helps a lot in AI Assisted development - Vibe Coding

 I'm from Android background - but that doesn't really matter anymore since the "invention" of Vibe Coding. So, In my previous post, I have briefly touch based about AI Assisted Development - Vibe Coding, if you missed it do checkout at :  AI Assisted Development - Vibe coding is good, when done right Now why I choose this title, Vibe coding is good when done right, because, when you are vibe coding with the Agentic IDEs, it generates more code in a spawn of minutes then a human can process. We are already seeing these Agentic IDEs, develop an operating system from scratch over night. In reality it would take about more than month, so the amount of code that is being written is larger than the amount code we can safely accept and understand. This is one of the concerns of Vibe coding, some talk about all the time and some doesn't care at all, and shows their app running localhost. But as a sensible developer like some, I'm also going to take advantage of these...