Я начал писать тестовые модули для моего текущего проекта. У меня действительно нет опыта с этим все же. Сначала я хочу полностью «получить его», поэтому в настоящее время я не использую ни свою платформу IoC, ни библиотеку-макет.
Мне было интересно, если что-то не так с предоставлением нулевых аргументов конструкторам объектов в модульных тестах. Позвольте мне привести пример кода:
public class CarRadio
{...}
public class Motor
{
public void SetSpeed(float speed){...}
}
public class Car
{
public Car(CarRadio carRadio, Motor motor){...}
}
public class SpeedLimit
{
public bool IsViolatedBy(Car car){...}
}
Еще один пример автомобильного кода (ТМ), сводится только к тем частям, которые важны для вопроса. Я сейчас написал тест примерно так:
public class SpeedLimitTest
{
public void TestSpeedLimit()
{
Motor motor = new Motor();
motor.SetSpeed(10f);
Car car = new Car(null, motor);
SpeedLimit speedLimit = new SpeedLimit();
Assert.IsTrue(speedLimit.IsViolatedBy(car));
}
}
Тест работает нормально. SpeedLimit
Нужно Car
с собой Motor
, чтобы делать свое дело. Это совсем не интересно CarRadio
, поэтому я предоставил для этого значение null.
Мне интересно, является ли объект, обеспечивающий правильную функциональность, не будучи полностью сконструированным, нарушением SRP или запахом кода. У меня есть это ноющее чувство, что оно есть, но speedLimit.IsViolatedBy(motor)
и не правильно - ограничение скорости нарушается автомобилем, а не мотором. Может быть, мне просто нужна другая точка зрения для модульных тестов по сравнению с рабочим кодом, потому что все намерение состоит в том, чтобы протестировать только часть целого.
Является ли создание объектов с нулем в модульных тестах запахом кода?
источник
Motor
вероятно, не должно бытьspeed
вообще. Он должен иметьthrottle
и вычислять наtorque
основе текущегоrpm
иthrottle
. Работа автомобиля состоит в том, чтобы использовать его,Transmission
чтобы интегрировать это в текущую скорость, и превратить это вrpm
сигнал, чтобы сигнализироватьMotor
... Но я полагаю, вы все равно не были заинтересованы в реализме, не так ли?null
радио правильно рассчитывается ограничение скорости. Теперь вы можете создать тест для проверки ограничения скорости с помощью радио; на всякий случай поведение отличается ...Ответы:
В случае приведенного выше примера разумно, что a
Car
может существовать без aCarRadio
. В этом случае я бы сказал, что не только допустимоCarRadio
иногда принимать значение NULL , но я бы сказал, что это обязательно. Ваши тесты должны гарантировать, что реализацияCar
класса является устойчивой и не генерирует исключения нулевого указателя, когда noCarRadio
присутствует.Однако давайте возьмем другой пример - давайте рассмотрим
SteeringWheel
. Давайте предположим, что aCar
должен иметь aSteeringWheel
, но тест скорости на самом деле не заботится об этом. В этом случае я бы не пропустил ноль, такSteeringWheel
как это толкаетCar
класс в места, где он не предназначен. В этом случае вам лучше создать некую разновидностьDefaultSteeringWheel
(для продолжения метафоры), заблокированную по прямой линии.источник
Car
невозможно построить безSteeringWheel
...Car
без безCarRadio
, не имеет ли смысла создавать конструктор, который не требует, чтобы его передавали, и вообще не беспокоился о явном нуле ?Car
бросили бы NRE с нулевым значениемCarRadio
, но соответствующий код дляSpeedLimit
не будет. В случае вопроса, который был опубликован сейчас, вы были бы правы, и я бы действительно сделал это.Да. Обратите внимание на определение запаха кода в C2 : «CodeSmell - это намек на то, что что-то может быть не так, а не определенность».
Если вы пытаетесь продемонстрировать, что
speedLimit.IsViolatedBy(car)
это не зависит отCarRadio
аргумента, переданного конструктору, то будущему сопровождающему, вероятно, будет понятнее сделать это ограничение явным, введя двойной тест, который не пройдёт тест, если он вызывается.Если, как более вероятно, вы просто пытаетесь сохранить себе работу по созданию того,
CarRadio
что, как вы знаете, не используется в этом случае, вы должны заметить, чтоCarRadio
неиспользованному факту просочиться в тест)Car
без указанияCarRadio
Я сильно подозреваю, что код пытается сказать вам, что вы хотите конструктор, который выглядит как
и тогда этот конструктор может решить, что делать с тем, что нет
CarRadio
или
или
и т.п.
Это второй интересный запах - для того, чтобы протестировать SpeedLimit, вы должны построить вокруг автомобиля всю машину, хотя единственное, что, вероятно, беспокоит проверка SpeedLimit - это скорость .
Это может быть подсказка, которая
SpeedLimit.isViolatedBy
должна принимать интерфейс роли, который реализует автомобиль , а не требовать всего автомобиля.IHaveSpeed
действительно паршивое имя; надеюсь, ваш язык домена будет улучшен.С этим интерфейсом ваш тест на
SpeedLimit
может быть намного прощеисточник
IHaveSpeed
интерфейс, поскольку (по крайней мере, в c #) вы не сможете создать экземпляр самого интерфейса.нет
Не за что. Напротив, если
NULL
это значение, которое доступно в качестве аргумента (некоторые языки могут иметь конструкции, которые запрещают передачу нулевого указателя), вы должны проверить это.Другой вопрос, что происходит, когда вы проходите
NULL
. Но это именно то , что представляет собой модульный тест: убедиться, что определенный результат происходит, когда для каждого возможного ввода. ИNULL
это потенциальный вход, так что вы должны иметь определенный результат , и вы должны иметь испытание для этого.Так что если ваш автомобиль должен быть конструктивным без радио, вы должны написать тест для этого. Если это не так и предполагается выдать исключение, вы должны написать тест для этого.
В любом случае, да, вы должны написать тест для
NULL
. Это был бы запах кода, если бы вы его оставили. Это означало бы, что у вас есть непроверенная ветвь кода, которая, как вы только что предполагали, будет волшебным образом не содержать ошибок.Обратите внимание, что я согласен с другими ответами, что ваш тестируемый код может быть улучшен. Тем не менее, если улучшенный код имеет параметры, которые являются указателями, передача
NULL
даже для улучшенного кода является хорошим тестом. Я хотел бы, чтобы тест показал, что происходит, когда я прохожуNULL
как мотор. Это не запах кода, это противоположность запаху кода. Это тестирование.источник
Car
здесь о тестировании . Хотя я ничего не вижу, но считаю неправильное утверждение, вопрос касается тестированияSpeedLimit
.NULL
к конструкторуCar
.SpeedLimit
. Вы можете видеть, что тест называется «SpeedLimitTest», иAssert
проверяется только методSpeedLimit
. ТестированиеCar
- например, «если ваш автомобиль должен быть сконструирован без радиоприемника, вы должны написать тест для этого» - будет происходить в другом тесте.Я лично не хотел бы вводить нули. Фактически, всякий раз, когда я внедряю зависимости в конструктор, первое, что я делаю, это вызывает ArgumentNullException, если эти инъекции равны нулю. Таким образом, я смогу безопасно использовать их в своем классе, не разнося десятки нулевых проверок. Вот очень распространенная модель. Обратите внимание на использование readonly, чтобы гарантировать, что эти зависимости, установленные в конструкторе, не могут быть установлены в null (или что-либо еще) впоследствии:
С учетом вышесказанного, я мог бы провести один или два модульных теста, в которых я вводил нули, просто чтобы убедиться, что мои нулевые проверки работают как положено.
Сказав это, это становится не проблема, когда вы делаете две вещи:
Так что для вашего примера у вас будет:
Более подробную информацию об использовании Moq можно найти здесь .
Конечно, если вы хотите понять это без насмешек, вы все равно можете сделать это, если вы используете интерфейсы. Просто создайте фиктивные CarRadio и Motor, как показано ниже, и введите их вместо:
источник
IMotor
бы былgetSpeed()
метод,DummyMotor
он должен был бы вернуть измененную скорость и после успешного завершенияsetSpeed
.Это не просто хорошо, это хорошо известная техника взлома зависимостей для вставки устаревшего кода в тестовый комплект. (См. «Эффективная работа с устаревшим кодом» Майкла Фезерса.)
Это пахнет? Что ж...
Тест не пахнет. Тест делает свою работу. Он объявляет «этот объект может быть нулевым, а тестируемый код по-прежнему работает правильно».
Запах находится в реализации. Объявляя аргумент конструктора, вы заявляете: «Этому классу нужна эта вещь для правильного функционирования». Очевидно, это не соответствует действительности. Все, что не соответствует действительности, немного вонючее.
источник
Давайте сделаем шаг назад к первым принципам.
Однако будут конструкторы или методы, которые принимают другие классы в качестве параметров. То, как вы справляетесь с этим, будет варьироваться от случая к случаю, но установка должна быть минимальной, поскольку они касаются цели. Могут быть сценарии, в которых передача значения NULL совершенно допустима, например, автомобиль без радиоприемника.
Я предполагаю , что здесь, что мы просто тестирование гоночной линии , чтобы начать с (продолжить тему автомобиля), но вы можете также хотите передать NULL другим параметрам , чтобы проверить , что любые защитные механизмы кода и исключение вручая функционируют в качестве обязательный.
Все идет нормально. Однако, что если NULL не является допустимым значением. Ну, вот вы попали в мрачный мир издевательств, заглушек, манекенов и подделок .
Если все это кажется слишком сложным, вы фактически уже используете пустышку, передавая NULL в конструкторе Car, поскольку это значение, которое потенциально не будет использоваться.
То, что должно стать ясным довольно быстро, это то, что вы потенциально можете нанести удар по своему коду с большой обработкой NULL. Если вы замените параметры класса интерфейсами, то вы также проложите путь для насмешек, DI и т. Д., Что делает их намного проще в обращении.
источник
Взглянув на ваш дизайн, я бы предположил, что
Car
он состоит из набораFeatures
. Функция может бытьCarRadio
, илиEntertainmentSystem
которая может состоять из таких вещей, какCarRadio
,DvdPlayer
и т. Д.Таким образом, как минимум, вы должны построить
Car
с наборомFeatures
.EntertainmentSystem
иCarRadio
будут реализациями интерфейса (Java, C # и т. д.),public [I|i]nterface Feature
и это будет экземпляр шаблона Composite.Следовательно, вы всегда будете создавать
Car
с,Features
а модульный тест никогда не будет создавать экземплярCar
без негоFeatures
. Хотя вы можете подумать оBasicTestCarFeatureSet
том, чтобы использовать макет с минимальным набором функций, необходимых для вашего самого простого теста.Просто некоторые мысли.
Джон
источник