如何对依赖于System.Console的函数进行单元测试?

时间:2012-12-20 00:14:30

标签: c# unit-testing

我无法弄清楚如何对功能进行单元测试。它用于获取用户输入密码样式,以便显示星号而不是用户键入的内容。所以我试图捕获控制台I / O以将其与预期值进行比较。

这是功能:

public string getMaskedInput(string prompt)
{
    string pwd = "";
    ConsoleKeyInfo key;
    do
    {
        key = Console.ReadKey(true);
        if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)
        {
            pwd = pwd += key.KeyChar;
            Console.Write("*");
        }
        else
        {
            if (key.Key == ConsoleKey.Backspace && pwd.Length > 0)
            {
                pwd = pwd.Substring(0, pwd.Length - 1);
                Console.Write("\b \b");
            }
        }

    }
    while (key.Key != ConsoleKey.Enter);
    return pwd;
}

测试:

public void getInputTest()
{
    //arrange
    var sr = new StringReader("a secret");
    var sw = new StringWriter();
    Console.SetOut(sw);
    Console.SetIn(sr);
    Input i = new Input();
    string prompt="what are you typing? ";

    //act
    string result = i.getMaskedInput(prompt);         

    //assert
    var writeResult = sw.ToString();
    Assert.IsTrue((writeResult == "what are you typing? ")&&(result=="a secret"));
编辑:我重新检查了我的单元测试,它有一个错误;现在我已经修好了,测试就挂了。单步执行测试表明它与Console.ReadKey()有关,我怀疑无法按StreamReader() ReadLine()的方式重定向。{/ p>

另外,这似乎是同一测试中的两个断言,是否正确测试此功能?

2 个答案:

答案 0 :(得分:4)

你不应该对这种行为进行单元测试。此代码严重依赖于来自控制台的外部数据。

但如果你被迫测试这个......

首先打破控制台的依赖性。使用某些类包装Console.Read和Console.Write等控制台操作。

public class ConsoleWrapper : IConsoleWrapper
{
    public ConsoleKeyInfo ReadKey()
    {
        return Console.ReadKey(true);
    }

    public void Write(string data)
    {
        Console.Write(data);
    }
}

还有一个接口IConsoleWrapper

public interface IConsoleWrapper
{
    ConsoleKeyInfo ReadKey();
    void Write(string data);
}

现在在你的功能中你可以做到

    public static string GetMaskedInput(string prompt, IConsoleWrapper console)
    {
        string pwd = "";
        ConsoleKeyInfo key;
        do
        {
            key = console.ReadKey();
            if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)
            {
                pwd += key.KeyChar;
                console.Write("*");
            }
            else
            {
                if (key.Key == ConsoleKey.Backspace && pwd.Length > 0)
                {
                    pwd = pwd.Substring(0, pwd.Length - 1);
                    console.Write("\b \b");
                }
            }
        }
        while (key.Key != ConsoleKey.Enter);
        return pwd;
    }
}

使用此界面,您现在可以模拟它并轻松检查被调用的方法及其参数。您还可以创建一些带有内部字符串的ConsoleStub,它可以模拟整个操作。

像这样。

public class ConsoleWrapperStub : IConsoleWrapper
{
    private IList<ConsoleKey> keyCollection;
    private int keyIndex = 0;

    public ConsoleWrapperStub(IList<ConsoleKey> keyCollection)
    {
        this.keyCollection = keyCollection;
    }

    public string Output = string.Empty;

    public ConsoleKeyInfo ReadKey()
    {
        var result = keyCollection[this.keyIndex];
        keyIndex++;
        return new ConsoleKeyInfo( (char)result ,result ,false ,false ,false);
    }

    public void Write(string data)
    {
        Output += data;
    }
}

此存根使您能够创建自己包含的测试方案。

例如

    [Test]
    public void If_Enter_first_then_return_empty_pwd()
    {
        // Arrange
        var stub = new ConsoleWrapperStub(new List<ConsoleKey> { ConsoleKey.Enter });
        var expectedResult = String.Empty;
        var expectedConsoleOutput = String.Empty;

        // Act

        var actualResult = Program.GetMaskedInput(string.Empty, stub);

        //     
        Assert.That(actualResult, Is.EqualTo(expectedResult));
        Assert.That(stub.Output, Is.EqualTo(expectedConsoleOutput));
    }

    [Test]
    public void If_two_chars_return_pass_and_output_coded_pass()
    {
        // Arrange
        var stub = new ConsoleWrapperStub(new List<ConsoleKey> { ConsoleKey.A, ConsoleKey.B, ConsoleKey.Enter });
        var expectedResult = "AB";
        var expectedConsoleOutput = "**";

        // Act

        var actualResult = Program.GetMaskedInput(string.Empty, stub);

        //     
        Assert.That(actualResult, Is.EqualTo(expectedResult));
        Assert.That(stub.Output, Is.EqualTo(expectedConsoleOutput));
    }

希望这有助于你和你得到一般的想法:)

修改

好的,我已经编辑了我的样本,并使用Nunit对其进行了测试。但是你必须记住,每个测试场景都必须以ENTER键结束。如果没有它,while循环将是无穷无尽的,并且会有异常KeynotFound,因为我们在List中有一组有限的字符。

答案 1 :(得分:0)

我做了一些调整,以防有人感兴趣。它处理混合大小写和其他控制台输出,例如提示和换行符。

PromptForPassword:

    internal static SecureString PromptForPassword(IConsoleWrapper console)
    {
        console.WriteLine("Enter password: ");
        var pwd = new SecureString();
        while (true)
        {
            ConsoleKeyInfo i = console.ReadKey(true);
            if (i.Key == ConsoleKey.Enter)
            {
                break;
            }
            else if (i.Key == ConsoleKey.Backspace)
            {
                if (pwd.Length > 0)
                {
                    pwd.RemoveAt(pwd.Length - 1);
                    console.Write("\b \b");
                }
            }
            else if (i.KeyChar != '\u0000') // KeyChar == '\u0000' if the key pressed does not correspond to a printable character, e.g. F1, Pause-Break, etc
            {
                pwd.AppendChar(i.KeyChar);
                console.Write("*");
            }
        }
        console.WriteLine();
        return pwd;
    }

接口:

public interface IConsoleWrapper
{
    void WriteLine(string value);
    ConsoleKeyInfo ReadKey(bool intercept);
    void Write(string value);
    void WriteLine();
    string ReadLine();
}

MockConsoleStub:

public class MockConsoleStub : IConsoleWrapper
{
    private readonly IList<ConsoleKeyInfo> ckiCollection;
    private int keyIndex = 0;

    public MockConsoleStub(IList<ConsoleKeyInfo> mockKeyInfoCollection)
    {
        ckiCollection = mockKeyInfoCollection;
    }

    public readonly StringBuilder Output = new StringBuilder();

    public ConsoleKeyInfo ReadKey()
    {
        var cki = ckiCollection[this.keyIndex];
        keyIndex++;
        return cki;
    }

    public void Write(string data)
    {
        Output.Append(data);
    }

    public void WriteLine(string value)
    {
        Output.AppendLine(value);
    }

    public void WriteLine()
    {
        Output.AppendLine();
    }

    public ConsoleKeyInfo ReadKey(bool intercept)
    {
        var cki = ckiCollection[this.keyIndex];
        keyIndex++;
        return cki;
    }

    public string ReadLine()
    {
        var sb = new StringBuilder();
        var cki = ckiCollection[this.keyIndex];
        keyIndex++;
        while (cki.Key != ConsoleKey.Enter)
        {
            sb.Append(cki.KeyChar);
            cki = ckiCollection[keyIndex];
            keyIndex++;
        }
        return sb.ToString();
    }
}

用法:

    [TestMethod]
    public void PromptForUsername_stub_password_GetsPassword()
    {
        var stub = new MockConsoleStub(new List<ConsoleKeyInfo>
        {
            new ConsoleKeyInfo('P', ConsoleKey.P, true, false, false),
            new ConsoleKeyInfo('@', ConsoleKey.Attention, true, false, false),
            new ConsoleKeyInfo('s', ConsoleKey.S, false, false, false),
            new ConsoleKeyInfo('s', ConsoleKey.S, false, false, false),
            new ConsoleKeyInfo('w', ConsoleKey.W, false, false, false),
            new ConsoleKeyInfo('o', ConsoleKey.O, false, false, false),
            new ConsoleKeyInfo('r', ConsoleKey.R, false, false, false),
            new ConsoleKeyInfo('d', ConsoleKey.D, false, false, false),
            new ConsoleKeyInfo('!', ConsoleKey.D1, true, false, false),
            new ConsoleKeyInfo('\u0000', ConsoleKey.Enter, false, false, false),
        });
        var password = Settings.PromptForPassword(stub);
        Assert.AreEqual("P@ssword!", SecureStringWrapper.ToString(password));
        Assert.AreEqual($"Enter password: {Environment.NewLine}*********{Environment.NewLine}", stub.Output.ToString());
    }

注意:SecureStringWrapper返回Byte数组或字符串。为了进行测试,我返回了一个字符串。