先模拟一个方法而不先嘲笑一个类

时间:2014-06-23 19:14:57

标签: c# unit-testing mocking tdd moq

我正在使用moq4来模拟 UnitTests 中的内容。我有一个课程说 TestClass ,其中一个名为 TestMethod 的方法是我想要测试的方法。 所以我的问题是我的 TestMethod 需要检查我的TestClass中的 testlist 。像这样: -

public Class TestClass
{    
    public readonly ISomeService _someService;

    public bool TestProperty { get; private set; }

    public List<string> testlist { get; private set; }

    Public TestClass(ISomeService someService)
    {
        _someService = someService;
    }

    public async Task<bool> TestMethod(string sd)
    {
      if(TestProperty) // here how can i control this property in UnitTest.
         return false; 

      testlist.contains(sd); // check in this list but list will be null.
    }

    public async Task<List<string>> SetTestListAndProperty()
    {
        testlist.Add("2");
        testlist.Add("3");

        TestProperty = false;
    } 

}

现在在 testmethodtests中,我已经模拟了ISomeService并传递给了TestClass构造函数

var someservicemock = new Mock<ISomeService>();

var testclassobj = new TestClass(someservicemock.Object);

现在我打电话给我的TestMethod

result = await testclassobj.TestMethod("id"); // it is throwing the exception that testlist is null.

//当TestProperty为false时,我也想测试TestMethod。那么我怎样才能设置它没有公共设置器的属性。它由其他方法( SetTestListAndProperty )设置。

所以我的问题是我可以在不模拟TestClass的情况下模拟TestClass testlist 吗? 如果这种方式有误,请告诉我,或者您也知道任何解决方法。 欢呼声

3 个答案:

答案 0 :(得分:1)

两件事:

  1. 我不建议您模拟所有。在收集的情况下,使用真实(非模拟)对象是完全可以的。模拟列表时的好处在哪里?您可以通过检查testlist属性来轻松测试所有内容。

  2. (这是旁注,但也会解决您的第一条评论。)尝试使您的类型不可变。您可以找到有关此主题的大量文章。只是搜索它。好处是您不必处理NullReferenceException和其他一些场景。对于您的示例,这意味着您应该在构造函数中初始化testlist

答案 1 :(得分:1)

我认为你的问题归结为:当测试中的方法取决于由其他方法设置的内部状态时该怎么办?

以下是一个例子:

class MyCollection<T> : IEnumerable<T>
{
    private List<T> _list = new List<T>();
    private Receiver _receiver;

    public MyCollection()
    {
        _receiver = receiver;
    }

    public void Add(T item)
    {
        _list.Add(item);
    }

    public void RemoveAndNotify(T item)
    {
        _list.Remove(item);
        _receiver.Notify(item);
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _list.GetEnumerator();
    }
}

(顺便说一句,调用您的类型TestClassTestPropertytestlist并没有真正帮助 - 读者不知道这些事情是如何相互关联的。它是令人困惑的是Foo,Bar和Baz。用猫,狗,人等来代替具体而有意义的名字。)

  1. 因此,假设您要对RemoveAndNotify方法进行单元测试。但是,如果不首先添加项目,则无法删除项目! 这是否意味着我必须模拟内部_list来插入虚拟数据? NO!
  2. 另外,如何确认项目确实已从内部列表中删除?我是否必须以某种方式检索内部_list并检查其内容(通过注入,反射等)? NO!
  3. 我认为你的问题的症结在于:单元测试不一定要测试一种方法! 单元测试测试行为的最小单位

    该行为单元可能涉及调用一个或更多方法。所以这是我如何单元测试与删除项目相关的行为。 我们想要测试两个行为

    1. 我们想测试一下,在我们删除它之后,该集合不再有一个项目。
    2. 我们想测试一个项目被移除后接收者会收到通知。

    3. [Fact]
      public void RemoveAndNotify_RemovesItem()
      {   
          //Arrange
          var mockReceiver = ...
          var collection = new MyCollection<int>(mockReceiver.Object);
      
          collection.Add(5);
      
          //Act
          collection.Remove(5);
      
          //Assert
          //internally calls GetEnumerator to verify that the collection no longer contains "5"
          Assert.AreEqual(Enumerable.Empty<int>(), collection); 
      }
      
      
      [Fact]
      public void RemoveAndNotify_NotifiesReceiver()
      {   
          //Arrange
          var mockReceiver = ...
          var collection = new MyCollection<int>(mockReceiver.Object);
      
          collection.Add(5);
      
          //Act
          collection.Remove(5);
      
          //Assert
          mockReceiver.Verify(rec => rec.Notify(5), Times.Once());
      }
      

      正如您所看到的,第一个单元测试在类(AddRemoveGetEnumerator)上调用了三个方法,以便测试一个行为单位

      底线:忘记“测试中的方法”,并考虑“测试中的行为”。你正在测试行为,而不是方法。

答案 2 :(得分:0)

我会创建另一个构造函数,它将列表作为参数,这样您就可以在测试中创建并填充包含测试数据的列表,然后将其提供给被测试的类。

public TestClass(ISomeService service, IList<string> list) {
    _someService = service;
    testList = list;
}

使用此构造函数,testList不会为null,除非您在。中传递null。

我知道有些人不想添加仅用于测试的方法。如果您不想公开另一个仅用于测试的构造函数,则可以标记新的构造函数internal并仅将内部结构暴露给您的测试程序集。

internal TestClass(ISomeService service, IList<string> list) {
    ...
}

在项目的AssemblyInfo.cs中:

[assembly: InternalsVisibleTo("your test assembly");

编辑:对于属性。如果您有不公开setter的属性,则可以标记属性virtual并使用Moq&lt; SetupGet方法设置属性返回值。

public class ClassUnderTest
{
    public virtual string TestProperty { get; private set; }
}

在你的测试中:

public void SomeTest() {
    var mock = new Mock<ClassUnderTest>();
    mock.SetupGet(m => m.TestProperty).Returns("hi");

    var result = mock.Object.TestProperty;

    Assert.That(result, Is.EqualTo("hi"); // should pass
}