Мой домен состоит из множества простых неизменяемых классов, таких как:
public class Person
{
public string FullName { get; }
public string NameAtBirth { get; }
public string TaxId { get; }
public PhoneNumber PhoneNumber { get; }
public Address Address { get; }
public Person(
string fullName,
string nameAtBirth,
string taxId,
PhoneNumber phoneNumber,
Address address)
{
if (fullName == null)
throw new ArgumentNullException(nameof(fullName));
if (nameAtBirth == null)
throw new ArgumentNullException(nameof(nameAtBirth));
if (taxId == null)
throw new ArgumentNullException(nameof(taxId));
if (phoneNumber == null)
throw new ArgumentNullException(nameof(phoneNumber));
if (address == null)
throw new ArgumentNullException(nameof(address));
FullName = fullName;
NameAtBirth = nameAtBirth;
TaxId = taxId;
PhoneNumber = phoneNumber;
Address = address;
}
}
Написание нулевых проверок и инициализация свойств уже становятся очень утомительными, но в настоящее время я пишу модульные тесты для каждого из этих классов, чтобы убедиться, что проверка аргументов работает правильно и что все свойства инициализированы. Это похоже на чрезвычайно скучную занятую работу с несоизмеримой выгодой.
Реальным решением было бы C # изначально поддерживать неизменяемость и необнуляемые ссылочные типы. Но что я могу сделать, чтобы улучшить ситуацию? Стоит ли писать все эти тесты? Было бы хорошей идеей написать генератор кода для таких классов, чтобы избежать написания тестов для каждого из них?
Вот что я сейчас основал на ответах.
Я мог бы упростить нулевые проверки и инициализацию свойств, чтобы они выглядели так:
FullName = fullName.ThrowIfNull(nameof(fullName));
NameAtBirth = nameAtBirth.ThrowIfNull(nameof(nameAtBirth));
TaxId = taxId.ThrowIfNull(nameof(taxId));
PhoneNumber = phoneNumber.ThrowIfNull(nameof(phoneNumber));
Address = address.ThrowIfNull(nameof(address));
Используя следующую реализацию Роберта Харви :
public static class ArgumentValidationExtensions
{
public static T ThrowIfNull<T>(this T o, string paramName) where T : class
{
if (o == null)
throw new ArgumentNullException(paramName);
return o;
}
}
Тестирование нулевых проверок легко с помощью GuardClauseAssertion
from AutoFixture.Idioms
(спасибо за предложение, Эсбен Сков Педерсен ):
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var assertion = new GuardClauseAssertion(fixture);
assertion.Verify(typeof(Address).GetConstructors());
Это может быть сжато еще дальше:
typeof(Address).ShouldNotAcceptNullConstructorArguments();
Используя этот метод расширения:
public static void ShouldNotAcceptNullConstructorArguments(this Type type)
{
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var assertion = new GuardClauseAssertion(fixture);
assertion.Verify(type.GetConstructors());
}
источник
Ответы:
Я создал шаблон t4 именно для таких случаев. Чтобы не писать много шаблонов для неизменных классов.
https://github.com/xaviergonz/T4Immutable T4Immutable - это шаблон T4 для приложений на C # .NET, который генерирует код для неизменяемых классов.
Говоря конкретно о ненулевых тестах, тогда, если вы используете это:
Конструктор будет таким:
Сказав это, если вы используете аннотации JetBrains для проверки нуля, вы также можете сделать это:
И конструктор будет таким:
Также есть еще несколько функций, чем этот.
источник
Вы можете получить небольшое улучшение с помощью простого рефакторинга, который может облегчить проблему написания всех этих заборов. Во-первых, вам нужен этот метод расширения:
Вы можете написать:
Возвращение исходного параметра в метод расширения создает свободный интерфейс, поэтому вы можете расширить эту концепцию с помощью других методов расширения, если хотите, и объединить их все вместе в своем назначении.
Другие методы более изящны по своей концепции, но постепенно становятся все более сложными по исполнению, такие как украшение параметра
[NotNull]
атрибутом и использование отражения таким образом .Тем не менее, вам могут не понадобиться все эти тесты, если ваш класс не является частью общедоступного API.
источник
null
аргумент в конструкторе, тест не будет пройден.throw
внешнюю часть конструктора; вы не передаете контроль в конструкторе где-то еще. Это просто полезный метод и способ бегать , если вы того пожелаете.В краткосрочной перспективе, вы не можете ничего сделать с утомительностью написания таких тестов. Тем не менее, есть некоторая помощь с выражениями throw, которые должны быть реализованы как часть следующего выпуска C # (v7), вероятно, в ближайшие несколько месяцев:
Вы можете поэкспериментировать с выражениями throw через веб-приложение Try Roslyn .
источник
Я удивлен, что никто не упомянул NullGuard . Он доступен через NuGet и автоматически встроит эти нулевые проверки в IL во время компиляции.
Так что ваш код конструктора будет просто
и NullGuard добавит эти нулевые проверки для вас, превратив его именно в то, что вы написали.
Тем не менее, обратите внимание, что NullGuard отключен , то есть он будет добавлять эти нулевые проверки к каждому методу и аргументу конструктора, получателю и установщику свойства и даже проверять возвращаемые значения метода, если вы явно не разрешите нулевое значение с
[AllowNull]
атрибутом.источник
[NotNull]
атрибута, а не для того, чтобы значение по умолчанию было пустым.Нет, наверное нет. Какова вероятность, что вы собираетесь все испортить? Какова вероятность того, что какая-то семантика изменится из-под вас? Как это повлияет, если кто-то все испортит?
Если вы тратите кучу времени на тестирование чего-то, что будет редко ломаться, и это тривиальное исправление, если оно есть ... возможно, оно того не стоит.
Может быть? Подобные вещи можно легко сделать с помощью рефлексии. Стоит подумать о создании кода для реального кода, поэтому у вас нет N классов, которые могут иметь человеческую ошибку. Предотвращение ошибок> обнаружение ошибок.
источник
if...throw
они будут иметьThrowHelper.ArgumentNull(myArg, "myArg")
, таким образом, логика все еще там, и вы не зависите от покрытия кода. ГдеArgumentNull
метод будет делатьif...throw
.Стоит ли писать все эти тесты?
Нет.
Потому что я почти уверен, что вы тестировали эти свойства с помощью других тестов логики, в которых используются эти классы.
Например, у вас могут быть тесты для класса Factory, которые имеют утверждение на основе этих свойств (например, экземпляр, созданный с правильно назначенным
Name
свойством).Если эти классы доступны общедоступному API, который используется каким-либо третьим пользователем / конечным пользователем (@EJoshua, спасибо, что заметили), тогда тесты на ожидаемое
ArgumentNullException
могут быть полезны.В ожидании C # 7 вы можете использовать метод расширения
Для тестирования вы можете использовать один метод параметризованного теста, который использует отражение для создания
null
ссылки для каждого параметра и подтверждения ожидаемого исключения.источник
Вы всегда можете написать метод, подобный следующему:
Как минимум, это сэкономит вам время при наборе текста, если у вас есть несколько методов, требующих такой проверки. Конечно, это решение предполагает, что ни один из параметров вашего метода не может быть
null
, но вы можете изменить это, чтобы изменить это, если вы того пожелаете.Вы также можете расширить это, чтобы выполнить другую проверку типа. Например, если у вас есть правило, что строки не могут быть чисто пробелами или пустыми, вы можете добавить следующее условие:
Метод GetParameterInfo, на который я ссылаюсь, в основном делает отражение (чтобы мне не приходилось вводить одно и то же снова и снова, что было бы нарушением принципа DRY ):
источник
Я не предлагаю бросать исключения из конструктора. Проблема в том, что вам будет трудно проверить это, так как вам нужно передать действительные параметры, даже если они не имеют отношения к вашему тесту.
Например: если вы хотите проверить, вызывает ли третий параметр исключение, вы должны правильно передать первый и второй. Таким образом, ваш тест больше не изолирован. когда ограничения первого и второго параметра изменяются, этот тестовый случай не пройден, даже если он проверяет третий параметр.
Я предлагаю использовать API проверки Java и выполнить внешний процесс проверки. Я предлагаю задействовать четыре класса (занятия):
Объект предложения с проверкой Java аннотировал параметры с помощью метода validate, который возвращает набор ConstraintViolations. Одним из преимуществ является то, что вы можете обойти эти объекты без утверждения, чтобы быть действительным. Вы можете отложить проверку, пока она не станет необходимой, без попытки создания экземпляра объекта домена. Объект проверки может использоваться на разных уровнях, так как это POJO без специальных знаний уровня. Это может быть частью вашего публичного API.
Фабрика для объекта домена, который отвечает за создание допустимых объектов. Вы передаете объект предложения, и фабрика вызовет проверку и создаст объект домена, если все в порядке.
Сам объект домена, который должен быть в основном недоступен для реализации другими разработчиками. Для целей тестирования я предлагаю иметь этот класс в объеме пакета.
Публичный интерфейс домена, чтобы скрыть конкретный объект домена и затруднить злоупотребление.
Я не предлагаю проверять на нулевые значения. Как только вы попадаете в слой домена, вы избавляетесь от перехода на нуль или возврата ноля, если у вас нет цепочек объектов, которые имеют концы, или функции поиска для отдельных объектов.
Еще один момент: написание как можно меньшего количества кода не является метрикой качества кода. Подумайте о соревнованиях 32k игр. Этот код - самый компактный, но и самый грязный код, который вы можете иметь, потому что он заботится только о технических проблемах и не заботится о семантике. Но семантика - это то, что делает вещи всеобъемлющими.
источник