In this post we will develop a simple calculator able to add, subtract, multiply and divide.
The code will be implemented in C# using Visual Studio 2022.
Once we have the main code operational, we will add xUnit and will test using Moq4.
Let’s start by showing some output from our simple calculator.
**** Simple Calculator **** 1. Add 2. Subtract 3. Multiply 4. Divide -1. Quit >>> selection: 1 >>> a: 1.2 >>> b: 3.4 <<< r: 4.6 **** Simple Calculator **** 1. Add 2. Subtract 3. Multiply 4. Divide -1. Quit >>> selection:
In this case, we selected the Add operation. We will add 1.2 and 3.4 and get as a result 4.6 which seems to be correct.
Now let’s try the subtraction operation.
**** Simple Calculator **** 1. Add 2. Subtract 3. Multiply 4. Divide -1. Quit >>> selection: 2 >>> a: 1.2 >>> b: 3.4 <<< r: -2.2 **** Simple Calculator **** 1. Add 2. Subtract 3. Multiply 4. Divide -1. Quit >>> selection:
After selecting the type of operation, we enter as parameters 1.2 and 3.4 and get a result of -2.2 which seems to be correct.
**** Simple Calculator **** 1. Add 2. Subtract 3. Multiply 4. Divide -1. Quit >>> selection: 3 >>> a: 1.2 >>> b: 3.4 <<< r: 4.08 **** Simple Calculator **** 1. Add 2. Subtract 3. Multiply 4. Divide -1. Quit >>> selection:
In this example we select multiplication. We then enter 1.2 and 3.4 as operands. The result returned by our calculator is 4.08 which seems to be correct.
**** Simple Calculator **** 1. Add 2. Subtract 3. Multiply 4. Divide -1. Quit >>> selection: 4 >>> a: 1.2 >>> b: 3.4 <<< r: 0.35294117647058826 **** Simple Calculator **** 1. Add 2. Subtract 3. Multiply 4. Divide -1. Quit >>> selection:
In this case we try the divide operation. Once again we enter 1.2 which will be divided by 3.4 for a result of 0.35294117647058826 which you can verify by using any calculator. Of course the resolution may vary depending on the application you use. I used the Scientific Calculator application that comes on Windows 11 and the result is: 0.35294117647058823529411764705882 which is quite similar up to the first 16 digits after the decimal point.
Let’s try one more operation. We will divide 1.2 by 0 in order to verify that the calculator is able to handle division by zero.
**** Simple Calculator **** 1. Add 2. Subtract 3. Multiply 4. Divide -1. Quit >>> selection: 4 >>> a: 1.2 >>> b: 0 <<< r: ∞ **** Simple Calculator **** 1. Add 2. Subtract 3. Multiply 4. Divide -1. Quit >>> selection:
As you can verify the result is set to infinity. I guess that is the way for the compiler to coalesce positive and negative infinity into one symbol.
namespace Calculator { internal class Program { /// <summary> /// Application. /// </summary> /// <param name="args"></param> static void Main(string[] args) { // **** initialization **** var done = false; double a = 0.0; double b = 0.0; string? str = null; // **** instantiate a calculator **** Calculator calc = new Calculator(); // **** loop until done **** while (!done) { // **** **** Console.WriteLine("\n**** Simple Calculator ****\n"); // **** menu **** Console.WriteLine("1. Add"); Console.WriteLine("2. Subtract"); Console.WriteLine("3. Multiply"); Console.WriteLine("4. Divide"); Console.WriteLine(); Console.WriteLine("-1. Quit"); // **** select option **** Console.Write("\n>>> selection: "); int selection = -1; if (!int.TryParse(Console.ReadLine(), out selection)) { Console.WriteLine("Main <<< unexpected selection: {0}", selection); continue; } // **** check if done **** if (selection == -1) { done = true; continue; } // **** prompt for arguments (if needed) **** if (selection >= 1 && selection <= 4) { // **** prompt for and get first argument **** Console.Write(">>> a: "); str = Console.ReadLine(); if (!IsValidDouble(str)) { Console.WriteLine("Main <<< unexpected a: {0}", str); continue; } double.TryParse(str, out a); // **** prompt for and get second argument **** Console.Write(">>> b: "); str = Console.ReadLine(); if (!IsValidDouble(str)) { Console.WriteLine("Main <<< unexpected b: {0}", str); continue; } double.TryParse(str, out b); } else { Console.WriteLine("Main <<< unexpected selection: {0}", selection); continue; } // **** perform operation **** switch (selection) { case 1: Console.WriteLine("<<< r: {0}", calc.add(a, b)); break; case 2: Console.WriteLine("<<< r: {0}", calc.subtract(a, b)); break; case 3: Console.WriteLine("<<< r: {0}", calc.multiply(a, b)); break; case 4: Console.WriteLine("<<< r: {0}", calc.divide(a, b)); break; } } } /// <summary> /// Determine if the string holds a valid double. /// If so returns true. /// </summary> /// <param name="ValueToTest"></param> /// <returns></returns> private static bool IsValidDouble(string? ValueToTest) { if (ValueToTest == null) { return false; } return double.TryParse(ValueToTest, out double d) && !(double.IsNaN(d) || double.IsInfinity(d)); } } }
This is the application that we have been experimenting with. Please note that this code was put together rather quickly because the subject of this post is not this application but the use of Moq 4 and xUnit.
The application performs the initialization step. We then instantiate the Calculator class. In some implementations the object may be instantiated on each pass of the following loop. If you instantiate outside of the loop it would be easier on the program. If you do it inside, you get the warm and fuzzy feeling that you have a fresh object to handle a single operation. If you have a reason for the first or second option please leave me a note at the bottom of this post.
After the operation selection is made and verified the code prompts the user for the two arguments that will be passed to the selected operation.
We are now ready to invoke the operation of interest and display the result.
Towards the bottom of the listing we have the IsValidDouble() method used to determine if the string entered by the user for one of the two arguments is a valid double or not.
using System; namespace Calculator { public class Calculator : ICalculator { public double add(double a, double b) { return a + b; } public double subtract(double a, double b) { return a - b; } public double multiply(double a, double b) { return a * b; } public double divide(double a, double b) { double result = 0.0; // **** divide integers (may throw exception) **** try { result = a / b; } catch (DivideByZeroException) { Console.WriteLine("Division of {0} by zero.", a); } // **** **** return result; } } }
This listing contains the Calculator class. Note that it inherits from the ICalculator interface.
The source contains the implementations of the four operations we need: addition, subtraction, multiplication and division. Division has to deal with the case of division by zero.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Calculator { public class CalculatorApplication { public double firstOp { get; set; } public double secondOp { get; set; } public string? op { get; set; } public double result { get; set; } } }
This code contains the declaration for the CalculatorApplication.
We have four fields with associated getters and setters.
The firstOp holds the value for the first operand. The secondOp holds the value for the second operand. The op field holds the operation which in our case would be + for additions, – for subtraction, * for multiplications and / for division. We should have declared the operators in an enum, but since we are not interested in the class but the Moq 4 and xUnit I did not spend the necessary time.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Calculator { public class CalculatorEvaluator { // **** **** private readonly IValidateArguments _validator; /// <summary> /// Constructor. /// Note we pass an interface as an argument. /// </summary> /// <param name="validator"></param> /// <returns></returns> public CalculatorEvaluator(IValidateArguments validator) { //_validator = validator; _validator = validator ?? throw new System.ArgumentNullException(nameof(validator)); } /// <summary> /// /// </summary> /// <param name="calcApp"></param> /// <returns></returns> /// <exception cref="InvalidOperationException"></exception> public double Evaluate(CalculatorApplication application) { // **** **** var isValid = _validator.IsValid(application); // **** **** if (!isValid) { throw new ArgumentException("argument not valid"); } // **** for ease of use **** double firstOp = application.firstOp; double secondOp = application.secondOp; // **** compute and return result **** switch (application.op) { case "+": return firstOp + secondOp; case "-": return firstOp - secondOp; case "*": return firstOp * secondOp; case "/": try { return firstOp / secondOp; } catch (DivideByZeroException) { throw new DivideByZeroException("divide by 0"); } default: throw new InvalidOperationException(); } } } }
This snippet holds the contents for the CalculatorEvaluator.cs source code.
The constructor receives an instance of the IValidateArguments which is saved for later use. In Moq 4 you can pass an interface (most common form) or an actual class. For the specifics please refer to the Moq 4 documentation.
The Evaluate() method evaluates the operation using the specified arguments.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Calculator { internal interface ICalculator { double add(double a, double b); double subtract(double a, double b); double multiply(double a, double b); double divide(double a, double b); } }
This code defines the interface to the Calculator.
We are asked to implement four functions with double arguments and the result should be returned as a double.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Calculator { public interface IValidateArguments { bool IsValid(CalculatorApplication application); void IsValid(CalculatorApplication application, out bool areValid); } }
The IValidateArguments interface calls for two IsValid() methods. Both do the same thing (opportunity to refactor). Depending on how the operation is called we might need to use one or the other method.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Calculator { public class ValidateArguments : IValidateArguments { /// <summary> /// /// </summary> /// <param name="application"></param> /// <returns></returns> /// <exception cref="NotImplementedException"></exception> public bool IsValid(CalculatorApplication application) { //// **** **** //throw new NotImplementedException("Simulate this real dependency being hard to use."); // **** initialization **** double result = 0.0; // **** for ease of use **** var firstOp = application.firstOp; var secondOp = application.secondOp; // **** compute and return result **** switch (application.op) { case "+": result = firstOp + secondOp; break; case "-": result = firstOp - secondOp; break; case "*": result = firstOp * secondOp; break; case "/": result = firstOp / secondOp; break; default: throw new InvalidOperationException(); } // **** **** return result.Equals(application.result); } /// <summary> /// /// </summary> /// <param name="application"></param> /// <param name="areValid"></param> /// <exception cref="NotImplementedException"></exception> public void IsValid(CalculatorApplication application, out bool areValid) { throw new NotImplementedException("Simulate this real dependency being hard to use."); } } }
This is the implementation of the interface.
As you can see the second implementation throws an exception. As we will see in a few we just use the first form of the IsValid() method.
//using Xunit; using Moq; namespace Calculator.Tests { public class CalculatorXUnitTests { /// <summary> /// Add two positive values. /// </summary> [Fact] public void AddPositiveValues() { // **** create a mock argument validator **** Mock<IValidateArguments> mockValidator = new Mock<IValidateArguments>(); // **** set calculator application **** var application = new CalculatorApplication { firstOp = 2, secondOp = 3, op = "+" }; // **** **** mockValidator.Setup(x => x.IsValid(application)).Returns(true); // **** sut == system under test (need to use the mock object) **** //var sut = new CalculatorEvaluator(new ValidateArguments()); //var sut = new CalculatorEvaluator(null); var sut = new CalculatorEvaluator(mockValidator.Object); // **** result of operation "2 + 3 = 5" **** var result = sut.Evaluate(application); // **** check result **** Assert.Equal(5, result); } /// <summary> /// Add two negative values. /// </summary> [Fact] public void AddNegativeValues() { // **** create a mock argument validator **** var mockValidator = new Mock<IValidateArguments>(); // **** set calculator application **** var application = new CalculatorApplication { firstOp = -2, secondOp = -3, op = "+" }; // **** **** mockValidator.Setup(x => x.IsValid(application)).Returns(true); // **** sut == system under test (need to use the mock object) **** var sut = new CalculatorEvaluator(mockValidator.Object); // **** result of operation "-2 + -3 = -5" **** var result = sut.Evaluate(application); // **** check result **** Assert.Equal(-5, result); } /// <summary> /// Add a positive and a negative value. /// </summary> [Fact] public void AddPositiveAndNegativeValues() { // **** create a mock argument validator **** var mockValidator = new Mock<IValidateArguments>(); // **** set calculator application **** var application = new CalculatorApplication { firstOp = 5, secondOp = -3, op = "+" }; // **** **** mockValidator.Setup(x => x.IsValid(application)).Returns(true); // **** sut == system under test (need to use the mock object) **** var sut = new CalculatorEvaluator(mockValidator.Object); // **** result of operation "5 + -3 = 2" **** var result = sut.Evaluate(application); // **** check result **** Assert.Equal(2, result); } /// <summary> /// Add a negative and a positive value. /// </summary> [Fact] public void AddNegativeAndPositiveValues() { // **** create a mock argument validator **** var mockValidator = new Mock<IValidateArguments>(); // **** set calculator application **** var application = new CalculatorApplication { firstOp = -5, secondOp = 3, op = "+" }; // **** **** mockValidator.Setup(x => x.IsValid(application)).Returns(true); // **** sut == system under test (need to use the mock object) **** var sut = new CalculatorEvaluator(mockValidator.Object); // **** result of operation "-5 + 3 = -2" **** var result = sut.Evaluate(application); // **** check result **** Assert.Equal(-2, result); } /// <summary> /// Subtract positive values. /// </summary> [Fact] public void SubtractPositiveValues() { // **** create a mock argument validator **** var mockValidator = new Mock<IValidateArguments>(); // **** set calculator application **** var application = new CalculatorApplication { firstOp = 5, secondOp = 3, op = "-" }; // **** **** mockValidator.Setup(x => x.IsValid(application)).Returns(true); // **** sut == system under test (need to use the mock object) **** var sut = new CalculatorEvaluator(mockValidator.Object); // **** result of operation "5 - 3 = 2" **** var result = sut.Evaluate(application); // **** check result **** Assert.Equal(2, result); } /// <summary> /// Subtract positive and negative values. /// </summary> [Fact] public void SubtractPositiveAndNegativeValues() { // **** create a mock argument validator **** var mockValidator = new Mock<IValidateArguments>(); // **** set calculator application **** var application = new CalculatorApplication { firstOp = 5, secondOp = -3, op = "-" }; // **** **** mockValidator.Setup(x => x.IsValid(application)).Returns(true); // **** sut == system under test (need to use the mock object) **** var sut = new CalculatorEvaluator(mockValidator.Object); // **** result of operation "5 - 3 = 2" **** var result = sut.Evaluate(application); // **** check result **** Assert.Equal(8, result); } /// <summary> /// Subtract negative and positive values. /// </summary> [Fact] public void SubtractNegativeAndPositiveValues() { // **** create a mock argument validator **** var mockValidator = new Mock<IValidateArguments>(); // **** set calculator application **** var application = new CalculatorApplication { firstOp = -5, secondOp = 3, op = "-" }; // **** **** mockValidator.Setup(x => x.IsValid(application)).Returns(true); // **** sut == system under test (need to use the mock object) **** var sut = new CalculatorEvaluator(mockValidator.Object); // **** result of operation "-5 - 3 = -8" **** var result = sut.Evaluate(application); // **** check result **** Assert.Equal(-8, result); } /// <summary> /// Subtract negative values. /// </summary> [Fact] public void SubtractNegativeValues() { // **** create a mock argument validator **** var mockValidator = new Mock<IValidateArguments>(); // **** set calculator application **** var application = new CalculatorApplication { firstOp = -5.0, secondOp = -3.0, op = "-" }; // **** **** mockValidator.Setup(x => x.IsValid(application)).Returns(true); // **** sut == system under test (need to use the mock object) **** var sut = new CalculatorEvaluator(mockValidator.Object); // **** result of operation "5 - 3 = 2" **** var result = sut.Evaluate(application); // **** check result **** Assert.Equal(-2.0, result); } /// <summary> /// Multiply two positive values. /// </summary> [Fact] public void MultiplyPositiveValues() { // **** create a mock argument validator **** Mock<IValidateArguments> mockValidator = new Mock<IValidateArguments>(); // **** set calculator application **** var application = new CalculatorApplication { firstOp = 2.0, secondOp = 3.0, op = "*" }; // **** **** mockValidator.Setup(x => x.IsValid(application)).Returns(true); // **** sut == system under test (need to use the mock object) **** var sut = new CalculatorEvaluator(mockValidator.Object); // **** result of operation "2 * 3 = 6" **** var result = sut.Evaluate(application); // **** check result **** Assert.Equal((2.0 * 3.0), result); } /// <summary> /// Multiply two negative values. /// </summary> [Fact] public void MultiplyNegativeValues() { // **** create a mock argument validator **** var mockValidator = new Mock<IValidateArguments>(); // **** set calculator application **** var application = new CalculatorApplication { firstOp = -2, secondOp = -3, op = "*" }; // **** **** mockValidator.Setup(x => x.IsValid(application)).Returns(true); // **** sut == system under test (need to use the mock object) **** var sut = new CalculatorEvaluator(mockValidator.Object); // **** result of operation "-2 + -3 = -5" **** var result = sut.Evaluate(application); // **** check result **** Assert.Equal((-2.0 * -3.0), result); } /// <summary> /// Divide two positive values. /// </summary> [Fact] public void DividePositiveValues() { // **** create a mock argument validator **** Mock<IValidateArguments> mockValidator = new Mock<IValidateArguments>(); // **** set calculator application **** var application = new CalculatorApplication { firstOp = 7, secondOp = 2, op = "/" }; // **** **** mockValidator.Setup(x => x.IsValid(application)).Returns(true); // **** sut == system under test (need to use the mock object) **** var sut = new CalculatorEvaluator(mockValidator.Object); // **** result of operation "7 / 2 = 3" **** var result = sut.Evaluate(application); // **** check result **** Assert.Equal((7.0 / 2.0), result); } /// <summary> /// Divide two negative values. /// </summary> [Fact] public void DivideNegativeValues() { // **** create a mock argument validator **** var mockValidator = new Mock<IValidateArguments>(); // **** set calculator application **** var application = new CalculatorApplication { firstOp = -2, secondOp = -3, op = "/" }; // **** **** mockValidator.Setup(x => x.IsValid(application)).Returns(true); // **** sut == system under test (need to use the mock object) **** var sut = new CalculatorEvaluator(mockValidator.Object); // **** result of operation "-2 / -3 = 0" **** var result = sut.Evaluate(application); // **** check result **** Assert.Equal((-2.0 / -3.0), result); } /// <summary> /// Divide two negative values. /// </summary> [Fact] public void DivideByZero() { // **** create a mock argument validator **** var mockValidator = new Mock<IValidateArguments>(); // **** set calculator application **** var application = new CalculatorApplication { firstOp = 7, secondOp = 0, op = "/" }; // **** **** mockValidator.Setup(x => x.IsValid(application)).Returns(true); // **** sut == system under test (need to use the mock object) **** var sut = new CalculatorEvaluator(mockValidator.Object); // **** result of operation "7 / 0 = infinite" **** var result = sut.Evaluate(application); // **** check result **** Assert.Equal(double.PositiveInfinity, result); } } }
Before we get into this code which implements a set of test cases, I apologize for the number of tests. Not sure we need so many. That said, it is good practice to have well defined names that reflect what a test does. That way if the test fails, we can get a good idea of what went wrong without looking at the actual code.
In each test we first declare a Moq object to use to validate an operation with two argument values and an operand.
The operation is evaluated.
The result returned by the evaluator is checked against our expected result. If they do not match, the unit test fails.
This figure illustrates that all our unit tests fail.
My suggestion is for you to change an assert and run the tests. The specified unit test will fail. The next step is to locate the offending line and put a breakpoint. You can then debug the unit test. It seems that the best approach is to move back to the breakpoint until you figure out what the problem is. To make it more interesting change the calculator code to induce different issues.
If interested, all the code for this post is in GitHub. Calculator is here and Calculator.Test is here.
Keep in mind that one of the best ways to learn is to read and experiment.
Enjoy;
John