【问题标题】:Can you unit test a void method which doesnt have return type?您可以对没有返回类型的 void 方法进行单元测试吗?
【发布时间】:2019-11-17 07:52:19
【问题描述】:

我有这个 void 方法,我想对其进行单元测试,但不幸的是,我在这方面没有太多经验。

public void findOccuranceMethod()
{
    string str = "The Haunting of Hill House!";
    Console.WriteLine("String: " + str);
    string occurString = "o";
    string replaceString = "MDDS";

    var array = str.Split(new[] { occurString }, StringSplitOptions.None);
    var count = array.Length - 1;
    string result = string.Join(replaceString, array);

    Console.WriteLine("String after replacing a character: " + result);
    Console.WriteLine("Number of replaces made: " + count);
    Console.ReadLine();

}

这是我尝试测试的 TestClass,但在我运行它时它一直在加载:

[TestMethod()]
public void findOccuranceMethodTest()
{
    // Arrange
    string expected = "The Haunting MDDSf Hill HMDDSuse!";

    // Act
    var result = new findOccurance();

    // Assert
    Assert.AreEqual(expected, result);
}

【问题讨论】:

  • 这段代码本身没有多大意义,也不能按原样编译。您能否添加足够的内容以使其实际编译并演示您所看到的行为?
  • 如果目标有依赖关系,你可以断言预期的行为
  • 你想用这个方法测试什么?这里没有真正的逻辑 - 只是一一执行语句。没有输入,没有输出......我根本不会费心测试这个。
  • 我同意 Zolar,这里没有太多要测试的,因为你的方法没有做太多,只是写 sometjng 到控制台,你不能真正测试,因为它是一个静态依赖。除此之外,即使是 void 方法也会产生某种结果 - 例如它创建的文件或它设置的属性 - 您可以对其进行测试。
  • @BMX:不,这不是您想要测试的,因为如果是这样,您也会尝试查找“替换字符后的字符串:”。不,您要测试的是您的 string-replace 是否按预期进行 - 因为如果确实如此,那么您已经知道输出是正确的。因此,取出字符串替换代码并使其成为自己的函数,理想情况下将相关字符串作为参数,并返回完成所有替换的字符串。该函数对于单元测试来说是微不足道的,您甚至不需要更改现有函数的签名。

标签: c# .net unit-testing methods void


【解决方案1】:

为可测试性正确重构代码

对这种逻辑进行编码和单元测试的正确方法是:

public class SomeProductionClass 
{
    public string findOccuranceMethod(string str) 
    {
        string occurString = "o";
        string replaceString = "MDDS";
        var array = str.Split(new[] { occurString }, StringSplitOptions.None);
        string result = string.Join(replaceString, array);

        return result;
    }
}

[TestMethod()]
public void findOccuranceMethodTest()
{
    // Arrange
    string expected = "The Haunting MDDSf Hill HMDDSuse!";
    var productionClass = new SomeProductionClass();

    // Act
    var result = productionClass.findOccuranceMethod("The Haunting of Hill House!");

    // Assert
    Assert.AreEqual(expected, result);
}

如何测试原代码

如果由于某种原因您无法控制生产代码,有两种选择:

  • 用抽象替换Console

  • 将标准输入/输出重新分配给自定义流:这是最不受欢迎的选项,因为它使设置/清理更加复杂,并且会干扰测试运行器。

在任何一种情况下,我们都不得修改被测代码以引入单独的测试/生产分支。例如,“如果在测试中运行,则执行 A,否则执行 B”——在这种情况下,我们实际上并不是在测试生产代码,而是在测试它的不同分支。

用抽象替换控制台

这里我们介绍IConsole作为System.Console的替代品:

public interface IConsole
{
    void WriteLine(string s);
    void ReadLine(); // void is enough for this example
}

// for use in production
public class RealConsole : IConsole
{
    public void WriteLine(string s)
    {
        Console.WriteLine(s); 
    }
    public void ReadLine()
    {
        Console.ReadLine();
    }
}

// for use in unit tests
public class TestConsole : IConsole
{
    public void WriteLine(string s)
    {
        Contents.Add(s);
    }
    public void ReadLine()
    {
    }

    public List<string> Contents { get; } = new List<string>();
}

生产代码将保持原帖中的状态,只是现在它使用 _console 作为依赖项:

public class SomeProductionClass
{
    private readonly IConsole _console;

    public SomeProductionClass(IConsole console)
    {
        _console = console;
    }

    public void findOccuranceMethod()
    {
        string str = "The Haunting of Hill House!";
        _console.WriteLine("String: " + str);
        string occurString = "o";
        string replaceString = "MDDS";

        var array = str.Split(new[] { occurString }, StringSplitOptions.None);
        var count = array.Length - 1;
        string result = string.Join(replaceString, array);

        _console.WriteLine("String after replacing a character: " + result);
        _console.WriteLine("Number of replaces made: " + count);
        _console.ReadLine();
    }
}

测试代码是:

[TestMethod()]
public void findOccuranceMethodTest()
{
    // Arrange
    string expectedString = "The Haunting MDDSf Hill HMDDSuse!";
    int expectedCount = 2;
    var console = new TestConsole();
    var productionClass = new SomeProductionClass(console);

    // Act
    productionClass.findOccuranceMethod();

    // Assert
    Assert.AreEqual(3, console.Contents.Count);
    Assert.AreEqual("String: The Haunting of Hill House!", console.Contents[0]);
    Assert.AreEqual(
        $"String after replacing a character: {expectedString}", 
        console.Contents[1]);
    Assert.AreEqual(
        $"Number of replaces made: {expectedCount}", 
        console.Contents[2]);
}

【讨论】:

  • 这是我打算写的确切相同的答案。第一个是重构以允许黑盒测试。第二个允许白盒测试和模拟依赖项。
  • 感谢您的回答。它在 //Act ...findOccuranceMethod 中抛出一个错误,表示当前上下文中不存在命名空间 findOccuranceMethod。
  • 你在哪里声明了这个方法,在哪个类和哪个命名空间中?也许有一个模棱两可的名字findOccuranceMethod
  • @BMX:由于您的代码不完整,并且每次都违反命名约定,因此我们无法知道东西在哪里。因此,无论您如何安排它们,您都必须调整它们以适应它们。这就是我们要求 MCVE 的部分原因。如果你有一个,这将不是问题;我们可以准确地告诉你应该把什么放在哪里。
  • 您当然应该将这两种方法都放在类中。我已经更新了我的答案,最初为了简洁起见,我把它省略了。
【解决方案2】:

如果您需要保留签名,您可以选择可选参数:

    public void optionSameSignature(Action<string> log = null, Func<string> read = null)
    {
        log = log ?? Console.WriteLine;
        read = read ?? Console.ReadLine;

        string str = "The Haunting of Hill House!";
        log("String: " + str);
        string occurString = "o";
        string replaceString = "MDDS";

        var array = str.Split(new[] { occurString }, StringSplitOptions.None);
        var count = array.Length - 1;
        string result = string.Join(replaceString, array);

        log("String after replacing a character: " + result);
        log("Number of replaces made: " + count);
        read();
    }

    public void Test()
    {
        List<string> lines = new List<string>();
        optionSameSignature(lines.Add, () => "");
    }

在更深层次上,输入和输出是一个技术细节,因此理想情况下,您应该尝试在没有技术细节的情况下拥有您的业务价值代码。这样的事情可能更接近您的实际需求:

    private static void mybusinessfunction(string input, out int count, out string result)
    {
        string occurString = "o";
        string replaceString = "MDDS";

        var array = input.Split(new[] { occurString }, StringSplitOptions.None);
        count = array.Length - 1;
        result = string.Join(replaceString, array);
    }

【讨论】:

  • 命名空间不能直接包含字段或方法等成员!
  • @BMX 我声明了吗?
  • 第二个不起作用,因为我上面说的那个原因
  • @BMX 我明白了 - 把它放到像“MyClass”这样的类中,你可以在不实例化为“MyClass.mybusinessfunction”的情况下达到该功能。
  • @Johannes BMX,您的问题也缺少 using 语句、命名空间和类。那么你的意思是什么?
猜你喜欢
  • 2011-06-25
  • 2014-05-10
  • 1970-01-01
  • 2014-04-16
  • 2010-12-09
  • 1970-01-01
  • 2019-10-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多