Как мне выполнить модульный тест для конкретного поведения машины?
Я тестирую статический метод, который создает строку URL после проверки прокси-серверов и имен хостов, а также всех видов вещей. Этот метод внутренне опирается на статический флаг System.Net.Sockets.Socket.OSSupportsIPv6
, Поскольку это статично, я не могу издеваться над этой зависимостью.
РЕДАКТИРОВАТЬ: (Это сильно упрощается... Я не могу изменить структуру метода с флагом, чтобы принять истину / ложь).
На машинах разработки XP рассматриваемый метод возвращает предсказуемый строковый результат (в данном случае http://hostname/....). Наш сервер сборки, который поддерживает IPv6, возвращает совершенно другой результат (он дает мне что-то вроде http://192.168.0.1:80/...). Пожалуйста, не спрашивайте почему - дело в том, что есть два разных типа вывода, которые зависят от зависимости операционной системы.
Тест должен проверить правильность возвращенного имени хоста или IP-адреса. Выходы легко проверить. Проблема в том, что я могу получить только один из возможных выходных данных, в зависимости от того, на какой машине запущен тест.
Как лучше всего писать тест в этом случае? Должен ли я добавить в свой тест оператор if, который проверяет флаг, а затем ищет два разных результата? Это кажется мне отрывочным, потому что
тест ведет себя по-разному в зависимости от среды, в которой он выполняется
Я в основном связываю тест с методом, потому что вам нужно знать внутреннюю часть метода, чтобы настроить два случая.
Должен ли я установить два теста, по одному для каждой среды, которые просто пройдут, если они находятся в неправильной среде? Пишу ли я какое-то сложное регулярное выражение, которое может отделить, какой результат он получил, и использовать больше логики if / else для его проверки?
Любые предложения помогут!
5 ответов
Можете ли вы скрыть статический метод за абстрактным интерфейсом?
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; } }
}
Вы можете подделать звонок, введя посредника. Например:
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;
}
}
Это не совсем приятно, но позволяет вам делать то, что вам нужно.
Ответ ChrisW аналогичен, но с использованием интерфейса. Обычно я был бы поклонником интерфейсного решения, но кажется, что это усложняет производственный код и конфигурацию. Мое решение, конечно, менее "чисто", но, возможно, более прагматично. Я позволю тебе судить:)
Вы можете посмотреть на упаковку всех параметров среды, которые могут измениться, в отдельный класс, реализующий интерфейс. Это можно было бы затем передать статическому методу, который вы написали, который затем вызывал бы интерфейс обратно, когда ему нужны различные настройки.
Это позволит вам макетировать параметры среды для стандартизации при тестировании, чтобы предотвратить возникшую проблему. Это также позволит вам изменить настройки для тестирования различных условий.
например
public interface IEnvironment
{
public bool SupportsIPV6 { get; }
}
И тогда ваш статический метод становится.
public static void DoSomething(IEnvironment environment)
{
if(environment.SupportsIPV6)
{
...
}
}
Затем предоставьте реализацию IEnvironment, которая вызывает статический метод System.Net, чтобы получить фактические настройки для использования, но фиктивную реализацию для тестирования с известным значением.
Если вы не хотите передавать интерфейс в статический метод, вы можете посмотреть на реализацию объекта с использованием одноэлементного шаблона, который затем вызывает этот интерфейс. Статический метод, который у вас есть, может затем использовать синглтон для доступа к любому текущему интерфейсу, хотя этот метод немного запутанный, поэтому я бы, вероятно, выбрал первый.
Например
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.....
}
}
Затем вызовите его из статического метода, используя.
public static void DoSomething(IEnvironemnt environment)
{
if(Environment.Current.SupportsIPV6)
{
...
}
}
И вы можете изменить это в своих тестах через
Environment.Current = new MockedEnvironment();
Хотя вам нужно остерегаться, если вы делаете какую-либо многопоточность с таким синглтоном.
Я не понимаю, почему ты не смеешься над этим. Ваш метод должен занять bool SupportsIPv6
параметр, и в реальном коде, вы передаете его System.Net.Sockets.Socket.OSSupportsIPv6
, Затем вы тестируете свой метод с вашим параметром, равным true и false, и убедитесь, что он выдает правильные форматы как для true, так и для false случаев. Вуаля, сделано.
Иногда бывает так, что слишком сложно провести полезный юнит-тест. Я не уверен, что это один из тех случаев, но стоит помнить.
Тем не менее, теперь определите, что означает "действительный"? Вы, конечно, можете проверить его правильность: [0-9]{1,3} - это начало регулярного выражения для этого.
Вы можете проверить, что это достижимо с помощью эхо-пакета ICMP (или ping(8)).
Если вы можете определить, что значит "действительный" для вас, это должно определить юнит-тест.