Как установить автоинкрементные идентификаторы в модульном тесте - PullRequest
0 голосов
/ 16 мая 2018

У меня следующий сценарий:

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

Как мне установить идентификатор?

public WorkingTime(string name, short numberOfHours, short numberOfShortDays, int workingGroupId)
{
    Name = name;
    NumberOfHours = numberOfHours;
    NumberOfShortDays = numberOfShortDays;
    WorkingGroupId = workingGroupId;
    ActivatedWorkingTimes = new List<WorkingTimeActivation>();
}

private ICollection<WorkingTimeActivation> _activatedWorkingTimes;

public int Id { get; private set; }
public string Name { get; set; }
public short NumberOfHours { get; set; }
public short NumberOfShortDays { get; set; }
public int WorkingGroupId { get; set; }
public virtual WorkingGroup WorkingGroup { get; set; }
public virtual ICollection<WorkingTimeActivation> ActivatedWorkingTimes { get => _activatedWorkingTimes; set => _activatedWorkingTimes = value; }

Пример теста:

var workingGroup = new WorkingGroup("WG", 3, Week.Sunday, 2);
workingGroup.AssignedWorkingTimes.Add(new WorkingTime("Winter", 8, 1, 1));
workingGroup.AssignedWorkingTimes.Add(new WorkingTime("Summer", 6, 0, 1));

Теперь я хочу установить workingGroupId

Должен ли я вместо этого использовать публичный сеттер?

Ответы [ 3 ]

0 голосов
/ 22 мая 2018

Если Id должен оставаться закрытым для библиотеки классов, а также должен быть протестирован с помощью проекта тестирования, на мой взгляд, это следующий лучший вариант при использовании C #:

Измените private set; на internal set; или создайте внутренний метод для установки идентификатора.

public int Id { get; internal set; }

В вашей библиотеке классов, в которой есть класс, который нужно проверить, в обозревателе решений в разделе Свойства в файле AssemblyInfo.cs добавьте следующую строку:

[assembly: InternalsVisibleTo("MyTestingAssembly")]

Это сделает внутренние элементы вашей библиотеки классов, которые необходимо протестировать, видимыми для проекта тестирования только . Это не решит проблему инкапсуляции Id из другого кода внутри библиотеки классов, но не позволит внутреннему беспокойству о Id быть видимым для других библиотек DLL, которые не тестируют библиотеку классов.

0 голосов
/ 24 мая 2018

Кажется, вы хотите сохранить свои сущности в доменном слое "чистыми", создав частный установщик. Это хорошая вещь. Один из способов обработки тестов или сопоставления DTO с сущностями заключается в создании метода расширения, который дает доступ к частному установщику.

Этот метод использует библиотеку FastMember Марка Гравелла:

using FastMember;
...

public static class DomainExtensions
{
    private static readonly IDictionary<Type, TypeAccessor> _accessors = new Dictionary<Type, TypeAccessor>();

    public static T With<T, TFieldOrProperty>(this T instance, Expression<Func<T, TFieldOrProperty>> fieldOrProperty, TFieldOrProperty value)
        where T : class
    {
        if (instance == null)
            return null;

        if (!(fieldOrProperty.Body is MemberExpression member))
            throw new ArgumentException($"Expression '{fieldOrProperty}' is not for a property or field.");

        try
        {
            if (!_accessors.TryGetValue(typeof(T), out var ta))
                lock (_accessors)
                    ta = _accessors[typeof(T)] = TypeAccessor.Create(typeof(T), true);

            if (ta[instance, member.Member.Name] != null)
            {
                ta[instance, member.Member.Name] = value;
                return instance;
            }
        }
        catch (Exception e)
        {
            Debug.WriteLine(e.Message);
        }

        // fallback to reflection
        var fi = member.Member as FieldInfo;
        fi?.SetValue(instance, value);
        var pi = member.Member as PropertyInfo;
        pi?.SetValue(instance, value);
        return instance;
    }

Тогда вы используете это так:

workingGroup.With(wg => wg.WorkingGroupId, 2);

Таким образом, ваша сущность остается чистой. Теперь вы можете сказать: «Хорошо, теперь я могу обойти сущность, доступную только для чтения». Это правда, но разработчик должен был бы явно и сознательно нарушить этот доступ, вызвав With().

Если тестовые случаи - единственное место, где вам нужен этот доступ, поместите метод With() в сборку вместе с вашими тестами. Это гарантирует, что ваш производственный код не может установить идентификатор.

0 голосов
/ 22 мая 2018

Должен ли я вместо этого использовать публичный сеттер?

Вероятно, нет, нет.

Есть две альтернативы для рассмотрения.

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

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

Базовый шаблон изолирует стратегию и предоставляет возможности, необходимые для контроля над тем, какая стратегия применяется. В тесте мы реализуем стратегический контракт, используя test double , который позволяет test поддерживать детерминистический контроль над тем, что в противном случае было бы произвольным побочным эффектом.

...