Test Driven Development
Test Driven Development (TDD) is software development approach in which test cases are developed to specify and validate what the code will do. In simple terms, test cases for each functionality are created and tested first and if the test fails then the new code is written in order to pass the test and making code simple and bug-free.
Test-Driven Development starts with designing and developing tests for every small functionality of an application. TDD framework instructs developers to write new code only if an automated test has failed. This avoids duplication of code.
Let’s take an example, as you can see in the above image, first you write tests to check the functionality. And then you will run it. Of course, the tests will fail because you have not written the code functionality to make it pass yet. Now only you start write to code to make the test pass and then run the tests again. If it still fails, then you will do tweaks and refine the code to make the test pass. Once the test starts to Pass then you will go to next test and start writing code for that. This is how the test driven development happens.
Following is a funny way of explaining how test driven development happen.
Test Automation Pyramid
The test automation pyramid represents the various types of tests and the frequency at which they should appear in the codebase’s test suite. It’s all about giving the developer immediate feedback that code changes don’t break existing features. The test pyramid has three distinct sections. Unit tests are at the lowest level, integration tests make up the middle tier, and end-to-end tests form the top. The test pyramid helps developers deliver quality software.
Why do we need to make a distinction between the types of tests in the codebase? Let’s answer this question by breaking down the tiers.
Unit Tests
Our test pyramid needs a strong base, and that’s where unit tests come in. This means they’re the most common type of test in the test pyramid.
What’s a unit test? A unit test focuses on testing a very small component or piece of functionality of the codebase. Its goal is to validate that the unit behaves as expected in isolated conditions. This could be an individual function or a class. Developers should focus on testing a number of scenarios ranging from happy path to error handling.
The unit test suite should run quickly because they’re the largest subset of tests in our pyramid and will continue to grow in number as new features are added. The unit test suite should be run any time a developer makes a code change. This gives the developer immediate feedback on whether or not their new code broke anything in the codebase. A fast unit test suite encourages developers to run the suite more often, which cuts down on the time spent debugging problems.
One way to build a strong unit test suite is through the practice of test-driven development (TDD). TDD can naturally lead to a strong unit test suite because it requires a test to be written before any code. As new features are added, new unit tests are created.
Unit tests are great for testing small pieces of a codebase. However, unit tests aren’t representative of a deployed application. And they don’t test the interactions of the app with the outside world. This is where integration tests come in.
Integration Tests
Unit tests aren’t enough to ensure the quality of a codebase, so now we’ll introduce integration tests. Integration tests make up the middle tier of our test automation pyramid. This means that integration tests should not appear as often as unit tests. Integration testing can be an overloaded term in the software development world, which is prone to confusion. I’ll define integration tests as tests that validate interactions with external components. Let’s unpack that a bit. External components can be anything that lives outside of your application, but that your app still depends on.
For instance, databases are a very common external dependency in software applications. It’s important to test that your application interacts with your database as intended. Database integration tests should ideally run against a real instance of a database similar to the production environment. However, this requires managing the database locally or in the build pipeline.
Furthermore, the integration with external services should also be tested. These types of tests can also be called service tests. Service integration tests should focus on the interaction of your code with the external service such as a RESTful API.
So why do developers even bother with unit tests if integration tests show our code is functioning with the external dependencies? Because these dependencies are outside our app, the feature we’re testing requires communication to the dependency. This will make a call to the database or web service, which can cause the integration test to run slower compared to unit tests. Furthermore, if we’re testing an integration with an external web service, it’ll require a preproduction environment capable of testing against. Finally, it may be difficult to test each scenario from an integration point of view, such as error cases, because we cannot control the responses from the dependency.
End-to-End Tests
Finally, we’re at the top of the test automation pyramid. At the end of the day, we want to make sure our entire application is functioning as expected, and that’s exactly what end-to-end tests help with. End-to-end tests are what they sound like; they test that your application is working from start to finish. In other words, they test the integration of the front end with the back end.
I like to think of end-to-end tests from the perspective of the end user. How would a person interact with my app? And how can I write a test from the user’s perspective? Let’s dig into an example. Our application more than likely has a login page. And we may want to test that the login page is working. This would require the following steps:
- Find the username text field and type the username.
- Locate the password text field and type the password.
- Click the submit button.
This could be a very time-consuming and tedious process for developers to test their code change each and every time. Not only that, but different developers may have different strategies for this manual test. So how can we make this process easier to manage and more reproducible?
End-to-end tests are at the top of the test automation pyramid because they can be slow and very fragile. And like integration tests, they may depend on external dependencies that may be unreliable.
Now by looking at all these definitions you will get to know the importance of having Unit tests, Integration tests and End to end tests.
And by now you should be able to realize why unit testing is very much important in development cycle. As you can see in the pyramid above, when you go towards the top level of the application, if you find any bugs at the latter stages of the application, it will be really costly to fix. Because you have built most of the functionalities and everything is depending on each other. Fixing a bug in one area might break a functionality in another area. This will make a chaos and need lot of effort to test it properly. Hence having Unit testing at first place will help you to find bugs in early stage and it will be easy to fix as well. Assume you are building a house. You find an issue in the foundation once you have built the walls and about to place the roof. It will be costly to fix the issue in the foundation because the whole house stands on it. If we could find it at the initial stage itself, we could fix it easily and will be less costly.
Therefore, Test-driven development is recommended in Software development lifecycle.