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

Jos*_*osh 0 c# asp.net integration-testing unit-testing

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

  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');
    }
Run Code Online (Sandbox Code Playgroud)

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

Ale*_*rov 5

您的代码本质上是不可测试的。

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

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

通过网络(我看到使用 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());
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们有能力测试单独的方法。
我用的是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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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


以下是该方法的测试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));
    }
}
Run Code Online (Sandbox Code Playgroud)