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
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
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
Post a Comment
Don't be a stranger! Speak your mind.