Test-Driven Development (TDD) is a discipline that helps to create error-free, quality code, and drives productivity by reducing and eliminating errors as well as providing many other benefits.
This entry-level training course is designed to bring you quickly up to speed with the basic features and processes involved in performing C# based test-driven development using xUnit. It covers the key principles and practices of test-driven development, which you can then utilize within your own software engineering projects.
In this course, you'll be guided through an end-to-end sample project in which we use test-driven development to build, test, and package a .NET Core 3.1 C# based library that will provide an API to convert Bitcoins into foreign currency.
If you have any feedback relating to this course, please contact us at firstname.lastname@example.org.
- Understand the fundamentals of TDD and xUnit
- Use TDD to build, test, and package a .NET Core 3.1 C# based library
- Perform TDD by performing Red-Green-Refactor iterations
- Learn how to use TDD to build, test, and package a .NET Core 3.1 C# based library
- Learn how to create unit tests using the xUnit testing framework
- Software developers interested in using TDD to build .NET Core applications using C#
To get the most out of this course, you should have a basic understanding of .NET Core and the C# programming language, as well as software development and the software development life cycle (SDLC).
Welcome back. In this lesson, I'm going to provide you with a quick introduction to test-driven development and related fundamentals, ensuring that you understand the motives behind test-driven development, and why it's important to utilize when engineering your own software. Along the way, I'll highlight various benefits attributed to test-driven development.
Let's begin. Test-driven development is an Agile software engineering practice that relies on the repetition of quick and short development cycles. Requirements get translated into very specific test cases. Production code is then implemented to improve and pass the respective tests. This is in stark contrast to Big Bang software development processes in which almost all production code is first written before it is proven to actually meet and pass the original business requirements.
At first, test-driven development may feel unnatural, backward, and costly, as it takes additional effort and time to produce the final working production code base. However, years of research and studies have proven and shown in fact that test-driven development can be quite the opposite, as you'll soon learn and discover for yourself as we proceed. From a testing point of view, test-driven development involves writing unit tests.
Unit tests are focused on testing the smallest units of the overall system. These types of tests are at the bottom of the testing stack. They are built and executed at the earliest moment and re-executed with the highest frequency. When adopting test-driven development, you'll often hear the associated phrase, red, green, refactor. Red, green, refactor is another Agile engineering pattern which stipulates a quick development iteration or cycle to minimize broad stroke assumptions about how your code should work versus the way it currently works.
Let's now review each part of the red, green, refactor workflow in finer detail. The red phase requires you to create a unit test before any production code is actually implemented. A test declares a number of expected conditions or assertions about how your production code will behave when used. For example, if we were implementing a square root function, we might declare a test that states the square root function when operating on the value nine should return the result three.
Further still and perhaps more importantly, we should validate edge cases, such as how the square root function should behave when either zero or a negative number is passed into it. Having created this test, execute the test to make sure that it fails first, which it must, since we haven't yet created the implementation. This is why this phase of the test-driven development cycle is named red.
Next, the green phase requires you to implement the feature that just failed. Continuing with the square root function, the green phase requires us to implement it in a manner such that the test now passes, i.e. the test goes green. It should be noted here that we only want to write just enough code to satisfy and pass the previous test assertions and no more. During the green phase, we also want to execute all other existing tests or the full test suite to ensure that our recently updated implementation does not introduce any unintentional regression or logic failures, et cetera. The green phase continues until all tests execute successfully and become green. This is why this phase of the test-driven development cycle is named green.
Finally, we arrive back at the refactor phase. In this phase, we may look to refactor the level of abstraction and/or optimize the code base in some particular way. Refactorings may also include those for performance reasons. That is, utilizing better algorithms, et cetera.
Optimizations can also address code quality issues, improving readability and maintainability. Optimizations may be driven by a need to adhere to the DRY principle. That is, removing and reducing code that is repeated. Alternatively, coding standard adherence may force you to fall in line with agreed standards thereby requiring refactorings. The good news is that after making any and maybe all of these types of refactorings, you can revalidate your refactorings have not broken the implementation by simply rerunning all existing unit tests.
The refactor phase should end with all tests again passing and returning green. Now that we have a clear understanding of what test-driven development is and the respective red, green, refactor workflow, let's refine our understanding of the process of actually creating tests, addressing specifically when and how often we should be writing unit tests, et cetera.
To do so, I'll first introduce you to Robert C. Martin. Robert C. Martin, who more commonly goes by the moniker Uncle Bob on the internet, is a well-known author who wrote the often referred to Agile book, "Clean Code."
Now, when it comes to knowing exactly when and how often to write unit tests, Uncle Bob provides guidance in the form of a popular set of rules referred to as the three rules of test-driven development. The three rules of test-driven development work like this. The first rule of test-driven development is, you are not allowed to write any production code until you have first written a test that fails.
The second rule of test-driven development is, you are not allowed to write more of a test than is sufficient to fail, and not compiling is failing. So as soon as the test fails to compile, you must stop writing it. And finally, the third rule of test-driven development is, you are not allowed to write any more production code than is sufficient to pass the currently failing test.
Now, collectively these three rules of test-driven development are really just trying to hone your test-driven development skills and fine tune them so that when you're performing test-driven development, you're doing so at a very granular level. The key idea here is that the tests you create are introduced early and frequently with the outcome being that your production code is validated as early as possible, ensuring that it is doing what it is designed to do and nothing else.
The test-driven development workflow is largely performed locally on the developer's workstation as he or she goes about implementing their requirements. Once they have completed their implementation for a particular feature, that feature is then pushed up into the source control system, which in turn should kick off a CICD build job. The build job itself should re-execute the entire test suite again to confirm the integrity of all items under test. That is, all of the individual classes, et cetera. This should happen before any other integration or end-to-end tests are performed.
Now, at build time, it is not untypical to have thousands of unit tests written and to continually rerun these anytime a build takes place to validate the functional correctness of all checked and coding assets within the source control system.
Finally, when it comes to performing test-driven development and creating your own unit tests, consider the following best practices. Individual tests should be small and focused. Test and assert for expected results. Test and assert for edge cases and known error conditions.
Execute tests regularly and often. And avoid creating tests which have external dependencies whose state is unknown and uncontrollable. Okay, that completes this lesson on test-driven development. In this lesson, you learnt about what test-driven development is, you learnt about the red, green, refactor workflow, you learnt about the three rules of test-driven development, and you learnt about how test-driven development leverages unit testing and unit tests.
Go ahead and now close this lesson, and I'll see you shortly in the next one.
Jeremy is a Content Lead Architect and DevOps SME here at Cloud Academy where he specializes in developing DevOps technical training documentation.
He has a strong background in software engineering, and has been coding with various languages, frameworks, and systems for the past 25+ years. In recent times, Jeremy has been focused on DevOps, Cloud (AWS, Azure, GCP), Security, Kubernetes, and Machine Learning.
Jeremy holds professional certifications for AWS, Azure, GCP, Terraform, Kubernetes (CKA, CKAD, CKS).