简体   繁体   English

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

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

Recently, I saw this code. 最近,我看到了这段代码。 Since I am trying to learn few things at once, I am having issues with this code but dont know how to resolve. 由于我尝试一次学习很少的东西,因此这段代码有问题,但是不知道如何解决。 I would like to be able to unit test this code 我希望能够对该代码进行单元测试

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

I have few problems with this code. 我有一些与此代码的问题。

  1. Name is CarFactory but this does not look like a Factory Pattern to me. 名称是CarFactory,但对我来说,这看起来不像是工厂模式。 It looks more like Locator Pattern 它看起来更像是定位器模式
  2. The class is static - and I heard static classes are bad for unit testing in frameworks like Moq and that they also hide dependencies. 该类是静态的-我听说静态类不利于在Moq等框架中进行单元测试,并且它们还隐藏了依赖项。 Say a method in another regular class uses this, for unit test, there is no way to know that that method depend on this static class 假设另一个常规类中的方法使用此方法进行单元测试,则无法知道该方法依赖于此静态类

I would like to ensure this class is called properly and I think based on my reading that this is Locator Pattern. 我想确保正确调用该类,并且根据我的阅读,我认为这是定位器模式。

I would also like to unit test this class and need help to make it unit testable using Moq. 我还想对此类进行单元测试,并且需要帮助以使其可以使用Moq进行单元测试。

Thanks to @ErikPhillips explanation below, I now understand that other classes using this class will be not testable. 感谢下面的@ErikPhillips解释,我现在知道使用该类的其他类将不可测试。 So, if I have a class like below: 所以,如果我有一个像下面这样的课程:

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

, GetRedCar() method will be difficult to test because it uses CarFactory static class and for a unit test or an outside client, there is nothing in GetRedCar() method API that suggest it is depending on this static class. ,GetRedCar()方法将很难测试,因为它使用CarFactory静态类,并且对于单元测试或外部客户端,GetRedCar()方法API中没有任何内容表明它依赖于此静态类。

I would like to refactor CarFactory class so other classes using it like in example above CarConsumer class can be tested properly. 我想重构CarFactory类,以便像上面的CarConsumer类中的示例那样使用它的其他类可以正确测试。

I would like to be able to unit test this code 我希望能够对该代码进行单元测试

What specific problems prevent you from unit testing this class? 哪些特定的问题阻止您对此类进行单元测试? It has two methods, seems pretty straight forward to write a unit test against. 它有两种方法,编写单元测试似乎很简单。

Name is CarFactory but this does not look like a Factory Pattern to me 名称是CarFactory,但对我来说,这看起来不像是工厂模式

I believe a Factory Pattern is 我相信工厂模式

The factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created 工厂方法模式是一种创建模式,该模式使用工厂方法来处理创建对象的问题,而不必指定将要创建的对象的确切类

I pass in the name of the car (so I haven't specified the type) and it creates the class for me. 我输入了汽车的名称(因此我没有指定类型),它为我创建了类。 That's pretty much it. 就是这样。 Is it a good example of one? 这是一个很好的例子吗? Not in my opinion, but my opinion of how well it's done doesn't change what it is. 我不认为,但是我认为它做得如何并不会改变它的本质。

This doesn't mean it's not Service Locator, but it definitely is a Factory Method. 这并不意味着它不是服务定位器,但绝对是工厂方法。 (Honestly it doesn't look like a service locator because it only delivers a single service) (老实说,它看起来不像服务定位器,因为它只提供一项服务)

unit testing in frameworks like Moq Moq等框架中的单元测试

Moq is not a Unit-Testing framework. Moq不是一个单元测试框架。 Moq is a Mocking Framework . Moq是一个模拟框架 Static classes are not easy to Mock . 静态类不容易模拟 If you can Mock it, you can unit-test with methods that require a mocked class. 如果可以模拟它,则可以使用需要模拟类的方法进行单元测试。

static classes .. that they also hide dependencies. 静态类..它们也隐藏依赖项。

Any poorly designed anything can do anything. 设计不好的任何东西都可以做任何事情。 Static Classes aren't by definition designed to hide anything. 静态类不是根据定义来隐藏任何内容的。

I would say in this instance, that this Static Class, prevents you from being able to Mock it easily to unit test other methods that rely on the Static Classes methods. 在这种情况下,我要说的是,此静态类使您无法轻松模拟它,以对依赖于静态类方法的其他方法进行单元测试。

I would also like to unit test this class and need help to make it unit testable using Moq. 我还想对此类进行单元测试,并且需要帮助以使其可以使用Moq进行单元测试。

Again, there is nothing preventing you from unit-testing this class. 同样,没有什么可以阻止您对该类进行单元测试。

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
}

The problem you may run into is 您可能遇到的问题是

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

Testing this method means that you are not in full control of the method because there is external code GetRedCar() relies on. 测试此方法意味着您无法完全控制该方法,因为存在依赖于GetRedCar()外部代码。 You cannot write a pure unit test here. 您不能在此处编写纯单元测试。

This is why you'd have to convert CarFactory to an instance class . 这就是为什么您必须将CarFactory转换为实例类的原因 And then make sure it's has the correct lifetime for whatever DI framework you're using. 然后确保它对于所使用的任何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;
   }
}

Now we can Moq ICarfactory and write pure unit test against GetRedCar() . 现在我们可以Moq ICarfactory并针对GetRedCar()编写纯单元测试。

The following is not recommended. 不建议以下内容。

If for whatever reason you are stuck with this type of Factory but you still want to write pure unit tests, you can do something like: 如果出于某种原因而对这种类型的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;
   }
}

We can Moq this type of Func, but it's really just a crutch for the real problem. 我们可以Moq这种类型的Func,但这实际上只是解决实际问题的关键。

I guess the real question I have is how to make my CarFactory so that methods from other classes using it can be tested using Moq? 我想我真正的问题是如何制作我的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