<!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 16:26:27 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="3SFIYZ6rCfXqcBOS6wowPzo48YzL-bzd2XfdosAahWoy8INWbNWklVwzNwrnBcBI-jHcJsPOQn9kl0f2kh1iBA";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: Продвинутое тестирование</title>
<meta name="description" content="Мокинг / Python: Продвинутое тестирование: Знакомимся с мокингом и учимся отличать моки от стабов">
<link rel="canonical" href="https://ru.hexlet.io/courses/python-advanced-testing/lessons/mocking/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Мокинг">
<meta property="og:title" content="Python: Продвинутое тестирование">
<meta property="og:description" content="Мокинг / Python: Продвинутое тестирование: Знакомимся с мокингом и учимся отличать моки от стабов">
<meta property="og:url" content="https://ru.hexlet.io/courses/python-advanced-testing/lessons/mocking/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="Ji2lnY7l7Tbghzw0oXYvEuXkPWIb3Ya0ltizxweoXHPJ_G6qfJtAVlbEGKyted9lJe0QyBPqeBYrOCmTVa-7HQ" />
<script src="/vite/assets/inertia-INZxX8jp.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-nkZBEvfU.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-6pOtQ3OW.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="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzYyOSwicHVyIjoiYmxvYl9pZCJ9fQ==--f7aaf7f059fd35008c7c2fb722680196834212ab/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Usability%20testing-bro.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk2NSwicHVyIjoiYmxvYl9pZCJ9fQ==--84278a1852c9c6fb13b80a69f395bac6e47a422e/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Cloud%20sync-bro.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NTIyMiwicHVyIjoiYmxvYl9pZCJ9fQ==--0e824331c2dcddbe1fa1aaa4188500e364c8c38c/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-bro.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-26T16:26:27.386Z","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":"gTh_jGFEVDVEKX_BSGNuzTlbisnLzqZ2qB6Hzv-PAQlu6bS7kzr5VfJqW1lEbJ66-VKnY8P5WNQV_h2arYjmZw","topics":[{"id":65913,"title":"Добрый день!\n\nНе знаю как другим, но для меня блок \"Объекты\" и примеры в нем совершенно непонятны :( Мне кажется, тут не хватает пояснений.\n\nИ объясните, пожалуйста, почему пример с \"test_for_each\" является замыканием?","plain_title":"Добрый день! Не знаю как другим, но для меня блок \"Объекты\" и примеры в нем совершенно непонятны :( Мне кажется, тут не хватает пояснений. И объясните, пожалуйста, почему пример с \"testforeach\" является замыканием? ","creator":{"public_name":"Александр","id":438638,"is_tutor":false},"comments":[{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":139407,"body":"Александр, приветствую!\n\n> И объясните, пожалуйста, почему пример с \"test_for_each\" является замыканием?\n\nВ `for_each()` мы используем функцию, которая ссылается на список за её пределами.","topic_id":65913},{"creator":{"public_name":"Александр","id":438638,"is_tutor":false},"id":141555,"body":"**Sergey K.**, благодарю! То есть, если будет создан глобальный список и в некоей функции будет реализована работа с этим списком, то это считается замыканием?","topic_id":65913},{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":139408,"body":"И на доработку блока Объекты поставил тикет. Расширю теорию. ","topic_id":65913},{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":141926,"body":"Когда мы говорим о замыкании, то имеем ввиду запоминание значений в момент именно определения функции.","topic_id":65913}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мокинг","entity_url":null,"active":true}},{"id":87595,"title":"Не согласен с вопросами, исходя из вашей же теории сравнение по типу что лучше не верно. \n Моки и стабы используются в разных ситуациях и имеют разные цели, поэтому утверждения \"Лучше стабы, чем моки\" и \"Лучше моки, чем стабы\" являются некорректными","plain_title":"Не согласен с вопросами, исходя из вашей же теории сравнение по типу что лучше не верно. Моки и стабы используются в разных ситуациях и имеют разные цели, поэтому утверждения \"Лучше стабы, чем моки\" и \"Лучше моки, чем стабы\" являются некорректными ","creator":{"public_name":"Нико Гамсахурдия","id":420792,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":175510,"body":"Добрый день, однозначно что лучше сказать правда нельзя, но по возможности стоит избегать чрезмерного мокинга.","topic_id":87595}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мокинг","entity_url":null,"active":true}},{"id":74460,"title":"Приветствую ! Упражнение решил, но было сложно. Сразу не понятно что именно нужно в мок поместить. Понимание пришло только когда после нескольких неудачным попыток заглянул в файл functions.py Из него стало ясно, что аргумент log - это функция. Для меня это не очевидно было. Может стоит написать об этом в задании прямым текстом ? Ну или намекнуть как-то:D","plain_title":"Приветствую ! Упражнение решил, но было сложно. Сразу не понятно что именно нужно в мок поместить. Понимание пришло только когда после нескольких неудачным попыток заглянул в файл functions.py Из него стало ясно, что аргумент log - это функция. Для меня это не очевидно было. Может стоит написать об этом в задании прямым текстом ? Ну или намекнуть как-то:D ","creator":{"public_name":"Сергей Прохоров","id":415431,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":155236,"body":"Ага, спасибо, дополнил в описании.","topic_id":74460}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мокинг","entity_url":null,"active":true}},{"id":82517,"title":"На объектах вообще ничего не понятно. Что это? Объясните кто-нибудь пожалуйста)","plain_title":"На объектах вообще ничего не понятно. Что это? Объясните кто-нибудь пожалуйста) ","creator":{"public_name":"Николай Кузьмин","id":481138,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":168099,"body":"Добрый день, подскажите что именно осталось непонятным?","topic_id":82517}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мокинг","entity_url":null,"active":true}},{"id":88050,"title":"Честно, вообще мало что понятно, слишком заумно написано, часто приходится додумывать по своему(причем не факт что додумываю правильно).","plain_title":"Честно, вообще мало что понятно, слишком заумно написано, часто приходится додумывать по своему(причем не факт что додумываю правильно). ","creator":{"public_name":"Евгений Прутян","id":550708,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":176094,"body":"Добрый день, подскажите что именно вызвало у вас трудности?","topic_id":88050},{"creator":{"public_name":"Arseniy","id":449611,"is_tutor":false},"id":177176,"body":"**Ivan Mamtsev**, поддержку Евгения, вот с моей точки зрения в чем проблема: 1ый пункт составляет мое пожелание к курсам, 2ой как раз больше наверное будет откликаться у Евгения\n\n1. Бывает что я делаю перерывы в обучении или прыгаю между курсами и при очередном чтении натыкаюсь на фразу типа \"возьмем функцию из прошлого упражнения\" ну или просто указывает функцияся get_private_fork_names - я думаю, что в курсе следовало бы какие-то ссылки сделать на повторяющиеся элементы, например, в данном уроке можно в какой-нибудь справочной части в конце указать что делает функция и может указать где она присутствует в курсе, чтобы легче было ориентироваться. Видно, что в текущей теме используем pook для подмены ответа с определенного api на github, но потом вызываем get_private_fork_names('hexlet') - тут не помешало бы напомнить, что делает функция и почему аргументом передаем 'hexlet'\n\n2. В первой половине теории атрибут calls как будто показал число вызовов на подмененный адрес в переменной mock, но прямым текстом об этом не сказано - ну то есть как минимум неплохо было бы сразу ссылку дать на calls в документации под приведенным примером, а лучше как-то в тексте обозначить его значение.\nИ хотя далее вместе с модулем unitest появляется call_count с пояснением в виде комментария в примере, то в начале указывать calls без объяснений и после этого подводить черту \"вот что такое мокинг\" - это не очень работающий момент.\nВ таком случае студент скорее начинает думать, что он что-то пропустил, и это **в лучшем случает** мотивирует на искания перед прочтением статьи до конца, а **в худшем** демотивирует продолжать чтение и таким образом затрудняет усвоение\n\n3. Дополнительно, в заданиях встречается отсылка на другие задания, типа \"В отличие от предыдущей версии задания, здесь нас интересует...\" - в текущем уроке не понимаю на какую особенность в предыдущей задаче тут отсылка (в предыдущем задаче был манкипатчинг, но да, не было логгирования - действительно ли это важная для поиска решения информация?) - может следует ставить задачи без референсов друг от друга или хотя бы указывать ссылку текст конкретной задачи тогда?","topic_id":88050},{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":177552,"body":"Добрый день, здесь как раз задачи связаны, потому что идея показать разные подходы тестирования и решения задач. И это лучше делать на одной и той же задаче, чем каждый раз новую вводить.","topic_id":88050}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мокинг","entity_url":null,"active":true}},{"id":88811,"title":"В задании написано, `Мы хотим убедиться, что она один раз отправляет сообщение c текстом 'Go!' в лог`. По идее же должна работать просто mock.assert_any_call('Go!') - нет?\nА в моем [решении](https://ru.hexlet.io/code_reviews/1042821) я на основе результата проверки решил поставить еще is None - и задачу прошел - честно говоря вообще не понял что случилось, можете пояснить?\n\nТакже я сначала писал другие assert'ы, когда удалось пройти 2 теста из 3 (далее спойлер):\n\n```\nassert mock.call_count == 1\nassert mock.call_args('Go!')\n```\n\nи никак не мог подобрать тест к последней ситуации fail2 где передается Boom! К нему можно написать отдельный assert?","plain_title":"В задании написано, Мы хотим убедиться, что она один раз отправляет сообщение c текстом 'Go!' в лог. По идее же должна работать просто mock.assertanycall('Go!') - нет? А в моем решении (https://ru.hexlet.io/code_reviews/1042821) я на основе результата проверки решил поставить еще is None - и задачу прошел - честно говоря вообще не понял что случилось, можете пояснить? Также я сначала писал другие assert'ы, когда удалось пройти 2 теста из 3 (далее спойлер): assert mock.call_count == 1 assert mock.call_args('Go!') и никак не мог подобрать тест к последней ситуации fail2 где передается Boom! К нему можно написать отдельный assert? ","creator":{"public_name":"Arseniy","id":449611,"is_tutor":false},"comments":[{"creator":{"public_name":"Павел","id":444848,"is_tutor":false},"id":177288,"body":"Привет! Внимательно посмотри что возвращает mock.call_args('Go!'). Также обрати внимание на документацию и атрибут args, который обычно идет после вызова мока: https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.call_args. В текущем варианте у тебя первые два теста в задании проходят исключительно на первом assertе.","topic_id":88811}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мокинг","entity_url":null,"active":true}},{"id":92277,"title":"Не мог пройти мимо:\n1. По моему мнению утверждение \"В тестировании очень популярен мокинг.\" противоречит утверждению \"Существуют ситуации, в которых моки нужны, но все таки в большинстве ситуаций их нужно избегать.\" Если очень популярен, то это в моем понимании - используется чаще всего, в большинстве случаев, однако в во втором утверждении в большинстве случаев следует избегать использование моков. Так где правда?\n\n2. Утверждение \"Иногда нам нужно не получить результат работы функции, а проверить, выполняет ли она нужное нам действие — например, шлет правильный HTTP-запрос с правильными параметрами\"\nА результат работы функции разве не может быть отправка HTTP-запроса? Может. Тогда можно же сравнить выходные данные с функции с ожидаемыми. Неоднозначно как-то.\n\n3. \n> В этом ключевое отличие мока от стаба:\n Стаб устраняет побочный эффект, чтобы не мешать проверке результата работы кода — например, возврату данных из функции\n Мок фокусируется на том, как конкретно работает код, что он делает внутри\n\nТак в чем отличие? Стаб устраняет, а мок не устраняет? Мок фокусируется на внутреннем коде, а стаб нет? Не понятна логика. В моем понимании должно быть сравнение по одним и тем же параметрам.\n\n4. Как-то обозначьте, что используются определенные методы mock, а не откуда-то взявшиеся с головы функции.\n\n5. Непонятно как работает, начиная со второй строчки:\n\n```\nthing = ProductionClass()\nthing.method = Mock(return_value=3)\nthing.method(3, 4, 5, key='value')\nassert thing.method.assert_called_with(3, 4, 5, key='value')\n```","plain_title":"Не мог пройти мимо: 1. По моему мнению утверждение \"В тестировании очень популярен мокинг.\" противоречит утверждению \"Существуют ситуации, в которых моки нужны, но все таки в большинстве ситуаций их нужно избегать.\" Если очень популярен, то это в моем понимании - используется чаще всего, в большинстве случаев, однако в во втором утверждении в большинстве случаев следует избегать использование моков. Так где правда? Утверждение \"Иногда нам нужно не получить результат работы функции, а проверить, выполняет ли она нужное нам действие — например, шлет правильный HTTP-запрос с правильными параметрами\" А результат работы функции разве не может быть отправка HTTP-запроса? Может. Тогда можно же сравнить выходные данные с функции с ожидаемыми. Неоднозначно как-то. В этом ключевое отличие мока от стаба: Стаб устраняет побочный эффект, чтобы не мешать проверке результата работы кода — например, возврату данных из функции Мок фокусируется на том, как конкретно работает код, что он делает внутри Так в чем отличие? Стаб устраняет, а мок не устраняет? Мок фокусируется на внутреннем коде, а стаб нет? Не понятна логика. В моем понимании должно быть сравнение по одним и тем же параметрам. Как-то обозначьте, что используются определенные методы mock, а не откуда-то взявшиеся с головы функции. Непонятно как работает, начиная со второй строчки: thing = ProductionClass() thing.method = Mock(return_value=3) thing.method(3, 4, 5, key='value') assert thing.method.assert_called_with(3, 4, 5, key='value') ","creator":{"public_name":"Валерий Гарькуша","id":433892,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":181942,"body":"1. Здесь нет противоречия, мокинг действительно популярен в динамических интерпретируемых языках, но мы считаем что его нужно использовать по-минимуму.\n\n2. Здесь как раз и обсуждается ситуация, когда результат функции это не отправка запроса, а что-то другое, но внутри она отправляет запросы. Если бы она возвращала запрос, то и проблем бы не было)\n\n3. Да, стабы ничего не знают о внутренностях. Например, мы тестируем все ту же функцию, которая посылает запрос, то стаб будет \"закрывать\" сервер к которому идет запрос, а мок будет \"закрывать\" метод или функцию внутри которая как запрос и отправляет.Т.е. стабам все равно на внутренее устройство, они симулируют внешние окружение/ресурсы и тд. А моки подменяют внутренности на другие.\n\n4.5. Тут вам поможет документация к библиотеке https://docs.python.org/3/library/unittest.mock.html#module-unittest.mock ","topic_id":92277}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мокинг","entity_url":null,"active":true}},{"id":101028,"title":"https://ru.hexlet.io/code_reviews/1473230 Направьте в правильное русло пожалуйста, все тесты анализировал, functions.py смотрел, не могу понять, что именно еще требуется?","plain_title":"https://ru.hexlet.io/code_reviews/1473230 Направьте в правильное русло пожалуйста, все тесты анализировал, functions.py смотрел, не могу понять, что именно еще требуется? ","creator":{"public_name":"Артём Мордвинов","id":793395,"is_tutor":false},"comments":[{"creator":{"public_name":"Артём Мордвинов","id":793395,"is_tutor":false},"id":192876,"body":"**Eugene**, Спасибо большое, все прошло!","topic_id":101028},{"creator":{"public_name":"Eugene","id":407464,"is_tutor":false},"id":192875,"body":"Тебе не нужно передавать в `mock` аргументы. Он будет вызван с теми аргументами, которые были переданы в функцию, которую ты мокаешь. Попробуй так:\n\n`get_files_count(get_fixture_path('flat'), mock)`\n\n","topic_id":101028}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мокинг","entity_url":null,"active":true}},{"id":103445,"title":"Доброго времени суток!\nВ первом примере ошибка:\n` assert mock.assert_any_call(1)\n assert mock.assert_any_call(2)\n assert mock.assert_any_call(3)`\nЗдесь не должно быть assert","plain_title":"Доброго времени суток! В первом примере ошибка: assert mock.assert_any_call(1) assert mock.assert_any_call(2) assert mock.assert_any_call(3) Здесь не должно быть assert ","creator":{"public_name":"Safar Galimzianov","id":650949,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":195940,"body":"Спасибо за замечание, поправим","topic_id":103445}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мокинг","entity_url":null,"active":true}},{"id":105136,"title":"Пожалуйста, переработайте курс целиком, уроки и задания, обратите внимание на отсутствие понятной для новичков связи между теорией и заданиями. На стандартный вопрос \"А что именно осталось непонятным\" ответ: \"Почти все, включая стиль и язык изложения. Особенно - отсутствие понятной связи между теорией в уроке и задачами\". Попробуйте потратить час-два : пусть просто автор/методист курса возьмет студента, начинающего, и посмотрит, как он пробует прорешать задачи, какие вопросы возникают, и насколько они похожи на то, что автор курса предполагал. И увидите колоссальный зазор. \nНовичкам невозможно простроить логику верного решения. Жалоб довольно много в комментариях, что должно кураторов/методистов навести на мысль. Спасибо","plain_title":"Пожалуйста, переработайте курс целиком, уроки и задания, обратите внимание на отсутствие понятной для новичков связи между теорией и заданиями. На стандартный вопрос \"А что именно осталось непонятным\" ответ: \"Почти все, включая стиль и язык изложения. Особенно - отсутствие понятной связи между теорией в уроке и задачами\". Попробуйте потратить час-два : пусть просто автор/методист курса возьмет студента, начинающего, и посмотрит, как он пробует прорешать задачи, какие вопросы возникают, и насколько они похожи на то, что автор курса предполагал. И увидите колоссальный зазор. Новичкам невозможно простроить логику верного решения. Жалоб довольно много в комментариях, что должно кураторов/методистов навести на мысль. Спасибо ","creator":{"public_name":"Ivan Vekhov","id":191383,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":197846,"body":"Спасибо за замечание. Поставил тикет на улучшение.","topic_id":105136}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мокинг","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":1894,"slug":"python_advanced_testing_mocking_exercise","name":null,"state":"active","kind":"exercise","language":"python","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"## tests/test_solution.py\n\nПротестируйте функцию `get_files_count(path, log)`, которая считает количество всех файлов в указанной директории и всех поддиректориях. В отличие от предыдущей версии задания, здесь нас интересует, что эта функция выполняет логирование с помощью функции `log()`. Мы хотим убедиться, что она один раз отправляет сообщение c текстом 'Go!' в лог. Для этого придется воспользоваться моком.\n\n### Подсказки\n\n* [unittest.mock.call_count](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.call_count)\n* [unittest.mock.call_args](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.call_args)\n* Обратите внимание в документации, где именно создается мок\n","prepared_readme":"## tests/test_solution.py\n\nПротестируйте функцию `get_files_count(path, log)`, которая считает количество всех файлов в указанной директории и всех поддиректориях. В отличие от предыдущей версии задания, здесь нас интересует, что эта функция выполняет логирование с помощью функции `log()`. Мы хотим убедиться, что она один раз отправляет сообщение c текстом 'Go!' в лог. Для этого придется воспользоваться моком.\n\n### Подсказки\n\n* [unittest.mock.call_count](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.call_count)\n* [unittest.mock.call_args](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.call_args)\n* Обратите внимание в документации, где именно создается мок\n","has_solution":true,"entity_name":"Мокинг"},"units":[{"id":4455,"name":"theory","url":"/courses/python-advanced-testing/lessons/mocking/theory_unit"},{"id":4456,"name":"quiz","url":"/courses/python-advanced-testing/lessons/mocking/quiz_unit"},{"id":6040,"name":"exercise","url":"/courses/python-advanced-testing/lessons/mocking/exercise_unit"}],"links":[{"id":422807,"name":"unittest.mock","url":"https://docs.python.org/3/library/unittest.mock.html#module-unittest.mock"},{"id":422808,"name":"Mock aren't stubs","url":"https://martinfowler.com/articles/mocksArentStubs.html"}],"ordered_units":[{"id":4455,"name":"theory","url":"/courses/python-advanced-testing/lessons/mocking/theory_unit"},{"id":4456,"name":"quiz","url":"/courses/python-advanced-testing/lessons/mocking/quiz_unit"},{"id":6040,"name":"exercise","url":"/courses/python-advanced-testing/lessons/mocking/exercise_unit"}],"id":1970,"slug":"mocking","state":"approved","name":"Мокинг","course_order":500,"goal":"Знакомимся с мокингом и учимся отличать моки от стабов","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"В тестировании очень популярен **мокинг**. Технически он похож на стабинг, и из-за этого их часто путают. Но все же они служат разным целям и используются в разных ситуациях. В этом уроке мы разберемся, что такое мокинг и зачем он нужен.\n\nДо этого момента мы рассматривали побочные эффекты как помеху тестирования нашей логики. Для их изоляции использовались либо стабы, либо прямое выключение логики в тестовой среде. После этого можно было спокойно проверять правильность работы функции.\n\nВ некоторых ситуациях требуется кое-что другое. Иногда нам нужно не получить результат работы функции, а проверить, выполняет ли она нужное нам действие — например, шлет правильный HTTP-запрос с правильными параметрами.\n\nДля этого понадобятся моки. Они проверяют, как выполняется код.\n\n## HTTP\n\nНачнем с такого фрагмента кода:\n\n```python\n@pook.on\ndef test_get_private_fork_names():\n # Возвращается мок, за которым можно следить\n mock = pook.get(\n \"https://api.github.com/orgs/hexlet/repos\",\n reply=200,\n response_json=[{\"fork\": true, \"name\": \"one\"}, {\"fork\": false, \"name\": \"two\"}],\n )\n\n get_private_fork_names(\"hexlet\")\n # Убеждаемся, что вызов был сделан\n assert mock.calls == 1\n```\n\nОтслеживание выполнения какого-то действия — это и есть **мокинг**. Мок проверяет, что какой-то код выполнился определенным образом. Это может быть вызов функции, HTTP-запрос и тому подобное.\n\nУ мока две задачи:\n\n* Убедиться в том, что событие произошло — например, функция передала данные\n* Отследить, каким конкретно образом оно произошло — функция передала конкретные данные\n\nЧто дает нам такая проверка? В этом случае не очень много. Да, мы убеждаемся, что вызов был, но само по себе это еще ни о чем не говорит. В чем же тогда польза моков?\n\nПредставьте, что мы бы разрабатывали библиотеку PyGithub — ту самую, что выполняет запросы к GitHub API. Вся суть этой библиотеки в том, чтобы выполнить правильные запросы с правильными параметрами. Поэтому там нужно обязательно проверять выполнение запросов с указанием точных URL-адресов. Только в таком случае можно быть уверенными, что она выполняет верные запросы.\n\nВ этом ключевое отличие мока от стаба:\n\n* Стаб устраняет побочный эффект, чтобы не мешать проверке результата работы кода — например, возврату данных из функции\n* Мок фокусируется на том, как конкретно работает код, что он делает внутри\n\nПри этом чисто технически мок и стаб создаются почти одинаково, за исключением того, что на мок вешают ожидания, проверяющие вызовы. Из-за этого моками часто называют стабы. Для себя всегда пытайтесь понять, о чем идет речь. Это важно, потому что от этого зависит фокус тестов.\n\n## Функции\n\nМоки довольно часто используют с функциями (методами). К примеру, они могут проверять:\n\n* Вызвана ли функция, сколько раз ее вызвали\n* Какие аргументы переданы в функцию, сколько всего аргументов\n* Что именно вернула функция\n\nПредположим, что мы хотим протестировать функцию `for_each()`. Она вызывает колбек для каждого элемента коллекции:\n\n```python\ndef for_each(coll, func):\n for c in coll:\n func(c)\n\n\nfor_each([1, 2, 3], lambda v: print(v))\n# => 1\n# => 2\n# => 3\n```\n\nЭта функция ничего не возвращает, поэтому напрямую ее не протестировать.\n\nМожно попробовать сделать это с помощью моков. Проверим, что она вызывает переданный колбек и передает туда нужные значения. Сделаем это с помощью модуля *mock*, который входит в стандартную библиотеку Python как часть *unittest*:\n\n```python\nfrom unittest.mock import Mock\n\n\ndef test_for_each():\n mock = Mock()\n\n for_each([1, 2, 3], lambda v: mock(v))\n\n # Проверяем, что она была вызвана с правильными аргументами нужное количество раз\n assert mock.call_count == 3\n mock.assert_any_call(1)\n mock.assert_any_call(2)\n mock.assert_any_call(3)\n```\n\nС помощью моков мы проверили, что функция была вызвана ровно три раза, и ей передавался новый элемент коллекции последовательно для каждого вызова.\n\nМожно сказать, что этот тест действительно проверяет работоспособность функции `for_each()`. Но можно сделать это проще, без мока и без завязки на внутреннее поведение. Для этого достаточно использовать замыкание:\n\n```python\ndef test_for_each():\n result = []\n numbers = [1, 2, 3]\n for_each(numbers, lambda x: result.append(x))\n assert result == numbers\n```\n\n## Объекты\n\nКроме использования моков для тестирования функций, они также могут использоваться для тестирования объектов.\n\nВ тестировании объектов моки могут использоваться для имитации поведения объектов, от которых зависит тестируемый объект. Например, если объект A зависит от объекта B, то можно создать мок-объект для объекта B и использовать его в тестах объекта A. Это позволит тестировать объект A, не затрагивая объект B и его зависимости.\n\n```python\nfrom unittest.mock import Mock\n\n\nclass MyObject:\n def __init__(self, dependency):\n self.dependency = dependency\n\n def my_method(self, arg):\n # какой-то метод, который зависит от зависимости\n return self.dependency.some_method(arg)\n\n\n# Создаем мок-объект для зависимости\nmock_dependency = Mock()\n\n# Создаем объект, который мы будем тестировать\nmy_object = MyObject(mock_dependency)\n\n# Имитируем поведение зависимости в мок-объекте\nmock_dependency.some_method.return_value = 42\n\n# Тестируем метод объекта\nresult = my_object.my_method(\"some argument\")\n\n# Проверяем, что метод объекта вызвал метод зависимости с правильным аргументом\nmock_dependency.some_method.assert_called_once_with(\"some argument\")\n\n# Проверяем, что метод объекта вернул правильный результат\nassert result == 42\n```\n\nЕще он умеет оборачивать существующую реализацию:\n\n```python\nthing = ProductionClass()\nthing.method = Mock(return_value=3)\nthing.method(3, 4, 5, key=\"value\")\n\nthing.method.assert_called_with(3, 4, 5, key=\"value\")\n```\n\n## Преимущества и недостатки\n\nСуществуют ситуации, в которых моки нужны, но все таки в большинстве ситуаций их нужно избегать. Моки слишком много знают о том, как работает код. Любой тест с моками из черного ящика превращается в прозрачный ящик.\n\n Повсеместное использование моков приводит к двум вещам:\n\n* После рефакторинга приходится переписывать тесты, даже если код работает правильно. Так происходит, потому что тесты завязаны на то, как конкретно работает код\n* Код может перестать работать. При этом тесты будут проходить, потому что они сфокусированы не на результатах работы кода, а на его устройстве внутри\n\nТам, где возможно использование реального кода — используйте реальный. Там, где возможно убедиться в работе кода без моков — делайте это без моков.\n\nИзлишний мокинг повышает стоимость поддержки тестов и делает их бесполезными. Идеальные тесты – тесты методом черного ящика.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":4459,"name":"theory","url":"/courses/python-advanced-testing/lessons/init/theory_unit"}],"links":[{"id":422799,"name":"JavaScript Testing Best Practice","url":"https://github.com/goldbergyoni/javascript-testing-best-practices"},{"id":422800,"name":"Как мы тестируем тесты. О проверке тестов в практических упражнениях этого курса","url":"https://help.hexlet.io/practice-guides/kak-my-testiruem-testy-utilita-suppressor/"}],"ordered_units":[{"id":4459,"name":"theory","url":"/courses/python-advanced-testing/lessons/init/theory_unit"}],"id":1973,"slug":"init","state":"approved","name":"Введение","course_order":100,"goal":"Знакомимся с курсом и его целями","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Тесты – это большая тема со множеством нюансов. Одно дело тестировать чистые функции. Совсем другое – это тестировать приложение, в котором есть взаимодействие с базой данных, отправка запросов по сети, электронных писем, где используются таймеры и асинхронность. Даже тестирование ошибок обладает своими особенностями.\n\nПри правильной организации тестов стоимость поддержки остается низкой, а скорость работы при этом растет. При неправильной организации очень легко попасть в ловушку — тесты вроде бы есть, но писать их тяжело, они постоянно ломаются, их приходится часто переписывать.\n\nВ этом курсе мы поговорим о том, как тестировать сложные ситуации с побочными эффектами, асинхронным кодом и таймерами. Мы рассмотрим такие темы:\n\n* Тестирование ошибок\n* Внешние данные и фикстуры\n* Организация тестовых данных\n* Изоляция побочных эффектов, стабы и инверсия зависимости\n* Моки и тестирование методом черного ящика\n"},"id":228,"slug":"python-advanced-testing","challenges_count":3,"name":"Python: Продвинутое тестирование","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы продолжите изучать тестирование. Вы научитесь тестировать реальные приложения, что значительно сложнее тестов вызова функций. Вы узнаете, как работают сетевые запросы к внешним ресурсам, взаимодействие с файловой системой, асинхронный код и внешние библиотеки со своими хитростями.\n\nЗнания из этого курса помогут разобраться в побочных эффектах и изучить эффективные способы управления ими.","kind":"advanced","updated_at":"2026-01-20T11:38:47.423Z","language":"python","duration_cache":33660,"skills":["Легко тестировать даже те приложения, которые много взаимодействуют с внешней средой","Тестировать код, активно работающий с файловой системой","Применять полиморфизм для подмены реализации неудобного в тестировании кода","Использовать стабы и моки по назначению"],"keywords":["фикстуры","стабы","моки","побочные эффекты","манки-патчинг","инверсия зависимостей"],"lessons_count":8,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjAwNiwicHVyIjoiYmxvYl9pZCJ9fQ==--8e170ffb04475403ad4ba5ebda5249beb23fe106/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJqcGciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--39ba06fa99226096df9fc6bb31f84e1d29ea98e9/image.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"},{"stack":{"id":40,"slug":"python-pytest-testing","title":"Pytest","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"finished","order":500,"duration_in_months":1},"id":68,"slug":"python-pytest-testing","title":"Pytest","subtitle":"Навык писать автотесты на Python, тестировать приложения и повышать их качество, улучшая надёжность кода","subtitle_for_lists":"Получите навык тестирования на Pytest","locale":"ru","current":true,"duration_in_months_text":"1 месяц","stack_slug":"python-pytest-testing","price_text":"от 3 900 ₽","duration_text":"1 месяц","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzYyOSwicHVyIjoiYmxvYl9pZCJ9fQ==--f7aaf7f059fd35008c7c2fb722680196834212ab/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Usability%20testing-bro.png"},{"stack":{"id":225,"slug":"devops-engineer-from-scratch","title":"DevOps-инженер с нуля","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"not_finished","order":50,"duration_in_months":14},"id":355,"slug":"devops-engineer-from-scratch","title":"DevOps-инженер с нуля","subtitle":"Полное погружение в DevOps: весь стек от Linux до Kubernetes","subtitle_for_lists":"Полное погружение в DevOps: весь стек от Linux до Kubernetes","locale":"ru","current":true,"duration_in_months_text":"14 месяцев","stack_slug":"devops-engineer-from-scratch","price_text":"от 6 792 ₽","duration_text":"14 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk2NSwicHVyIjoiYmxvYl9pZCJ9fQ==--84278a1852c9c6fb13b80a69f395bac6e47a422e/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Cloud%20sync-bro.png"},{"stack":{"id":466,"slug":"middle-python","title":"Middle-python разработчик","audience":"for_programmers","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"not_finished","order":2001,"duration_in_months":5},"id":594,"slug":"middle-python","title":"Мидл-python разработчик","subtitle":"Углубите знания Python, архитектуры приложений и работы с базами данных","subtitle_for_lists":"Углубите знания Python, архитектуры приложений и работы с базами данных","locale":"ru","current":true,"duration_in_months_text":"5 месяцев","stack_slug":"middle-python","price_text":"от 4 050 ₽","duration_text":"5 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NTIyMiwicHVyIjoiYmxvYl9pZCJ9fQ==--0e824331c2dcddbe1fa1aaa4188500e364c8c38c/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-bro.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/python-advanced-testing/lessons/mocking/theory_unit","version":"0b0c6d4ebbd40fd58630a0dd89cc25544ccdf24e","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">Теория: Мокинг</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Мокинг","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"><p>В тестировании очень популярен <strong>мокинг</strong>. Технически он похож на стабинг, и из-за этого их часто путают. Но все же они служат разным целям и используются в разных ситуациях. В этом уроке мы разберемся, что такое мокинг и зачем он нужен.</p>
<p>До этого момента мы рассматривали побочные эффекты как помеху тестирования нашей логики. Для их изоляции использовались либо стабы, либо прямое выключение логики в тестовой среде. После этого можно было спокойно проверять правильность работы функции.</p>
<p>В некоторых ситуациях требуется кое-что другое. Иногда нам нужно не получить результат работы функции, а проверить, выполняет ли она нужное нам действие — например, шлет правильный HTTP-запрос с правильными параметрами.</p>
<p>Для этого понадобятся моки. Они проверяют, как выполняется код.</p>
<h2 id="heading-2-1">HTTP</h2>
<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">@pook.on
def test_get_private_fork_names():
# Возвращается мок, за которым можно следить
mock = pook.get(
"https://api.github.com/orgs/hexlet/repos",
reply=200,
response_json=[{"fork": true, "name": "one"}, {"fork": false, "name": "two"}],
)
get_private_fork_names("hexlet")
# Убеждаемся, что вызов был сделан
assert mock.calls == 1</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>Отслеживание выполнения какого-то действия — это и есть <strong>мокинг</strong>. Мок проверяет, что какой-то код выполнился определенным образом. Это может быть вызов функции, HTTP-запрос и тому подобное.</p>
<p>У мока две задачи:</p>
<ul>
<li>Убедиться в том, что событие произошло — например, функция передала данные</li>
<li>Отследить, каким конкретно образом оно произошло — функция передала конкретные данные</li>
</ul>
<p>Что дает нам такая проверка? В этом случае не очень много. Да, мы убеждаемся, что вызов был, но само по себе это еще ни о чем не говорит. В чем же тогда польза моков?</p>
<p>Представьте, что мы бы разрабатывали библиотеку PyGithub — ту самую, что выполняет запросы к GitHub API. Вся суть этой библиотеки в том, чтобы выполнить правильные запросы с правильными параметрами. Поэтому там нужно обязательно проверять выполнение запросов с указанием точных URL-адресов. Только в таком случае можно быть уверенными, что она выполняет верные запросы.</p>
<p>В этом ключевое отличие мока от стаба:</p>
<ul>
<li>Стаб устраняет побочный эффект, чтобы не мешать проверке результата работы кода — например, возврату данных из функции</li>
<li>Мок фокусируется на том, как конкретно работает код, что он делает внутри</li>
</ul>
<p>При этом чисто технически мок и стаб создаются почти одинаково, за исключением того, что на мок вешают ожидания, проверяющие вызовы. Из-за этого моками часто называют стабы. Для себя всегда пытайтесь понять, о чем идет речь. Это важно, потому что от этого зависит фокус тестов.</p>
<h2 id="heading-2-2">Функции</h2>
<p>Моки довольно часто используют с функциями (методами). К примеру, они могут проверять:</p>
<ul>
<li>Вызвана ли функция, сколько раз ее вызвали</li>
<li>Какие аргументы переданы в функцию, сколько всего аргументов</li>
<li>Что именно вернула функция</li>
</ul>
<p>Предположим, что мы хотим протестировать функцию <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">for_each()</code>. Она вызывает колбек для каждого элемента коллекции:</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">def for_each(coll, func):
for c in coll:
func(c)
for_each([1, 2, 3], lambda v: print(v))
# => 1
# => 2
# => 3</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>
<p>Можно попробовать сделать это с помощью моков. Проверим, что она вызывает переданный колбек и передает туда нужные значения. Сделаем это с помощью модуля <em>mock</em>, который входит в стандартную библиотеку Python как часть <em>unittest</em>:</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">from unittest.mock import Mock
def test_for_each():
mock = Mock()
for_each([1, 2, 3], lambda v: mock(v))
# Проверяем, что она была вызвана с правильными аргументами нужное количество раз
assert mock.call_count == 3
mock.assert_any_call(1)
mock.assert_any_call(2)
mock.assert_any_call(3)</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>
<p>Можно сказать, что этот тест действительно проверяет работоспособность функции <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">for_each()</code>. Но можно сделать это проще, без мока и без завязки на внутреннее поведение. Для этого достаточно использовать замыкание:</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">def test_for_each():
result = []
numbers = [1, 2, 3]
for_each(numbers, lambda x: result.append(x))
assert result == numbers</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>
<h2 id="heading-2-3">Объекты</h2>
<p>Кроме использования моков для тестирования функций, они также могут использоваться для тестирования объектов.</p>
<p>В тестировании объектов моки могут использоваться для имитации поведения объектов, от которых зависит тестируемый объект. Например, если объект A зависит от объекта B, то можно создать мок-объект для объекта B и использовать его в тестах объекта A. Это позволит тестировать объект A, не затрагивая объект B и его зависимости.</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">from unittest.mock import Mock
class MyObject:
def __init__(self, dependency):
self.dependency = dependency
def my_method(self, arg):
# какой-то метод, который зависит от зависимости
return self.dependency.some_method(arg)
# Создаем мок-объект для зависимости
mock_dependency = Mock()
# Создаем объект, который мы будем тестировать
my_object = MyObject(mock_dependency)
# Имитируем поведение зависимости в мок-объекте
mock_dependency.some_method.return_value = 42
# Тестируем метод объекта
result = my_object.my_method("some argument")
# Проверяем, что метод объекта вызвал метод зависимости с правильным аргументом
mock_dependency.some_method.assert_called_once_with("some argument")
# Проверяем, что метод объекта вернул правильный результат
assert result == 42</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">thing = ProductionClass()
thing.method = Mock(return_value=3)
thing.method(3, 4, 5, key="value")
thing.method.assert_called_with(3, 4, 5, key="value")</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>
<h2 id="heading-2-4">Преимущества и недостатки</h2>
<p>Существуют ситуации, в которых моки нужны, но все таки в большинстве ситуаций их нужно избегать. Моки слишком много знают о том, как работает код. Любой тест с моками из черного ящика превращается в прозрачный ящик.</p>
<p>Повсеместное использование моков приводит к двум вещам:</p>
<ul>
<li>После рефакторинга приходится переписывать тесты, даже если код работает правильно. Так происходит, потому что тесты завязаны на то, как конкретно работает код</li>
<li>Код может перестать работать. При этом тесты будут проходить, потому что они сфокусированы не на результатах работы кода, а на его устройстве внутри</li>
</ul>
<p>Там, где возможно использование реального кода — используйте реальный. Там, где возможно убедиться в работе кода без моков — делайте это без моков.</p>
<p>Излишний мокинг повышает стоимость поддержки тестов и делает их бесполезными. Идеальные тесты – тесты методом черного ящика.</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="/programs/python-pytest-testing?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">1 месяц</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">Pytest</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Получите навык тестирования на Pytest</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/eyJfcmFpbHMiOnsiZGF0YSI6MzYyOSwicHVyIjoiYmxvYl9pZCJ9fQ==--f7aaf7f059fd35008c7c2fb722680196834212ab/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Usability%20testing-bro.png" alt="Pytest" 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">от 3 900 ₽</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="/programs/devops-engineer-from-scratch?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">14 месяцев</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">DevOps-инженер с нуля</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полное погружение в DevOps: весь стек от Linux до Kubernetes</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/eyJfcmFpbHMiOnsiZGF0YSI6Mzk2NSwicHVyIjoiYmxvYl9pZCJ9fQ==--84278a1852c9c6fb13b80a69f395bac6e47a422e/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Cloud%20sync-bro.png" alt="DevOps-инженер с нуля" 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="/programs/middle-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">5 месяцев</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, архитектуры приложений и работы с базами данных</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/eyJfcmFpbHMiOnsiZGF0YSI6NTIyMiwicHVyIjoiYmxvYl9pZCJ9fQ==--0e824331c2dcddbe1fa1aaa4188500e364c8c38c/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-bro.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">от 4 050 ₽</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/python-advanced-testing/lessons/mocking/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 / 8</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/python-advanced-testing/lessons/mocking/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-CdBlNCiQ.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-nkZBEvfU.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>