单元测试从另一个方法内部调用的方法的返回值

时间:2012-01-26 11:05:07

标签: c# .net unit-testing tdd

我有一个类似的方法:

public List<MyClass> DoSomething(string Name, string Address, string Email, ref string ErrorMessage)
{
  //Check for empty string parameters etc now go and get some data   
  List<MyClass> Data = GetData(Name, Address, Email);

  /*************************************************************    
  //How do I unit test that the data variable might be empty???    
  *************************************************************/

  List<MyClass> FormattedData = FormatData(Data);
  return FormattedData;
}

我刚学习TDD /单元测试。我的问题是,如何编写测试以确保如果GetData返回一个空列表我将ErrorMessage设置为某个东西,然后返回一个空列表?

4 个答案:

答案 0 :(得分:3)

单元测试不是你添加到现有方法中间的东西,它是关于测试与系统其余部分隔离的小代码单元,这样你就可以确信单元的行为应该如此。

所以,你应该编写第二个类,其中唯一的责任是测试DoSomething所居住的类(让我们称之为此类Daddy和测试类DaddyTests)的行为如下你期待它。然后,您可以编写一个调用DoSomething的测试方法,并确保ErrorMessage设置得恰当(ErrorMessage也应该是out参数,而不是ref,除非您也传递了一个值。)

为方便此测试,您需要确保GetData不返回任何数据。通过在假提供程序中传入一组空数据,可以做到这一点,但在更复杂的情况下,可能必须将整个类替换为伪/模拟等价物:使用接口和依赖注入使得此任务非常简单。 (通常,提供程序在Daddy构造期间设置,而不是作为DoSomething调用中的参数。)

public class Daddy {
    public List<MyClass> DoSomething(string Name, string Address,                  string Email, out string ErrorMessage, IDataProvider provider)
    {
      //Check for empty string parameters etc now go and get some data   
      List<MyClass> Data = provider.GetData(Name, Address, Email);

      if (Data.Count == 0)
      {
          ErrorMessage = "Oh noes";
          return Enumerable.Empty<MyClass>();
      }

      List<MyClass> formattedData = FormatData(Data);
      return formattedData;
    }
}

[TestClass]
public class DaddyTest {
    [TestMethod]
    public void DoSomethingHandlesEmptyDataSet() {
        // set-up
        Daddy daddy = new Daddy();

        // test
        IList<MyClass> result = daddy.DoSomething("blah",
                                                  "101 Dalmation Road",
                                                  "bob@example.com",
                                                  out error,
                                                  new FakeProvider(new Enumerable.Empty<AcmeData>())); // a class we've written to act in lieu of the real provider

        // validate
        Assert.NotNull(result); // most testing frameworks provides Assert functionality
        Assert.IsTrue(result.Count == 0);
        Assert.IsFalse(String.IsNullOrEmpty(error));
    }
}

}

答案 1 :(得分:2)

开发时你应该留下一个“入口点”进行测试:

public List<MyClass> DoSomething(string Name, string Address,
              string Email, ref string ErrorMessage, IDataProvider provider)
{
  //Check for empty string parameters etc now go and get some data   
  List<MyClass> Data = provider.GetData(Name, Address, Email);

  List<MyClass> FormattedData = FormatData(Data);
  return FormattedData;
}

在单元测试中,您应该mock IDataProvider

它被称为依赖注入(DI)或控制反转(IOC)

常见的模拟库:

该列表取自here

维基百科文章:

Dependency Injection
Inversion Of Control


NUnit伪代码:

[Test]
public void When_user_forgot_password_should_save_user()
{
    // Arrange
    var errorMessage = string.Empty;
    var dataProvider = MockRepository.GenerateStub<IDataProvider>();
    dataProvider.Stub(x => x.GetData("x", "y", "z", ref errorMessage )).Return(null);

    // Act
    var result DoSomething("x","y","z", ref errorMessage, dataProvider);

    // Assert

    //...
}

答案 2 :(得分:1)

[TestMethod]
public void MyDoSomethingTest()
{
   string errorMessage = string.Empty;
   var actual = myClass.DoSomething(..., ref errorMessage)
   Assert.AreEqual("MyErrorMessage", errorMessage);
   Assert.AreEqual(0, FormattedData.Count);
}

我假设如果没有任何数据格式,格式化程序将返回一个空列表。

由于你想验证方法的最终结果,我不会试图找出从GetData函数返回的内容,因为它是你要验证它是空列表的actaul返回值,也许是FormatData没有'崩溃。

如果你想尽快退出这个函数你可以检查是否有任何参数是空的och为空,在这种情况下只做

errorMessage = "Empty parameters are not allowed";
return new List<MyClass>();

答案 3 :(得分:1)

IMO,行

List<MyClass> Data = GetData(Name, Address, Email);

应该在课外。方法签名更改为

public List<MyClass> DoSomething(List<MyClass> data, ref string ErrorMessage)

这种方法变得更容易测试,因为您可以轻松改变输入以测试所有可能的边缘情况。

另一种方法是让模拟依赖项公开GetData方法,然后设置它以返回各种结果。所以你的班级现在看起来像:

class ThisClass
{
   [Import]
   public IDataService DataService {get; set;}

   public List<MyClass> DoSomething(string Name, string Address, string Email, ref string ErrorMessage)
   {
     //Check for empty string parameters etc now go and get some data   
     List<MyClass> Data = IDataService.GetData(Name, Address, Email); // using dependency

     List<MyClass> FormattedData = FormatData(Data);
     return FormattedData;
   }
}