How do I unit test for machine specific behaviour?

旧城冷巷雨未停 提交于 2019-12-14 02:08:04

问题


I am testing a static method that builds a URL string after checking proxies and hostnames and all kinds of things. This method internally relies on the static flag System.Net.Sockets.Socket.OSSupportsIPv6. Because it's static, I cannot mock this dependency.

EDIT: (This is simplified down a lot... I can't modify the structure of the method with the flag to accept a true/false).

On XP development machines, the method under question returns a predictable string result (in this case, http://hostname/.... ). Our build server, which support IPv6, returns a totally different result (it gives me something like http://192.168.0.1:80/....). Please don't ask why - the point is that there are two different output types that vary on an operating system dependency.

The test needs to validate that the returned hostname or IP address is valid. The outputs are easy to check. The problem is that I can only get one of the possible outputs, depending on which machine the test is run on.

What's the best practice for writing the test in this case? Do I put an if statement in my test which checks the flag and then looks for the two different outputs? This seems sketchy to me because

  1. the test is behaving differently depending on the environment it's run in

  2. I'm basically coupling the test to the method, because you need to know the internals of the method to set up the two cases.

Do I set up two tests, one for each environment, that simply pass if they're in the wrong environment? Do I write some complex regular expression that can separate out what kind of result it got and use more if/else logic to verify it?

Any suggestions would help!


回答1:


Can you hide the static method behind an abstract interface?

interface ISupportIPv6
{
  bool supported { get; }
}

//class for unit testing
class TestSupportsIPv6 : ISupportIPv6
{
  public bool supported { get { return true; } }
}

//another class for more unit testing
class TestDoesNotSupportIPv6 : ISupportIPv6
{
  public bool supported { get { return false; } }
}

//class for real life (not unit testing)
class OsSupportsIPv6 : ISupportIPv6
{
  public bool supported { get {
    return System.Net.Sockets.Socket.OSSupportsIPv6; } }
}



回答2:


You could fake the call by introducing an intermediary. For instance:

public static class NetworkUtils
{
    private static bool? forcedIPv6Support;

    /// <summary>
    /// Use this instead of Socket.OSSupportsIPv6 to
    /// allow for testing on different operating systems.
    /// </summary>
    public static SupportsIPv6
    {
        get { return forcedIPv6Support ?? Socket.OSSupportsIPv6; }
    }

    /// <summary>Only use in testing!</summary>
    public static void ForceIPv6Support(bool? value)
    {
        forcedIPv6Support = value;
    }
}

It's not entirely pleasant, but it lets you do what you need to.

ChrisW's answer is similar, but using an interface. Normally I'd be a fan of the interface solution, but it feels like it's making the production code and configuration more complicated. My solution is certainly less "pure" but possibly more pragmatic. I'll let you judge :)




回答3:


You could look at wrapping all of the environmental settings that could change in a separate class that implements an interface. This could then be passed to the static method you have written which would then call back against the interface when it needs the various settings.

This would allow you to mock the environmental settings to standardize when testing to prevent the issue you are seeing. It would also let you vary the settings to test different conditions.

e.g.

public interface IEnvironment
{
    public bool SupportsIPV6 { get; }
}

And then your static method becomes.

public static void DoSomething(IEnvironment environment)
{
    if(environment.SupportsIPV6)
    {
        ...
    }
}

Then provide an implementation of IEnvironment that calls against the static System.Net method to get the actual setting for use, but a mock implementation for testing with a known value.

If you don't want to pass the interface into the static method you could possibly look at implementing an object using the singleton pattern that then calls into this interface. The static method you have could then use the singleton to access whatever the current interface is although this method is a little messy so I'd probably go with the former.

E.g.

public Environment : IEnvironment
{
  private static IEnvironment current = new Environment();
  public static IEnvironment Current
  {
    get { return current; }
    set { current = value; }
  }

  public bool SupportsIPV6
  {
    return System.Net.....
  }
}

Then call into it from your static method using.

public static void DoSomething(IEnvironemnt environment)
{
    if(Environment.Current.SupportsIPV6)
    {
        ...
    }
}

And you could change it in your tests via

Environment.Current = new MockedEnvironment();

Although you'd need to watch out if you are doing any multithreading with a singleton like that.




回答4:


I don't understand why you can't mock this. Your method should take a bool SupportsIPv6 parameter, and in the real code, you pass it System.Net.Sockets.Socket.OSSupportsIPv6. Then you test your method with your parameter being both true and false, and make sure it spits out the right formats for both true and false cases. Voila, done.




回答5:


It does happen sometimes that it's too hard to make a useful unit test. I'm not confident this is one of those cases, but it's worth remembering.

That said, now, define what "valid" means? You can certainly test of it's well-formed: [0-9]{1,3} is a start on a regex for that.

You can test that it's reachable with an ICMP echo packet (or a ping(8)).

If you can define what "valid" means to you, that should defione a unit test.



来源:https://stackoverflow.com/questions/865559/how-do-i-unit-test-for-machine-specific-behaviour

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!