【问题标题】:How to unit test code that requires user input c#如何对需要用户输入c#的代码进行单元测试
【发布时间】:2020-07-22 09:28:15
【问题描述】:

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

  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');
    }

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

【问题讨论】:

标签: c# asp.net unit-testing integration-testing


【解决方案1】:

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

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

你应该摆脱静态方法。因为你不能把它们弄假。

应该通过网络(我看到使用 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));
    }
}

【讨论】:

  • 非常感谢!!你能解释一下为什么方法是内部的而不是公开的吗?
  • @Josh - 它们是为encapsulation 目的而制作的。要测试这些方法,请使用InternalsVisibleToAttribute
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-03
    相关资源
    最近更新 更多