Введение
Этим уроком открывается цикл практических занятий по написанию тестов с применением различных инструментов.
unittest - инструмент для тестирования в Python.
Это стандартный модуль для написания юнит-тестов на Python. Unittest это порт JUnit с Java. Иными словами, и в коде модуля, и при написании тестов легко прослеживается ООП стиль, что весьма удобно для тестирования процедур и классов.
Документация доступна по следующим ссылкам: python3, python2
В данном инструменте много возможностей: проверки (assert*), декораторы, позволяющие пропустить отдельный тест (@skip, @skipIf) или обозначить сломанные тесты (@expectedFailure) и этим не заканчивается список. Использование assert'ов с лихвой покрывает нужды при написании тестов.
Описание unittest
Полезная черта unittest - автоматизированное тестирование. Есть и другие:
- можно собирать тесты в группы
- собирать результаты выполнения тестов (например, для отчета)
- ООП стиль позволяет уменьшить дублирование кода при схожих объектах тестирования
В использовании unittest присутствуют несколько концепций
test case
test case -это наименьшая единица тестирования. Он проверяет конкретный ответ для конкретного набора входных данных.
test suite
test suite представляет собой сборник тестовых случаев, тестовых наборов. Используется для агрегирования тестов, которые должны выполняться вместе.
test fixture
test fixture - это фиксированное состояние объектов используемых в качестве исходного при выполнении тестов.
Цель использования fixture - если у вас сложный test case, то подготовка нужного состояния легко может занимать много ресурсов (например, вы считаете функцию с определенной точностью и каждый следующий знак точности в расчетах занимает день). Используя fixture (на сленге - фикстуры) предварительную подготовку состояния пропускаем и сразу приступаем к тестированию.
Test fixture может выступать, например, в виде:
- состояние базы данных
- набор переменных среды
- набор файлов с необходимым содержанием.
test runner
test runner - это компонент, который организует выполнение тестов и предоставляет результат пользователю.
Рекомендации к написанию тестов
При написании тестов следует исходить из следующих принципов:
- Работа теста не должна зависеть от результатов работы других тестов.
- Тест должен использовать данные, специально для него подготовленные, и никакие другие.
- Тест не должен требовать ввода от пользователя
- Тесты не должны перекрывать друг друга (не надо писать одинаковые тесты 20 раз). Можно писать частично перекрывающие тесты.
- Нашел баг -> напиши тест
- Тесты надо поддерживать в рабочем состоянии
- Модульные тесты не должны проверять производительность сущности (класса, функции)
- Тесты должны проверять не только то, что сущность работает корректно на корректных данных, но и то что ведет себя адекватно при некорректных данных.
- Тесты надо запускать регулярно
Практика
К написанию тестов стоит относится также как и к основному коду.
Написание тестов является хорошей инвестицией в будущее программы:
- Когда ваша программа становится настолько большой, что не помещается целиком у вас в голове, то это отличный звоночек, что стоит покрывать все тестами
- Если сейчас ваша программа не испытывает проблем, то через какое-то время библиотеки, которые вы используете, могут начать обновляться без обратной совместимости. Вот здесь-то тесты помогут
- Когда вы занимаетесь рефакторингом кода - тесты помогут не сломать лишнего
Есть и другие причины писать тесты. В целом, практика показывает, что до тестов надо дорасти - в какой-то момент приходит понимание зачем же тратить на них время.
В качестве примеров использования unittest продемонстрирую и опишу основные возможности модуля. На мой взгляд это те 20% которые помогут сделать 80% результата.
Пример синтаксиса №1
Рассмотрим следующий код:
В данном примере показан общий шаблон для большинства тестов - здесь и наследование от TestCase, здесь и два простых теста, а также перегрузка встроенных в TestCase методов:
- Метод def setUp(self) вызывается ПЕРЕД каждым тестом.
- Метод def tearDown(self) вызывается ПОСЛЕ каждого теста
Список подобных готовых функций такой:
- setUp – подготовка прогона теста; вызывается перед каждым тестом.
- tearDown – вызывается после того, как тест был запущен и результат записан. Метод запускается даже в случае исключения (exception) в теле теста.
- setUpClass – метод вызывается перед запуском всех тестов класса.
- tearDownClass – вызывается после прогона всех тестов класса.
- setUpModule – вызывается перед запуском всех классов модуля.
- tearDownModule – вызывается после прогона всех тестов модуля.
Если запустить скрипт:
То получим:
Теперь мы знаем как писать тест, как запускать. Перейдем к освещению вариантов использования тестов.
Пример синтаксиса №2 - №...
Прямо в док-строках описал когда выбрасывают ошибки эти проверки. В примере показаны как новые названия, так и их старые аналоги (в старых проектах такие еще встречаются)
Практические примеры
Перейдем к более практическим примерам. На них и рассмотрим еще некоторые важные способности unittest
- Как тестировать конструктор?
- Как тестировать структуры данных?
- Как тестировать работу с БД?
Как тестировать конструктор?
Приведу пример, как можно протестировать конструктор
В коде видно, что есть класс, в конструкторе которого кидается исключение если значение аргумента не удовлетворяет условию.
Это условие ловится функцией self.assertRaises или ее старым названием self.failUnlessRaises
Во втором примере показан тест на количество аргументов в классе.
Как тестировать структуру данных?
Продемонстрирую пример тестирования структуры данных. В данном случае есть класс Point, в котором хранится два float’а x,y. А дальше весь код такой же как и для других тестов.
Как тестировать работу с БД?
Примера под тестирование БД не приведу, однако, расскажу об общих соображениях.
Работа с базой не так проста, как с обычной функцией, ведь база - это не просто программный код, база данных - это объект, сохраняющий своё состояние. И если мы начнём в процессе тестирования изменять данные в базе, то после каждого теста база будет изменяться. Это может помешать последующим тестам и необратимо испортить базу данных.
Ключ к решению проблемы - транзакции. Одна из особенностей этого механизма состоит в том, что до тех пор пока транзакция не завершена, вы всегда можете отменить все изменения и вернуть базу в состояние на момент начала транзакции.
Алгоритм такой:
- открываем транзакцию;
- если нужно, выполняем подготовительные действия для тестирования;
- выполняем модульный тест (или просто запускаем сценарий, работу которого хотим проверить);
- проверяем результат работы сценария;
- отменяем транзакцию, возвращая базу данных в исходное состояние.
Даже если в тестируемом коде останутся незакрытые транзакции, внешний ROLLBACK всё равно откатит все изменения корректно.
Помимо транзакций стоит обратить внимание, что тестирование следует делать на тестовой базе данных. Идеальным случаем будет полная инициализация окружения перед исполнением теста.
Выводы
В уроке описаны базовые элементы работы с unittest. Не были затронуты статусы тестов, отчеты о тестировании. Однако, данной теории хватит для написания тестов. Давайте это сейчас и проверим, переходите к практике.
<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 20:00:26 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="EqZaZLJ0mRXA090POOCqnMMNNY8F9_lqy6YZ_BFE2yL9d5FTQAo0dXaQ-Zc071rrAwQYJQ3AB8h2RoOoQ0M8TA";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Мир Python: тестирование с помощью unittest | Python для продвинутых</title>
<meta name="description" content="Мир Python: тестирование с помощью unittest / Python для продвинутых: В уроке вы узнаете как писать свои тесты. Как сделать свои программы стабильными.">
<link rel="canonical" href="https://ru.hexlet.io/courses/advanced_python/lessons/python_testing_unittest/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Мир Python: тестирование с помощью unittest">
<meta property="og:title" content="Python для продвинутых">
<meta property="og:description" content="Мир Python: тестирование с помощью unittest / Python для продвинутых: В уроке вы узнаете как писать свои тесты. Как сделать свои программы стабильными.">
<meta property="og:url" content="https://ru.hexlet.io/courses/advanced_python/lessons/python_testing_unittest/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="3hDMtCz9OxibJQi8bQIJBdqgo2ZhMv9DuQZfa0KaDpUxwQeD3oOWeC1mLCRhDflyGqmOzGkFAeEE5sU_EJ3p-w" />
<script src="/vite/assets/inertia-DfXos102.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-cb8xch9l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzczMSwicHVyIjoiYmxvYl9pZCJ9fQ==--f5df4883f3f678321cb4fa96e9ce657bd5ee1adf/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Static%20website-cuate.png"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T20:00:26.337Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"ZJH3tbt8KZe8kJ-cJODCF51kIyOA9qUL-akS0nCQcciLQDyCSQKE9wrTuwQo7zJgXW0OiYjBW6lESYiGIpeWpg","topics":[{"id":1674,"title":"написав решение для первой функции.\nЯ для test_task2_func1 и для test_task2_func1 прописал pass\nпри этом проверка полностью прошла, хотя задание не выполнено.\nКакая-то ошибка.\nПроверьте пожалуйста.","plain_title":"написав решение для первой функции. Я для testtask2func1 и для testtask2func1 прописал pass при этом проверка полностью прошла, хотя задание не выполнено. Какая-то ошибка. Проверьте пожалуйста. ","creator":{"public_name":"Андрей Волосович","id":65096,"is_tutor":false},"comments":[{"creator":{"public_name":"Test Test","id":70939,"is_tutor":false},"id":4098,"body":"А что вы считаете за выполнение задания? Контролировать сколько тестов вы написали?","topic_id":1674},{"creator":{"public_name":"Дмитрий Старцев","id":84768,"is_tutor":false},"id":4129,"body":"Правильная проверка тестов - это их прогон на функциях с ошибками. Тесты должны находить ошибки и фейлиться.","topic_id":1674}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мир Python: тестирование с помощью unittest","entity_url":null,"active":true}},{"id":913,"title":"очень сырой курс, скачете галопом по европам, множество грамматических ошибок.","plain_title":"очень сырой курс, скачете галопом по европам, множество грамматических ошибок. ","creator":{"public_name":"Василий Колесников","id":81,"is_tutor":false},"comments":[{"creator":{"public_name":"navi","id":46766,"is_tutor":false},"id":1836,"body":"Очень сложно трудно уследить за мыслью автора курса, теория для \"галочки\", практические задания не логичны и труднопонимаемы. ","topic_id":913}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мир Python: тестирование с помощью unittest","entity_url":null,"active":true}}],"lesson":{"exercise":null,"units":[{"id":313,"name":"theory","url":"/courses/advanced_python/lessons/python_testing_unittest/theory_unit"},{"id":315,"name":"quiz","url":"/courses/advanced_python/lessons/python_testing_unittest/quiz_unit"}],"links":[],"ordered_units":[{"id":313,"name":"theory","url":"/courses/advanced_python/lessons/python_testing_unittest/theory_unit"},{"id":315,"name":"quiz","url":"/courses/advanced_python/lessons/python_testing_unittest/quiz_unit"}],"id":219,"slug":"python_testing_unittest","state":"approved","name":"Мир Python: тестирование с помощью unittest","course_order":300,"goal":"В уроке вы узнаете как писать свои тесты. Как сделать свои программы стабильными.","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"## Введение\n\n> Этим уроком открывается цикл практических занятий по написанию тестов с применением различных инструментов.\n\nunittest - инструмент для тестирования в Python.\nЭто стандартный модуль для написания юнит-тестов на Python. Unittest это порт JUnit с Java. Иными словами, и в коде модуля, и при написании тестов легко прослеживается ООП стиль, что весьма удобно для тестирования процедур и классов.\n\nДокументация доступна по следующим ссылкам: [python3](https://docs.python.org/3/library/unittest.html), [python2](https://docs.python.org/2/library/unittest.html)\n\nВ данном инструменте много возможностей: проверки (**assert***), декораторы, позволяющие пропустить отдельный тест (**@skip**, **@skipIf**) или обозначить сломанные тесты (**@expectedFailure**) и этим не заканчивается список. Использование assert'ов с лихвой покрывает нужды при написании тестов.\n\n\n## Описание unittest\n\nПолезная черта unittest - автоматизированное тестирование. Есть и другие:\n\n- можно собирать тесты в группы\n- собирать результаты выполнения тестов (например, для отчета)\n- ООП стиль позволяет уменьшить дублирование кода при схожих объектах тестирования\n\nВ использовании unittest присутствуют несколько концепций\n\n### test case\n\ntest case -это наименьшая единица тестирования. Он проверяет конкретный ответ для конкретного набора входных данных.\n\n### test suite\n\ntest suite представляет собой сборник тестовых случаев, тестовых наборов. Используется для агрегирования тестов, которые должны выполняться вместе.\n\n### test fixture\ntest fixture - это фиксированное состояние объектов используемых в качестве исходного при выполнении тестов.\n\nЦель использования fixture - если у вас сложный test case, то подготовка нужного состояния легко может занимать много ресурсов (например, вы считаете функцию с определенной точностью и каждый следующий знак точности в расчетах занимает день). Используя fixture (на сленге - фикстуры) предварительную подготовку состояния пропускаем и сразу приступаем к тестированию.\n\nTest fixture может выступать, например, в виде:\n- состояние базы данных\n- набор переменных среды\n- набор файлов с необходимым содержанием.\n\n### test runner\n\ntest runner - это компонент, который организует выполнение тестов и предоставляет результат пользователю.\n\n\n\n## Рекомендации к написанию тестов\n\nПри написании тестов следует исходить из следующих принципов:\n\n- Работа теста не должна зависеть от результатов работы других тестов.\n- Тест должен использовать данные, специально для него подготовленные, и никакие другие.\n- Тест не должен требовать ввода от пользователя\n- Тесты не должны перекрывать друг друга (не надо писать одинаковые тесты 20 раз). Можно писать частично перекрывающие тесты.\n- Нашел баг -> напиши тест\n- Тесты надо поддерживать в рабочем состоянии\n- Модульные тесты не должны проверять производительность сущности (класса, функции)\n- Тесты должны проверять не только то, что сущность работает корректно на корректных данных, но и то что ведет себя адекватно при некорректных данных.\n- Тесты надо запускать регулярно\n\n\n## Практика\n\nК написанию тестов стоит относится также как и к основному коду.\n\nНаписание тестов является хорошей инвестицией в будущее программы:\n\n- Когда ваша программа становится настолько большой, что не помещается целиком у вас в голове, то это отличный звоночек, что стоит покрывать все тестами\n- Если сейчас ваша программа не испытывает проблем, то через какое-то время библиотеки, которые вы используете, могут начать обновляться без обратной совместимости. Вот здесь-то тесты помогут\n- Когда вы занимаетесь рефакторингом кода - тесты помогут не сломать лишнего\n\nЕсть и другие причины писать тесты. В целом, практика показывает, что до тестов надо дорасти - в какой-то момент приходит понимание зачем же тратить на них время.\n\nВ качестве примеров использования unittest продемонстрирую и опишу основные возможности модуля. На мой взгляд это те 20% которые помогут сделать 80% результата.\n\n### Пример синтаксиса №1\n\n\nРассмотрим следующий код:\n\n\n```python\n# -*- encoding: utf-8 -*-\n\nimport unittest\n\n\nclass TestUM(unittest.TestCase):\n def setUp(self):\n pass\n\n def tearDown(self):\n pass\n\n def test_numbers_3_4(self):\n self.assertEqual(3 * 4, 12)\n\n def test_strings_a_3(self):\n self.assertEqual('a' * 3, 'aaa')\n\n\nif __name__ == '__main__':\n unittest.main()\n```\n\nВ данном примере показан общий шаблон для большинства тестов - здесь и наследование от TestCase, здесь и два простых теста, а также перегрузка встроенных в TestCase методов:\n\n- Метод def setUp(self) вызывается _ПЕРЕД_ каждым тестом.\n- Метод def tearDown(self) вызывается _ПОСЛЕ_ каждого теста\n\nСписок подобных готовых функций такой:\n\n- setUp – подготовка прогона теста; вызывается перед каждым тестом.\n- tearDown – вызывается после того, как тест был запущен и результат записан. Метод запускается даже в случае исключения (exception) в теле теста.\n- setUpClass – метод вызывается перед запуском всех тестов класса.\n- tearDownClass – вызывается после прогона всех тестов класса.\n- setUpModule – вызывается перед запуском всех классов модуля.\n- tearDownModule – вызывается после прогона всех тестов модуля.\n\n Если запустить скрипт:\n\n```bash\npython example.py\n```\n\nТо получим:\n\n\n```python\npython example1.py\n..\n----------------------------------------------------------------------\nRan 2 tests in 0.000s\n\n\nOK\n```\n\n\nТеперь мы знаем как писать тест, как запускать. Перейдем к освещению вариантов использования тестов.\n\n\n### Пример синтаксиса №2 - №...\n\n\n```python\n# -*- encoding: utf-8 -*-\n\nimport unittest\n\n\nclass TestUM(unittest.TestCase):\n\n def testAssertTrue(self):\n \"\"\"\n Вызывает ошибку если значение аргумента != True\n :return:\n \"\"\"\n self.assertTrue(True)\n\n def testFailUnless(self):\n \"\"\"\n Устаревшее название для assertTrue()\n Вызывает ошибку если значение аргумента != True\n :return:\n \"\"\"\n self.failUnless(True)\n\n def testFailIf(self):\n \"\"\"\n Устаревшая функция, теперь называется assertFalse()\n :return:\n \"\"\"\n self.failIf(False)\n\n def testAssertFalse(self):\n \"\"\"\n Если значение аргумент != False, то кидает ошибку\n :return:\n \"\"\"\n self.assertFalse(False)\n\n def testEqual(self):\n \"\"\"\n Проверка равенства двух аргументов\n :return:\n \"\"\"\n self.failUnlessEqual(1, 3 - 2)\n\n def testNotEqual(self):\n \"\"\"\n Проверка НЕ равенства двух аргументов\n :return:\n \"\"\"\n self.failIfEqual(2, 3 - 2)\n\n def testEqualFail(self):\n \"\"\"\n Ругается если значение аргументов равно\n :return:\n \"\"\"\n self.failIfEqual(1, 2)\n\n def testNotEqualFail(self):\n \"\"\"\n Ругается если значение аргументов не равно\n :return:\n \"\"\"\n self.failUnlessEqual(2, 3 - 1)\n\n def testNotAlmostEqual(self):\n \"\"\"\n Старое название функции.\n Теперь называется assertNotAlmostEqual()\n Сравнивает два аргумента с округлением, можно задать это округление\n Ругается если значения равны\n :return:\n \"\"\"\n self.failIfAlmostEqual(1.1, 3.3 - 2.0, places=1)\n\n def testAlmostEqual(self):\n \"\"\"\n Старое название функции\n Теперь называется assertAlmostEqual()\n Сравнивает два аргумента с округлением, можно задать это округление\n Ругается если значения не равны\n :return:\n \"\"\"\n self.failUnlessAlmostEqual(1.1, 3.3 - 2.0, places=0)\n\n\nif __name__ == '__main__':\n unittest.main()\n```\n\nПрямо в док-строках описал когда выбрасывают ошибки эти проверки. В примере показаны как новые названия, так и их старые аналоги (в старых проектах такие еще встречаются)\n\n\n### Практические примеры\n\n\nПерейдем к более практическим примерам. На них и рассмотрим еще некоторые важные способности unittest\n\n\n- Как тестировать конструктор?\n- Как тестировать структуры данных?\n- Как тестировать работу с БД?\n\n\n##### **Как тестировать конструктор?**\n\n\nПриведу пример, как можно протестировать конструктор\n\n\n```python\n# -*- encoding: utf-8 -*-\n\nimport unittest\n\n\nclass MyClass(object):\n def __init__(self, foo):\n if foo != 1:\n raise ValueError(\"foo is not equal to 1!\")\n\n\nclass MyClass2(object):\n def __init__(self):\n pass\n\n\nclass TestFoo(unittest.TestCase):\n def testInsufficientArgs(self):\n foo = 0\n self.failUnlessRaises(ValueError, MyClass, foo)\n\n def testArgs(self):\n self.assertRaises(TypeError, MyClass2, (\"fsa\", \"fds\"))\n\n\nif __name__ == '__main__':\n unittest.main()\n```\n\nВ коде видно, что есть класс, в конструкторе которого кидается исключение если значение аргумента не удовлетворяет условию.\nЭто условие ловится функцией self.assertRaises или ее старым названием self.failUnlessRaises\n\n\nВо втором примере показан тест на количество аргументов в классе.\n\n\n##### **Как тестировать структуру данных?**\nПродемонстрирую пример тестирования структуры данных. В данном случае есть класс Point, в котором хранится два float’а x,y. А дальше весь код такой же как и для других тестов.\n\n\n```python\n# -*- encoding: utf-8 -*-\n\nimport unittest\n\n\nclass Point(object):\n def __init__(self, x, y):\n self.x = float(x)\n self.y = float(y)\n\n def __str__(self):\n return f\"({self.x}, {self.y})\"\n\n def __eq__(self, other):\n return True if ((self.x == other.x) and (self.y == other.y)) else False\n\n def __ne__(self, other):\n return True if ((self.x != other.x) or (self.y != other.y)) else False\n\n\nclass TestPoint(unittest.TestCase):\n def setUp(self):\n self.A = Point(5, 6)\n self.B = Point(6, 10)\n self.C = Point(5.0, 6.0)\n self.D = Point(-5, -6)\n\n def test_init(self):\n self.assertEqual((self.A.x, self.A.y), (float(5), float(6)), \"Полученные значения не являются вещественными!!!\")\n self.assertEqual((self.B.x, self.B.y), (float(6), float(10)),\n \"Полученные значения не являются вещественными!!!\")\n self.assertEqual((self.C.x, self.C.y), (float(5), float(6)), \"Полученные значения не являются вещественными!!!\")\n self.assertEqual((self.D.x, self.D.y), (float(-5), float(-6)),\n \"Полученные значения не являются вещественными!!!\")\n\n def test_str(self):\n self.assertTrue(str(self.A) == \"(5.0, 6.0)\", \"Неправильный вывод на экран!!!\")\n self.assertTrue(str(self.B) == \"(6.0, 10.0)\", \"Неправильный вывод на экран!!!\")\n self.assertTrue(str(self.C) == \"(5.0, 6.0)\", \"Неправильный вывод на экран!!!\")\n self.assertTrue(str(self.D) == \"(-5.0, -6.0)\", \"Неправильный вывод на экран!!!\")\n\n def test_eq(self):\n self.assertTrue(self.A == self.C,\n \"Данные две точки равны, а в результате тестирования, они оказались неравными!!!\")\n self.assertFalse(self.A == self.B,\n \"Данные две точки неравны, а в результате тестирования, они оказались равными!!!\")\n self.assertFalse(self.A == self.D,\n \"Данные две точки неравны, а в результате тестирования, они оказались равными!!!\")\n\n def test_ne(self):\n self.assertFalse(self.A != self.C,\n \"Данные две точки равны, а в результате тестирования, они оказались неравными!!!\")\n self.assertTrue(self.A != self.B,\n \"Данные две точки неравны, а в результате тестирования, они оказались равными!!!\")\n self.assertTrue(self.A != self.D,\n \"Данные две точки неравны, а в результате тестирования, они оказались равными!!!\")\n\n\nif __name__ == '__main__':\n unittest.main()\n```\n\n##### **Как тестировать работу с БД?**\n\nПримера под тестирование БД не приведу, однако, расскажу об общих соображениях.\n\nРабота с базой не так проста, как с обычной функцией, ведь база - это не просто программный код, база данных - это объект, сохраняющий своё состояние. И если мы начнём в процессе тестирования изменять данные в базе, то после каждого теста база будет изменяться. Это может помешать последующим тестам и необратимо испортить базу данных.\n\nКлюч к решению проблемы - транзакции. Одна из особенностей этого механизма состоит в том, что до тех пор пока транзакция не завершена, вы всегда можете отменить все изменения и вернуть базу в состояние на момент начала транзакции.\nАлгоритм такой:\n\n1. открываем транзакцию;\n2. если нужно, выполняем подготовительные действия для тестирования;\n3. выполняем модульный тест (или просто запускаем сценарий, работу которого хотим проверить);\n4. проверяем результат работы сценария;\n5. отменяем транзакцию, возвращая базу данных в исходное состояние.\n\nДаже если в тестируемом коде останутся незакрытые транзакции, внешний ROLLBACK всё равно откатит все изменения корректно.\n\n\nПомимо транзакций стоит обратить внимание, что тестирование следует делать на тестовой базе данных. Идеальным случаем будет полная инициализация окружения перед исполнением теста.\n\n\n## Выводы\n\nВ уроке описаны базовые элементы работы с unittest. Не были затронуты статусы тестов, отчеты о тестировании. Однако, данной теории хватит для написания тестов. Давайте это сейчас и проверим, переходите к практике.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":301,"name":"theory","url":"/courses/advanced_python/lessons/python_exceptions/theory_unit"},{"id":303,"name":"quiz","url":"/courses/advanced_python/lessons/python_exceptions/quiz_unit"}],"links":[],"ordered_units":[{"id":301,"name":"theory","url":"/courses/advanced_python/lessons/python_exceptions/theory_unit"},{"id":303,"name":"quiz","url":"/courses/advanced_python/lessons/python_exceptions/quiz_unit"}],"id":211,"slug":"python_exceptions","state":"approved","name":"Мир Python: исключения","course_order":100,"goal":"Вы узнаете об исключениях, как их использовать, как создавать свои и многое другое","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"## Введение\n\nВ этом уроке вы узнаете о важном средстве языка, без которого крупная программа не может обойтись. Речь пойдет об исключениях. Что это такое, как ими пользоваться и как создавать собственные?\n\nИсключительные ситуации или исключения (exceptions) – это ошибки, обнаруженные при исполнении. Например, к чему приведет попытка чтения несуществующего файла? Или если файл был случайно удален пока программа работала? Такие ситуации обрабатываются при помощи исключений.\n\nЕсли же Python не может понять, как обойти сложившуюся ситуацию, то ему не остается ничего кроме как поднять руки и сообщить, что обнаружил ошибку. В общем, исключения необходимы, чтобы сообщать программисту об ошибках.\n\nПростейший пример исключения - деление на ноль:\n\n\n```python\n>>> 100 / 0\nTraceback (most recent call last):\n File \"\", line 1, in\n 100 / 0\nZeroDivisionError: division by zero\n```\n\nВ данном случае интерпретатор сообщил нам об исключении ZeroDivisionError – делении на ноль.\n\n## Traceback\n\nВ большой программе исключения часто возникают внутри. Чтобы упростить программисту понимание ошибки и причины такого поведения Python предлагает Traceback или в сленге – трэйс. Каждое исключение содержит краткую информацию, но при этом полную, информацию о месте появления ошибки. По трэйсу найти и исправить ошибку становится проще.\n\nРассмотрим такой пример:\n\n```python\nTraceback (most recent call last):\n File \"/home/username/Develop/test/app.py\", line 862, in _handle\n return route.call(**args)\n File \"/home/username/Develop/test/app.py\", line 1729, in wrapper\n rv = callback(*a, **ka)\n File \"/home/username/Develop/test/__init__.py\", line 76, in wrapper\n body = callback(*args, **kwargs)\n File \"/home/username/Develop/test/my_app.py\", line 16, in index\n raise Exception('test exception')\n```\nВ данном примере чётко видно, какой путь исполнения у программы. Смотрим снизу вверх и по шагам понимаем, как же мы докатились до такого исключения.\n\nРассмотрим какие ещё встречаются комментарии к исключениям:\n\n```python\n>>> 2 + '1'\n\nTraceback (most recent call last):\n File \"\", line 1, in\n 2 + '1'\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n```\n\nВ данном примере при попытке сложить целое число и строку мы получаем исключение TypeError. В описании сразу же становится ясно, что же мы не так написали.\n\n```python\n>>> int('qwerty')\n\nTraceback (most recent call last):\n File \"\", line 1, in\n int('qwerty')\nValueError: invalid literal for int() with base 10: 'qwerty'\n```\n\nПриведение строчки к целому числу приводит к исключению ValueError.\n\nВ трэйсе этих двух примеров можно прочесть, что в таком-то файле на такой-то строчке есть ошибки.\n\nНа этом список встроенных исключений не заканчивается, в следующем разделе рассмотрены основные исключения и причины их возникновения.\n\n## Иерархия исключений\n\nИсключение, которое вы не увидите при выполнении кода – это BaseException – базовое исключение, от которого берут начало остальные.\n\nВ иерархии исключений две основные группы:\n\n- Системные исключения и ошибки\n- Обыкновенные исключения\n\nЕсли обработку первых лучше не делать (если и делать, то надо четко понимать для чего), то обработку вторых целиком и полностью Python возлагает на плечи программиста.\n\nК системным можно смело отнести:\n\n- SystemExit – исключение, порождаемое функцией sys.exit при выходе из программы.\n- KeyboardInterrupt – возникает при прерывании программы пользователем (обычно сочетанием клавиш Ctrl+C).\n- GeneratorExit — возникает при вызове метода close объекта generator.\n\nОстальные исключения – это \"обыкновенные\". Спектр уже готовых исключений велик.\n\nДля Python2 иерархию исключений можно представить так:\n\n[](http://i.imgur.com/2755APp.png)\n\nСписок исключений покрывает большой объем ситуаций и ошибок программиста. Если предупреждения (warning) только просят обратить внимание, то ошибки уже могут остановить исполнение программы.\n\nВ Python3 появились новые исключения и иерархия стала такова:\n\n[](http://i.imgur.com/AenGQYk.png)\n\nВ целом заметно, что при создании Python3 добавлен блок новых исключений. Но даже этих почти 70 исключений не хватает при написании программ на языке Python.\n\n## Использование исключений\n\nМы рассмотрели что такое исключения, какие они бывают и как их анализировать. Но до сих пор явно не рассмотрели такую важную вещь, как их использование.\n\nНачнем с обработки.\n\n### Обработка исключений\n\nДавайте рассмотрим случай с делением на 0.\n\n```python\n>>> a = 100\n>>> b = 0\n>>> c = a / b\n```\n\nДанный код приведет к исключению ZeroDivisionError. Чтобы этого не случилось, воспользуемся конструкцией `try..except`, например, так:\n\n```python\n>>> try:\n... a = 100\n... b = 0\n... c = a / b\n... except ZeroDivisionError as e:\n... print(e)\n...\ndivision by zero\n```\n\nЕсли исполнить этот код, то на консоль будет выведена строка \"*integer division or modulo by zero*\". Казалось бы, что толком ничего это нам не дало, ошибка все также есть. Однако в блок except можно поместить обработку.\n\nНапример, мы условились, что значение переменной *c* в случае ошибки деления равно -1. Тогда модифицируем код:\n\n```python\n>>> try:\n... a = 100\n... b = 0\n... c = a / b\n... except ZeroDivisionError as e:\n... c = -1\n>>> c\n-1\n```\n\nПеред тем как идти дальше, рассмотрим ещё одну возможность.\n\nПускай у нас файл с данными в файловой системе, и необходимо его прочитать. В этом случае сразу же всплывают несколько исключительных ситуаций, такие как: нет файла, файл битый, файл пустой (по заданию мы знаем, что в нём данные) и другие.\n\nИспользуя исключения, можно вот так решить эту задачу:\n\n```python\ntry:\n filepath = 'test_file.txt'\n with open(filepath, 'r') as fio:\n result = fio.readlines()\n if not result:\n raise Exception(\"File is empty\")\n\nexcept IOError as e:\n result = []\nexcept Exception as e:\n result = []\n print(e)\n```\n\nВ данном вымышленном коде новый ход – перехват нескольких видов исключений. Когда исключение брошено, оно сравнивается сверху вниз с каждым типом, пока не найдено совпадение. Если совпадения нет, то исключение пойдет наверх по цепочке исполнения кода.\n\nЕсли обработка для разных типов исключений одинакова, то уменьшить количество кода становится не проблемой:\n\n```python\ntry:\n your_code\nexcept (IOError, Exception) as e:\n print(e)\n```\n\n### Вызов исключений\n\nПри работе с исключениями программист тратит большую часть времени на обработку, но при этом возникают ситуации, когда исключениями надо и бросать в других.\n\nНа сленге программистов \"бросить исключение\" означает написать код, который при исполнении будет инициировать исключительную ситуацию.\n\nНапример, функция, которая решает квадратное уравнение. Вы условились, что корни только вещественные, тогда в случае комплексных корней стоит бросить исключение.\n\nЧтобы бросить исключение необходимо воспользоваться **raise**\n\nПример:\n\n```python\nraise IOError(\"текст исключения\")\n```\n\nгде IOError это класс исключения.\n\nЕсли при обработке исключения вы желаете пробросить его ещё выше, то следует написать такой код:\n\n```python\ntry:\n your_code\nexcept Exception as e:\n raise\n```\n\n### Собственные исключения\n\nПри написании собственных программ разумное желание добавить выразительности коду, а так же обратить внимание других программистов на особые исключительные ситуации. Для решения этой задачи стоит использовать собственные исключения.\n\nВ минимальном исполнении необходимо наследоваться от какого-нибудь класса в иерархии исключений. Например так:\n\n```python\nclass MyException(Exception):\n pass\n```\n\nТогда можно бросить своё исключение:\n\n```python\nraise MyException(Exception)\n```\n\nЛегко заметить, мы создаем класс, а значит всё, что мы знаем о классах, справедливо и для исключений. Можно завести переменные и делать их обработку.\n\nКак правило, исключения это очень маленькие классы. Они должны выполняться максимально быстро.\n\n## Дополнение: Полная форма try..except\n\nФорма `try...except` не полная, полной же является `try..except..else..finally`.\n\nПрименение полной конструкции может заметно упростить код, а также сделать его более безопасным.\n\nПредставим, что в программе происходит чтение файла и необходимо убедиться, что объект файла был корректно закрыт и что не возникло никакого исключения. Этого можно достичь с применением блока finally.\n\nИными словами, finally выполняет блок инструкций в любом случае, было ли исключение, или нет. А инструкция else выполняется в том случае, если исключения не было.\n\nВ целом, использование полной формы таково:\n\n```python\ntry:\n исполяем какой-то код\nexcept Exception as e:\n обработка исключения\nelse:\n код, который будет исполнен в случае, когда не возникает исключения\nfinally:\n код, который гарантированно будет исполнен последним (всегда исполняется)\n```\n\n## Выводы\n\nВ уроке рассмотрены вопросы связанные с исключениями:\n\n- Что такое исключение\n- Какие типы исключений присутствуют в языке\n- Как обрабатывать исключения\n- Как вызвать исключения\n- Как создавать собственные исключения\n"},"id":50,"slug":"advanced_python","challenges_count":0,"name":"Python для продвинутых","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"free","description":"Текстовый курс, посвященный более глубоким темам языка программирования Python.","kind":"sandbox","updated_at":"2026-01-20T11:55:13.437Z","language":"python","duration_cache":6360,"skills":["Поддерживать существующий код","Отладка и написание тестов","CI/CD"],"keywords":[],"lessons_count":5,"cover":"/vite/assets/course-DLoqF1jj.png"},"recommendedLandings":[{"stack":{"id":7,"slug":"python","title":"Python-разработчик","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"finished","order":10,"duration_in_months":10},"id":7,"slug":"python","title":"Python-разработчик ","subtitle":"Изучите Python, Django, REST и Fast API для создания веб-приложений","subtitle_for_lists":"Изучите Python, Django, REST и Fast API для создания веб-приложений","locale":"ru","current":true,"duration_in_months_text":"10 месяцев","stack_slug":"python","price_text":"от 6 792 ₽","duration_text":"10 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzczMSwicHVyIjoiYmxvYl9pZCJ9fQ==--f5df4883f3f678321cb4fa96e9ce657bd5ee1adf/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Static%20website-cuate.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":true,"accessToCourseExists":true},"url":"/courses/advanced_python/lessons/python_testing_unittest/theory_unit","version":"8f286f6358a90a7bef2263b3a6edf5a90a94fa42","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Python для продвинутых</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Мир Python: тестирование с помощью unittest</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Мир Python: тестирование с помощью unittest","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"Python для продвинутых"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><h2 id="heading-2-1">Введение</h2>
<blockquote>
<p>Этим уроком открывается цикл практических занятий по написанию тестов с применением различных инструментов.</p>
</blockquote>
<p>unittest - инструмент для тестирования в Python.
Это стандартный модуль для написания юнит-тестов на Python. Unittest это порт JUnit с Java. Иными словами, и в коде модуля, и при написании тестов легко прослеживается ООП стиль, что весьма удобно для тестирования процедур и классов.</p>
<p>Документация доступна по следующим ссылкам: <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://docs.python.org/3/library/unittest.html" rel="noopener noreferrer" target="_blank">python3</a>, <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://docs.python.org/2/library/unittest.html" rel="noopener noreferrer" target="_blank">python2</a></p>
<p>В данном инструменте много возможностей: проверки (<strong>assert</strong>*), декораторы, позволяющие пропустить отдельный тест (<strong>@skip</strong>, <strong>@skipIf</strong>) или обозначить сломанные тесты (<strong>@expectedFailure</strong>) и этим не заканчивается список. Использование assert'ов с лихвой покрывает нужды при написании тестов.</p>
<h2 id="heading-2-2">Описание unittest</h2>
<p>Полезная черта unittest - автоматизированное тестирование. Есть и другие:</p>
<ul>
<li>можно собирать тесты в группы</li>
<li>собирать результаты выполнения тестов (например, для отчета)</li>
<li>ООП стиль позволяет уменьшить дублирование кода при схожих объектах тестирования</li>
</ul>
<p>В использовании unittest присутствуют несколько концепций</p>
<h3 id="heading-3-3">test case</h3>
<p>test case -это наименьшая единица тестирования. Он проверяет конкретный ответ для конкретного набора входных данных.</p>
<h3 id="heading-3-4">test suite</h3>
<p>test suite представляет собой сборник тестовых случаев, тестовых наборов. Используется для агрегирования тестов, которые должны выполняться вместе.</p>
<h3 id="heading-3-5">test fixture</h3>
<p>test fixture - это фиксированное состояние объектов используемых в качестве исходного при выполнении тестов.</p>
<p>Цель использования fixture - если у вас сложный test case, то подготовка нужного состояния легко может занимать много ресурсов (например, вы считаете функцию с определенной точностью и каждый следующий знак точности в расчетах занимает день). Используя fixture (на сленге - фикстуры) предварительную подготовку состояния пропускаем и сразу приступаем к тестированию.</p>
<p>Test fixture может выступать, например, в виде:</p>
<ul>
<li>состояние базы данных</li>
<li>набор переменных среды</li>
<li>набор файлов с необходимым содержанием.</li>
</ul>
<h3 id="heading-3-6">test runner</h3>
<p>test runner - это компонент, который организует выполнение тестов и предоставляет результат пользователю.</p>
<h2 id="heading-2-7">Рекомендации к написанию тестов</h2>
<p>При написании тестов следует исходить из следующих принципов:</p>
<ul>
<li>Работа теста не должна зависеть от результатов работы других тестов.</li>
<li>Тест должен использовать данные, специально для него подготовленные, и никакие другие.</li>
<li>Тест не должен требовать ввода от пользователя</li>
<li>Тесты не должны перекрывать друг друга (не надо писать одинаковые тесты 20 раз). Можно писать частично перекрывающие тесты.</li>
<li>Нашел баг -> напиши тест</li>
<li>Тесты надо поддерживать в рабочем состоянии</li>
<li>Модульные тесты не должны проверять производительность сущности (класса, функции)</li>
<li>Тесты должны проверять не только то, что сущность работает корректно на корректных данных, но и то что ведет себя адекватно при некорректных данных.</li>
<li>Тесты надо запускать регулярно</li>
</ul>
<h2 id="heading-2-8">Практика</h2>
<p>К написанию тестов стоит относится также как и к основному коду.</p>
<p>Написание тестов является хорошей инвестицией в будущее программы:</p>
<ul>
<li>Когда ваша программа становится настолько большой, что не помещается целиком у вас в голове, то это отличный звоночек, что стоит покрывать все тестами</li>
<li>Если сейчас ваша программа не испытывает проблем, то через какое-то время библиотеки, которые вы используете, могут начать обновляться без обратной совместимости. Вот здесь-то тесты помогут</li>
<li>Когда вы занимаетесь рефакторингом кода - тесты помогут не сломать лишнего</li>
</ul>
<p>Есть и другие причины писать тесты. В целом, практика показывает, что до тестов надо дорасти - в какой-то момент приходит понимание зачем же тратить на них время.</p>
<p>В качестве примеров использования unittest продемонстрирую и опишу основные возможности модуля. На мой взгляд это те 20% которые помогут сделать 80% результата.</p>
<h3 id="heading-3-9">Пример синтаксиса №1</h3>
<p>Рассмотрим следующий код:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"># -*- encoding: utf-8 -*-
import unittest
class TestUM(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def test_numbers_3_4(self):
self.assertEqual(3 * 4, 12)
def test_strings_a_3(self):
self.assertEqual('a' * 3, 'aaa')
if __name__ == '__main__':
unittest.main()</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>В данном примере показан общий шаблон для большинства тестов - здесь и наследование от TestCase, здесь и два простых теста, а также перегрузка встроенных в TestCase методов:</p>
<ul>
<li>Метод def setUp(self) вызывается <em>ПЕРЕД</em> каждым тестом.</li>
<li>Метод def tearDown(self) вызывается <em>ПОСЛЕ</em> каждого теста</li>
</ul>
<p>Список подобных готовых функций такой:</p>
<ul>
<li>setUp – подготовка прогона теста; вызывается перед каждым тестом.</li>
<li>tearDown – вызывается после того, как тест был запущен и результат записан. Метод запускается даже в случае исключения (exception) в теле теста.</li>
<li>setUpClass – метод вызывается перед запуском всех тестов класса.</li>
<li>tearDownClass – вызывается после прогона всех тестов класса.</li>
<li>setUpModule – вызывается перед запуском всех классов модуля.</li>
<li>tearDownModule – вызывается после прогона всех тестов модуля.</li>
</ul>
<p>Если запустить скрипт:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">python example.py</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>То получим:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">python example1.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Теперь мы знаем как писать тест, как запускать. Перейдем к освещению вариантов использования тестов.</p>
<h3 id="heading-3-10">Пример синтаксиса №2 - №...</h3>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"># -*- encoding: utf-8 -*-
import unittest
class TestUM(unittest.TestCase):
def testAssertTrue(self):
"""
Вызывает ошибку если значение аргумента != True
:return:
"""
self.assertTrue(True)
def testFailUnless(self):
"""
Устаревшее название для assertTrue()
Вызывает ошибку если значение аргумента != True
:return:
"""
self.failUnless(True)
def testFailIf(self):
"""
Устаревшая функция, теперь называется assertFalse()
:return:
"""
self.failIf(False)
def testAssertFalse(self):
"""
Если значение аргумент != False, то кидает ошибку
:return:
"""
self.assertFalse(False)
def testEqual(self):
"""
Проверка равенства двух аргументов
:return:
"""
self.failUnlessEqual(1, 3 - 2)
def testNotEqual(self):
"""
Проверка НЕ равенства двух аргументов
:return:
"""
self.failIfEqual(2, 3 - 2)
def testEqualFail(self):
"""
Ругается если значение аргументов равно
:return:
"""
self.failIfEqual(1, 2)
def testNotEqualFail(self):
"""
Ругается если значение аргументов не равно
:return:
"""
self.failUnlessEqual(2, 3 - 1)
def testNotAlmostEqual(self):
"""
Старое название функции.
Теперь называется assertNotAlmostEqual()
Сравнивает два аргумента с округлением, можно задать это округление
Ругается если значения равны
:return:
"""
self.failIfAlmostEqual(1.1, 3.3 - 2.0, places=1)
def testAlmostEqual(self):
"""
Старое название функции
Теперь называется assertAlmostEqual()
Сравнивает два аргумента с округлением, можно задать это округление
Ругается если значения не равны
:return:
"""
self.failUnlessAlmostEqual(1.1, 3.3 - 2.0, places=0)
if __name__ == '__main__':
unittest.main()</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Прямо в док-строках описал когда выбрасывают ошибки эти проверки. В примере показаны как новые названия, так и их старые аналоги (в старых проектах такие еще встречаются)</p>
<h3 id="heading-3-11">Практические примеры</h3>
<p>Перейдем к более практическим примерам. На них и рассмотрим еще некоторые важные способности unittest</p>
<ul>
<li>Как тестировать конструктор?</li>
<li>Как тестировать структуры данных?</li>
<li>Как тестировать работу с БД?</li>
</ul>
<h5 id="heading-5-12"><strong>Как тестировать конструктор?</strong></h5>
<p>Приведу пример, как можно протестировать конструктор</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"># -*- encoding: utf-8 -*-
import unittest
class MyClass(object):
def __init__(self, foo):
if foo != 1:
raise ValueError("foo is not equal to 1!")
class MyClass2(object):
def __init__(self):
pass
class TestFoo(unittest.TestCase):
def testInsufficientArgs(self):
foo = 0
self.failUnlessRaises(ValueError, MyClass, foo)
def testArgs(self):
self.assertRaises(TypeError, MyClass2, ("fsa", "fds"))
if __name__ == '__main__':
unittest.main()</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>В коде видно, что есть класс, в конструкторе которого кидается исключение если значение аргумента не удовлетворяет условию.
Это условие ловится функцией self.assertRaises или ее старым названием self.failUnlessRaises</p>
<p>Во втором примере показан тест на количество аргументов в классе.</p>
<h5 id="heading-5-13"><strong>Как тестировать структуру данных?</strong></h5>
<p>Продемонстрирую пример тестирования структуры данных. В данном случае есть класс Point, в котором хранится два float’а x,y. А дальше весь код такой же как и для других тестов.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"># -*- encoding: utf-8 -*-
import unittest
class Point(object):
def __init__(self, x, y):
self.x = float(x)
self.y = float(y)
def __str__(self):
return f"({self.x}, {self.y})"
def __eq__(self, other):
return True if ((self.x == other.x) and (self.y == other.y)) else False
def __ne__(self, other):
return True if ((self.x != other.x) or (self.y != other.y)) else False
class TestPoint(unittest.TestCase):
def setUp(self):
self.A = Point(5, 6)
self.B = Point(6, 10)
self.C = Point(5.0, 6.0)
self.D = Point(-5, -6)
def test_init(self):
self.assertEqual((self.A.x, self.A.y), (float(5), float(6)), "Полученные значения не являются вещественными!!!")
self.assertEqual((self.B.x, self.B.y), (float(6), float(10)),
"Полученные значения не являются вещественными!!!")
self.assertEqual((self.C.x, self.C.y), (float(5), float(6)), "Полученные значения не являются вещественными!!!")
self.assertEqual((self.D.x, self.D.y), (float(-5), float(-6)),
"Полученные значения не являются вещественными!!!")
def test_str(self):
self.assertTrue(str(self.A) == "(5.0, 6.0)", "Неправильный вывод на экран!!!")
self.assertTrue(str(self.B) == "(6.0, 10.0)", "Неправильный вывод на экран!!!")
self.assertTrue(str(self.C) == "(5.0, 6.0)", "Неправильный вывод на экран!!!")
self.assertTrue(str(self.D) == "(-5.0, -6.0)", "Неправильный вывод на экран!!!")
def test_eq(self):
self.assertTrue(self.A == self.C,
"Данные две точки равны, а в результате тестирования, они оказались неравными!!!")
self.assertFalse(self.A == self.B,
"Данные две точки неравны, а в результате тестирования, они оказались равными!!!")
self.assertFalse(self.A == self.D,
"Данные две точки неравны, а в результате тестирования, они оказались равными!!!")
def test_ne(self):
self.assertFalse(self.A != self.C,
"Данные две точки равны, а в результате тестирования, они оказались неравными!!!")
self.assertTrue(self.A != self.B,
"Данные две точки неравны, а в результате тестирования, они оказались равными!!!")
self.assertTrue(self.A != self.D,
"Данные две точки неравны, а в результате тестирования, они оказались равными!!!")
if __name__ == '__main__':
unittest.main()</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<h5 id="heading-5-14"><strong>Как тестировать работу с БД?</strong></h5>
<p>Примера под тестирование БД не приведу, однако, расскажу об общих соображениях.</p>
<p>Работа с базой не так проста, как с обычной функцией, ведь база - это не просто программный код, база данных - это объект, сохраняющий своё состояние. И если мы начнём в процессе тестирования изменять данные в базе, то после каждого теста база будет изменяться. Это может помешать последующим тестам и необратимо испортить базу данных.</p>
<p>Ключ к решению проблемы - транзакции. Одна из особенностей этого механизма состоит в том, что до тех пор пока транзакция не завершена, вы всегда можете отменить все изменения и вернуть базу в состояние на момент начала транзакции.
Алгоритм такой:</p>
<ol>
<li>открываем транзакцию;</li>
<li>если нужно, выполняем подготовительные действия для тестирования;</li>
<li>выполняем модульный тест (или просто запускаем сценарий, работу которого хотим проверить);</li>
<li>проверяем результат работы сценария;</li>
<li>отменяем транзакцию, возвращая базу данных в исходное состояние.</li>
</ol>
<p>Даже если в тестируемом коде останутся незакрытые транзакции, внешний ROLLBACK всё равно откатит все изменения корректно.</p>
<p>Помимо транзакций стоит обратить внимание, что тестирование следует делать на тестовой базе данных. Идеальным случаем будет полная инициализация окружения перед исполнением теста.</p>
<h2 id="heading-2-15">Выводы</h2>
<p>В уроке описаны базовые элементы работы с unittest. Не были затронуты статусы тестов, отчеты о тестировании. Однако, данной теории хватит для написания тестов. Давайте это сейчас и проверим, переходите к практике.</p></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/python?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">10 месяцев</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">С нуля</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Python-разработчик </p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите Python, Django, REST и Fast API для создания веб-приложений</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzczMSwicHVyIjoiYmxvYl9pZCJ9fQ==--f5df4883f3f678321cb4fa96e9ce657bd5ee1adf/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Static%20website-cuate.png" alt="Python-разработчик " loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 6 792 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/advanced_python/lessons/python_testing_unittest/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 5</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/advanced_python/lessons/python_testing_unittest/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-Bukl1lYy.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>