Анатомия юнит тестирования
Юнит тесты — обязательная часть моих проектов. Это база, к которой добавляются другие виды тестов. В статье Тестирование и экономика проекта я рассказал почему тестирование выгодно для экономики проекта и показал, что юнит тестирование лидирует с экономической точки зрения. В комментариях было высказано мнение, что тестирование требует больших усилий, и даже юнит тестирование неприемлемо из-за этого. Одной из причин этого является неопытность команды в тестировании. Чтобы написать первую тысячу тестов команда тратит много времени, пробуя и анализируя различные подходы.
В этой статье я расскажу о лучших практиках, к которым я пришел за более чем 10 лет тестирования различных проектов. Эти практики позволят начать юнит тестирование без заметного снижения производительности программистов.
Я определяю юнит тестирования как тестирование одного продакш юнита в полностью контролируемом окружении.
Продакшн юнит — это обычно класс, но может быть также и функция, и файл.
Важно, чтобы юнит соответствовал принципал SOLID, в этом случае юнит тесты будут лаконичными. Юниты узкоспециализированны и очень хорошо выполняют одну конкретную задачу, для которой они созданы. В большинстве случаев юниты взаимодействуют друг с другом, делегируя выполнение специализированных задач.
Полностью контролируемое окружение — это окружение имитирующие среду, в которой юнит работает в программе, но полностью открытое для тестирования и настройки. Поведение окружения задается для конкретного тестового кейса через лаконичный API и любое поведение вне этого кейса для него не определено. В этом окружении не должно быть других продакшн юнитов, иначе возрастает сложность тестов и из юнит тестирования мы переходим к интеграционному тестированию.
О наследование
Постарайтесь не применять наследование. Вместо него используйте композицию зависимостей. Часто наследование применяют для реализации принципа DRY (don’t repeat yourself), вынося общий код в родителя, но тем самым нарушая принцип KISS (keep it simple stupid), увеличивая сложность юнитов.
AAA (Arrange, Act, Assert) паттерн
Если посмотреть на юнит тест, то для большинства можно четко выделить 3 части кода:
Arrange (настройка) — в этом блоке кода мы настраиваем тестовое окружение тестируемого юнита;
Act — выполнение или вызов тестируемого сценария;
Assert — проверка того, что тестируемый вызов ведет себя определенным образом.
Этот паттерн улучшает структуру кода и его читабельность, однако начинать писать тест нужно всегда с элемента Act.
Driven approach
Прежде чем продолжить рассмотрение структуры теста, я хотел бы рассказать немного о подходе, который я называю Driven Approach. К сожалению, я не могу перевести это лаконично на русский язык. Поэтому просто расскажу, что за этим стоит, и может быть, вы поможете с лаконичным переводом.
Суть в том, что код, который вы пишите, должен иметь причину своего существования. Важно, чтобы причина была существующей, а не предполагаемой, и эта причина должна иметь в конечном итоге связь с бизнес историей.
С чего мы начинаем разработку конкретного функционала? — с требований бизнеса, которые типично выглядят так: “Пользователь с любой ролью должен иметь возможность создать запись, таким образом, он выполнит такую то бизнес операцию”.
Используя driven approach первое что мы должны сделать —
- Это создать место в UI слое, где пользователь может создать запись, скажем, страницу в приложении, на которой будет кнопка “Создать запись”. Почему мы это сделали? — потому что это требует бизнес история.
- Кнопка “Создать запись” будет требовать реализации обработчика click события.
- Обработчик события будет требовать реализации создания записи в терминах слоя бизнес логики.
- В случае клиент-серверной архитектуры, клиент будет обращаться к некоторому end point на стороне сервера для создания этой записи.
- Сервер, в свою очередь, может работать с базой данных, где такая запись должна быть создана в отдельной таблице.
Данный подход позволяет небольшими шагами реализовывать сложные бизнес истории, оставаясь все время сфокусированным только на нужном функционале, и избегать over engineering.
AAS (Act, Assert, Setup) паттерн
AAS — этот тот же AAA паттерн, но с измененным порядком частей, отсортированных с учетом Driven approach и переименованной Arrange частью в Setup, чтобы отличать их по названию.
Первое, что мы делаем, при создании теста — мы создаем Act. Обычно это создание экземпляра класса тестируемого юнита и вызов его функции. С одной стороны — это самый простой шаг, а с другой это то, что диктует нам бизнес история.
Второе — мы проверяем что Act действует ожидаемо. Мы пишем Assert часть, где выражаем требуемые последствия Act, в том числе с точки зрения бизнес истории.
И вот, когда у нас готовы первые 2 части, мы можем остановиться и подумать, как наш юнит будет выполнять требуемые действия. Да, да, именно сейчас мы можем первый раз задуматься, как реализовать его.
Смотрите, сам вид Act и его сигнатура продиктованы предыдущим шагом, тут нам нечего изобретать. Как предыдущий шаг хочет вызывать наш юнит, так он и будет его вызывать. Ожидаемые действия тоже продиктованы предыдущим шагом и самой бизнес историей.
Так что именно сейчас, когда мы будем писать последнюю часть теста, мы можем остановиться и продумать, как наш юнит будет работать и какое runtime окружение ему для этого нужно. И здесь мы переходим более подробно к “Контролируемому окружению” и дизайну юнита.
Принципы SOLID
Из принципа SOLID, с точки зрения юнит тестирования очень важны 2 принципа:
Single responsibility principle — позволяет снизить количество тест кейсов для юнита. В среднем на юнит должно приходиться от 1 до 9 тест кейсов. Это очень хороший индикатор качества юнита — если тест кейсов больше или хочется их сгруппировать, то вам точно нужно разделить его на два и больше независимых юнитов.
Dependency inversion principle — позволяет легко создавать и управлять сложнейшими окружениями для тестирования через IoC контейнеры. В соответствии с данным принципом, юнит должен зависеть от абстракций, что позволяет передавать ему любые реализации его зависимостей. В том числе, и не продакшен реализации, созданные специально для его тестирования. Эти реализации не имеют в себе никакой бизнес логики и созданы не только под конкретный тестируемый юнит, но и под конкретный сценарий его тестирования. Обычно они создаются с помощью одной из библиотек для mock объектов, такой как moq.
IoC контейнеры позволяют автоматически создавать экземпляр тестируемого юнита и экземпляры его зависимостей, сразу реализованные как mock объекты. Использование такого IoC контейнера очень важный шаг к снижению стоимости поддержания кода и его дружелюбности к автоматическому рефакторингу.
Качество кода
Кстати, несколько слов о качестве кода тестов и продакшн. Самым качественным кодом должен быть код тестов. Причина этому одна — это его размер. На 1 строку продакшн кода в среднем приходиться 2-3 строки тестового кода, то есть его в 2-3 раза больше чем продакшн кода. В этих условиях он должен хорошо читаться, быть структурированным, иметь хорошую типизацию и быть очень дружелюбным к инструментам автоматического рефакторинга. Это цели, которые достойны отдельных мероприятий и усилий.
Однотипность тестирования
Много приложения реализовано в распределенной и модульной архитектуре, где разные части написаны на различных языках, скажем, клиент-серверные приложения, где клиент написан под веб на typescript и сервер написанный на c#. Важной целью для таких проектов будет приведение тестов для любой части, независимо от языка к единому подходу. Это значит, что все тесты на проекте используют AAA или AAS подход. Все тесты используют mock библиотеки с похожим API. Все тесты используют IoC. И все тесты используют одинаковые метафоры. Это позволяет повысить переносимость удачных практик на разные части проекта, упростить адаптацию новых коллег (выучил раз и применяй везде).
Количество тестов для одного продакшн юнита
В среднем, на один продакшн юнит приходиться 1-9 тестов. Если тестов больше или у вас возникло желание сгруппировать тесты, то это — четкий сигнал проверить код продакшн юнита. Вполне возможно, что он нуждается в декомпозиции.
Моя команда создает клиент-серверные приложения, где мы используем angular на клиенте и .net core для серверной части. В следующей статье я хочу показать на примерах, как мы пишем юнит тесты под angular и с#. Как мы делаем их похожими, как располагаем в проектах, какие библиотеки применяем.
- Тестирование IT-систем
- Управление разработкой
JavaScript: Сигнатура функции
Функция Math.pow() , возводящая число в какую-нибудь степень, принимает два параметра: какое число возводить и в какую степень возводить. Если вызывать pow() без параметров, то вернется NaN . Функция честно пытается выполнить возведение в степень, но если значение не передано, то интерпретатор автоматически передает ей undefined . JavaScript заставляет программистов быть более аккуратным, чем остальные языки. В большинстве языков, если передать в функцию меньше параметров, чем она ожидает, то возникнет ошибка, — но только не в JavaScript. NaN вернется и при передаче любых не числовых значений:
const result = Math.pow(2, 'boom'); console.log(result); // => NaN
Другая функция может иметь другое число параметров и другие типы параметров. Например, может существовать функция, которая принимает три параметра: число, строку и ещё одно число.
Откуда мы знаем, сколько каких параметров нужно функции Math.pow() и какого типа будет «возврат»? Мы заглянули в сигнатуру этой функции. Сигнатура определяет входные параметры и их типы, а также выходной параметр и его тип. Про функцию Math.pow() можно почитать в документации. В разделе «Синтаксис» есть такой текст:
Math.pow(base, exponent) Параметры base Основание степени. exponent Показатель степени, в которую возводится основание base.
Это сигнатура функции и короткое пояснение на русском языке. Документация позволяет понять, сколько аргументов у функции и какого они типа, возвращает ли что-то функция и если да, то какого типа возвращаемое значение.
Задание
Теперь ваша очередь посмотреть на сигнатуру функции в документации и разобраться, как её использовать. Можете читать документацию на русском языке, но программист должен уметь читать документацию на английском. Используйте словари или переводчики при необходимости. Лучше сразу привыкать и подтягивать навыки чтения на английском, иначе будут сложности в будущем.
В Math есть функция ceil() . Изучите её документацию.
Напишите программу, которая использует функцию Math.ceil() с константой number и выводит результат на экран.
Упражнение не проходит проверку — что делать?
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
- Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Мой код отличается от решения учителя
Это нормально , в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Прочитал урок — ничего не понятно
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Полезное
Определения
- Сигнатура функции — формальное описание типов параметров и типа возвращаемого значения функции.
7 правил хорошего тона при написании Unit-тестов
Привет, коллеги! Сегодня я бы хотел поговорить о Unit-тестировании и некоторых “правилах” при их написании. Конечно, они неформальные и не обязательны к выполнению, но при их соблюдении всем будет приятно и легко читать и поддерживать тесты, которые вы написали. Мы в Wrike видели достаточно Unit-тестов, чтобы понять основные проблемы, которые возникают при их написании и поддержке, и сформулировать несколько правил для их предотвращения.
1. Unit-тесты нужно писать. Да, как бы банально это не звучало, но писать их нужно. Каждый кусок логики приложения должен быть протестирован, чтобы в будущем избежать проблем. А они могут возникнуть при изменении логики, рефакторинге, или даже при обновлении версии зависимых библиотек. И чем больше покрытие кода тестами, тем быстрее проблема будет обнаружена и исправлена.
2. Это правило очень актуально для тех, кого заставляют покрывать код тестами, и оно звучит так: Тесты — это тоже код, и относиться к нему нужно как к рабочему коду. Это касается и нейминга переменных, и форматирования кода внутри теста, и, особенно, названий тестовых методов. Конечно, написание адекватного имени переменной занимает немного больше времени и ударов по клавиатуре, чем “int i = 0;”, но это повышает читабельность тестов и легкость их поддержки.
Угадайте, что проверяет упавший тестовый метод?)
3. Третье правило
настоящего джентльмена — следи за путями. Конечно, всегда неприятно увидеть при запуске тестов, вот такую ошибку:
И даже не потому, что тебя зовут не Andrey, а потому что у тебя мак. И как же быть в такой ситуации, спросите вы? Ответ прост — Относительные пути. Вот пример —
Лучше всего использовать Unix разделитель (/). Это и гораздо лаконичнее, и меньше шансов получить непредвиденную ошибку.
4. Чаще используйте заглушки (моки) вместо реальных объектов. Моки — это здорово! Ими можно управлять так, как нужно в конкретном тесте. Но, конечно, не стоит забывать сбрасывать состояние заглушек перед каждым тестовым методом. Использование заглушек повышает автономность теста и его гибкость. Не нужно подгонять состояние системы для конкретного случая, а просто настроил заглушку на возвращение нужного значения при вызове определенного метода и все. Хочется проверить другую ситуацию — исправил возвращаемое значение на другое. Легко и просто. И самое главное, что состояние всей системы при этом не изменяется — она ничего не записывает на диск, не передает по сети, не пересчитывает массивы данных, не лезет в другие сервисы. Просто заглушка и возвращаемое значение.
Для использования заглушек в тестах я использую фреймворк Mockito. С его помощью создавать заглушки очень просто. Вот например:
Здесь создается мок объекта calendar и передается в объект calendarService. Далее моки инициализируются в методе setUp. Затем непосредственно внутри теста мок настраивается и тест проверяет isModern, если тип календаря разный или не задан вовсе. При этом не пришлось пересоздавать CalendarService, а создание моков и генерация возвращаемых значений заняло всего несколько строк.
5. Пишите осмысленные сообщения на случай падения теста. Самое часто встречающееся сообщение, которое я видел, разбирая упавшие тесты на TeamCity — это
Ну сразу же все понятно! Но бывает, что сообщение об ошибке все-таки есть, но пользы от него…
А вот уже хорошее, но еще не идеальное, сообщение, в котором сразу описано, что проверялось
Идеальным можно считать сообщение, которое не только показывает что мы проверяем, но и почему мы это ожидаем
Но здесь показана достаточно простая проверка, а если нужно сравнить пару массивов с данными в разном порядке? Какое сообщение нужно написать здесь? Для этого я советую воспользоваться фреймворком AssertJ. По ссылке много простых и понятных примеров использования, после которых вам захочется воспользоваться этим фреймворком! AssertJ позволяет меньше задумываться о написании сообщения в случае ошибки, а также проверить кучу всего одной строкой, экономя место. Например проверка:
Выдаст нам замечательное сообщение об ошибке:
И все понятно — что случилось и по какой причине! Можно идти и исправлять ошибки.
И еще раз — Адекватные сообщения в ошибках тестов экономят время и нервы тех, кто будет эти тесты разбирать. Возможно, это будете вы сами через год.
6. Убирайте за собой мусор (нет, это не про запуск GarbageCollector-a). Создали файл, сделали запись в базу или дернули ручку создания пользователя? Не поленитесь и почистите за собой после теста. Файлы копятся, база обрастает кучей мусора и в системе появляются толпы фейковых пользователей. Старайтесь сохраняйте в чистоте не только своё рабочее место, но и рабочее окружение. UPD Как правильно указали в комментариях, этот пункт относится только к интеграционному тестированию.
7. Проверьте, что тест запускается где-то еще, помимо вашей локальной машины. Если у вас есть сервер CI или какое-то другое место, где вы прогоняете тесты, проверьте, что тест запустился и там. Например, тесты на сервере CI запускаются из определенного пакета, а вы положили свой в другой пакет. Или тесты запускаются по определенному имени, например *UTest, а вы назвали свой класс TestUid. Или тесты запускаются по группам, а вы забыли проставить определенную группу для своего теста. Или… Можно придумать много случаев, когда свеженаписанный тест так ниразу и не запустится где-то кроме вашей локальной машины. И тогда пользы от него не так уж и много!
В результате получился небольшой список правил хорошего тона при написании Unit-тестов. Надеюсь эта статья поможет ещё немного поднять культуру тестов и тестирования в вашей команде. Если вам есть чем дополнить мой список, добро пожаловать в комментарии.
Тестирование библиотеки DLL C++
Область применения:Visual Studio Visual Studio для Mac Visual Studio Code
В этом разделе описывается один способ создания модульных тестов для библиотеки DLL на C++ для приложений на универсальной платформе Windows (UWP) с использованием среды тестирования Майкрософт для C++. Библиотека DLL RooterLib демонстрирует концепции теории пределов из математического анализа за счет реализации функции, которая вычисляет оценку квадратного корня из заданного числа. Библиотеки DLL могут быть включены в приложение UWP, демонстрирующее пользователям интересные вещи, которые можно сделать с помощью математических функций.
В этом разделе показано использование модульного тестирования в качестве первого шага разработки. При таком подходе сначала необходимо написать метод теста, который проверяет определенное поведение тестируемой системы, а затем написать код, который проходит этот тест. Порядок описанных ниже процедур можно изменить, и сначала написать код, который требуется протестировать, а затем написать сами модульные тесты.
В этом разделе также создается одно решение Visual Studio и отдельные проекты для модульных тестов и для тестируемой библиотеки DLL. Модульные тесты можно включить непосредственно в проект библиотеки DLL или создать отдельные решения для модульных тестов и для библиотеки DLL. Рекомендации по выбору структуры см. в разделе Добавление модульных тестов в существующие приложения C++.
Создание решения и проекта модульного теста
Начнем с создания нового тестового проекта. В меню Файл последовательно выберите пункты Создатьи >Проект. В диалоговом окне Создание нового проекта в поле поиска введите «тест» и задайте Язык как C++. В списке шаблонов проектов выберите Приложение модульного тестирования (универсальное приложение Windows).
- В диалоговом окне «Новый проект» разверните узел Установленные>Visual C++ и выберите Универсальные приложения Windows. В списке шаблонов проектов выберите Приложение модульного тестирования (универсальное приложение Windows).
- Назовите проект RooterLibTests , укажите расположение, назовите решение RooterLib , установите флажок Создать каталог для решения.
- В новом проекте откройте файл unittest1.cpp. Обратите внимание на следующие условия.
- Каждый тест определяется с использованием TEST_METHOD(YourTestName) <. >. Стандартную сигнатуру функции писать не требуется. Сигнатура создается макросом TEST_METHOD. Макрос создает функцию экземпляра, которая возвращает значение void. Она также создает статическую функцию, которая возвращает сведения о тестовом методе. Эти сведения позволят обозревателю тестов найти этот метод.
- Тестовые методы группируются в классы с помощью TEST_CLASS(YourClassName) <. >. Во время выполнения тестов создается экземпляр каждого тестового класса. Тестовые методы вызываются в неопределенном порядке. Можно задать особые методы, которые вызываются до и после каждого модуля, класса или метода. Дополнительные сведения см. в разделе Использование Microsoft.VisualStudio.TestTools.CppUnitTestFramework.
Проверка с помощью обозревателя тестов, что тесты запускаются
- Добавьте код теста:
TEST_METHOD(TestMethod1)
Обратите внимание, что класс Assert содержит несколько статических методов, которые можно использовать для проверки результатов в тестовых методах.
Добавление в решение проекта библиотеки DLL
В обозревателе решений выберите имя решения. В контекстном меню выберите команду Добавить, а затем — пункт Новый проект. В диалоговом окне Добавление нового проекта задайте Язык как C++ и введите «DLL» в поле поиска. В списке результатов выберите Приложение модульного тестирования (универсальная платформа Windows — C++ или CX).
- В диалоговом окне Добавление нового проекта выберите DLL (UWP apps) (DLL — приложения UWP).
- Добавьте следующий код в файл RooterLib.h:
// The following ifdef block is the standard way of creating macros which make exporting // from a DLL simpler. All files within this DLL are compiled with the ROOTERLIB_EXPORTS // symbol defined on the command line. This symbol should not be defined on any project // that uses this DLL. This way any other project whose source files include this file see // ROOTERLIB_API functions as being imported from a DLL, whereas this DLL sees symbols // defined with this macro as being exported. #ifdef ROOTERLIB_EXPORTS #define ROOTERLIB_API __declspec(dllexport) #else #define ROOTERLIB_API __declspec(dllimport) #endif //ROOTERLIB_EXPORTS class ROOTERLIB_API CRooterLib < public: CRooterLib(void); double SquareRoot(double v); >;
Комментарии содержат пояснения к блоку ifdef не только для разработчика библиотеки DLL, но и для тех, кто ссылается на библиотеку DLL в своих проектах. С помощью свойств проекта библиотеки DLL можно добавить в командную строку символ ROOTERLIB_EXPORTS. Класс CRooterLib объявляет конструктор и метод оценки SqareRoot .
- В обозревателе решений выберите проект RooterLib, а затем пункт Свойства в контекстном меню.
- В диалоговом окне страницы свойств RooterLib разверните узел Свойства конфигурации, затем — узел C++ и выберите параметр Препроцессор.
- Выберите пункт в списке Определения препроцессора, а затем добавьте ROOTERLIB_EXPORTS диалоговом окне Определения препроцессора.
// constructor CRooterLib::CRooterLib() < >// Find the square root of a number. double CRooterLib::SquareRoot(double v)
Сделать функции dll видимыми для тестового кода
- Добавьте RooterLib в проект RooterLibTests.
- В обозревателе решений выберите проект RooterLibTests, а затем в контекстном меню последовательно выберите Добавить>Ссылки.
- В диалоговом окне Добавление ссылки откройте вкладку Проекты. Затем выберите элемент RouterLib.
#include "..\RooterLib\RooterLib.h"
TEST_METHOD(BasicTest) < CRooterLib rooter; Assert::AreEqual( // Expected value: 0.0, // Actual value: rooter.SquareRoot(0.0), // Tolerance: 0.01, // Message: L"Basic test failed", // Line number - used if there is no PDB file: LINE_INFO()); >
- Постройте решение. Новый тест появится в обозревателе тестов в узле Незапускавшиеся тесты.
- В обозревателе тестов выберите Запустить все. Вы настроили тест и проекты кода и подтвердили, что можно выполнять тесты, которые запускают функции из проекта кода. Теперь можно начать писать реальные тесты и код.
Итеративное расширение тестов и обеспечение их успешного выполнения
- Добавьте новый тест.
TEST_METHOD(RangeTest) < CRooterLib rooter; for (double v = 1e-6; v < 1e6; v = v * 3.2) < double expected = v; double actual = rooter.SquareRoot(v*v); double tolerance = expected/1000; Assert::AreEqual(expected, actual, tolerance); >>;
Совет Рекомендуется не изменять пройденные тесты. Вместо этого добавьте новый тест, обновите код так, чтобы тест проходил успешно, а затем добавьте еще один тест и т. д. При изменении пользователями требований отключите тесты, которые больше не являются корректными. Создайте новые тесты и сделайте так, чтобы они работали по одному в инкрементном режиме.
Совет Убедитесь в том, что каждый тест завершается сбоем, сразу после того, как вы написали его. Это поможет избежать распространенной ошибки, заключающейся в написании теста, который никогда не завершается сбоем.
#include . // Find the square root of a number. double CRooterLib::SquareRoot(double v) < double result = v; double diff = v; while (diff >result/1000) < double oldResult = result; result = result - (result*result - v)/(2*result); diff = abs (oldResult - result); >return result; >
Разрабатывайте код, добавляя тесты по одному. После каждой итерации проверяйте, все ли тесты завершаются успешно.
Отладка непройденного теста
- Добавьте еще один тест в файл unittest1.cpp:
// Verify that negative inputs throw an exception. TEST_METHOD(NegativeRangeTest) < wchar_t message[200]; CRooterLib rooter; for (double v = -0.1; v >-3.0; v = v - 0.5) < try < // Should raise an exception: double result = rooter.SquareRoot(v); swprintf_s(message, L"No exception for input %g", v); Assert::Fail(message, LINE_INFO()); >catch (std::out_of_range ex) < continue; // Correct exception. >catch (. ) < swprintf_s(message, L"Incorrect exception for %g", v); Assert::Fail(message, LINE_INFO()); >> >;
- Установите точку останова перед функцией SquareRoot .
- В контекстном меню непройденного теста выберите Отладить выбранные тесты. Когда выполнение прекратится на точке останова, выполните код по шагам.
- Добавьте код в файл RooterLib.cpp, чтобы перехватить исключение:
#include . double CRooterLib::SquareRoot(double v) < //Validate the input parameter: if (v < 0.0) < throw std::out_of_range("Can't do square roots of negatives"); >.
Теперь все тесты проходят успешно.
Рефакторинг кода без изменения тестов
- Упростите основной расчет функции SquareRoot :
// old code //result = result - (result*result - v)/(2*result); // new code result = (result + v/result) / 2.0;
Совет Стабильный набор хороших модульных тестов придает уверенность в том, что изменение кода не привело к появлению ошибок. Рефакторинг должен осуществляться отдельно от других изменений.