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

How to create a unit test for dictionary 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().

我会先做 non-static。否则每个测试都会影响下一个,除非你清除堆栈。如果每次测试都可以 calculator = new Calculator() 就更容易了。

从外观上看,操作是作为字符串发送到计算器的。它没有显示,但我猜数字也是。不清楚。

为了确保您的计算器正常工作,您可能希望向其发送一些数字和运算,并确保计算器完成后计算结果正确。一种方法是使用参数化测试。也就是说,您编写了一个测试,但向它发送了多组输入。

这是一个 NUnit 示例。请记住,我不知道你的计算器如何工作的确切细节,所以这更像是一个指针,而不是一个完美的例子。

首先我必须创建一个基本的 Calculator class,因为使用静态 class 编写单元测试要困难得多。 (等等,你的堆栈和字典不见了!更多相关信息。)

public class Calculator 
{
    public string DisplayedResult { get; }
    public void SendInput(string input)
    {

    }
}

这个想法是 SendInput 就像按下一个按钮,当你按下一个按钮时 DisplayedResult 会发生变化。因此,如果您按“2 + 2 2 =”,它将显示“24”。如果您按“5 / 0 =”,它可能会显示“错误”。如果有一个“清除”按钮,那么“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);
}

每个测试用例都有一个输入——一系列由字符串分隔的操作。这使得创建每个测试用例变得容易。它也有一个预期的结果。我们希望计算器显示什么?

测试发送输入并验证您是否获得预期结果。您可以将它分成多个测试,或者您可以进行一个包含大量案例的大型测试。您可以根据需要编写任意数量的测试。您可以添加另一个参数来描述正在测试的内容。

两个重要细节:

设计必须考虑测试。静态 classes 更难测试。此外,更容易测试 class 是否公开了一个易于测试的接口 - 输入和输出。如果某件事难以测试,则通常表示设计存在困难。所以我们会调整到更容易测试的设计是正常的。

字典和堆栈呢?如果这就是您想要实现计算器的方式,那很好 - 您可以添加它。也许 SendInput 方法与它们交互并根据需要更新 DisplayedResult

重要的是测试不会与该实施细节相关联。如果您改变主意,不使用堆栈和字典,而是编写完全不同的代码来处理这些操作怎么办?这些测试仍然完全有效。只要您的实施有效,无论是什么,测试都会通过。