I would like to make an rpn calculator and create unit test using nunit for the dictionary to test operations, but i don't know how to make it.
static Stack<double> stack { get; set; } = new Stack<double>();
static Dictionary<string, Action> operators = new Dictionary<string, Action>
{
[" "] = () => { stack.Push(stack.Pop() stack.Pop()); },
["-"] = () => { var x = stack.Pop(); stack.Push(stack.Pop() - x); },
["*"] = () => { stack.Push(stack.Pop() * stack.Pop()); },
["/"] = () => { var x = stack.Pop(); if (x == 0) throw new DivideByZeroException(); stack.Push(stack.Pop() / x); },
["clr"] = () => { stack.Clear(); },
["!"] = () => { var x = stack.Pop(); stack.Push(x == 0 ? 1 : 0); },
["!="] = () => { stack.Push(stack.Pop() == stack.Pop() ? 0 : 1); },
["%"] = () => { var x = stack.Pop(); stack.Push(stack.Pop() % x); },
[" "] = () => { var x = stack.Pop(); x ; stack.Push(x); },
["--"] = () => { var x = stack.Pop(); x--; stack.Push(x); },
}
EDIT: Executing is like
while (true)
{
Display();
var readLine = Console.ReadLine();
var tokens = readLine.Split(" ").Where(t => t != string.Empty).ToArray();
foreach (var token in tokens)
{
try
{
operators[token].Invoke();
}
catch(KeyNotFoundException)
{
stack.Push(double.Parse(token));
}
}
}
CodePudding user response:
I think you should put stack
and operators
as private
fields in some class
called for example CalculatingService
. after that you should create public
method Calculate(...)
, which will return
calculated value:
public class CalculatingService : ICalculatingService
{
private readonly Stack<double> stack { get; set; }
private readonly Dictionary<string, Func<double>> operators;
public CalculatingService()
{
stack = new Stack<double>();
InitDictionary();
}
public double Calculate(string @operator) =>
operators[@operator].Invoke();
public void ClearData() => stack.Clear();
private void InitDictionary() => operators = new Dictionary<string, Func<double>>
{
[" "] = () => stack.Pop() stack.Pop(),
["-"] = () => { var x = stack.Pop(); return stack.Pop() - x; },
["*"] = () => stack.Pop() * stack.Pop(),
["/"] = () => { var x = stack.Pop(); if (x == 0) throw new DivideByZeroException(); return stack.Pop() / x; },
["!"] = () => { var x = stack.Pop(); return x == 0 ? 1 : 0; },
["!="] = () => stack.Pop() == stack.Pop() ? 0 : 1,
["%"] = () => { var x = stack.Pop(); return stack.Pop() % x; },
[" "] = () => { var x = stack.Pop(); x ; return x; },
["--"] = () => { var x = stack.Pop(); x--; return x; }
};
}
now you can create test methods for all methods in CalculatingService
. You can test Calculate(...)
by writing x test methods for x operators. I dunno, how do u manage stack
- you have to write additional public
methods in CalculatingService
for stack
management like I wrote ClearData()
.
CodePudding user response:
I'd start by making it non-static. Otherwise each test will affect the next unless you clear the stack. It's easier if you can just have calculator = new Calculator()
in each test.
From the look of it operations are sent to the calculator as strings. It's not shown, but I'm guessing that the digits are also. That's not clear.
In order to make sure your calculator works, presumably you'll want to send it some numbers and operations and make sure that when it's done the calculator has the correct result. One way to do that is with parameterized tests. That is, you write one test but send it multiple sets of inputs.
Here's an NUnit example. Please remember that I don't know the exact details of how your calculator works, so this is more of a pointer and not a perfect example.
First I had to create a basic Calculator
class, because it's much harder to write unit tests with a static class. (But wait, your stack and dictionary are missing! More on that.)
public class Calculator
{
public string DisplayedResult { get; }
public void SendInput(string input)
{
}
}
The idea is that SendInput
is like pressing a button, and when you press a button DisplayedResult
changes. So if you press "2 2 2 =" it would display "24." If you press "5 / 0 =" it might display "error". If there's a "clear" button then "5 / 0 = C 2 2 =" might display "4". (First there was an error, then it was cleared, then there was another operation.)
Now we can write a parameterized NUnit test like this:
[Test]
[TestCase("2 2 2 =","24")]
[TestCase("5 / 0 =", "error")]
[TestCase("5 / 0 = C 2 2 =", "4")]
public void Calculator_Displays_Expected_Results(string input, string expectedResult)
{
var inputsToSend = input.Split(' ');
var calculator = new Calculator();
foreach (var inputToSend in inputsToSend)
{
calculator.SendInput(inputToSend);
}
Assert.AreEqual(expectedResult, calculator.DisplayedResult);
}
Each test case has an input - a series of operations separated by strings. That makes it easy to create each test case. It also has an expected result. What do we expect the calculator to display?
The test sends the inputs and verifies that you get the expected result. You could separate it into multiple tests, or you could have one big test with lots of cases. You can write as many tests as you want. You could add another argument with a description of what's being tested.
Two important details:
The design has to take testing into account. Static classes are harder to test. Also, it's easier to test if the class exposes an interface that makes it easy to test - inputs and outputs. If something is hard to test it often represents a difficulty with the design. So it's normal that we'll adjust to a design that's easier to test.
What about the dictionary and the stack? If that's how you want to implement the calculator, that's fine - you can add that. Perhaps the SendInput
method interacts with them and updates the DisplayedResult
as needed.
What's important is that the tests won't be tied to that implementation detail. What if you change your mind and don't use a stack and a dictionary, and you write completely different code to handle those operations? The tests will still be exactly as valid. As long as your implementation works, whatever it is, the tests will pass.