【问题标题】:Unit Testing Factory/Service Locator - Static class单元测试工厂/服务定位器 - 静态类
【发布时间】:2019-06-29 21:42:00
【问题描述】:

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

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 类)可以正确测试。

【问题讨论】:

    标签: c# unit-testing moq factory-pattern service-locator


    【解决方案1】:

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

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

    名称是 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;
       }
    }
    

    现在我们可以起订量 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;
       }
    }
    

    我们可以起订量这种类型的 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}'");
      }
    }
    

    【讨论】:

    • 我真的很喜欢你的解释。这正是我所需要的它证实了我的怀疑,即对这个静态类 GetRedCar() 的依赖取决于使 GetRedCar() 方法不可测试。我有几篇关于起订量和单元测试的文章,以及与工厂/服务定位器相关的文章,所以很多这对我来说都是新鲜的,有点了解,但不能真正把零碎放在一起。我想我真正的问题是如何制作我的 CarFactory 以便其他使用它的类的方法可以使用 Moq 进行测试?
    • @ErikPhillips 非常非常感谢!
    猜你喜欢
    • 2011-08-03
    • 1970-01-01
    • 2016-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多