Следует ли писать модульные тесты для геттеров и сеттеров?

145

Должны ли мы писать тесты для наших геттеров и сеттеров или это излишне?

Гектор421
источник
Я так не думаю. Вы не должны писать тестовый пример для геттера / сеттера.
Harry Joy
1
Я так понимаю, вы имеете в виду Java? Это особенно острый вопрос для Java, а тем более для более современных языков.
skaffman 01
@skaffman Какие современные языки не имеют свойств? Конечно, такие языки, как Java, требуют, чтобы они были полными телами методов, но это не делает его логическим отличным от, скажем, C #.
Клаус Йоргенсен
2
@Claus: Он не сказал свойств, он сказал геттеры и сеттеры. В java вы пишете их вручную, на других языках вы получаете лучшую поддержку.
skaffman 01

Ответы:

190

Я бы сказал нет.

@Will сказал, что вы должны стремиться к 100% покрытию кода, но, на мой взгляд, это опасное отвлечение. Вы можете писать модульные тесты со 100% покрытием, но при этом ничего не тестировать.

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

Если, с другой стороны, ваши геттеры и сеттеры делают больше, чем просто получают и устанавливают (т. Е. Являются достаточно сложными методами), то да, их следует протестировать. Но не пишите модульный тестовый пример только для проверки геттеров или сеттеров - это пустая трата времени.

Скаффман
источник
7
Глупо стремиться к 100% покрытию кода. В конечном итоге вы закончите покрытие кода, который не публикуется, и / или кода без сложности. Такие вещи лучше оставить для автогенерации, но даже автогенерация бессмысленна. Лучше переключить внимание на важное тестирование, например на интеграционные.
Клаус Йоргенсен
8
@Claus: Я согласен, за исключением того момента, когда я сосредоточился на интеграционном тестировании. Модульные тесты имеют решающее значение для хорошего дизайна и дополняют интеграционные тесты.
skaffman 01
5
Я думаю, что 100% покрытие кода при запуске всего набора тестов - хорошая цель. Установщики методов получения свойств и другой код запускаются как часть более крупных тестов. Последнее, что будет рассмотрено, вероятно, касается обработки ошибок. И если обработка ошибок не охвачена модульными тестами, она никогда не будет охвачена. Вы действительно хотите отправить продукт, содержащий код, который никогда не запускался?
Anders Abel
Я бы не согласился с этим советом. Да, геттеры и сеттеры просты, но их также на удивление легко испортить. Несколько строк простых тестов, и вы знаете, что они работают и продолжают работать.
Чарльз
Возможно, вы тестируете ненужные сеттеры, которые вы (или инструмент) создали только для того, чтобы иметь «округленный» POJO. Вы можете использовать Gson.fromJson для каждого экземпляра для «раздувания» POJOS (сеттеры не нужны). В этом случае я предпочитаю удалить неиспользуемые сеттеры.
Альберто Гаона
39

Рой Ошеров в своей знаменитой книге «Искусство модульного тестирования» говорит:

Свойства (геттеры / сеттеры в Java) - хорошие примеры кода, который обычно не содержит никакой логики и не требует тестирования. Но будьте осторожны: как только вы добавите какую-либо проверку внутри свойства, вы захотите убедиться, что логика проверяется.

ажидков
источник
1
Учитывая, сколько времени уходит на их тестирование, и вероятность того, что поведение будет добавлено, я не понимаю, почему бы и нет. Я подозреваю, что если на него надавить, он ответил бы: «Ну, а почему бы и нет?».
Sled
1
В важных ранних книгах часто есть плохие советы. Я видел очень много случаев, когда люди быстро вставляли геттеры и сеттеры и совершали ошибки, потому что они сокращали и вставляли или забывали об этом. или это-> или что-то в этом роде. Их легко проверить, и их обязательно нужно проверять.
Чарльз
35

Громкое ДА с TDD


Примечание : этот ответ продолжает получать положительные голоса, хотя потенциально это плохой совет. Чтобы понять почему, взгляните на его младшую сестру ниже.


Спорные хорошо, но я бы утверждать, что тот, кто отвечает «нет» на этот вопрос отсутствует фундаментальное понятие TDD.

Для меня ответ - твердое « да», если вы будете следовать TDD. Если нет, то правдоподобный ответ - нет.

DDD в TDD

TDD часто упоминается как имеющий три основных преимущества.

  • Защита
    • Убедитесь, что код может измениться, но не его поведение .
    • Это позволяет проводить столь важную практику рефакторинга .
    • Вы получаете этот TDD или нет.
  • дизайн
    • Вы указываете, что что-то должно делать, как оно должно себя вести, прежде чем реализовывать это.
    • Это часто означает более обоснованные решения по реализации .
  • Документация
    • Набор тестов должен служить документацией со спецификациями (требованиями).
    • Использование тестов для этой цели означает, что документация и реализация всегда находятся в согласованном состоянии - изменение одного означает изменение другого. Сравните с сохранением требований и дизайна в отдельном текстовом документе.

Отдельная ответственность от реализации

Программистам ужасно соблазнительно думать об атрибутах как о чем-то значимом, а о геттерах и сеттерах как о каких-то накладных расходах.

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

Гораздо важнее указать, что объект должен:

Разрешить своим клиентам изменять свое состояние

а также

Разрешить клиентам запрашивать его состояние

затем как это состояние фактически сохраняется (для которого атрибут является наиболее распространенным, но не единственным способом).

Такой тест, как

(The Painter class) should store the provided colour

важен для документации TDD.

Тот факт, что конечная реализация является тривиальной (атрибут) и не несет никакой защиты, должен быть вам неизвестен при написании теста.

Отсутствие двусторонней инженерии ...

Одной из ключевых проблем в мире разработки систем является отсутствие технологии « туда и обратно» 1 - процесс разработки системы фрагментирован на несвязанные подпроцессы, артефакты которых (документация, код) часто несовместимы.

1 Броди, Майкл Л. «Джон Милопулос: зашивание семян концептуального моделирования». Концептуальное моделирование: основы и приложения. Springer Berlin Heidelberg, 2009. 1–9.

... и как TDD решает эту проблему

Это часть документации TDD, которая гарантирует, что спецификации системы и ее кода всегда согласованы.

Сначала спроектируй, потом внедри

В рамках TDD мы сначала пишем неудачный приемочный тест, только затем пишем код, который позволяет им пройти.

В BDD более высокого уровня мы сначала пишем сценарии, а затем заставляем их проходить.

Почему вы должны исключать сеттеры и геттеры?

Теоретически в TDD вполне возможно, чтобы один человек написал тест, а другой реализовал код, который позволяет ему пройти.

Так что спросите себя:

Должен ли человек, пишущий тесты для класса, упомянуть геттеры и сеттеры.

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

Очевидно, что если вы сначала напишете код, ответ может быть не таким однозначным.

Ижаки
источник
2
Это только одна сторона медали. Подумайте о вещах, которые вы могли бы сделать вместо тестирования просто ради тестирования. Как сказал Кент Бек, вам платят за рабочий код, а не за рабочие тесты.
Георгий Олейников
@GeorgiiOleinikov Вы читали мой другой ответ ниже? Это в значительной степени соответствует вашему мнению.
Ижаки
21

ТЛ; др: Да вы должны , и с OpenPojo это тривиально.

  1. Вы должны выполнить некоторую проверку в ваших геттерах и сеттерах, поэтому вы должны это тестировать. Например, setMom(Person p)не следует разрешать ставить своей матерью кого-либо младше себя.

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

  3. Распространенная ошибка - void setFoo( Object foo ){ foo = foo; }там, где она должна быть void setFoo( Object foo ){ this.foo = foo; }. (В первом случае , fooкоторый записывается на это параметр неfoo поле на объекте ).

  4. Если вы возвращаете массив или коллекцию, вы должны проверить, будет ли геттер выполнять защитные копии данных, переданных в сеттер, перед возвратом.

  5. В противном случае, если у вас есть самые простые сеттеры / геттеры, то их модульное тестирование добавит, возможно, около 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 );
Санки
источник
11
Я знаю, что это уже устарело, но: СЛЕДУЕТ ли вам выполнять проверку в ваших геттерах и сеттерах? У меня создалось впечатление, что setMom должна делать то, что она говорит. Если это проверка, то не должно ли это быть validateAndSetMom? Или лучше, разве код проверки не должен существовать ГДЕ-ТО, кроме простого объекта? Что мне здесь не хватает?
ndtreviv
14
Да, вы всегда должны проверять свои данные. Если нет, то почему бы просто не использовать публичную переменную? Это становится тем же самым. Все преимущество использования сеттеров по сравнению с переменными заключается в том, что они позволяют гарантировать, что ваш объект никогда не будет недействительным, например, если возраст является отрицательным числом. Если вы не сделаете этого в объекте, вы будете делать это где-нибудь в другом месте (например, бог-объект "службы" ), и это не совсем ООП в тот момент.
Sled
1
Что ж, это, наверное, главный момент моей недели. Спасибо.
Sled
19

Да, но не всегда изолированно

Позвольте мне уточнить:

Что такое модульный тест?

Из раздела Эффективная работа с устаревшим кодом 1 :

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

Обратите внимание, что в ООП, где вы найдете геттеры и сеттеры, единицей является класс , а не обязательно отдельные методы .

Что такое хороший тест?

Все требования и тесты соответствуют логике Хоара :

{P} C {Q}

Куда:

  • {P}является предварительным условием ( дано )
  • Cэто условие срабатывания ( когда )
  • {Q}постусловие ( тогда )

Затем следует изречение:

Тестовое поведение, а не реализация

Это означает, что вы не должны проверять, как Cдостигается постусловие, вы должны проверять, что {Q}это результат C.

Когда дело доходит до ООП, Cэто класс. Таким образом, вы не должны тестировать внутренние эффекты, только внешние эффекты.

Почему бы не тестировать геттеры и сеттеры bean по отдельности

Геттеры и сеттеры могут включать некоторую логику, но до тех пор, пока эта логика не имеет внешнего эффекта - делая их 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.

Ижаки
источник
13

Если цикломатическая сложность геттера и / или сеттера равна 1 (а они обычно так и есть), то ответ - нет, не следует.

Поэтому, если у вас нет SLA, требующего 100% покрытия кода, не беспокойтесь и сосредоточьтесь на тестировании важного аспекта вашего программного обеспечения.

PS Не забудьте различать методы получения и установки, даже в таких языках, как C #, где свойства могут казаться одним и тем же. Сложность установщика может быть выше, чем получателя, и, таким образом, проверять юнит-тест.

Клаус Йоргенсен
источник
4
Хотя на геттерах стоит проверить защитное копирование
Sled
13

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

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

Существуют очевидные причины, по которым следует использовать свойства чтения / записи, такие как свойства ViewModel, привязанные к полям ввода и т. Д.

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

  1. Вам не хватает тестов, которые косвенно используют свойства
  2. Недвижимость не используется

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

Bryanbcook
источник
8

Юмористический, но мудрый подход : The Way of Testivus

"Напишите тест, который вы можете сегодня"

Тестирование геттеров / сеттеров может оказаться излишним, если вы опытный тестировщик, а это небольшой проект. Однако, если вы только начинаете учиться модульному тестированию или эти геттеры / сеттеры могут содержать логику (как в setMom()примере @ ArtB ), тогда было бы неплохо написать тесты.

Muymoo
источник
1
Ваша ссылка должна указывать на самом деле: Путь Testivus
JJS
3

На самом деле это была недавняя тема между моей командой и мной. Мы стремимся к 80% покрытию кода. Моя команда утверждает, что геттеры и сеттеры реализованы автоматически, а компилятор генерирует некоторый базовый код за кулисами. В этом случае, учитывая, что сгенерированный код не является навязчивым, действительно не имеет смысла тестировать код, который компилятор создает для вас. У нас также было обсуждение асинхронных методов, и в этом случае компилятор генерирует целую кучу кода за кулисами. Это другой случай, и мы ДЕЙСТВИТЕЛЬНО тестируем его. Длинный ответ, краткий, обсудите это со своей командой и решите для себя, стоит ли его тестировать.

Кроме того, если вы, как и мы, используете отчет о покрытии кода, вы можете добавить атрибут [ExcludeFromCodeCoverage]. Наше решение состояло в том, чтобы использовать это для моделей, которые просто имеют свойства, использующие методы получения и установки, или для самого свойства. Таким образом, это не повлияет на общий% покрытия кода при запуске отчета о покрытии кода, если это то, что вы используете для расчета процентов покрытия кода. Удачного тестирования!

Девин С
источник
2

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

Когда вы проверяете покрытие вручную по его красивой раскраске, можно утверждать, что простые геттеры и сеттеры не нуждаются в тестировании (хотя я всегда это делаю).

Когда вы проверяете только процент покрытия кода в своем проекте, тогда процент покрытия тестом, например 80%, не имеет смысла. Вы можете проверить все нелогичные части и забыть некоторые важные части. В этом случае только 100% означает, что вы протестировали весь жизненно важный код (а также весь нелогический код). Как только он достигает 99,9%, понимаешь, что что-то забыл.

Между прочим: покрытие кода - это последняя проверка, чтобы убедиться, что вы полностью (модульно) протестировали класс. Но 100% покрытие кода не обязательно означает, что вы действительно протестировали всю функциональность класса. Поэтому модульные тесты всегда следует реализовывать в соответствии с логикой класса. В конце концов, вы запускаете освещение, чтобы узнать, не забыли ли вы что-нибудь. Когда вы все сделаете правильно, вы получите 100% с первого раза.

Еще одна вещь: недавно работая в крупном банке в Нидерландах, я заметил, что Sonar показал 100% покрытие кода. Однако я знал, что чего-то не хватает. Проверяя процент покрытия кода для каждого файла, он показал, что файл с более низким процентом. Процент всей кодовой базы был настолько большим, что в одном файле процент не отображался как 99,9%. Так что вы, возможно, захотите это увидеть ...

Radboud
источник
1

Я провел небольшой анализ охвата, достигнутого в самом коде JUnit .

Одна из категорий непокрытого кода - «слишком простой для тестирования» . Сюда входят простые методы получения и установки, которые разработчики JUnit не тестируют.

С другой стороны, JUnit не имеет (не устаревшего) метода длиной более 3 строк, который не покрывается никаким тестом.

Avandeursen
источник
1

Я бы сказал: ДА. Ошибки в методах получения и установки могут незаметно проникнуть внутрь и вызвать некоторые уродливые ошибки.

Я написал библиотеку, чтобы упростить этот и некоторые другие тесты. Единственное, что вам нужно написать в своих тестах JUnit, это следующее:

        assertTrue(executor.execute(Example.class, Arrays.asList( new DefensiveCopyingCheck(),
            new EmptyCollectionCheck(), new GetterIsSetterCheck(),
            new HashcodeAndEqualsCheck(), new PublicVariableCheck())));

-> https://github.com/Mixermachine/base-test

Аарон Дитц
источник
0

Да, особенно если элемент, который нужно получить, является объектом класса, унаследованного от абстрактного класса. Ваша IDE может предупреждать вас, а может и не предупреждать о том, что определенное свойство не инициализировано.

А затем какой-то явно несвязанный тест вылетает из-за a, NullPointerExceptionи вам требуется время, чтобы понять, что свойства gettable на самом деле нет, чтобы получить его.

Хотя это все равно не так плохо, как обнаружить проблему в производстве.

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

Что касается геттеров и сеттеров примитивов, то может возникнуть вопрос: тестирую ли я свою программу или тестирую JVM или CLR? Вообще говоря, JVM не требует тестирования.

Алонсо дель Арте
источник