Book Image

C# and .NET Core Test Driven Development

By : Ayobami Adewole
Book Image

C# and .NET Core Test Driven Development

By: Ayobami Adewole

Overview of this book

This book guides developers to create robust, production-ready C# 7 and .NET Core applications through the practice of test-driven development process. In C# and .NET Core Test-Driven Development, you will learn the different stages of the TDD life cycle, basics of TDD, best practices, and anti-patterns. It will teach you how to create an ASP.NET Core MVC sample application, write testable code with SOLID principles and set up a dependency injection for your sample application. Next, you will learn the xUnit testing framework and learn how to use its attributes and assertions. You’ll see how to create data-driven unit tests and mock dependencies in your code. You will understand the difference between running and debugging your tests on .NET Core on LINUX versus Windows and Visual Studio. As you move forward, you will be able to create a healthy continuous integration process for your sample application using GitHub, TeamCity, Cake, and Microsoft VSTS. By the end of this book, you will have learned how to write clean and robust code through the effective practice of TDD, set up CI build steps to test and build applications as well as how to package application for deployment on NuGet.
Table of Contents (11 chapters)

What we can do to prevent bad code

Writing clean code requires a conscious effort of maintaining professionalism and following best industry standards throughout the stages of the software development process. Bad code should be avoided right from the onset of software project development, because the accumulation of bad debt through bad code can slow down software project completion and create future issues after the software has been deployed to production.

To avoid bad code, you have to be lazy, as the general saying goes that lazy programmers are the best and smartest programmers because they hate repetitive tasks, such as having to go back to fix issues that could have been prevented. Try to use programming styles and approaches that avoid writing bad code, to avoid having to rewrite your code in order to fix avoidable issues, bugs, or to pay technical debts.

Loose coupling

Loose coupling is the direct opposite of tight coupling. This is a good object-oriented programming practice of separation of concerns by allowing components to have little or no information of the internal workings and implementation of other components. Communication is done through interfaces. This approach allows for an easy substitution of components without many changes to the entire code base. The sample code in the Tight coupling section can be refactored to allow loose coupling:

//The dependency injection would be done using Ninject
public ISmppManager smppManager { get; private set; }

public void SendSMS()
{
smppManager.SendMessage("0802312345","Hello", "John");
}

public class SmppManager
{
private string sourceAddress;
private SmppClient smppClient;

public SmppManager()
{
smppClient = new SmppClient();
smppClient.Start();
}

public void SendMessage(string recipient, string message, string senderName)
{
// send message using referenced library
}
}
public interface ISmppManager
{
void SendMessage(string recipient, string message, string senderName);
}

Sound architecture and design

Bad code can be avoided through the use of a good development architecture and design strategy. This will ensure that development teams and organizations have a high-level architecture, strategy, practices, guidelines, and governance plans that team members must follow to prevent cutting corners and avoiding bad code throughout the development process.

Through continuous learning and improvement, software development team members can develop thick skins towards writing bad code. The sample code snippet in the Bad or broken designs section can be refactored to be thread-safe and avoid thread-related issues, as shown in the following code:

public class SMTPGateway
{
private static SMTPGateway smtpGateway=null;
private static object lockObject= new object();

private SMTPGateway()
{
}

public static SMTPGateway SMTPGatewayObject
{
get
{
lock (lockObject)
{
if (smtpGateway==null)
{
smtpGateway = new SMTPGateway();
}
}
return smtpGateway;
}
}
}

Preventing and detecting code smell

Programming styles and coding formats that result in code smell should be avoided. By adequately paying attention to the details, bad code pointers discussed in the Code smell section should be avoided. The replicated lines of code in the two methods of the source code mentioned in the Code smell section can be refactored to a third method. This avoids replication of code and allows for easy modifications:

[HttpGet]
public ActionResult GetAllTransactions()
{
var yearsAndMonths=GetYearsAndMonths();
ViewBag.Transactions= GetTransactions(yearsAndMonths.Item1,yearsAndMonths.Item2);
return View();
}

[HttpGet]
public ActionResult SearchTransactions()
{
var yearsAndMonths=GetYearsAndMonths();
ViewBag.Years = yearsAndMonths.Item1;
ViewBag.Months = yearsAndMonths.Item2;
return View();
}

private (List<string>, List<string>) GetYearsAndMonths(){
List<string> years = new List<string>();
for (int i = DateTime.Now.Year; i >= 2015; i--)
years.Add(i.ToString());
List<string> months = new List<string>();
for (int j = 1; j <= 12; j++)
months.Add(j.ToString());
return (years,months);
}

Also, the method with a long list of parameters in the Code smell section can be refactored to use C# Plain Old CLR Object (POCO) for clarity and reusability:

public void ProcessTransaction(Transaction transaction)
{
//Do something
}

public class Transaction
{
public string Username{get;set;}
public string Password{get;set;}
public float TransactionAmount{get;set;}
public string TransactionType{get;set;}
public DateTime Time{get;set;}
public bool CanProcess{get;set;}
public bool RetryOnfailure{get;set;}
}

Development teams should have guidelines, principles, and coding conventions and standards developed jointly by the team members and should be constantly updated and refined. These, when used effectively, will prevent code smell in the software code base and allow for the easy identification of potential bad code by team members.

C# coding conventions

Using the guidelines in C# coding conventions facilitates the mastery of writing clean, readable, easy to modify, and maintainable code. Use variable names that are descriptive and represent what they are used for, as shown in the following code:

int accountNumber;

string firstName;

Also, having more than one statement or declaration on a line clogs readability. Comments should be on a new line and not at the end of the code. You can read more about C# coding conventions at: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/inside-a-program/coding-conventions.

Succinct and proper documentation

You should always try to write self-documenting code. This can be achieved through good programming style. Write code in such a manner that your classes, methods, and other objects are self-documenting. A new developer should be able to pick your code and not have to be stressed out before understanding what the code does and its internal structure.

Coding elements should be descriptive and meaningful to provide an insight to the reader. In situations where you have to document a method or class to provide further clarity, adopt the Keep It Simple Short (KISS) approach, briefly stating the reasons for a certain decision. Check the following code snippet; nobody wants to have to read two pages of documentation for a class containing 200 lines of code:

///
/// This class uses SHA1 algorithm for encryption with randomly generated salt for uniqueness
///
public class AESEncryptor
{
//Code goes here
}
KISS also known as Keep it Simple, Stupid, is a design principle that states that most systems work at their best when they are kept simple rather than making them unnecessarily complex. The principle aims at aiding programmers to keep the code simple as much as possible, to ensure that code can be easily maintained in the future.

Why test-driven development?

Each time I enter a discussion with folks not practicing test-driven development, they mostly have one thing in common, which is that it consumes time and resources and it does not really give a return on investment. I usually reply to them by asking which is better, detecting bugs and potential bottlenecks and fixing them while the application is being developed or hotfixing bugs when the application is in production? Test-driven development will save you a lot of problems and ensure you produce robust and issue-free applications.

Building for longevity

To avoid future problems resulting from issues when making modifications to a system in production as a result of changes in user requirements, as well as bugs which get exposed because of inherent bad code in a code base and accumulated technical debt, you need to have the mindset of developing with the future in mind and embracing changes.

Use flexible patterns and always employ good object-oriented development and design principles when writing code. The requirements of most software projects change over their life cycles. It is wrong to assume that a component or part might not change, so try and put a mechanism in place to allow the application to be graceful and accept future changes.