使用反射来设置私有方法的返回类型。

huangapple go评论93阅读模式
英文:

Use reflection to set the return type of a private method

问题

我被委托为糟糕编写的遗留代码编写单元测试。
我应该为这些代码编写测试,但不能以任何方式更改它们。它们是只读的。(公司政策,不要碰这个!)

请看下面的示例:

//这个类不能被更改或重构。它就是这样的。
public class OldUntouchableCode
{
    public string DoStuff()
    {
        var result = DoInternalStuff();
        return result + " new stuff";
    }

    private string DoInternalStuff()
    {
        //进行不使用依赖注入客户端的外部调用。
        //这意味着无法对外部调用进行模拟。
        //这个调用的输出根据外部调用的返回值而改变。
        //这使得该方法的返回值是不可预测的。
        var client = new HttpClient();
        /*
         * 在这里有一堆使用 HttpClient 进行外部调用的代码
         */
        return "The result of the external calls";
    }
}

OldUntouchableCode代表我必须为其编写测试的代码。DoStuff公共方法使用DoInternalStuff私有方法来完成其工作。然而,这个私有方法进行了无法模拟的外部调用。

尝试为这段代码编写测试会导致以下情况:

var sut = new OldUntouchableCode();
//我需要将 DoInternalStuff() 的返回值设置为特定值,以便测试 DoStuff() 方法。
//这个测试可能通过,也可能不通过,这取决于外部调用的返回值。这个返回值是不可预测的。
var actual = sut.DoStuff();
var expected = "expected value new stuff";

Console.WriteLine($" Test {(expected == actual ? "Passed":"Failed")}");

由于DoInternalStuff方法的行为是不可预测的,无法可靠地测试DoStuff方法的输出。

明确一点,我并不打算测试私有方法。

我已经发现可以使用反射找到这个私有方法的MethodInfo

var methodInfo = sut.GetType().GetMethod("DoInternalStuff", BindingFlags.NonPublic | BindingFlags.Instance);

上述代码成功捕获了DoInternalStuff私有方法的MethodInfo。我可以在调试器中看到该对象包含了该方法的正确名称。

在这个阶段,我需要一种方法告诉运行时,在这个OldUntouchableCode实例上无论外部调用返回什么,都从DoInternalStuff私有方法返回我指定的特定值。

换句话说,我需要告诉运行时:“听着!当我运行sut.DoStuff()时,你最终会调用DoInternalStuff方法。当你这样做时,无论该方法内部发生了什么,都从该方法返回这个特定的字符串。”

这个目标能实现吗?

英文:

I've been tasked with writing unit tests for poorly authored legacy code.
The code that I should write tests for can't get changed in any way whatsoever. It's READ-ONLY.(Company policy, don't touch this!)

Please look at the example below:

//This class can't be changed or refactored. It is what it is.
public class OldUntouchableCode
{
    public string DoStuff()
    {
        var result = DoInternalStuff();
        return result + " new stuff";
    }

    private string DoInternalStuff()
    {
        //Making external calls that DON'T use the dependency-injected clients.
        //This means that the external calls can't be mocked.
        //The output of this call changes depending on what the external call returns.
        //This makes the return value of this method unpredictable.
        var client = new HttpClient();
        /*
         * imagine a bunch of code here that uses the HttpClient to make external calls
         */
        return "The result of the external calls";
    }
}

OldUntouchableCode represents the code that I must write tests for. The DoStuff public method uses the DoInternalStuff private method to perform its job. However, this private method makes external calls that can't get mocked.

Trying to write tests for this code results in the following situation:

var sut = new OldUntouchableCode();
//I need to set the return value of DoInternalStuff() to a specific value, so I can test the DoStuff() method.
//This test might or might not pass, depending on what the external call returns. This return value is unpredictable.
var actual = sut.DoStuff();
var expected = "expected value new stuff";

Console.WriteLine($" Test {(expected == actual ? "Passed":"Failed")}");

Since the DoInternalStuff method behaves in an unpredictable manner, the output of the DoStuff method can't get tested reliably.

To be perfectly clear, I'm NOT trying to test the private method.

I've discovered already that I can find this private method's MethodInfo with reflection:

var methodInfo = sut.GetType().GetMethod("DoInternalStuff", BindingFlags.NonPublic | BindingFlags.Instance);

The above code successfully captures the MethodInfo of the DoInternalStuff private method. I can see in the debugger that this object contains the correct name of this method.

At this stage, I need a way to tell the runtime to return a specific value(that I determine) from the DoInternalStuff private method on this instance of the OldUntouchableCode no matter what the external call returns.

In other words, I need to tell the runtime: "Listen! When I run sut.DoStuff(), you will eventually invoke the DoInternalStuff method. When you do so, return THIS particular string from that method, no matter what occurs inside of that method."

Is this goal possible to achieve?

答案1

得分: 1

据我所知,很遗憾,这是不可能的,如果你不能修改内部的HttpClient,事情会变得复杂。

你唯一能修改内部执行请求的方式可能是使用框架事件,但这对我来说太复杂了,我无法理解这是否可行。

可能更简单的方法是选择一些启发式规则来确定返回值。

例如,通过查询whois.com获取当前IP地址的方法将根据运行该方法的机器而返回不同的IP地址。
然而,你可以检查返回值是否符合IP地址的正确格式。

对于查询当前城市的方法,你可以列出CI/CD运行器或开发人员可能所在的城市列表,并检查结果是否包含在列表中。

提高抽象级别,找出方法总是返回的内容(比如“当前城市”或“本地链接IPv6地址”),然后进行测试。

英文:

As far as I know this is unfortunately impossible, and if you can't modify the internal HttpClient, things are gonna get complicated.

The only way you could modify the internally executed request might be the framework event, but this is too complicated for me to understand how possible this is.

Probably easier would be to choose some heuristic for what can be returned.

For example, a method getting the current IP address by quering whois.com will return a different IP address depending on which machine runs it.
You could however check whether or not the returned value is in the correct format for an IP address instead.

For a method querying the current city, you could make a list of possible cities a CI/CD runner or developer might be in and check if the result is included in the list.

Go up a level of abstraction, find out what the method always returns (like "the current city" or "a link-local IPv6 address", and test that.

huangapple
  • 本文由 发表于 2023年8月9日 04:30:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/76863018.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定