简体   繁体   English

单元测试嵌套方法

[英]Unit Testing nested methods

While I understand that when unit testing a method it is very important to mock all it's dependencies, what I still have confusion about is what happens when the method is nested?虽然我知道在对方法进行单元测试时,模拟它的所有依赖项非常重要,但我仍然感到困惑的是当方法嵌套时会发生什么? Do I mock only the dependencies of the parent method or do I mock the dependencies of the child method(s) as well or do I set expectations on the calls to the dependent object and set the exact return values so that I can perform the test that I want?我是只模拟父方法的依赖关系,还是也模拟子方法的依赖关系,还是设置对依赖对象调用的期望并设置确切的返回值,以便我可以执行测试我想要的?

For instance, in the below example, if we want to unit test the method B, do we only mock IHttpClientFactory & ILogger or do we also set the method's return value to what we are actually expecting because otherwise when the test method executes it goes ahead and tries to execute methodC where it fails because the value of client after the line var client = _clientFactory.CreateClient() executes is null?例如,在下面的例子中,如果我们想对方法 B 进行单元测试,我们是只模拟IHttpClientFactoryILogger还是我们也将方法的返回值设置为我们实际期望的值,否则当测试方法执行时它会继续并尝试在失败的地方执行 methodC,因为在var client = _clientFactory.CreateClient()执行之后的client值是 null?

using System.Net.Http;
...

public class classA
{
   private readonly IHttpClientFactory _clientFactory;
   private sting url = "...";
   private ILogger _log { get; set; }
   ...

   public classA(ILogger log, IHttpClientFactory clientFactory, ...)
   {
     _log = log;
     _clientFactory = clientFactory;
     ...
   }

   public string methodB(string inputB)
   {
      var varB = methodC(inputB);
      ...
      return ..;
   }

   public string methodC(string inputC)
   {
      ...
      var client = _clientFactory.CreateClient();
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
      HttpResponseMessage httpResponseMessage = await client.PostAsync(url, new StringContent(inputC, Encoding.UTF8, "application/json"));
      responJsonText = await httpResponseMessage.Content.ReadAsStringAsync();
      ...
      return ..;
   }

}

So you have an HTTP client, a high-level method that gets you some structured data and a low-level method that gets you the contents of the response.因此,您有一个 HTTP 客户端,一个为您提供一些结构化数据的高级方法和一个为您获取响应内容的低级方法。

These things are more of an art than hard-cut rules, but the rule I prefer most of the time is to write code that can have all of its I/O abstracted and then mock or test-implement the I/O itself.这些东西更像是一门艺术,而不是硬性规则,但我最喜欢的规则是编写可以抽象所有 I/O 的代码,然后模拟或测试实现 I/O 本身。 This way the most amount of business logic would be testable.这样,最多的业务逻辑将是可测试的。

I/O can be many things - file, network, user input, but even things such as getting a certificate from the cert store or reading registry setting. I/O 可以是很多东西 - 文件、网络、用户输入,甚至是从证书存储中获取证书或读取注册表设置之类的东西。 Any data that originates at runtime from outside the process is I/O, no matter the method.任何在运行时来自进程外部的数据都是 I/O,无论采用何种方法。

When you mock functionality, the most common things you're interested in is validating the method's input or emulating its output (or both).当您模拟功能时,您最感兴趣的最常见的事情是验证方法的输入或模拟其输出(或两者)。 So in your mock, you shouldn't be too concerned with the actual implementation since you're not testing your mocked method - you're testing whatever is calling it.所以在你的模拟中,你不应该太关心实际的实现,因为你没有测试你的模拟方法 - 你正在测试调用它的任何东西。

So... about your sample code.所以...关于您的示例代码。 If you're trying to test MethodB, you'd need MethodC to have a test implementation - either by mocking the HttpClient it relies on or by making it virtual and having it overridden in test.如果您要测试 MethodB,则需要 MethodC 进行测试实现 - 通过virtual它所依赖的 HttpClient 或使其成为virtual并在测试中覆盖它。

Side note: reuse the HttpClient, keep it with the class旁注:重用 HttpClient,与类保持一致

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM