如何为字典c#创建单元测试



我想制作一个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));
}
}               
}

我认为你应该把stackoperators作为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

重要的是测试不会绑定到实现细节。如果你改变主意,不再使用堆栈和字典,而是编写完全不同的代码来处理这些操作,那会怎么样呢?测试仍然是完全有效的。只要你的实现能工作,不管它是什么,测试都会通过。

最新更新