代码之家  ›  专栏  ›  技术社区  ›  cd491415

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

  •  0
  • cd491415  · 技术社区  · 7 年前

    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. 这个类是静态的——我听说静态类不适合在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 回复  |  直到 7 年前
        1
  •  4
  •   Erik Philips Gabriel Costa    7 年前

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

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

    名字是CarFactory,但在我看来,这不像是一个工厂模式

    工厂模式

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

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

    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()

    这就是为什么你必须把汽车工厂改造成汽车工厂的原因 实例类

    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;
       }
    }
    

    我们可以禁止这种类型的函数,但它实际上只是解决实际问题的一根拐杖。

    我想我真正的问题是如何制作我的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}'");
      }
    }