ASP.NET: EntityFaker helper for unit-testing или помощник для написания Unit-тестов

ru-RU | создано: 05.08.2014 | опубликовано: 05.08.2014 | обновлено: 31.12.2017 | просмотров за всё время: 4424

Писать тесты при написании программ (сайтов, библиотек, контролов) признак хорошего тона в программировании. А если учесть, что написание тестов требуют достаточно много времени, то помощник в этом деле не помешает.

Что это такое EntityFaker

Это библиотека создана для упрощения написания тестов для ASP.NET MVC, но я уверен, что вы можете использовать ее и в других типах проектов. Генератор призван создавать сущности и заполнять примитивные типы свойств (только открытые) простыми данными.

Инструменты и всё такое

Я буду использовать Visual Studio 2013 Ultimate, проект на платформе ASP.NET MVC 5. В отдельном проекте отведенном под тесты у меня для генерации заглушек используется RhinoMocks. Для написания тестов я использую на этот раз MSTests.

Как установить

Для того чтобы в вашем тестовом проекте появилась возможность использовать генератор, надо установить nuget-пакет:

PM> Install-Package EntityFaker 
Installing 'EntityFaker 1.0.1'.
Successfully installed 'EntityFaker 1.0.1'.
Adding 'EntityFaker 1.0.1' to Calabonga.Mvc.Lipix.
Successfully added 'EntityFaker 1.0.1' to Calabonga.Mvc.Lipix.

PM> 

Это пример установки в мой проект.

Пример использования

Допустим у меня есть некий класс, который мне нужно использовать в тестах.

public class Picture : IdentityBase {
    
  [Required]
  [StringLength(50)]
  [Display(ResourceType = typeof(Resources.Resources), Name = "Route")]
  public string Name { get; set; }

  [Display(ResourceType = typeof(Resources.Resources), Name = "DisplayName")]
  [StringLength(50)]
  public string DisplayName { get; set; }

  [Display(ResourceType = typeof(Resources.Resources), Name = "Description")]
  [DataType(DataType.MultilineText)]
  public string Description { get; set; }

  [Required]
  [Display(ResourceType = typeof(Resources.Resources), Name = "CreatedAt")]
  [DataType(DataType.DateTime)]
  public DateTime CreatedAt { get; set; }

  [Display(ResourceType = typeof(Resources.Resources), Name = "LibraryId")]
  public int LibraryId { get; set; }

  [ForeignKey("LibraryId")]
  public virtual Library Library { get; set; }

  [Display(ResourceType = typeof(Resources.Resources), Name = "DisplayNameEn")]
  [StringLength(50)]
  public string DisplayNameEn { get; set; }

  [Display(ResourceType = typeof(Resources.Resources), Name = "DescriptionEn")]
  [DataType(DataType.MultilineText)]
  public string DescriptionEn { get; set; }

  public string Src {
    get {
      var library = string.Empty;

      if (Library != null) {
        library = Library.Name;
      }

      return string.Format("/gallery/{0}/{1}", library, Name);
    }
  }
}

Также у меня есть некоторый слой логики, который управляется этими классами. Я покажу интерфейс:

public interface IImagesProcessor
{
  PagedList<Category> GetCategories(int pageIndex, int defaultPageSize);
  PagedList<Library> GetLibraries(int pageIndex, int defaultPageSize, Category category);
  PagedList<Picture> GetPictures(int pageIndex, int defaultPageSize, string library);
  Picture GetById(int id);
  void Delete(Picture item);
  void Update(Picture item);
}

Unit-тесты в примерах

Итак, для того чтобы было проще описать как использовать этот помощник, сразу приведу пример теста. Для начала метод инициализации теста:

[TestInitialize]
public void Setup() {
  var context = MockRepository.GenerateStub<IContext>();
  context.Categories = DataContextBuilder.GetCategoriesDbSet();
  context.Pictures = DataContextBuilder.GetPisturesDbSet(_picturesCount);
  var logNotify = MockRepository.GenerateStub<ILogNotifyService>();
  var fileService = MockRepository.GenerateStub<IFileService>();

  _processor = new ImagesProcessor(context, fileService, logNotify);
}

Немного прокомментирую приведенный листинг. Строка 3 – создается заглушка для IContext, в моем проекте этот интерфейс представляет DbContext. Вот этот интерфейс для ознакомления:

public interface IContext
{
  IDbSet<Category> Categories { get; set; }
  IDbSet<Library> Libraries { get; set; }
  IDbSet<Picture> Pictures { get; set; }
         // много удалено для краткости ...
}

Но не в этом “соль”. Обратите внимание на строки 4-5, где как раз в сгенерированную “заглушку” дополняются данными некоторые свойства, а именно Categories и Pictures. Вот как выглядит билдер:

public static class DataContextBuilder
{
  public static IDbSet<Category> GetCategoriesDbSet(int count = 5)
  {
    return DbSetter<Category>.GetObject(EntityFaker.CreateListOf<Category>(count));
  }
    
  public static IDbSet<Picture> GetPisturesDbSet(int count = 5)
  {
    return DbSetter<Picture>.GetObject(EntityFaker.CreateListOf<Picture>(count));
  }
}

Вот самая “соль”. В строке 5 и 10 используется EntityFaker для генерации коллекций сущностей, однако, можно генерировать и единичные сущности. Чтобы было совсем всё понятно, если я захочу создать один объект типа Category, я вызову генератор так:

152-10

Как вы видите, ничего хитрого нет, свойства заполняются значением названий свойств с подстановкой номера по порядку по индексу генерации. А если я вызову метод (как в строке 5 предыдущего листинга), то результатом будет:

152-20

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

Внимание: Применение немного изменилось. Теперь для того чтобы использовать генератор, требуется создать его экземпляр и уже у него вызывать методы.