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. Okay, so we've heard all the theory about test-driven development and so now it's time to put it into practice. And to do so, I've put together this example C# test-driven development project. This project uses .NET Core 3.1. It's written in C#, and we'll use test-driven development principles to actually rebuild this.
Now, at any stage of the development in the demonstration, if you wanna skip forward you can come back to this repository and within this repository there are a number of branches, one per each of the demonstrations that I'm gonna give.
Now, instead of having to write all of the code from scratch, if you just want to skip forward to one of the other lessons, then you can basically check out their particular branch, and then you'll have the code assets available. So the idea behind the sample project, is that we're going to build a bitcoin converter library, written in C#. And that particular library, once compiled and built, can then be imported into other .NET projects.
The library itself will contain a single class, this is called the converter SVC, or converter service class. And inside that class, there will be two public methods, get exchange rate and convert bitcoins. The get exchange rate method actually makes API calls over the network to CoinDesk. CoinDesk is a new site specializing in bitcoin and digital currencies, and they provide an API at this location here for getting information about the current exchange rate in particular currencies.
When it comes to doing the test driven development aspects of this project, we're going to use X unit which is a unit testing library or framework specifically for .NET Now on this development I'm actually going to use .NET Core 3.1.
The demonstrations as we go along, will introduce you to various tools and frameworks. I've already mentioned, we're going to use X unit, for unit testing. This will be used to implement our unit test. I'll also introduce you to Mock, a mocking library. Now, the reason for introducing you to Mock is when we go about writing our unit tests, we'll need to make sure that this external dependency on CoinDesks' API, is actually able to be mocked out. Because again, our unit tests shouldn't become integration tests.
More on this slide. I'll introduce you to GitHub actions. So, as we build the code using test-driven development principles, we'll check it into GitHub, and then we'll use GitHub actions to provide us with CycD features, to perform automated building and testing of our C# library. And then finally, I'll also introduce you to coveralls.
Coveralls allows us to upload our code coverage reports and to its own servers, so that we can see visuals as to how much code coverage we've got in terms of the unit test that we've created. It's a really cool service, but again, more on that later.
As earlier mentioned, there are a number of branches on this particular repository named step one, step two, down to step nine. As the demonstration proceeds, I'll go through the coding steps, as incorporated in each of those branches, sequentially.
Now at any stage, you can quickly jump to lessons ahead, and they will map back to these branches. Again, the essence of this test driven development example is to build the bitcoin converter library, and that library is going to contain two public methods. The first of which is the get exchange rate method. It takes a single input, which is a currency, and uses that to then communicate with the CoinDesk provided API, to get the current conversion rate for bitcoins in that currency.
The second method convert bitcoins also takes a currency as an input, and a value of coins, and then does the conversion. Now, once our bitcoin converter library has been created using test-driven development principles, we can then take the library itself and actually import it into other projects. And to demonstrate that aspect I've also provided a bitcoin converter client, which is just a C# console based project. This will allow us to actually run the program, importing a currency, and a number of bitcoins, and getting the conversion back. And I'll surely demonstrate this to give you the complete context of what we're doing.
Again, I've also mentioned that we'll have a demonstration on GitHub actions. Whenever I update the code and I check it back into the repository, we'll get this automation done for us within GitHub, where it compiles the library for us. This will also run our unit tests. And as a bonus, we'll also set it up to do code coverage testing, with the reports then submitted into Coveralls for visualization.
Now, to make sure that the demonstrations given here, can be performed on any operating system, whether it be Mac OS, windows, or Linux. I've provided a Vagrant file. By simply setting up the Vagrant box, you'll be able to follow along all of the demonstrations given, regardless of the operating system that you're working on. I'll demonstrate this shortly.
Okay. Let's go back to a demonstration of the bitcoin converter client. The first thing I'll do is I'll copy the git clone URL like so. I'll jump into my terminal, and I'll do git clone with the pasted URL. Next, I'll jump into the new directory. I'll do a directory listing. And here we can see, we've got the GitHub repo locally, and that we're in the main branch.
Next, I'll fire up visual studio code in the current directory. I'll bring up the integrated terminal, and here we'll launch our Vagrant box, which uses this Vagrant file. I do so by simply running Vagrant, up. So this is going to spin up in Ubuntu 18.04 server for us. The initial download of the box, if you don't have it locally already takes a couple of minutes.
Okay, that is completed successfully. So from here, we can SSH into the box by running Vagrant, SSH. Excellent. So now within the Ubuntu 18.04 instance. The very next thing I'll do is I'll need to install the .NET Core 3.1 framework.
Jumping back to our instructions. If I scroll down, I'll copy these instructions, and paste them back within our Vagrant instance. Okay, so now this is actually going ahead and installing .NET Core 3.1 on this Vagrant box, and that will give us the ability to then start running our .NET commands such as .NET build and .NET test.
While that is running, what I quickly want to demonstrate is this bitcoin converter dot client, which is a console based project, of which imports our bitcoin converter dot code DLL. Now this particular library is the one that I'm going to use to actually build using test-driven development principles.
Okay, excellent. It looks like our .Net Core 3.1 framework has been installed. We can confirm this by running .net--version. And indeed we're running 3.1.404 Now the next thing I want to draw your attention to is the fact that Vagrant conveniently maps or mounts the slash Vagrant directory, to a host directory in which the Vagrant file existed when we did the Vagrant up command.
So here we can see all of the same files and folders structure as we have here. So remember visual studio code is running on my local machine and I've used Vagrant to set up an instance I've then within the integrated terminal within visual studio code, done a Vagrant SSH, to give me a session within that box. And the cool thing is, is that these directories are basically one in the same. What that means is I can make edits up here, and they'll reflect down in this particular directory.
To demonstrate the bitcoin converter dot client executable, I'm going to first copy this directory, recursively, into to the Vagrant home directory, like so. I'll then navigate into this new copy. And from here, I'll run a .NET build to builder. So this is going to do a quick restore of any dependencies that this particular project has. And then it will go ahead and compile the source code.
Okay, excellent. The build has succeeded, and the next thing we want to do is actually publish it so that we get our executable out of that. Now to do this, I'm going to go back to our GitHub repository. And I'm going to skip ahead to step eight, which demonstrates the building of this project. And then I'll scroll down and I'm just going to copy this command here. And then I'm going to paste it back in the terminal, like so, and this will generate an executable.
Okay, that's completed. The next thing I'll quickly do is I'll run a Sudu at dash get, dash y, install, the tree command. And then I'll use the tree command to show you the directory structure that now exists within this folder. So here we can see that an executable subdirectory exists and our executable is in there.
So, let's try and run this. So I'll go to executable and I'll run this program. So here we can see that it's requesting user input. Coins. So let's say seven bitcoins, and then asks us for a currency, USD, Great British Pounds or Euro. So I'll go with USD.
So what that has done, is that that has called the CoinDesk currency API to get the exchange rate for USD dollars and then done the conversion of seven bitcoins, into that USD dollar amount. Again, I can do, say 100 bitcoins, and this time we'll do it in Euro. And there we go.
So we've got our value back. And just to confirm that this indeed is going off to the API, let's jump into our browser. And this time I'll bring up the CoinDesk calculator. And I will convert say 100 bitcoins into USD. And we can see here it converts into roughly $1,719,388.
Let's do the same within our own converter, okay, 100 USD And there we go. We roughly get the same amount, but keep in mind this is a real time service and the currency exchange rates are actually changing in a matter of seconds. So that gives you the context of what we're building. So we're not building the client itself. The client is going to use our library, it's the library that we're building.
Now, I'm going to build this from the ground up meaning that I'll head back into the Vagrant directory. And what I want to do is clean out the directory and start from scratch. To do so, I'm going to run the command ls. for the current directory. I'm going to pipe the results, which are all of the folders and file names into the grid command. I'm going to do an invert selection and I'm going to map on .Vagrant.
Now, the reason I'm doing this is I want to delete all files and folders, except for the .Vagrant meta directory. Vagrant uses, the .vagrant directory to store metadata about the current Vagrant box. In particular it stores the SSH key that is used to SSH into the box. Therefore, if we lose this directory, we lose our ability to SSH back into this particular Vagrant instance. And then finally I'll pipe the results through xis, to the remove command, doing it recursively.
Okay. That looks good. Now, to confirm that we've still got our .vagrant directory, I'll use the tree command like so. Excellent, so that looks good. We've still got our .vagrant meta directory available and that will continue to allow us to Vagrant SSH into this box. If we were to shut down the Vagrant box and then later resume it. Now, again, you don't need to do any of this. I'm just tearing a down to start fresh so that I can demonstrate how I'm going to build up the solution for you.
Okay. So now what we want to do is, we want to set up and initialize our new .NET Core 3.1 C# library, or our solution. To do so, if you jump back into a GitHub repository, and we change to the stick one branch, we can copy this command here, which will set up our new .NET solution. Okay, that's completed successfully.
Next. I want to add our X unit project into the solution. To do so, I'll copy these commands. So what that's done is that's set up a new project called a bitcoin converter.test. And that this project has been set up to use X unit for our unit testing framework of choice. And it's created an initial example unit test.
Now, we can confirm that everything is in place by simply running a .NET test command, which will execute that particular example unit test. In here, we can see that the test run was successful and that one unit test passed. So this is a good start. And this is really where we start to actually perform the test driven development of our bitcoin converter library. We do so even before we've actually created a project, which is going to store the actual implementation. This is the whole idea of red green refactor. That is, we create the test first, before the implementation, we run the test that fails, and then we write the implementation and have the test pass.
Let's begin this particular phase. So the first thing I'll do is I'm going to update the namespace. In this case, I'm going to add Cloud Academy to the front of it. That will do. I'm then going to update our test class. I'm going to call it, bitcoin converter SVC And then for our very first test I'm going to create a test named get exchange rate _ USD _ returns USD exchange rate.
So the idea behind this naming convention is the first part will be method under test, the second part will be the state used within the test. So this will be our input currency. And then the third part of the name is the expected response or the expectation. Next for our implementation, I'm going to use the arrange act assert pattern.
So we start with the arrange part, we then do the act, and then we finish with an assertion. Okay. So for a range, I'll have an implicit variable called converter service. And I'm going to say that will hold an instance of our converter service, which we haven't implemented yet.
Okay. That looks good. When it comes to act, I'm going to set up another implicit variable called, exchange rate. And this will be equal to the value returned by our method, that is under test. And that needs to take an input, which will be the currency. And again, the currency for this particular unit test will be USD for USD dollars. We need to set up our session.
So I'm going to set up an expected value, set to 100. So 100 being a fictitious value for now. And then finally, I'm going to use X units assert object and on it I'm going to call the equal static method. And takes an expected value, and an actual value, which will be our exchange rate. I'll save this file. And this represents our first unit test.
Now, the next thing we want to do is run .NET test to execute our unit tests. And this should fail as it does because our implementation does not yet exist. This is the red part of the red green refactor workflow. So what we need to do now is actually go and perform the implementation. To do so we need to set up a new project, which is our class library. I'll jump back to the documentation. And I'm going to copy this command here.
Watch. It's going to create a new .NET class library named bitcoin converter.code using the .NET Core 3.1 framework. And then I'll add that to our existing solution. Okay. I'm going to paste those commands. Okay. That looks good. And we've got our new project.
So within this project, an example class has been created. And what we'll need to do is go ahead and perform the implementation for this. To perform the implementation, our unit test tells us a number of things. It tells us that we should have a class called convert service. Actually I'll update this to be converted service. So I'll copy this. And then I'll jump into our implementation.
Our class is going to be converted service. Again in the namespace I'll update this, and I'll add Cloud Academy, like so and I'll just say that exists in the Cloud Academy.bitcoin namespace. Within our class we'll want a default constructor. Taking zero parameters, and then it should also have a method called get exchange rate. Back within our implementation, I'm now going to create that method, it will be public, returning an int, paste in the name, and it needs to take an input which we know is going to be a string, and it will be our currency.
For our first iteration, I'll simply have enough statement, that checks if the input currency equals USD. And if it does, I'm going to return the value 100. I'll save that. I'll copy the class name and update our file name, just to keep things consistent. And at this stage I'll rerun our test. So again, our .NET test phase has failed. So the reason being is I think the namespace which holds our converter SVC class, needs to be the same base namespace here. So I'll update this namespace, like so. Save, and rerun the test. So again, our .NET test phase has failed. I believe that this is probably due to the fact that our bitcoin converter. test project doesn't actually have a current reference back to our bitcoin converter library. So we need to add that in.
So to do that back within our instructions, I'll copy this command here, jump back into the terminal, and I'll run this command. So what that has done, is that that's added a reference of our bitcoin class library into our X unit test library. We can see that here. So our project file has been updated with this.
Okay. So we're making progress, but our test has failed again. And this kind of failure of LTS is not a problem. In fact, this is a good thing because it's telling us that we have issues and we need to correct them. Here we can see that it's telling us that not all code paths have a return value. In this case, we returned back to our implementation.
So what we need to do here is add another return statement and in this case, I'll just return zero. I'll save this. I'll rerun our tests again. And excellent. Our first test has now passed. We've completed the red, the green, and now we're at the stage where we move into the refactor phase.
So in this case, we're going to go back into our unit test, it's an extra unit test, and then do some more implementation. So let's do that. And this time, what I'm going to do as I'm going to just copy the existing unit test paste it like so, and I'm going to provide a test for Great British Pounds. I'll set it up to be a fictitious failure of 200. I've saved the unit tests, and I'll rerun, and this should fail, as it has, because one of our unit test, does not have an implementation for great British pounds. So let's do that.
So we jump back into our implementation and we'll expand it. So I'll just copy this. Paste. And set the return value to be 200, save. Rerunning the test, this should now turn green, and a does. So I've got two tests and both have passed. Let's round it out, by adding in a test for Euros.
So again, our naming convention remains the same. We're calling the same method, but just with a different input parameter, and with a different expectation for an output value. In this case, all four euros. And I'll just increment it by 100 being 300, save, and rerun the test which will fail, two will pass, the third one will fail. This is the red phase.
Go back to our implementation. Set this to Euro, 300, save. Rerun the test and we'll get three of three, hopefully. And this is our green phase. So here we can see we've done a couple of iterations of the red green refactor approach for test-driven development. Nice and easy.
Up until now, we've completed the test driven development, for this particular method, the get exchange rate method. I will now leave it as an exercise, and challenge for yourself to use test-driven development to perform the implementation of the convert bitcoins method.
To help you along, I'll provide the following tip, convert bitcoins should call the get exchange rate method which we've just completed. And then multiply it by the number of coins passed into the convert bitcoins method, returning the result. Don't worry if you get stuck. To see and use the implementation that I have provided, check out the step one branch, And within that branch I have provided implementations for both the unit test and the method under test.
Okay. That now completes this initial test-driven development demonstration. Go ahead and close it, 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).