C# – TDD

By | 06/01/2021

In this post, we will see how to create a simple Console application (a calculator), using TDD.
But first of all, what is TDD?
From Wikipedia:
“Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: requirements are turned into very specific test cases, then the code is improved so that the tests pass. This is opposed to software development that allows code to be added that is not proven to meet requirements.”
In a nutshell, with TDD, we should have to start our development from the tests.
Every cycle has three main steps:
RED: when we create a test at the beginning it will fail, because we have to implement the functionality.
GREEN: we develop the code in order to pass the test.
REFACTORY: when everything works fine, we have to optimize the code.

Now, we will create a very simple application, using TDD as development process.
In detail, we will develop a simple Calculator that in input accepts two integer numbers greater than 0.
We open Visual Studio and we create a blank project called TheCalculator and then, we create a Console Application project called Calculator and a xUnit Test Project called TestCalculator:

Finally, we delete the file UnitTest1.cs and we create a class called TDD_Calculator.
Now, we can start with the tests:

Check the values in input are integer greater than 0

using Calculator;
using Xunit;

namespace TestCalculator
{
    public class TDD_Calculator
    {
        [Theory]
        [InlineData("4", "0")]
        [InlineData("4", "h")]
        [InlineData("B", "1")]
        [InlineData("A", "B")]
        [InlineData("0", "1")]
        public void CheckInput_PassingAStringInInputOrANumberLessThanOne_ShouldGiveFalseAndAMessageError(string val1, string val2)
        {
            // arrange
            var operation = "+";
            string message;

            Calc objCal = new Calc();

            // act
            bool result = objCal.CheckInput(val1, val2, operation, out message);

            // assert
            Assert.False(result);
            Assert.Equal("The values in input must be two numbers greater than 0", message);
        }
    }
}



We can see that it doesn’t compile because we are using the class Calc that doesn’t exist.
In order to fix it, in the project Calculator, we create a class called Calc with a method called CheckInput that in output will give a bool and a message
Then, we add the dependence in TestCalculator with the project Calculator in order to use the class.

namespace Calculator
{
    public class Calc
    {
        public bool CheckInput(string val1, string val2, string operation, out string message)
        {
            message = string.empty;
            return false;
        }
    }
}



Now it compiles but, if we run the test, it will fail because we haven’t implemented the logic.
So, in order to fix it, we modify the method CheckInput:

namespace Calculator
{
    public class Calc
    {
        public bool CheckInput(string val1, string val2, string operation, out string message)
        {
            message = "The values in input must be two numbers greater then 0";
            return false;
        }
    }
}



If we run the test, it will pass:

Now, we will test the method with two integer in input:

using Calculator;
using Xunit;

namespace TestCalculator
{
    public class TDD_Calculator
    {
        ...
        ...
        ...
        [Theory]
        [InlineData("14", "10")]
        [InlineData("44", "1")]
        [InlineData("23", "9")]
        [InlineData("143", "102")]
        public void CheckInput_PassingTwoNumbersInInput_ShouldGiveTrue(string val1, string val2)
        {
            // arrange
            var operation = "+";
            string message;

            Calc objCal = new Calc();

            // act
            bool result = objCal.CheckInput(val1, val2, operation, out message);

            // assert
            Assert.True(result);
        }
    }
}



If we run the test, it will not work because we haven’t implemented the logic in the CheckInput method:

In order to fix the test, we have to modify the CheckInput method:

namespace Calculator
{
    public class Calc
    {
        public bool CheckInput(string val1, string val2, string operation, out string message)
        {
            int _val1, _val2;
            message = string.Empty;

            try
            {
                _val1 = System.Convert.ToInt32(val1);
                _val2 = System.Convert.ToInt32(val2);
                if (_val1 <= 0 || _val2 <= 0)
                {
                    message = "The values in input must be two numbers greater then 0";
                    return false;
                }
            }
            catch
            {
                message = "The values in input must be two numbers greater then 0";
                return false;
            }

            return true;
        }
    }
}



If we run the test, it will pass:

Check the value in operator must be “+”, “-“, “/” or “*”

using Calculator;
using System;
using System.Collections.Generic;
using System.Text;
using Xunit;

namespace TestCalculator
{
    public class TDD_Calculator
    {
        ...
        ...
        ...
        [Theory]
        [InlineData(">")]
        [InlineData("<")]
        [InlineData(",")]
        [InlineData("&")]
        [InlineData("$")]
        [InlineData("£")]
        public void Run_PassingTwoNumberInInputAndAWrongOperation_ShouldGiveFalseAndAMessage(string inputOperation)
        {
            // arrange
            var val1 = "5";
            var val2 = "5";
            var operation = inputOperation;
            string message;

            Calc objCal = new Calc();

            // act
            bool result = objCal.CheckInput(val1, val2, operation, out message);

            // assert
            Assert.False(result);
            Assert.Equal("The operation in input must be '+', '-', '/' and '*'", message);
        }

        [Theory]
        [InlineData("+")]
        [InlineData("-")]
        [InlineData("/")]
        [InlineData("*")]
        public void Run_PassingTwoNumberInInputAndACorrectOperation_ShouldGiveTrue(string inputOperation)
        {
            // arrange
            var val1 = "5";
            var val2 = "5";
            var operation = inputOperation;
            string message;

            Calc objCal = new Calc();

            // act
            bool result = objCal.CheckInput(val1, val2, operation, out message);

            // assert
            Assert.True(result);
        }
    }
}



If we run the tests, they will not pass because we haven’t implemented the logic in CheckInput:

namespace Calculator
{
    public class Calc
    {
        public bool CheckInput(string val1, string val2, string operation, out string message)
        {
            int _val1, _val2;
            message = string.Empty;

            try
            {
                _val1 = System.Convert.ToInt32(val1);
                _val2 = System.Convert.ToInt32(val2);
                if (_val1 <= 0 || _val2 <= 0)
                {
                    message = "The values in input must be two numbers greater then 0";
                    return false;
                }
            }
            catch
            {
                message = "The values in input must be two numbers greater then 0";
                return false;
            }

            if (operation != "+" && operation != "-" && operation != "/" && operation != "*")
            {
                message = "The operation in input must be '+', '-', '/' and '*'";
                return false;
            }

            return true;
        }
    }
}



Now, if we run the tests, they will pass:

Check the functionalities

Now, we test the four functionalities and we will start with the sum:

using Calculator;
using Xunit;

namespace TestCalculator
{
    public class TDD_Calculator
    {
        ... 
        ...
        ...
        [Theory]
        [InlineData(1, 1)]
        [InlineData(3, 3)]
        [InlineData(10, 10)]
        public void Run_SelectAddition_ShouldGiveTheSumOfTwoNumbersInInput(int inputVal1, int inputVal2)
        {
            // arrange
            var operation = "+";

            Calc objCal = new Calc();

            // act
            decimal result = objCal.Elaboration(inputVal1, inputVal2, operation);

            // assert
            Assert.Equal(inputVal1 + inputVal2, result);
        }
    }
}



We can see that it doesn’t compile because the class Calc doesn’t have a method called Elaboration.
So, in order to fix it, we have to implement this method in Calc:

namespace Calculator
{
    public class Calc
    {
        ...
        ...
        ...
        public decimal Elaboration(int val1, int val2, string operation)
        {
            decimal result = 0;

            return result;
        }
    }
}



Now it compiles but, if we run the test, it will fail because we haven’t implemented the logic.

In order to fix the test, we have to implement the logic in Elaboration:

namespace Calculator
{
    public class Calc
    {
        ...
        ...
        ...
        public decimal Elaboration(int val1, int val2, string operation)
        {
            decimal result = 0;

            result = val1 + val2;

            return result;
        }
    }
}



If we run the test, it will pass.

Obviously, if we create the tests for the other functionalities, without implement the logic in Elaboration, all tests will fail:

using Calculator;
using Xunit;

namespace TestCalculator
{
    public class TDD_Calculator
    {
        ...
        ...
        ...
        [Theory]
        [InlineData(6, 2)]
        [InlineData(34, 12)]
        [InlineData(3, 30)]
        public void Run_SelectSubtraction_ShouldGiveTheSubstractionOfTwoNumbersInInput(int inputVal1, int inputVal2)
        {
            // arrange
            var operation = "-";

            Calc objCal = new Calc();

            // act
            decimal result = objCal.Elaboration(inputVal1, inputVal2, operation);

            // assert
            Assert.Equal(inputVal1 - inputVal2, result);
        }

        [Theory]
        [InlineData(6, 2)]
        [InlineData(34, 12)]
        [InlineData(3, 30)]
        public void Run_SelectDivision_ShouldGiveTheDivisionOfTwoNumbersInInput(int inputVal1, int inputVal2)
        {
            // arrange
            var operation = "/";

            Calc objCal = new Calc();

            // act
            decimal result = objCal.Elaboration(inputVal1, inputVal2, operation);

            // assert
            Assert.Equal(inputVal1 / inputVal2, result);
        }


        [Theory]
        [InlineData(6, 2)]
        [InlineData(34, 12)]
        [InlineData(3, 30)]
        public void Run_SelectMultiplication_ShouldGiveTheMultiplicationOfTwoNumbersInInput(int inputVal1, int inputVal2)
        {
            // arrange
            var operation = "*";

            Calc objCal = new Calc();

            // act
            decimal result = objCal.Elaboration(inputVal1, inputVal2, operation);

            // assert
            Assert.Equal(inputVal1 * inputVal2, result);
        }
    }
}



So, in order to pass the tests, we have to modify the method Elaboration:

namespace Calculator
{
    public class Calc
    {
        ...
        ...
        ...
        public decimal Elaboration(int val1, int val2, string operation)
        {
            switch (operation)
            {
                case "+":
                    return val1 + val2;
                case "-":
                    return val1 - val2;
                case "/":
                    return val1 / val2;
                default:
                    return val1 * val2;
            }
        }
    }
}



Now, if we run the test again, they will pass:

We have done!
We have created and tested our application.
Now, we will optimize the code in Calc and we will create a Console Application:

using System.Collections.Generic;

namespace Calculator
{
    public class Calc
    {
        public bool CheckInput(string val1, string val2, string operation, out string message)
        {
            message = string.Empty;

            if (!CheckValuesInInput(val1, val2))
            {
                message = "The values in input must be two numbers greater then 0";
                return false;
            }

            if (!CheckOperation(operation))
            {
                message = "The operation in input must be '+', '-', '/' and '*'";
                return false;
            }

            return true;
        }

        private bool CheckValuesInInput(string val1, string val2)
        {
            try
            {
                var intVal1 = System.Convert.ToInt32(val1);
                var intVal2 = System.Convert.ToInt32(val2);
                if (intVal1 <= 0 || intVal2 <= 0)
                {
                    return false;
                }
                return true;
            }
            catch
            {
                return false;
            }
        }

        private bool CheckOperation(string operation)
        {
            List<string> lstOperation = new List<string> { "+", "-", "/", "*" };
            if (!lstOperation.Contains(operation))
            {
                return false;
            }

            return true;
        }



        public decimal Elaboration(int val1, int val2, string operation)
        {
            switch (operation)
            {
                case "+":
                    return val1 + val2;
                case "-":
                    return val1 - val2;
                case "/":
                    return val1 / val2;
                default:
                    return val1 * val2;
            }
        }
    }
}



using System;

namespace Calculator
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Insert the first value");
            string val1 = Console.ReadLine();
            Console.WriteLine("Insert the second value");
            string val2 = Console.ReadLine();
            Console.WriteLine("Insert the operation");
            string operation = Console.ReadLine();

            Calc objCalc = new Calc();

            string message;

            if (objCalc.CheckInput(val1, val2, operation, out message))
            {
                Console.WriteLine($"The result of operation is: {objCalc.Elaboration(System.Convert.ToInt32(val1), System.Convert.ToInt32(val2), operation).ToString()}");
            }
            else
            {
                Console.WriteLine(message);
            }
        }
    }
}



If we run the Console Application, this will be the result:



Leave a Reply

Your email address will not be published. Required fields are marked *