Подтвердить что ты не робот

Философские вопросы о тестируемом развитии

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

  • Как вы обрабатываете большие изменения? Когда дело доходит до тестирования отдельных функций (некоторые параметры, значение результата, несколько побочных эффектов), TDD не требует больших усилий. Но как насчет того, когда вам нужно тщательно переделать что-то большое, например? переключение из библиотеки анализа SAX в библиотеку разбора DOM? Как вы придерживаетесь цикла тестирования-кода-рефактора, когда ваш код находится в промежуточном состоянии? После того, как вы начнете внесение изменений, вы получите множество неудачных тестов, пока не закончите капитальный ремонт (если вы не поддерживаете какой-то класс mongrel, который использует как DOM, так и SAX, пока вы не закончите конвертацию, но это довольно странно), Что происходит с малым шагом тестового кода-рефакторинга в этом случае? В течение всего этого процесса вы больше не будете двигаться на небольших, полностью проверенных шагах. Должно быть, люди с этим справляются.
  • При тестировании GUI или кода базы данных с помощью mocks, что вы действительно тестируете? Mocks построены так, чтобы вернуть именно тот ответ, который вам нужен, и как вы знаете, что ваш код будет работать с реальной базой данных? В чем преимущества автоматических тестов для такого рода вещей? Это несколько улучшает доверие, но а) оно не дает вам такой же уверенности, что полный unit test должен, и b) в определенной степени, разве вы не просто проверяете, что ваши предположения работают с вашим кодом, а скорее чем ваш код работает с DB или GUI?

Может ли кто-нибудь указать мне на хорошие тематические исследования по использованию тестовой разработки в крупных проектах? Это расстраивает то, что я могу в основном найти только примеры TDD для отдельных классов.

Спасибо!

4b9b3361

Ответ 1

Как вы обрабатываете большие изменения?

Насколько это необходимо.

Иногда рефакторинги имеют большую поверхность, но тривиальны в деталях. Это можно сделать довольно большими шагами. Слишком много усилий, пытаясь разбить их, будет ненужным.

Я бы сказал, что изменение этой библиотеки в этой категории. Вы помещаете XML и получаете некоторое представление. Пока ваше представление не изменяется (из графика, представляющего состояние в поток событий), переключатель библиотеки легко.

В большинстве случаев рефакторинг не является тривиальным и должен быть разбит. Проблема в том, когда делать большие шаги и когда более мелкие. Мое замечание состоит в том, что я очень плохо оцениваю влияние изменения. Большинство программных средств достаточно сложно, что вы можете подумать, что они меняются, легко управляемы, но тогда есть целая мелкая печать, которая должна работать снова. Поэтому я начинаю с изменения. Но я готов откинуть все, если он начнет становиться непредсказуемым. Я бы сказал, что это происходит в одном из десяти рефакторингов. Но это будет тяжело. Вы должны отслеживать часть системы, которая не ведет себя так, как вы ожидаете. Теперь проблема должна быть разделена на несколько меньших задач. Я решаю одну проблему за раз и проверяю, когда это будет сделано. (Несколько итераций возврата и расщепления не являются редкостью.)

Если вы измените синтаксический анализатор XML и его представление в коде, это должно быть, по крайней мере, два отдельных рефакторинга.

Макетирование

Вы тестируете протокол связи между объектами/слоями с макетными объектами.

Весь макет подхода может быть, хотя и коммуникационной моделью, как модель OSI. Когда уровень X получает вызов с параметром x, он вызывает слой Z с параметрами a и b. Ваш тест указывает этот протокол связи.

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

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

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

Итак, ваша стратегия тестирования GUI должна быть ясной. Части кода GUI, которые не могут быть протестированы изолированно, должны быть протестированы с помощью макетных тестов (когда эта кнопка нажата, служба X вызывается с параметром y).

Базы данных немного загрязняют воду. Вы не можете издеваться над базой данных, если только вы не собираетесь переопределить поведение каждой базы данных, которую хотите поддержать. Но это не unit test, поскольку вы интегрируете внешнюю систему. Я заключил мир с этой концептуальной проблемой и подумал о DAO и базе данных как об одном неотъемлемом подразделении, которое можно протестировать на основе государственного теста. (К сожалению, эта единица ведет себя по-разному, когда у нее есть свой день оракула по сравнению со своим днем ​​mysql. И она может сломаться посередине и сказать вам, что она не может разговаривать сама с собой.)

Ответ 2

При тестировании GUI или кода базы данных с насмешливо, что вы действительно тестируете? Mocks построены так, чтобы ответьте, что хотите, так как вы знаете что ваш код будет работать с реальная база данных? Что это преимущества автоматических тестов для этого Такие вещи? Это улучшает доверие несколько, но а) это не дает вам такой же уровень уверенности в том, что полный unit test должен, и b) до в определенной степени, вы не просто проверяя, что ваши предположения работают с вашим кодом, а не с вашим код работает с DB или GUI?

Это мой подход: для уровня доступа к базе данных (DAL) я не использовать mock для моего unit test. Вместо этого я запускаю тесты в реальной базе данных, хотя и другой, чем производственная база данных. Поэтому в этом смысле вы можете сказать, что я не запускаю unit test в базе данных. Для приложений NHibernate я поддерживаю две базы данных с одинаковой схемой, но другой тип базы данных (ORM упрощает это). Я использую sqlite для моего автоматизированного тестирования и настоящую базу данных MySQL или SQL для ad-hoc-тестирования.

Только один раз я использовал mock для модульного тестирования DAL; и что, когда я использовал сильно типизированный набор данных как ОРМ (большая ошибка!). То, как я это делал, состояло в том, чтобы Typemock вернул мне издеваемую копию полной таблицы, чтобы я мог выполнить select * на ней, Позже, когда я оглянулся, я бы хотел, чтобы я никогда не делал этого, но это было давно, и мне хотелось, чтобы я использовал правильный ОРМ.

Что касается GUI, возможно до unit test взаимодействия GUI. То, как я это делал, это использовать шаблон MVP, чтобы разделить модель, представление и презентатор. Фактически для этого типа приложений я тестирую только на презентаторе и модели, в которых я использую Typemock (или

Ответ 3

Мои 2 цента...

  • если ваши тесты ломаются, потому что вы переключили тип анализатора XML - это указывает, что тесты хрупкие. В тестах следует указать что, а не как. Из этого следует, что в этом случае тесты каким-то образом знают, что вы используете механизм анализа SAX (деталь реализации); которых они не должны. Исправьте эту проблему, и вы должны быть лучше с большими изменениями.
  • Когда вы абстрагируете GUI или Mocks от тестов через интерфейс, вы гарантируете, что ваш испытуемый субъект, который использует mocks (как дубликаты для фактических сотрудников), работает по назначению. Вы можете изолировать ошибки в коде от ошибок в ваших сотрудниках. Mocks поможет вам быстро сохранить свой тестовый набор. Вам также нужны тесты, которые подтверждают, что ваш настоящий сотрудник также соответствует интерфейсу И проверяет, что ваши настоящие соавторы правильно "подключены".

Ответ 4

С точки зрения обработки больших операций... целью TDD является проверка поведения вашего кода и того, как он взаимодействует со службами, от которых он зависит. Если вы хотите использовать TDD, и вы переходите от парсера DOM к парсеру SAX, и вы сами пишете саксовый парсер, вы должны написать тесты, которые проверяли поведение анализатора SAX на основе известного ввода, т.е. XML-документа. Анализатор SAX может зависеть от набора вспомогательных объектов, которые на самом деле могут быть изначально изделены с целью тестирования поведения анализатора SAX. Когда вы были готовы написать код реализации для вспомогательных объектов, вы могли бы написать тесты вокруг их ожидаемого поведения на основе известного ввода. В примере анализатора SAX вы должны писать отдельные классы для реализации этого поведения, чтобы не мешать существующему коду, который зависит от DOM Parser. Фактически, вы можете создать интерфейс IXMLParser, который DOM-парсер и синтаксический анализатор SAX реализуют так, чтобы вы могли их отключать по своему усмотрению.

Что касается использования mocks или stub, то причина, по которой вы используете Mock или Stub, заключается в том, что вам не интересно тестировать внутреннюю работу Mock или Stub, но вы заинтересованы в тестировании внутренней работы того, что зависят от макета или заглушки, и это то, что вы действительно проверяете с точки зрения единицы. Если вы заинтересованы в написании интеграционных тестов, тогда вы должны написать интеграционные тесты, а не модульные тесты. Я нахожу, что написание кода в стиле TDD полезно для того, чтобы помочь мне определить структуру и организацию моего кода вокруг поведения, которое меня просят предоставить.

Я не знаком с какими-либо ситуационными исследованиями, но я уверен, что они там.

Ответ 5

При тестировании GUI или кода базы данных с насмешливо, что вы действительно тестируете?

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

// Production code in class UserFormController:

void changeUserNameButtonClicked() {
  String newName = nameTextBox.getText();
  if (StringUtils.isEmpty(newName)) {
    errorBox.showError("User name may not be empty !");
  } else {
    User user = engine.getCurrentUser();
    user.name = newName;
    engine.saveUser(user);
  }
}

// Test code in UserFormControllerTest:

void testValidUserNameChange() {
  nameTextBox = createMock(TextBox.class);
  expect(nameTextBox.getText()).andReturn("fred");
  engine = createMock(Engine.class);
  User user = createMock(user);
  user.setName("fred");
  expectLastCall();
  expect(engine.getCurrentUser()).andReturn(user);
  engine.saveUser(user);
  expectLastCall();
  replay(user, engine, nameTextBox);

  UserFormController controller = new UserFormController();
  controller.setNameTextBox(nameTextBox);
  controller.setEngine(engine);
  controller.changeUserNameButtonClicked();  

  verify(user, engine, nameTextBox);
}

void testEmptyUserNameChange() {
  nameTextBox = createMock(TextBox.class);
  errorBox = createMock(ErrorBox.class);
  expect(nameTextBox.getText()).andReturn("");
  errorBox.showError("User name may not be empty !");
  expectLastCall();
  replay(nameTextBox, errorBox);

  UserFormController controller = new UserFormController();
  controller.setNameTextBox(nameTextBox);
  controller.setErrorBox(errorBox);
  controller.changeUserNameButtonClicked();  

  verify(nameTextBox, errorBox);
}

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

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

Ответ 6

  • Как вы обрабатываете большие изменения?

    • Шаг за шагом. Я работал над несколькими нетривиальными программами и всегда мог разбить все на небольшие изменения (требуя часов и, возможно, дней). Например, переписывание 30Mpv-сайта разбилось на одну страницу за раз. Это переходило с одного языка на другой, записывая (небольшие) тесты по мере того, как мы шли, сохраняя сайт с частыми развертываниями. В другом проекте мы превратили веб-приложение GUI в безголовый сервер. Это включало в себя много небольших шагов через месяц или два работы, а затем в конечном итоге отбрасывало большую часть веб-кода. Но мы смогли сохранить все тесты, пока мы шли. Мы сделали это не потому, что пытались что-то доказать, а потому, что это был лучший способ повторного использования кода и тестов.

    • Большим шагам могут помочь тесты с более широким охватом. Например, пример SAX- > DOM будет иметь тест интеграции на высоком уровне, который бы проверял конечное поведение. Однако, когда я сделал что-то подобное, я написал гораздо меньшие поведенческие тесты по различным типам обработки node, и их преобразование можно было выполнить один за другим.

  • При тестировании графического интерфейса или кода базы данных с помощью mocks, что вы действительно тестируете?

    • Вам всегда нужно убедиться, что вы пишете ценные тесты. Это может быть сложно. Легко, даже если вы думаете, написать некоторые довольно избыточные тесты.
    • Mocks не имеет смысла, когда вы пытаетесь проверить запросы к базе данных. Они полезны, когда вы пытаетесь "выкрикивать" слой ниже того, что вы тестируете... поэтому они полезны в тесте контроллера, где вы будете издеваться над поведением уровня сервиса - который вы будете тестировать самостоятельно. Для тестирования запросов к базе данных вам необходимо загрузить базу данных с соответствующими приборами, которые будут проверять то, что вы пытаетесь сделать. Это можно сделать с помощью приспособлений или тщательного тестового кода. Это заставляет задуматься о том, чтобы получить право, поэтому хорошо иметь хорошо продуманный набор данных прибора, который позволит вам писать хорошие тесты запросов к базе данных, охватывая как можно больше важных случаев.

    • Да, вы проверяете свои предположения с помощью mocks - но вы также должны тестировать эти предположения отдельно. Альтернатива - проверить их все вместе - прекрасно, но более хрупко. Это означает, что тест тестирует больше кода и, следовательно, может легко сломаться.

Ответ 7

Что касается угла базы данных, как упоминал Ngu Soon Hui, вы должны (IMHO) использовать что-то вроде DBUnit, которое будет установлено (так что вы можете проверить ожидаемые результаты), но использует реальную базу данных, которую будет использовать реальное приложение.

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

Ответ 8

Обработка больших изменений

По моему опыту, они относительно нечасты. Когда они случаются, обновление тестов - незначительная проблема. Хитрость заключается в том, чтобы выбрать правильную детализацию для тестов. Если вы протестируете открытый интерфейс, обновления будут идти быстро. Если вы проверите частный код реализации, переход от SAX к парсеру DOM будет большим временем сакса, и вы почувствуете dom.; -)

Тестирование GUI-кода

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

Тестирование кода базы данных

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

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

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

Непрерывная интеграция действительно помогает при тестировании, поскольку она гарантирует, что тесты выполняются, и делает видимыми нарушения. Очень рекомендуется.

EDIT: Чтобы быть откровенным, у меня есть проблемы с развязкой кода доступа к данным во многих случаях и в конечном итоге с использованием баз тестовой палубы. Даже интеграционные тесты, подобные этим, оказались ценными, хотя они медленнее и хрупки. Как я уже сказал, я все еще учусь.