Фабрика модульных испытаний / Сервисный локатор - Статический класс - PullRequest
0 голосов
/ 06 февраля 2019

Недавно я видел этот код.Поскольку я пытаюсь изучить несколько вещей одновременно, у меня возникают проблемы с этим кодом, но я не знаю, как их решить.Я хотел бы иметь возможность модульного тестирования этого кода

public static class CarFactory
{
    private static readonly IDictionary<string, Type> CarsRegistry = new Dictionary<string, Type>();

    public static void Register<TCar>(string car) where TCar : CarBase, new()
    {
        if (!CarsRegistry.ContainsKey(car))
        {
            CarsRegistry.Add(car, typeof(TCar));
        }
    }

    public static ICar Create(string car)
    {
        if (CarsRegistry.ContainsKey(car))
        {
            CarBase newCar = (CarBase)Activator.CreateInstance(CarsRegistry[car]);
            return newCar;
        }

        throw new NotSupportedException($"Unknown '{car}'");
    }
}

У меня есть несколько проблем с этим кодом.

  1. Имя - CarFactory, но это не похоже на Factory Pattern длямне.Это больше похоже на шаблон локатора
  2. Класс является статическим - и я слышал, что статические классы плохи для модульного тестирования в таких средах, как Moq, и что они также скрывают зависимости.Скажем, метод в другом обычном классе использует это, для модульного теста нет способа узнать, что этот метод зависит от этого статического класса

Я хотел бы убедиться, что этот класс вызывается правильно, и я думаю,исходя из моего прочтения, что это шаблон локатора.

Я также хотел бы провести модульное тестирование этого класса и мне нужна помощь, чтобы сделать его тестируемым на модуле с использованием Moq.

Благодаря приведенному ниже объяснению @ErikPhillips я понимаю, что другие классы, использующие этот класс, не будут тестироваться.Итак, если у меня есть класс, подобный приведенному ниже:

public class CarConsumer
{
   public void ICar GetRedCar()
   {
     var result = CarFactory.Create("Tesla");
     result.Color = Color.Red;
     return result;
   }
}

, метод GetRedCar () будет сложно протестировать, поскольку он использует статический класс CarFactory и для модульного теста или внешнего клиента, в GetRedCar ничего нет() метод API, который предполагает, что он зависит от этого статического класса.

Я хотел бы реорганизовать класс CarFactory, чтобы другие классы, использующие его, как в примере выше, класс CarConsumer, могли быть протестированы должным образом.

1 Ответ

0 голосов
/ 06 февраля 2019

Я хотел бы иметь возможность модульного тестирования этого кода

Какие конкретные проблемы мешают вам провести модульное тестирование этого класса?У него есть два метода, кажется довольно простым для написания модульного теста.

Имя - это CarFactory, но для меня это не похоже на Factory Pattern

Я считаю, Фабричный шаблон - это

Фабричный шаблон метода - это шаблон создания, который использует фабричные методы для решения проблемы создания объектов без указания точного класса объекта, которыйбудет создан

Я передаю имя автомобиля (поэтому я не указал тип), и он создает класс для меня.Вот и все.Это хороший пример одного?Не по моему мнению, но мое мнение о том, насколько хорошо это сделано, не меняет того, чем оно является.

Это не означает, что это не локатор службы, но это определенно фабричный метод. (Честно говоря, это не похоже на локатор служб, потому что он предоставляет только одну услугу)

модульное тестирование в таких средах, как Moq

Moqне является структурой юнит-тестирования.Moq - это Mocking Framework .Статические классы не легко Макет .Если вы можете использовать Mock, вы можете выполнить модульное тестирование с помощью методов, для которых требуется проверяемый класс.

статические классы .. чтобы они также скрывали зависимости.

Любой плохо разработанныйвсе может сделать что угодно.Статические классы по определению не предназначены для того, чтобы что-либо скрывать.

В этом случае я бы сказал, что этот статический класс не позволяет вам легко смоделировать его для модульного тестирования других методов, которые полагаются на статические классы.методы.

Я также хотел бы провести модульное тестирование этого класса и мне нужна помощь, чтобы сделать его модульным для тестирования с использованием Moq.

Опять же, ничто не мешает вамтестирование этого класса.

public class CarFactoryTests
{  
  public class MoqCar : CarBase { }

  public void Register_WithValidParameters_DoesNotThrowException
  {
    // Act
    Assert.DoesNotThrow(() => CarFactory.Register<MoqCar>(
      nameof(Register_WithValidParameters_DoesNotThrowException)));
  }

  public void Create_WithValidCar_DoesNotThrowException
  {
    CarFactory.Register<MoqCar>(
      nameof(Create_WithValidParameters_DoesNotThrowException));

    Assert.DoesNotThrow(() => CarFactory.Create(
      nameof(Create_WithValidParameters_DoesNotThrowException));
  }

  // etc etc
}

Проблема, с которой вы можете столкнуться:

public class CarConsumer
{
   public void ICar GetRedCar()
   {
     var result = CarFactory.Create("Tesla");
     result.Color = Color.Red;
     return result;
   }
}

Тестирование этого метода означает, что вы не полностью контролируете метод, поскольку существует внешний код GetRedCar() опирается на.Вы не можете написать чистый модульный тест здесь.

Вот почему вам нужно преобразовать CarFactory в экземплярный класс .И затем убедитесь, что он имеет правильное время жизни для любой используемой вами структуры DI.

public class CarConsumer
{
   private ICarFactory _carFactory;
   public CarConsumer(ICarFactory carFactory)
   {
     _carFactory = carFactory;
   }

   public void ICar GetRedCar()
   {
     var result = _carFactory.Create("Tesla");
     result.Color = Color.Red;
     return result;
   }
}

Теперь мы можем Moq ICarfactory и написать чистый модульный тест по GetRedCar().

Следующее не рекомендуется.

Если по какой-либо причине вы застряли с этим типом Фабрики, но все еще хотите писать чистые модульные тесты, вы можете сделать что-то вроде:

public class CarConsumer
{
   private Func<string, ICar> _createCar;
   public CarConsumer(Func<string, ICar> createCar= CarFactory.Create)
   {
     _createCar = createCar;
   }

   public void ICar GetRedCar()
   {
     var result = _createCar("Tesla");
     result.Color = Color.Red;
     return result;
   }
}

Мы можем Moq этого типа Func, но на самом деле это простокостыль для реальной проблемы.

Наверное, реальный вопрос, который у меня возникает, состоит в том, как сделать мой CarFactory таким образом, чтобы методы из других классов, использующих его, можно было протестировать с помощью Moq?

public interface ICarFactory
{
  void Register<TCar>(string car) where TCar : CarBase, new();
  ICar Create(string car);
}

public class CarFactory : ICarFactory
{
  private readonly IDictionary<string, Type> CarsRegistry 
    = new Dictionary<string, Type>();

  public void Register<TCar>(string car) where TCar : CarBase, new()
  {
    if (!CarsRegistry.ContainsKey(car))
    {
      CarsRegistry.Add(car, typeof(TCar));
    }
  }

  public ICar Create(string car)
  {
    if (CarsRegistry.ContainsKey(car))
    {
      CarBase newCar = (CarBase)Activator.CreateInstance(CarsRegistry[car]);
      return newCar;
    }

    throw new NotSupportedException($"Unknown '{car}'");
  }
}
...