我想制作一个rpn计算器,并使用nunit为字典创建单元测试来测试操作,但我不知道如何制作。
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); },
}
编辑:执行像
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));
}
}
}
我认为你应该把stack
和operators
作为private
字段在一些class
称为例如CalculatingService
。之后,您应该创建public
方法Calculate(...)
,这将return
计算值:
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; }
};
}
现在您可以为CalculatingService
中的所有方法创建测试方法。您可以通过为x个操作符编写x个测试方法来测试Calculate(...)
。我不知道,你如何管理stack
-你必须在CalculatingService
中为stack
管理编写额外的public
方法,就像我写ClearData()
一样。
我首先将其设置为非静态。否则,每个测试都将影响下一个测试,除非您清除堆栈。如果你能在每个测试中都有calculator = new Calculator()
,那就容易多了。
从它的外观来看,操作被作为字符串发送给计算器。没有显示出来,但我猜数字也显示出来了。那不清楚。
为了确保你的计算器工作,你可能会想要发送一些数字和操作,并确保计算器完成后得到正确的结果。一种方法是使用参数化测试。也就是说,您编写一个测试,但是向它发送多组输入。
这是一个NUnit的例子。请记住,我不知道你的计算器是如何工作的确切细节,所以这更像是一个指针,而不是一个完美的例子。
首先,我必须创建一个基本的Calculator
类,因为用静态类编写单元测试要困难得多。(但是等等,你的堆栈和字典不见了!更多信息)
public class Calculator
{
public string DisplayedResult { get; }
public void SendInput(string input)
{
}
}
这个想法是,SendInput
就像按下按钮,当你按下按钮时,DisplayedResult
会发生变化。所以如果你按下& 2 + 2 ="它会显示"24;如果你按"5/0 ="它可能会显示"错误"。如果有"clear"按钮然后"5/0 = C 2 + 2 ="可能会显示"4"。(首先是一个错误,然后是清除,然后是另一个操作)
现在我们可以像这样编写一个参数化的NUnit测试:
[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);
}
每个测试用例都有一个输入——由字符串分隔的一系列操作。这使得创建每个测试用例变得很容易。它也有一个预期的结果。我们期望计算器显示什么?
测试发送输入并验证您是否获得了预期的结果。你可以把它分成多个测试,或者你可以有一个包含很多用例的大测试。您可以编写任意多的测试。您可以添加另一个参数,描述正在测试的内容。
两个重要细节:
设计必须考虑到测试。静态类更难测试。此外,如果类公开了易于测试的接口(输入和输出),则测试会更容易。如果有些东西难以测试,那么它通常代表着设计上的困难。因此,我们将调整为更容易测试的设计是正常的。
那字典和堆栈呢?如果你想这样实现计算器,很好,你可以加上它。也许SendInput
方法与它们交互,并根据需要更新DisplayedResult
。
重要的是测试不会绑定到实现细节。如果你改变主意,不再使用堆栈和字典,而是编写完全不同的代码来处理这些操作,那会怎么样呢?测试仍然是完全有效的。只要你的实现能工作,不管它是什么,测试都会通过。