Подтвердить что ты не робот

Издевательствование с использованием Moq в С#

У меня есть следующий код:

public interface IProductDataAccess
{
    bool CreateProduct(Product newProduct);
}

Класс ProductDataAccess реализует этот интерфейс.

public class ProductBusiness
{
    public bool CreateProduct(Product newProduct)
    {
        IProductDataAccess pda = new ProductDataAccess();
        bool result = pda.CreateProduct(newProduct);
        return result;
    }
}

В этом случае, как создать метод unit test для CreateProduct, высмеивая интерфейс IProductDataAccess? Я думал о наличии открытого экземпляра IProductDataAccess внутри ProductBusiness и инициализировать его с помощью объекта Mock<IProductDataAccess>, но не является хорошей практикой раскрывать доступ к данным на уровне пользовательского интерфейса. Может ли кто-нибудь мне помочь?

4b9b3361

Ответ 1

Классический пример, который демонстрирует, что если вы не можете выполнить модульное тестирование определенного компонента, РЕФАКТОР это!

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

В вашем примере класс ProductBusiness тесно связан с классом ProductDataAccess. Вы можете отделить его, используя (как большинство ответов предлагают) внедрение зависимостей. Таким образом, вы окажетесь в зависимости от абстракции IProductDataAccess, а не от конкретной ее реализации.

Еще один момент, на который следует обратить внимание: когда вы пишете тесты/спецификации для бизнес-уровня, вы обычно хотите проверить "поведение", а не "состояние". Итак, хотя у вас могут быть утверждения, которые проверяют, было ли возвращено "true", ваши тесты должны действительно проверять, были ли ожидаемые вызовы доступа к данным, которые были установлены с помощью MOQ, действительно выполнены с использованием .Verify API MOQ.

Попробуйте добавить поведенческие тесты, в которых вы ожидаете, что на уровне доступа к данным будет сгенерировано исключение (используя API.Throws), и проверьте, нужна ли вам какая-либо специальная обработка на бизнес-уровне.

Как предполагает Кевин, будет работать следующая реализация ProductBusiness:

public class ProductBusiness
{
  private readonly IProductDataAccess  _productDataAccess;

  public ProductBusiness(IProductDataAccess productDataAccess)
  {
      _productDataAccess = productDataAccess;
  }

  public bool CreateProduct(Product newProduct)
  {
    bool result=_productDataAccess.CreateProduct(newProduct);
    return result;
  }
}

и используйте любую среду тестирования xunit, чтобы написать тест как:

 var mockDataAccess = new Mock<IProductDataAccess>();
 mockDataAccess.Setup(m => m.CreateProduct(It.IsAny<Product>())).Returns(true);
 var productBusiness = new ProductBusiness(mockDataAccess.Object);
 //behavior to be tested

Ответ 2

Вы должны ввести интерфейс IProductDataAccess в качестве зависимости:

public class ProductBusiness
{
    private IProductDataAccess _productDataAccess;    

    public ProductBusiness(IProductDataAccess productDataAccess)
    {
        _productDataAccess = productDataAccess;
    }

    public bool CreateProduct(Product newProduct)
    {
        bool result = _productDataAccess.CreateProduct(newProduct);
        return result;
    }
}

Затем вы можете заменить его макетом в своих тестах:

var productDataAccess = new Mock<IProductDataAccess>();
var productBusiness = new ProductBusiness(productDataAccess.Object);

Ответ 3

В соответствии с тем, как вы разработали свой класс ProductBusiness, вы не можете изменить реализацию IProductDataAccess с помощью макета. Рекомендуемый шаблон для этого , где вы взять зависимости типа через конструктор. Таким образом, ваш класс будет выглядеть следующим образом:

public class ProductBusiness
{
  private readonly IProductDataAccess  _productDataAccess;

  public ProductBusiness(IProductDataAccess productDataAccess)
  {
      _productDataAccess = productDataAccess;
  }

  public bool CreateProduct(Product newProduct)
  {
      bool result = _productDataAccess.CreateProduct(newProduct);
      return result;
  }
}

Теперь вы можете протестировать свой класс, используя фальшивую фреймворк, например . Например:

var mockDataAccess = new Mock<IProductDataAccess>();
mockDataAccess
    .Setup(m => m.CreateProduct(It.IsAny<Product>()))
    .Returns(true);

var productBusiness = new ProductBusiness(mockDataAccess.Object);
// ... test behaviour here

Теперь вы можете изменить способ выполнения команды mock на шаге настройки и убедиться, что ваш метод CreateProduct работает правильно.

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

Ответ 4

Невозможно создать конкретный ProductDataAccess внутри вашего метода CreateProduct. Вместо этого IProductDataAccess должна быть зависимой от инъекции. Это можно сделать одним из двух способов:

Ввод свойств:

public class ProductBusiness
{
    IProductDataAccess Pda {get; set;}
}

var productBusiness = new ProductBusiness();
productBusiness.Pda = new ProductDataAccess();
productBusiness.Pda = new MockProductDataAccess();

Или инъекция конструктора:

public class ProductBusiness
{
    private readonly IProductDataAccess _pda;

    public ProductBusiness(IProductDataAccess pda)
    {

        _pda = pda;
    }
}

var productBusiness = new ProductBusiness(new ProductDataAccess());
var productBusiness = new ProductBusiness(new MockProductDataAccess());

Встраивание конструктора обычно является рекомендуемым подходом. Включение свойств используется для необязательных зависимостей (например, создайте конкретный NullLogger по умолчанию в конструкторе и используйте это свойство для необязательного ввода рабочего журнала).