Модульное тестирование внедрения зависимостей - PullRequest
2 голосов
/ 10 июня 2019

Я использую Autofac для IoC

Вот мой класс инициатора контейнера, который отвечает за регистрацию зависимостей.

 public class ContainerInit
 {
      public static IContainer BuildContainer()
      {
            var conFac = new ContainerFactory();
            var builder = new ContainerBuilder();
            builder.Register(conFac).As<IContainerFactory>().SingleInstance();
            builder.Register(c=> new MainClass(conFac)).As<IMainClass>().SingleInstance();
            builder.Register(c=> new Database(conFac)).As<IDatabase>().SingleInstance();
             var logger = LoggUtil.CreateLogger();
            builder.Register(logger).As<ILogger>().SingleInstance();

            var container = builder.Build();
            ContainerFactory.SetContainer(container);
            return container;
      }
 }

Проблема с этим подходом заключается в том, что мне нужно передать IContainerFactory конструктору каждого класса, который я использую в своем приложении, следующим образом

  public class MainClass: IMainClass
  {       
      private readonly ILogger _logger;
      private readonly IDatabase _db;
      public MainClass(IContainerFactory containerFactory)
      {
              _logger = containerFactory.GetInstance<ILogger>();  
              _db =  containerFactory.GetInstance<IDatabase>(); //example       
      }
      public AddDetails(Data data)
      {
        //do some business operations 
        _db.Add(data);
        _logger.Information("added");
      }
  }

Так что эти классы сложно провести модульное тестирование.

Как найти хорошее решение?

Ответы [ 3 ]

2 голосов
/ 10 июня 2019

Лучшим подходом было бы передать зависимости, которые вам нужны в вашем классе, в ваш конструктор:

public class MainClass : IMainClass
{       
    private readonly ILogger _logger;
    private readonly IDatabase _db;

    public MainClass(ILogger logger, IDatabase db)
    {
        _logger = logger;  
        _db = db;
    }

    public void AddDetails(Data data)
    {
        //do some business operations 
        _db.Add(data);
        _logger.Information("added");
    }
}

Тогда вы можете использовать фальшивый фреймворк, такой как Moq , чтобы смоделировать ваш классзависимости и выполнить проверки на предмет того, были ли вызваны зависимости:

[TestClass]
public class UnitTest1
{
    private Mock<ILogger> _mockLogger = new Mock<ILogger>();
    private Mock<IDatabase> _mockDb = new Mock<IDatabase>();

    [TestMethod]
    public void TestMethod1()
    {
        // arrange
        var mainClass = new MainClass(_mockLogger.Object, _mockDb.Object);
        var data = new Data();

        // act
        mainClass.AddDetails(data);

        // assert    
        _mockDb
            .Verify(v => v.Add(data), Times.Once);
    }
}

Я бы не проверял ваше сообщение журнала, поскольку это могло измениться и сделать тест хрупким.Проверяйте только функциональность, которая необходима для выполнения того, для чего предназначен метод.

2 голосов
/ 10 июня 2019

Ваш текущий Anti-Pattern Service Locator - это то, что делает ваш код трудным для тестирования в отдельности, а также вводит класс в заблуждение относительно того, от чего он на самом деле зависит.

MainClass следует реорганизовать, чтобы следовать Принцип явных зависимостей

public class MainClass : IMainClass  
    private readonly ILogger logger;
    private readonly IDatabase db;

    public MainClass(ILogger logger, IDatabase db) {
        this.logger = logger;  
        this.db = db;
    }

    public void AddDetails(Data data) {
        //do some business operations 
        db.Add(data);
        logger.Information("added");
    }
}

Аналогичный шаблон следует использовать и для любого другого вашего класса, который зависит от фабрики контейнеров, например Database.

Однако вам также необходимо соответствующим образом изменить рефакторинг регистрации контейнера

public class ContainerInit {
    public static IContainer BuildContainer() {
        var builder = new ContainerBuilder();
        builder.RegisterType<MainClass>().As<IMainClass>().SingleInstance();
        builder.RegisterType<Database>().As<IDatabase>().SingleInstance();
        var logger = LoggUtil.CreateLogger();
        builder.Register(logger).As<ILogger>().SingleInstance();

        var container = builder.Build();
        return container;
    }
}

Тестирование MainClass потребовало бы, чтобы вы высмеивали только необходимые зависимости тестируемого класса.

[TestClass]
public class MainClassTests {    
    [TestMethod]
    public void Should_AddDetails_To_Database() {
        // Arrange
        var mockDb = new Mock<IDatabase>();
        var data = new Data();
        var mainClass = new MainClass(Mock.Of<ILogger>(), mockDb.Object);

        // Act
        mainClass.AddDetails(data);

        // Assert    
        mockDb.Verify(_ => _.Add(data), Times.Once);
    }
}
0 голосов
/ 10 июня 2019

Здесь я хотел бы поделиться решением, которое я использую в своем проекте

Чтобы выполнить модульное тестирование определенной функции, я использую структуру ниже

[TestClass]
public class TestSomeFunction
{
    public IComponentContext ComponentContext { get; set; }       

    [TestInitialize]
    public void Initialize()
    {
       //Registering all dependencies required for unit testing
       this.ComponentContext = builder.Build(); //You have not build your container in your question
    }

    [TestMethod]
    public void Testfunction()
    {
       //Resolve perticular dependency
       var _logger = containerFactory.Resolve<ILogger>();   
       //Test my function
       //use _logger 
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...