问题 测试和模拟私有/受保护的方法。许多帖子但仍然无法使一个例子有效


我已经看过很多关于“嘲弄私人方法”的帖子和问题,但仍然无法使其发挥作用而且找不到真正的答案。 让我们忘记代码的味道,你不应该这样做....

根据我的理解,我做了以下事情:

1)创建了一个类库“MyMoqSamples”

2)添加了对Moq和NUnit的引用

3)编辑AssemblyInfo文件并添加       [assembly:InternalsVisibleTo(“DynamicProxyGenAssembly2”)]       [assembly:InternalsVisibleTo(“MyMoqSamples”)]

4)现在需要测试私有方法。因为它是私有方法,所以它不是接口的一部分。

5)添加以下代码

[TestFixture]
public class Can_test_my_private_method
{
    [Test]
    public void Should_be_able_to_test_my_private_method()
    {
        // TODO how do I test my DoSomthing method?
    }
}

public class CustomerInfo
{
    public string Name { get; set; }
    public string Surname { get; set; }
}

public interface ICustomerService
{
    List<CustomerInfo> GetCustomers();
}

public class CustomerService : ICustomerService
{
    public List<CustomerInfo> GetCustomers()
    {
        return new List<CustomerInfo> { new CustomerInfo { Surname = "Bloggs", Name = "Jo" } };
    }

    protected virtual void DoSomething()
    {
    }
}

你能举例说明你如何测试我的私有方法吗? 非常感谢


12492
2018-06-05 06:30


起源



答案:


您描述的步骤将Moq设置为测试内部类和成员,因此与测试受保护或私有方法没有任何关系

测试私有方法有点气味,你应该真正测试公共API。如果您认为该方法非常重要并且需要单独进行测试,那么它是否应该属于自己的类,然后可以自行测试?

如果您的心脏已经开始测试上面的受保护方法,那么您可以在测试程序集中滚动自己的Mock:

public class CustomerServiceMock : CustomerService {
    public void DoSomethingTester() {
         // Set up state or whatever you need
         DoSomething();
    }

}

[TestMethod]
public void DoSomething_WhenCalled_DoesSomething() {
    CustomerServiceMock serviceMock = new CustomerServiceMock(...);
    serviceMock.DoSomethingTester();
 }

如果它是私人的,你可能会做一些狡猾的反思,但走这条路是测试地狱的方法。


更新

虽然你已经在你的问题中给出了示例代码,但我并没有真正看到你想要如何“测试”受保护的方法,所以我会想出一些人为的...

假设您的客户服务如下: -

 public CustomerService : ICustomerService {

      private readonly ICustomerRepository _repository;

      public CustomerService(ICustomerRepository repository) {
           _repository = repository;
      } 

      public void MakeCustomerPreferred(Customer preferred) {
           MakePreferred(customer);
           _repository.Save(customer);
      }

      protected virtual void MakePreferred(Customer customer) {
          // Or more than likely some grungy logic
          customer.IsPreferred = true;
      }
 }

如果您想测试受保护的方法,您可以执行以下操作: -

[TestClass]
public class CustomerServiceTests {

     CustomerServiceTester customerService;
     Mock<ICustomerRepository> customerRepositoryMock;

     [TestInitialize]
     public void Setup() {
          customerRepoMock = new Mock<ICustomerRepository>();
          customerService = new CustomerServiceTester(customerRepoMock.Object);
     }


     public class CustomerServiceTester : CustomerService {    
          public void MakePreferredTest(Customer customer) {
              MakePreferred(customer);
          }

          // You could also add in test specific instrumentation
          // by overriding MakePreferred here like so...

          protected override void MakePreferred(Customer customer) {
              CustomerArgument = customer;
              WasCalled = true;
              base.MakePreferred(customer);
          }

          public Customer CustomerArgument { get; set; }
          public bool WasCalled { get; set; }
     }

     [TestMethod]
     public void MakePreferred_WithValidCustomer_MakesCustomerPreferred() {
         Customer customer = new Customer();
         customerService.MakePreferredTest(customer);
         Assert.AreEqual(true, customer.IsPreferred);
     }

     // Rest of your tests
}

这个“模式”的名称是Test特定的子类(基于xUnit测试模式术语),您可能希望在此处看到更多信息: -

http://xunitpatterns.com/Test-Specific%20Subclass.html

根据您的评论和之前的问题,您似乎已经承担了对某些遗留代码进行单元测试(或自己做出决定)的任务。在这种情况下,所有遗留代码的圣经都是Michael Feathers的书。它涵盖了这样的技术以及重构和技术,以处理将“不可测试的”类和方法分解为更易于管理的东西,我强烈推荐它。


14
2018-06-06 13:29



我知道你去哪里并且有意义。刚开始研究新项目和大量代码而不是单元测试。将所有这些私有方法重构为内部类并将其公开是否是一项很好的工作?是否可以测试内部课程? - user9969
可以使用InternalsVisibleTo属性测试内部类,就像您在问题开头所描述的那样。 - John Foster
捡起剩下的问题。更深层次的问题是你为什么要首先测试一个私人成员,为什么你想让Moq这样做呢? - John Foster
我个人认为Moq在测试依赖于ICustomerService的类时可用于隔离依赖项。您似乎要问的是如何测试ICustomerService的实现以及注入服务可能依赖的依赖项,我不确定Moq如何帮助您执行此操作。 - John Foster
相反,尝试测试公共API,如果要测试在API调用期间是否调用受保护的方法,那么通过在测试代码中继承CustomerService就可以轻松完成。 - John Foster


答案:


您描述的步骤将Moq设置为测试内部类和成员,因此与测试受保护或私有方法没有任何关系

测试私有方法有点气味,你应该真正测试公共API。如果您认为该方法非常重要并且需要单独进行测试,那么它是否应该属于自己的类,然后可以自行测试?

如果您的心脏已经开始测试上面的受保护方法,那么您可以在测试程序集中滚动自己的Mock:

public class CustomerServiceMock : CustomerService {
    public void DoSomethingTester() {
         // Set up state or whatever you need
         DoSomething();
    }

}

[TestMethod]
public void DoSomething_WhenCalled_DoesSomething() {
    CustomerServiceMock serviceMock = new CustomerServiceMock(...);
    serviceMock.DoSomethingTester();
 }

如果它是私人的,你可能会做一些狡猾的反思,但走这条路是测试地狱的方法。


更新

虽然你已经在你的问题中给出了示例代码,但我并没有真正看到你想要如何“测试”受保护的方法,所以我会想出一些人为的...

假设您的客户服务如下: -

 public CustomerService : ICustomerService {

      private readonly ICustomerRepository _repository;

      public CustomerService(ICustomerRepository repository) {
           _repository = repository;
      } 

      public void MakeCustomerPreferred(Customer preferred) {
           MakePreferred(customer);
           _repository.Save(customer);
      }

      protected virtual void MakePreferred(Customer customer) {
          // Or more than likely some grungy logic
          customer.IsPreferred = true;
      }
 }

如果您想测试受保护的方法,您可以执行以下操作: -

[TestClass]
public class CustomerServiceTests {

     CustomerServiceTester customerService;
     Mock<ICustomerRepository> customerRepositoryMock;

     [TestInitialize]
     public void Setup() {
          customerRepoMock = new Mock<ICustomerRepository>();
          customerService = new CustomerServiceTester(customerRepoMock.Object);
     }


     public class CustomerServiceTester : CustomerService {    
          public void MakePreferredTest(Customer customer) {
              MakePreferred(customer);
          }

          // You could also add in test specific instrumentation
          // by overriding MakePreferred here like so...

          protected override void MakePreferred(Customer customer) {
              CustomerArgument = customer;
              WasCalled = true;
              base.MakePreferred(customer);
          }

          public Customer CustomerArgument { get; set; }
          public bool WasCalled { get; set; }
     }

     [TestMethod]
     public void MakePreferred_WithValidCustomer_MakesCustomerPreferred() {
         Customer customer = new Customer();
         customerService.MakePreferredTest(customer);
         Assert.AreEqual(true, customer.IsPreferred);
     }

     // Rest of your tests
}

这个“模式”的名称是Test特定的子类(基于xUnit测试模式术语),您可能希望在此处看到更多信息: -

http://xunitpatterns.com/Test-Specific%20Subclass.html

根据您的评论和之前的问题,您似乎已经承担了对某些遗留代码进行单元测试(或自己做出决定)的任务。在这种情况下,所有遗留代码的圣经都是Michael Feathers的书。它涵盖了这样的技术以及重构和技术,以处理将“不可测试的”类和方法分解为更易于管理的东西,我强烈推荐它。


14
2018-06-06 13:29



我知道你去哪里并且有意义。刚开始研究新项目和大量代码而不是单元测试。将所有这些私有方法重构为内部类并将其公开是否是一项很好的工作?是否可以测试内部课程? - user9969
可以使用InternalsVisibleTo属性测试内部类,就像您在问题开头所描述的那样。 - John Foster
捡起剩下的问题。更深层次的问题是你为什么要首先测试一个私人成员,为什么你想让Moq这样做呢? - John Foster
我个人认为Moq在测试依赖于ICustomerService的类时可用于隔离依赖项。您似乎要问的是如何测试ICustomerService的实现以及注入服务可能依赖的依赖项,我不确定Moq如何帮助您执行此操作。 - John Foster
相反,尝试测试公共API,如果要测试在API调用期间是否调用受保护的方法,那么通过在测试代码中继承CustomerService就可以轻松完成。 - John Foster


您的问题似乎有两个部分。

  1. 如何模拟受保护的方法:

    http://blogs.clariusconsulting.net/kzu/mocking-protected-members-with-moq/

  2. 如何在测试中触发此受保护/私有行为的调用

    这里的答案是你通过公开的东西触发它。如果你想强制它直接发生(即,实际上调用一些东西 protected 直接没有中间帮助器,你需要使用反射。这是设计 - 语言提供保护机制作为强制封装的一种方式。

我建议你考虑一下你在试验中试图展示/证明的内容。如果你发现自己编写了一个复杂的测试,那么你就是在做错了。也许您想要做的事情可以分解为独立测试?也许您正在编写集成测试,而不是单元测试?但是有很多关于不良测试的文章,当你学会编写好的测试时,更多的文章对你有意义。我最喜欢的两个是 http://www.codethinked.com/post/2009/06/30/What-is-Unit-Testing.aspx 和 http://www.codethinked.com/post/2009/11/05/Ite28099s-Okay-To-Write-Unit-Tests.aspx


1
2018-06-06 14:01



感谢您的回复。我刚刚加入了一家公司,他们开发了大量的代码而没有单一的单元测试。他们从来没有做过TDD等。并且发现难以解释“你不应该测试私有方法”等概念。你测试的行为不是实现等等......现在让我们忘记一下我们应该做什么和不应该这样做。是否可以使用Moq直接测试私有方法?我似乎已经采取了所有必要的步骤,但我不断得到我应该做什么和不应做的答案(我很感激)但不是实际的例子。谢谢! - user9969
那些链接非常好!我喜欢他们我可以转发他们。(为了我的情况) - user9969
Ruben,如果说重构这些内部方法并将它们公开并且内部类将我处于相同的位置?试着想一下没有“黑客”的变通办法 - user9969
根据您与@sighohwell的对话...您也希望将有效的遗留代码工作放在您的阅读清单上。但我敢打赌,你会发现xUnit测试模式最有用 - 你有基本的机械理解,但只需将它们联系在一起 - Ruben Bartelink
顺便说一下,我只是+ 1d @ sighohwell的回答。你回馈给你认为他们正在帮助的人的方式是+ 1ing。看看他/她试图获得积分的努力程度。 - Ruben Bartelink