The course is part of this learning path
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 demonstration, I'm going to round it out by adding in some unit tests to test error conditions. I'm also going to update the actual implementation to contain error handling capabilities. So returning to our unit testing file, I'll scroll to the bottom, and this time I'm going to add in the following new unit test.
So the key point of this unit test is that it's acting on the ConvertBitcoins method, it's testing the following state, BitcoinsAPIServiceUnavailable, and when this happens, it returns negative one, which will be our example response code for this particular service when that condition happens. So I'm now going to copy in the mocking code that will allow us to mock this particular unit test.
Now without going through all of the code line by line, the key line items are these ones here. So what we're doing here is that within this particular mock object, we're setting up the HTTP response to have a status code of service unavailable. And we then exercise our method under test, which is ConvertBitcoins, and the expectation is that we get a negative one value back and then we assert on it. So we'll save that and then we'll do our test, we'll run .Net test, and this should be red because we haven't actually implemented the error handling within our implementation.
So, nine tests passed and one failed, which is the new one we've just added. So we'll now refactor our implementation to add in the error handling. So I'll start off in the GetExchangeRate method, and what I'm going to do here is I'm going to set up a new variable, and this will also give us a chance to clean up some of the code that's duplicated. I'll set rate to be zero, I'm then going to add in a new try-catch block, and I'll move our call into the try block, like so. I'll copy in the parsing of our response into the same block, and then I'll also parse the rate within the try block as well, like so. So rateStr.GetString, and we'll need to rename this variable, like so.
Okay, that looks good, and I'll now implement a catch, and this time I'll simply return rate set to negative one if something fails. Now I can remove all of this, and then finally we'll update the return value so that it calls Math.Round, and we'll make sure that rate is always returned with four decimals only. So that's cleaned up a lot of stuff and it's made the code much tidier and we've got our error handling in there for that particular method, so that's good. We can now run our tests, and we're still not quite there, so we need to fix up a couple more things.
So we need to update the ConvertBitcoins method, so we add an if block here, we'll change this line here to be returning just the exchange rate, we'll remove the multiplication of coins on the end of it, and then we'll test if the exchange rate is greater or equal to zero, then we'll set dollars to be the exchange rate multiplied by coins. I'll give dollars an initial value of zero, else I'll return dollars as being negative one, and then finally we continue on and return dollars again, but what I'll do here is I'll make sure that the return value is also to four decimal places. Save that and rerun our tests, and this time everything should turn green, nope. I've got one mistake here, I need to change this to double.
Okay, excellent, so our 10th unit test, which tests an error condition, has now passed. Let's do another one, so back within our unit tests, we'll do one final test, and this one will test that a exception is thrown. So I'll copy this portion of our previous unit test method, and this time I'll call it, the state that we're going to test will be Bitcoins less then zero. So if we pass in a negative amount, then this should throw an argument exception.
So I'll now paste in the implementation, and all this is doing is it is calling out mock converter, calling the ConvertBitcoins on that, and we're passing in a negative value for the number of Bitcoins, this could be any value, say negative three, it could be negative six, and then we're going to assert that an argument exception has been thrown, so let's save this. Now actually, before we do the implementation, we forgot to run our tests, so we want to make sure they return to red.
Excellent, so the new unit test hasn't been implemented therefore it's failing, and so back within our class, we now need to do the implementation. So this is quite easy to do, all we need to do is within the ConvertBitcoins method, we do a test at the start, if coins, which is passed in, and actually, we want to be passing in double, so if coins, which is passed in, is less than zero, then we simply throw a new argument exception stating "Number of coins should be positive". Save that and we'll rerun our tests. And again, we're back into the green phase where all of our tests have now passed, so that's really good.
Now, one last thing that I want to do, it's been bugging me since we started, is to get rid of all of the kind of magic strings that we really don't want scattered within our code. So when I say magic strings I'm talking about our representation of currency, currently these are all strings. So what we really want to be doing is typing that, so I'll jump up to the start.
So instead of having a string, what we want to do is have a converter service enumeration called Currency.USD. So I'll replace this one, dot Great British pounds, likewise we'll replace this one with Euro, and then we also need to do it here. Now, this kind of code refactoring is starting to be something that would be considered rather significant, but the great thing is that we've got all these unit tests, so we're insulated in terms of knowing whether, once we've completed our refactoring, whether everything works as expected, and that's the real benefit of unit testing.
Imagine having to do this 10 minutes before a production release, you wouldn't want to because you could not guarantee that if you didn't have a unit test in place that your code would work. So a couple more. Okay, I'll save that, and I'll rerun our tests, now everything should fail, as it does, you actually get a compilation issue and that's expected because we need to set up this enumeration.
So let's now jump over into our ConverterSvc class, and this time I will implement a new enum, I'll call it Currency, and it will have three possible values, USD, Great British pounds, and Euros. Then finally, we can update our methods here to also take a typed currency as opposed to a string, and likewise here. Save that, rerun our tests, and luckily, in this case, it's pointed out that our implementation is still incorrect, so we need to update line 41, which is this guy here, and because this is a typed value we need to return it to a string. Save that, rerun, again, our unit tests have picked up something else, so we need to, oh yes, so we need to jump back into our unit tests, and again, we forgot to update this guy here, so this is now a ConverterSvc.Currency, okay. Rerun the tests, compilation has succeeded and our unit tests have now passed, so an absolute great result, and this kind of brings us to the end of the implementation.
So we've tidied up all our code, we've built it using test-driven development principles, we've gone through numerous iterations of red-green refactoring, and we've got a really nice, clean code base. And the unit tests are unit tests, they're not integration tests, there's no external dependencies, so everything is really well designed at this stage.
So this concludes the actual build of the library, it's now functional. What we're going to do now going forward is demonstrate some other areas, some other tools that we can bring into the project that help us to do things like automated builds when we check in the code, and how to generate code coverage reports for our unit tests.
So go ahead and close this and we'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).