1

我无法弄清楚如何对函数进行单元测试。它用于获取用户输入密码样式,以便显示星号而不是用户键入的内容。所以我试图捕获控制台 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()

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

4

2 回答 2

7

你不应该对这种行为进行单元测试。此代码在很大程度上取决于来自控制台的外部数据。

但是,如果您被迫对此进行测试...

首先打破对控制台的依赖。用一些类包装控制台操作,如 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,因为我们在列表中的字符集有限。

于 2012-12-20T08:28:30.157 回答
0

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

提示密码:

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

模拟控制台存根:

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 返回字节数组或字符串。为了测试,我返回一个字符串。

于 2020-03-06T16:15:52.250 回答