如何单元测试需要用户输入的代码 C#

How to unit test code that requires user input c#

这是我第一次做单元 tests/integration 测试,我有一个问题。所以我开始对我的代码进行单元测试,但是我有一个方法,它实际上是整个应用程序的逻辑,其中调用了多个方法,并且需要用户输入。我如何测试该方法?方法如下:

  public async Task RunAsync()
    {
      
        var watch = System.Diagnostics.Stopwatch.StartNew();
        var playAgain = 'y';

        do
        {
            var attempts = 1;
            var foundNumber = false;
            while (attempts < 10 && foundNumber == false)
            {
                try
                {
                    var inputNumber = int.Parse(GetInput());

                    if (inputNumber == _randomNumber)
                    {
                        foundNumber = true;
                        OnSuccesfulGuess(watch, attempts);

                    }
                    else if (attempts < 10)
                    {
                        OnWrongGuessWithinAttempts(inputNumber);
                    }
                    else
                    {
                        Console.WriteLine("Oops, maybe next time.");                      
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Please enter a number");
                }

                attempts++;
            }

            playAgain = PlayAgain(playAgain);
            _randomNumber = await GetRandomNumber(1, 100);
            Log.Information("User wants to play again");
        }
        while (playAgain == 'y' || playAgain == 'Y');
    }

这是我 运行 在我的程序 class 中启动应用程序的方法。

通常,单元测试方法是针对可能return的不同结果而制定的。您可以创建一个接口来处理此方法并根据预期输出(模拟)传达给定值。检查这个 post 可能会有帮助!: How do I apply unit testing to C# function which requires user input dynamically?

您的代码基本上无法测试。

该方法做了太多工作。应该拆分成几个小的可以单独测试。

您应该摆脱静态方法。因为你买不到假货。

通过网络获取数据(我看到使用 WebSocket),以及从数据库或文件系统获取数据,应该被带出来。您应该将现成的数据传递给此方法。


这是修改后的代码,分解成小方法。日志记录和事件已从代码中删除,以免使解释复杂化。

public class App
{
    private readonly Random _random = new Random();

    private Task<int> GetRandomNumber(int min, int max)
    {
        return Task.FromResult(_random.Next(min, max));
    }

    internal int GetInput()
    {
        Console.WriteLine("Please guess a number between 1 and 100");
        int value;

        while (true)
        {
            string input = Console.ReadLine();
            bool result = int.TryParse(input, out value);

            if (!result)
                Console.WriteLine("Not a number");
            else if (value < 1 || value > 100)
                Console.WriteLine("Must be between 1 and 100");
            else
                break;
        }
        return value;
    }

    internal bool PlayAgain()
    {
        Console.WriteLine("Do you want to play again?");
        string input = Console.ReadLine();
        return input == "Y" || input == "y";
    }

    internal void Guessing(int randomNumber)
    {
        int attempts = 1;
        while (attempts < 10)
        {
            var inputNumber = GetInput();
            // logging
            if (inputNumber == randomNumber)
            {
                // OnSuccesfulGuess
                return;
            }
            else
            {
                // OnWrongGuessWithinAttempts
            }
            attempts++;
        }
        Console.WriteLine("Oops, maybe next time.");
        // logging
    }

    public async Task RunAsync()
    {
        do
        {
            int randomNumber = await GetRandomNumber(1, 100);
            Guessing(randomNumber);
        }
        while (PlayAgain());
    }
}

现在我们可以测试单个方法了。
我使用 MSTest。

[DataTestMethod]
[DataRow("Y")]
[DataRow("y")]
public void PlayAgain_InputY_ReturnsTrue(string value)
{
    using (var reader = new StringReader(value))
    {
        Console.SetIn(reader);
        var app = new App();

        bool result = app.PlayAgain();

        Assert.IsTrue(result);
    }
}

[DataTestMethod]
[DataRow("N")]
[DataRow("boo")]
[DataRow("")]
public void PlayAgain_InputNotY_ReturnsFalse(string value)
{
    using (var reader = new StringReader(value))
    {
        Console.SetIn(reader);
        var app = new App();

        bool result = app.PlayAgain();

        Assert.IsFalse(result);
    }
}

我们对其他方法做同样的事情。


这里是 GetInput 方法的测试。

由于内部有一个循环,当输入错误的值时会无限期地运行,我们必须通过输入正确的值来中断它。这是通过换行传递两个值来完成的:"0\n50"。输入不正确的值是对输出字符串的测试,然后用正确的值中断循环。

[DataTestMethod]
[DataRow("1")]
[DataRow("50")]
[DataRow("100")]
public void GetInput_InputCorrectString_ReturnsNumber(string value)
{
    using (var reader = new StringReader(value))
    {
        Console.SetIn(reader);
        var app = new App();

        int actual = app.GetInput();
        int expected = int.Parse(value);

        Assert.AreEqual(expected, actual);
    }
}

[DataTestMethod]
[DataRow("0\n50")]
[DataRow("101\n50")]
public void GetInput_InputSmallerOrGreaterValue_WritesMessage(string value)
{
    using (var reader = new StringReader(value))
    using (var writer = new StringWriter())
    {
        Console.SetIn(reader);
        Console.SetOut(writer);
        var app = new App();

        _ = app.GetInput();

        string actualMessage = writer.ToString();
        string expectedMessage = "Must be between 1 and 100";

        Assert.IsTrue(actualMessage.Contains(expectedMessage));
    }
}

[DataTestMethod]
[DataRow("x\n50")]
[DataRow("qwerty\n50")]
public void GetInput_InputNotNumber_WritesMessage(string value)
{
    using (var reader = new StringReader(value))
    using (var writer = new StringWriter())
    {
        Console.SetIn(reader);
        Console.SetOut(writer);
        var app = new App();

        _ = app.GetInput();

        string actualMessage = writer.ToString();
        string expectedMessage = "Not a number";

        Assert.IsTrue(actualMessage.Contains(expectedMessage));
    }
}