Test-driven development has been a buzzword for many years so the benefits test-driven development (TDD) brings to development teams should be well known by now. Even though the term TDD is ubiquitous with quality assurance standard operating procedures, why are so many development teams still not utilizing the process? There are multiple reasons why development teams have not adopted this process and that is one topic that will be investigated in this article. This article will also go into the topics of how to develop software using TDD and how to overcome some of the obstacles to adopting the TDD process.
We’ll start by discussing some of the common obstacles that prevent development teams from adopting and implementing the TDD process, but I would be remiss to not list a few of the benefits that TDD brings to development teams.
- TDD helps reduce bugs during development
- TDD aides in reducing debugging time
- TDD enforces development teams to adopt an end user mindset
- TDD helps development teams avoid collateral damage
- TDD helps achieve YAGNI (You Aren’t Going To Need It)
These are some pretty considerable advantages for teams using agile processes but can be just as beneficial for development teams using other software development processes. With these advantages come some of the disadvantages listed below.
- TDD requires a larger investment of time and resources
- TDD requires specialty skills needed from resources
- TDD does not bode well with undefined scope or refactoring
- Developers tend to find ways around utilizing TDD during “crunch mode”
Let’s break down each disadvantage and provide ways that these disadvantages can be mitigated or alleviated. The first and most leering disadvantage is the requirement for a larger investment of time and resources due to the extra time needed for unit testing. This is evidently obvious to teams once stories are estimated and sprints are planned. If your team is not taking the extra time into consideration when estimating stories, then I can honestly say with the kindest of intentions that YOU ARE DOING SOMETHING WRONG!
TDD lends itself to be advantageous to agile teams because of the intended reduction in fallout during later, and sometimes client-facing, stages of the SDLC. Even though the TDD process should, in theory, reduce the amount of costly time and resources needed for bug fixing during a UAT or even production phase, I have found very few instances where TDD actually saved the team money or time. This can be very difficult for teams to justify when building software for external clients who have a limited budget.
How To Sell The TDD Process To Clients
Since focusing on cost savings is not a viable option when trying to sell the TDD process to clients, it is important for teams to stress the importance of the quality benefits that come with TDD. I have seen companies taking two different approaches to selling the TDD process to clients. The first approach I found companies taking is up-selling the process at a premium with the benefit being the increased deliverance of quality. I don’t necessarily prefer this approach since it can feel like there is a lack of concern for quality from the development team unless the client is willing to pay for it. From personal experience, I have found this approach can introduce a lack of trust from the client because the client may feel like they are paying for an additional measure that should be an included cost. This can be compared to the way most people feel when paying for an extended automobile warranty or life insurance policy.
The other approach I found that is utilized when selling the TDD process is the company being upfront with the client that the TDD process is simply part of the team’s development process to ensure the highest quality of software is delivered to the customer. This is more difficult for the initial proposal/bid, but I have found this is the approach that will elicit the most trust with clients and most likely to result in reoccurring contracts/sales. When selling the TDD process as an integrated part of your SDLC process, it can be beneficial to have a standard fallout percentage promise to the customer in order to justify the higher cost of TDD. The lower the fallout percentage promise is, the more comfortable the client will be with investing in the development team in providing software solutions. The percentage should be based on historical data from the team to prevent unrealistic expectations being presented to the client.
How To Use TDD
Okay, now that we are done with the build-up of the TDD process, we can discuss how to implement TDD into our development process. Just to be completely clear though, this explanation will throw in elements from Acceptance Test Driven Development (ATDD) because I believe it brings in some very useful benefits to the traditional TDD process.
Test-driven development follows three activities as shown in the diagram below. The first step is to write a failing test. The following step is to write just enough code to make the test pass. Lastly, you should refactor the code to help achieve YAGNI. These steps may be repeated as many times as needed to thoroughly test the functionality.
Unit Test Structure
In order to write your failing test, you will need to know the structure of a good unit test. The structure of any well-structured unit test will follow the AAA structure. The three As are Arrange, Act, and Assert. The following explanation will not go too far in depth on how to mock dependencies or every type of assertion that can be used for unit testing, but it should provide a base of understanding to start experimenting with unit testing frameworks. Before getting into the three As, it’s important to understand how to set up your testing class.
When creating your test class, there is actually not much you need to set up when using Visual Studio’s default MS Test Suite. Actually, the only thing you will need to do to let Visual Studio know it’s a test class is to decorate the class header with the TestClass attribute. The TestClass attribute is used to make sure the test methods are available when running the tests.
The image above shows a very simple method called Init that has an attribute called TestInitialize. Just as the attribute name sounds, this method is initialized with each test to provide the data used as the backing for your test(s). This method, sometimes called a testbed, is usually a method that populates the backing data through hard-coded values. Even though it is common practice to use hard-coded data, I have found some development teams using external files such as excel files and designated databases for establishing a test bed. The most important principle when establishing a test bed is the data must remain constant to provide more reliable tests.
Finally, let’s break down the structure of the web method above. The first attribute with the name of TestMethod is not only the most important but required to ensure the test is available to run. The attributes below the TestMethod attribute are descriptor attributes used for classifying and easily grouping the test methods. We will discuss how they are grouped a little later. The access modifier for the method is public and the return type is void since we do not need to return anything. The last important part to mention is that when creating method headers, the more descriptive the name of the method, the better any other developer or reviewer will understand the intention of what the method should be testing.
I won’t get too deep into what is happening with the assignment of the options variable in this blog post since it is not as important for those wanting to purely learn about TDD, but just know that it is utilizing a .Net Core tool for creating the database context options to use an in-memory database for mocking of the manipulation of data without using the standard repository structure. Within our arrange step, we are initializing the database context and adding the data from our testbed that was created in the Init method.
Immediately after arranging the data, it’s time for action. The next step is initializing the banking service and then calling the transfer method. Lastly, the test method is calling the assert method from the Microsoft Unit Testing framework to verify the results. The assert methods are verifying that after transferring 50 dollars from account two into account one, account two and account one should both have 150 dollars in their respective accounts. Now that we have gone through the parts of a unit test, let’s review the TDD process.
Now that we are all aware of how to write our test classes, let’s review how we will use these classes in the TDD process. The first step will include writing a test class like the one shown above and then writing the test method that will be used to test the smallest unit of the functionality intended to test. Before writing a single line of functional code, the test method used to test the code is written to “drive” the functional code.
Before you write any test code, you should run all current tests to make sure there are no failing tests already. If all current tests pass, then you are free to create your test class. If any tests fail, they will need to be fixed before continuing. After verifying all tests pass, then you should write your test method to test the piece of functional code you plan on coding. Once you finish writing just enough code to make your test pass, it’s time to move on to writing the next test until your test methods have tested an “acceptable” amount of the functional code. Once all tests for the functionality are complete, run all the tests and make sure they all pass. The test results should look as above. With the image above, you can also see how MS Test Explorer kindly groups test methods by the attributes you put on each test method’s header to help promote a clean and meaningful organization of test methods.
Now that we are aware of how to create unit tests and how they are utilized within the TDD process, we should have what we need to start putting the process in practice. It is important to remember to consider whether the additional effort and time with TDD will result in a positive ROI for your project. With that said, my stance has always been that quality assurance processes such as TDD or peer programming can always provide long-term value for a development team by changing the development teams mindset from excellent coders to coordinated user-centric engineers.
Refer to https://github.com/kalbert1/Going-Undercoder-TDD for the source code related to this TDD content.