簡體   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