Должны ли мы писать тесты для наших геттеров и сеттеров или это излишне?
Должны ли мы писать тесты для наших геттеров и сеттеров или это излишне?
Ответы:
Я бы сказал нет.
@Will сказал, что вы должны стремиться к 100% покрытию кода, но, на мой взгляд, это опасное отвлечение. Вы можете писать модульные тесты со 100% покрытием, но при этом ничего не тестировать.
Модульные тесты предназначены для проверки поведения вашего кода выразительным и значимым образом, а геттеры / сеттеры - только средство для достижения цели. Если вы в тестах используете геттеры / сеттеры для достижения своей цели по тестированию «реальной» функциональности, этого достаточно.
Если, с другой стороны, ваши геттеры и сеттеры делают больше, чем просто получают и устанавливают (т. Е. Являются достаточно сложными методами), то да, их следует протестировать. Но не пишите модульный тестовый пример только для проверки геттеров или сеттеров - это пустая трата времени.
Рой Ошеров в своей знаменитой книге «Искусство модульного тестирования» говорит:
Свойства (геттеры / сеттеры в Java) - хорошие примеры кода, который обычно не содержит никакой логики и не требует тестирования. Но будьте осторожны: как только вы добавите какую-либо проверку внутри свойства, вы захотите убедиться, что логика проверяется.
Примечание : этот ответ продолжает получать положительные голоса, хотя потенциально это плохой совет. Чтобы понять почему, взгляните на его младшую сестру ниже.
Спорные хорошо, но я бы утверждать, что тот, кто отвечает «нет» на этот вопрос отсутствует фундаментальное понятие TDD.
Для меня ответ - твердое « да», если вы будете следовать TDD. Если нет, то правдоподобный ответ - нет.
TDD часто упоминается как имеющий три основных преимущества.
Программистам ужасно соблазнительно думать об атрибутах как о чем-то значимом, а о геттерах и сеттерах как о каких-то накладных расходах.
Но атрибуты - это деталь реализации, а сеттеры и геттеры - это контрактный интерфейс, который фактически заставляет программы работать.
Гораздо важнее указать, что объект должен:
Разрешить своим клиентам изменять свое состояние
а также
Разрешить клиентам запрашивать его состояние
затем как это состояние фактически сохраняется (для которого атрибут является наиболее распространенным, но не единственным способом).
Такой тест, как
(The Painter class) should store the provided colour
важен для документации TDD.
Тот факт, что конечная реализация является тривиальной (атрибут) и не несет никакой защиты, должен быть вам неизвестен при написании теста.
Одной из ключевых проблем в мире разработки систем является отсутствие технологии « туда и обратно» 1 - процесс разработки системы фрагментирован на несвязанные подпроцессы, артефакты которых (документация, код) часто несовместимы.
1 Броди, Майкл Л. «Джон Милопулос: зашивание семян концептуального моделирования». Концептуальное моделирование: основы и приложения. Springer Berlin Heidelberg, 2009. 1–9.
Это часть документации TDD, которая гарантирует, что спецификации системы и ее кода всегда согласованы.
В рамках TDD мы сначала пишем неудачный приемочный тест, только затем пишем код, который позволяет им пройти.
В BDD более высокого уровня мы сначала пишем сценарии, а затем заставляем их проходить.
Почему вы должны исключать сеттеры и геттеры?
Теоретически в TDD вполне возможно, чтобы один человек написал тест, а другой реализовал код, который позволяет ему пройти.
Так что спросите себя:
Должен ли человек, пишущий тесты для класса, упомянуть геттеры и сеттеры.
Поскольку методы получения и установки публичный интерфейс класса, то ответ, очевидно , да , или не будет никакого способа набора или запроса состояния объекта. Однако способ сделать это не обязательно путем тестирования каждого метода по отдельности, подробнее см. Мой другой ответ .
Очевидно, что если вы сначала напишете код, ответ может быть не таким однозначным.
ТЛ; др: Да вы должны , и с OpenPojo это тривиально.
Вы должны выполнить некоторую проверку в ваших геттерах и сеттерах, поэтому вы должны это тестировать. Например, setMom(Person p)
не следует разрешать ставить своей матерью кого-либо младше себя.
Даже если вы не делаете ничего из этого сейчас, скорее всего, вы сделаете это в будущем, тогда это будет хорошо для регрессионного анализа. Если вы хотите разрешить установку матерей, null
вам следует пройти тест на это, если кто-то изменит это позже, это укрепит ваши предположения.
Распространенная ошибка - void setFoo( Object foo ){ foo = foo; }
там, где она должна быть void setFoo( Object foo ){ this.foo = foo; }
. (В первом случае , foo
который записывается на это параметр неfoo
поле на объекте ).
Если вы возвращаете массив или коллекцию, вы должны проверить, будет ли геттер выполнять защитные копии данных, переданных в сеттер, перед возвратом.
В противном случае, если у вас есть самые простые сеттеры / геттеры, то их модульное тестирование добавит, возможно, около 10 минут максимум для каждого объекта, так каковы потери? Если вы добавите поведение, у вас уже есть скелетный тест, и вы получите это регрессионное тестирование бесплатно. Если вы используете Java, у вас нет оправдания, поскольку существует OpenPojo . Существует существующий набор правил, который вы можете включить, а затем просканировать весь проект, чтобы убедиться, что они последовательно применяются в вашем коде.
Из их примеров :
final PojoValidator pojoValidator = new PojoValidator();
//create rules
pojoValidator.addRule( new NoPublicFieldsRule () );
pojoValidator.addRule( new NoPrimitivesRule () );
pojoValidator.addRule( new GetterMustExistRule () );
pojoValidator.addRule( new SetterMustExistRule () );
//create testers
pojoValidator.addTester( new DefaultValuesNullTester () );
pojoValidator.addTester( new SetterTester () );
pojoValidator.addTester( new GetterTester () );
//test all the classes
for( PojoClass pojoClass : PojoClassFactory.getPojoClasses( "net.initech.app", new FilterPackageInfo() ) )
pojoValidator.runValidation( pojoClass );
Позвольте мне уточнить:
Из раздела Эффективная работа с устаревшим кодом 1 :
Термин модульное тестирование имеет долгую историю в разработке программного обеспечения. Общей для большинства концепций модульных тестов является идея, что они являются тестами, изолированными от отдельных компонентов программного обеспечения. Что такое компоненты? Определение варьируется, но при модульном тестировании мы обычно имеем дело с наиболее атомарными поведенческими единицами системы. В процедурном коде единицы часто являются функциями. В объектно-ориентированном коде единицами являются классы.
Обратите внимание, что в ООП, где вы найдете геттеры и сеттеры, единицей является класс , а не обязательно отдельные методы .
Все требования и тесты соответствуют логике Хоара :
{P} C {Q}
Куда:
{P}
является предварительным условием ( дано )C
это условие срабатывания ( когда ){Q}
постусловие ( тогда )Затем следует изречение:
Тестовое поведение, а не реализация
Это означает, что вы не должны проверять, как C
достигается постусловие, вы должны проверять, что {Q}
это результат C
.
Когда дело доходит до ООП, C
это класс. Таким образом, вы не должны тестировать внутренние эффекты, только внешние эффекты.
Геттеры и сеттеры могут включать некоторую логику, но до тех пор, пока эта логика не имеет внешнего эффекта - делая их bean-аксессорами 2 ) тест должен будет заглядывать внутрь объекта и тем самым не только нарушать инкапсуляцию, но и проверять реализацию.
Поэтому вам не следует тестировать геттеры и сеттеры bean-компонентов изолированно. Это плохо:
Describe 'LineItem class'
Describe 'setVAT()'
it 'should store the VAT rate'
lineItem = new LineItem()
lineItem.setVAT( 0.5 )
expect( lineItem.vat ).toBe( 0.5 )
Хотя if setVAT
вызовет исключение, соответствующий тест будет уместен, поскольку теперь есть внешний эффект.
Практически нет смысла изменять внутреннее состояние объекта, если такое изменение не влияет на внешнее, даже если такой эффект наступает позже.
Таким образом, тест для сеттеров и геттеров должен быть связан с внешним эффектом этих методов, а не с внутренним.
Например:
Describe 'LineItem class'
Describe 'getGross()'
it 'should return the net time the VAT'
lineItem = new LineItem()
lineItem.setNet( 100 )
lineItem.setVAT( 0.5 )
expect( lineItem.getGross() ).toBe( 150 )
Вы можете подумать про себя:
Погодите, мы
getGross()
здесь не тестируемsetVAT()
.
Но в случае setVAT()
неисправности этот тест все равно не пройдет.
1 Feathers, M., 2004. Эффективная работа с унаследованным кодом. Prentice Hall Professional.
2 Мартин Р.К., 2009. Чистый код: руководство по созданию гибкого программного обеспечения. Pearson Education.
Если цикломатическая сложность геттера и / или сеттера равна 1 (а они обычно так и есть), то ответ - нет, не следует.
Поэтому, если у вас нет SLA, требующего 100% покрытия кода, не беспокойтесь и сосредоточьтесь на тестировании важного аспекта вашего программного обеспечения.
PS Не забудьте различать методы получения и установки, даже в таких языках, как C #, где свойства могут казаться одним и тем же. Сложность установщика может быть выше, чем получателя, и, таким образом, проверять юнит-тест.
Хотя для свойств есть обоснованные причины, в объектно-ориентированном дизайне распространено убеждение, что раскрытие состояния элемента через свойства - это плохой дизайн. Статья Роберта Мартина о принципе открытости-закрытости расширяет это, заявляя, что свойства поощряют связывание и, следовательно, ограничивают возможность закрытия класса от модификации - если вы измените свойство, все потребители класса также должны будут измениться. Он считает, что раскрытие переменных-членов - это не обязательно плохой дизайн, это может быть просто плохой стиль. Однако если свойства доступны только для чтения, вероятность злоупотреблений и побочных эффектов меньше.
Лучший подход, который я могу предложить для модульного тестирования (и это может показаться странным), - это сделать как можно больше свойств защищенными или внутренними. Это предотвратит связывание и не позволит писать глупые тесты для геттеров и сеттеров.
Существуют очевидные причины, по которым следует использовать свойства чтения / записи, такие как свойства ViewModel, привязанные к полям ввода и т. Д.
С практической точки зрения, модульные тесты должны обеспечивать функциональность с помощью общедоступных методов. Если код, который вы тестируете, использует эти свойства, вы получаете покрытие кода бесплатно. Если окажется, что эти свойства никогда не выделяются покрытием кода, существует очень большая вероятность, что:
Если вы напишете тесты для геттеров и сеттеров, вы получите ложное ощущение покрытия и не сможете определить, действительно ли свойства используются функциональным поведением.
Юмористический, но мудрый подход : The Way of Testivus
"Напишите тест, который вы можете сегодня"
Тестирование геттеров / сеттеров может оказаться излишним, если вы опытный тестировщик, а это небольшой проект. Однако, если вы только начинаете учиться модульному тестированию или эти геттеры / сеттеры могут содержать логику (как в setMom()
примере @ ArtB ), тогда было бы неплохо написать тесты.
На самом деле это была недавняя тема между моей командой и мной. Мы стремимся к 80% покрытию кода. Моя команда утверждает, что геттеры и сеттеры реализованы автоматически, а компилятор генерирует некоторый базовый код за кулисами. В этом случае, учитывая, что сгенерированный код не является навязчивым, действительно не имеет смысла тестировать код, который компилятор создает для вас. У нас также было обсуждение асинхронных методов, и в этом случае компилятор генерирует целую кучу кода за кулисами. Это другой случай, и мы ДЕЙСТВИТЕЛЬНО тестируем его. Длинный ответ, краткий, обсудите это со своей командой и решите для себя, стоит ли его тестировать.
Кроме того, если вы, как и мы, используете отчет о покрытии кода, вы можете добавить атрибут [ExcludeFromCodeCoverage]. Наше решение состояло в том, чтобы использовать это для моделей, которые просто имеют свойства, использующие методы получения и установки, или для самого свойства. Таким образом, это не повлияет на общий% покрытия кода при запуске отчета о покрытии кода, если это то, что вы используете для расчета процентов покрытия кода. Удачного тестирования!
На мой взгляд, покрытие кода - это хороший способ увидеть, не упустили ли вы какую-либо функциональность, которую вам следует охватить.
Когда вы проверяете покрытие вручную по его красивой раскраске, можно утверждать, что простые геттеры и сеттеры не нуждаются в тестировании (хотя я всегда это делаю).
Когда вы проверяете только процент покрытия кода в своем проекте, тогда процент покрытия тестом, например 80%, не имеет смысла. Вы можете проверить все нелогичные части и забыть некоторые важные части. В этом случае только 100% означает, что вы протестировали весь жизненно важный код (а также весь нелогический код). Как только он достигает 99,9%, понимаешь, что что-то забыл.
Между прочим: покрытие кода - это последняя проверка, чтобы убедиться, что вы полностью (модульно) протестировали класс. Но 100% покрытие кода не обязательно означает, что вы действительно протестировали всю функциональность класса. Поэтому модульные тесты всегда следует реализовывать в соответствии с логикой класса. В конце концов, вы запускаете освещение, чтобы узнать, не забыли ли вы что-нибудь. Когда вы все сделаете правильно, вы получите 100% с первого раза.
Еще одна вещь: недавно работая в крупном банке в Нидерландах, я заметил, что Sonar показал 100% покрытие кода. Однако я знал, что чего-то не хватает. Проверяя процент покрытия кода для каждого файла, он показал, что файл с более низким процентом. Процент всей кодовой базы был настолько большим, что в одном файле процент не отображался как 99,9%. Так что вы, возможно, захотите это увидеть ...
Я провел небольшой анализ охвата, достигнутого в самом коде JUnit .
Одна из категорий непокрытого кода - «слишком простой для тестирования» . Сюда входят простые методы получения и установки, которые разработчики JUnit не тестируют.
С другой стороны, JUnit не имеет (не устаревшего) метода длиной более 3 строк, который не покрывается никаким тестом.
Я бы сказал: ДА. Ошибки в методах получения и установки могут незаметно проникнуть внутрь и вызвать некоторые уродливые ошибки.
Я написал библиотеку, чтобы упростить этот и некоторые другие тесты. Единственное, что вам нужно написать в своих тестах JUnit, это следующее:
assertTrue(executor.execute(Example.class, Arrays.asList( new DefensiveCopyingCheck(),
new EmptyCollectionCheck(), new GetterIsSetterCheck(),
new HashcodeAndEqualsCheck(), new PublicVariableCheck())));
Да, особенно если элемент, который нужно получить, является объектом класса, унаследованного от абстрактного класса. Ваша IDE может предупреждать вас, а может и не предупреждать о том, что определенное свойство не инициализировано.
А затем какой-то явно несвязанный тест вылетает из-за a, NullPointerException
и вам требуется время, чтобы понять, что свойства gettable на самом деле нет, чтобы получить его.
Хотя это все равно не так плохо, как обнаружить проблему в производстве.
Было бы неплохо убедиться, что все ваши абстрактные классы имеют конструкторы. В противном случае проверка геттера может предупредить вас о проблеме.
Что касается геттеров и сеттеров примитивов, то может возникнуть вопрос: тестирую ли я свою программу или тестирую JVM или CLR? Вообще говоря, JVM не требует тестирования.