繁体   English   中英

单元测试工厂/服务定位器-静态类

[英]Unit Testing Factory/Service Locator - Static class

最近,我看到了这段代码。 由于我尝试一次学习很少的东西,因此这段代码有问题,但是不知道如何解决。 我希望能够对该代码进行单元测试

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,但对我来说,这看起来不像是工厂模式。 它看起来更像是定位器模式
  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类中的示例那样使用它的其他类可以正确测试。

我希望能够对该代码进行单元测试

哪些特定的问题阻止您对此类进行单元测试? 它有两种方法,编写单元测试似乎很简单。

名称是CarFactory,但对我来说,这看起来不像是工厂模式

我相信工厂模式

工厂方法模式是一种创建模式,该模式使用工厂方法来处理创建对象的问题,而不必指定将要创建的对象的确切类

我输入了汽车的名称(因此我没有指定类型),它为我创建了类。 就是这样。 这是一个很好的例子吗? 我不认为,但是我认为它做得如何并不会改变它的本质。

这并不意味着它不是服务定位器,但绝对是工厂方法。 (老实说,它看起来不像服务定位器,因为它只提供一项服务)

Moq等框架中的单元测试

Moq不是一个单元测试框架。 Moq是一个模拟框架 静态类不容易模拟 如果可以模拟它,则可以使用需要模拟类的方法进行单元测试。

静态类..它们也隐藏依赖项。

设计不好的任何东西都可以做任何事情。 静态类不是根据定义来隐藏任何内容的。

在这种情况下,我要说的是,此静态类使您无法轻松模拟它,以对依赖于静态类方法的其他方法进行单元测试。

我还想对此类进行单元测试,并且需要帮助以使其可以使用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()编写纯单元测试。

不建议以下内容。

如果出于某种原因而对这种类型的Factory感到困惑,但仍想编写纯单元测试,则可以执行以下操作:

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}'");
  }
}

暂无
暂无

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

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