<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>@easy_dev991</title><generator>teletype.in</generator><description><![CDATA[Делюсь интересными находками/лайфхаками в процессе разработки под iOS, и возможно ты найдешь что-то полезное для себя!]]></description><image><url>https://img1.teletype.in/files/0b/1a/0b1a0809-912b-4a06-801a-ac4f5e10a1bf.png</url><title>@easy_dev991</title><link>https://teletype.in/@easy_dev991</link></image><link>https://teletype.in/@easy_dev991?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/easy_dev991?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/easy_dev991?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Wed, 08 Apr 2026 13:44:35 GMT</pubDate><lastBuildDate>Wed, 08 Apr 2026 13:44:35 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@easy_dev991/hlx_HA24Cgy</guid><link>https://teletype.in/@easy_dev991/hlx_HA24Cgy?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991</link><comments>https://teletype.in/@easy_dev991/hlx_HA24Cgy?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991#comments</comments><dc:creator>easy_dev991</dc:creator><title>Какой макбук выбрать в 2026 году</title><pubDate>Sat, 28 Mar 2026 10:19:10 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/7c/ca/7cca7d4a-d86e-4dce-acee-14c2ddc59bca.png"></media:content><description><![CDATA[<img src="https://img2.teletype.in/files/16/2f/162f3954-4a8e-41c5-9496-7cad859f3172.png"></img>Предыдущая статья про 2025 год доступна по ссылке - большинство рекомендаций из нее все еще актуальны, кроме цен и версии Xcode, конечно.]]></description><content:encoded><![CDATA[
  <p id="GWNq">Предыдущая статья про 2025 год доступна по <a href="https://telegra.ph/115-Kakoj-makbuk-vybrat-v-2025-godu-04-16" target="_blank">ссылке</a> - большинство рекомендаций из нее все еще актуальны, кроме цен и версии <code>Xcode</code>, конечно.</p>
  <p id="ZOyD">За год изменилось немного: появился чип <strong>m5 </strong>и вышел <strong>Xcode 26...</strong></p>
  <h2 id="Макбук-для-работы">Макбук для работы</h2>
  <p id="RFz4">Рекомендации по поводу рабочего макбука двигаем на <strong>1</strong> цифру вперед, то есть на работе заказываем <strong>MacBook Pro m5 pro </strong>- как обычно, даже в базовой конфигурации это будет отличный вариант на несколько лет вперед.</p>
  <p id="Ulxm">Если компания не выдает технику, то есть смысл взять то, на что хватает денег из линейки <strong>m3</strong>-<strong>m5</strong>. <strong>m1</strong>-<strong>m2</strong> не рассматриваем, потому что они уже прилично состарились, а <strong>m2</strong> и вовсе проиграл в некоторых тестах предшественнику.</p>
  <h2 id="Макбук-не-для-работы">Макбук не для работы</h2>
  <p id="tB2h">В 2020 году я купил себе <strong>MacBook Pro 13&#x27;&#x27; m1 </strong>за<strong> 150к </strong>(на официальном сайте с доставкой на дом, чудеса вообще), и он отлично прослужил мне до второй половины 2025 года, когда я его продал за <strong>64к</strong> - это была отличная продажа и я очень доволен таким успехом, и покупатель будет еще долго доволен, т.к. ему макбук нужен был не для программирования, а для повседневного использования.</p>
  <p id="wQLY">В 2026 не могу рекомендовать макбуки из линейки <strong>m1-m2</strong>, так что находим выгодные варианты <strong>m3</strong>-<strong>m5</strong> (как и для работы) на маркетплейсах и радуемся запасу производительности на ближайшие 3-5 лет.</p>
  <p id="rAkm">Очевидно, <strong>m5</strong> прослужит лучше в течение 5 лет, чем <strong>m3</strong>, но для рядового пользователя, полагаю, разницы не будет вообще - не всем же нужно запускать <code>Xcode</code>/<code>Android Studio</code> каждый день по несколько раз и ждать выполнение unit/ui-тестов 😁</p>
  <h2 id="yaz9">Macbook Neo</h2>
  <p id="Lriu">Сделал отдельный раздел специально для этой модели, потому что в личку мне уже написали гениальную идею купить этот макбук для ios-разработки.</p>
  <p id="nfIW">Этот раздел статьи не для слабонервных!</p>
  <p id="I6kO">.</p>
  <p id="xc0g">.</p>
  <p id="rM6S">.</p>
  <p id="Dc1f">.</p>
  <p id="ljUM">.</p>
  <p id="ILCT">.</p>
  <p id="uSq4">Я серьезно, если вы фанат Apple или просто ярый поклонник всех макбуков, то лучше не читать дальше!</p>
  <p id="VjEX">.</p>
  <p id="G5Hd">.</p>
  <p id="AHif">.</p>
  <p id="I9uX">.</p>
  <p id="Xx1J">.</p>
  <p id="ulIb">.</p>
  <p id="7gTg">Вы еще тут? Окей, поехали)</p>
  <p id="hWNE">Специально для этой модели я покажу скриншоты цен (актуально на конец марта - начало апреля 2026) без рекламы конкретного маркетплейса, и потом мы вместе сравним с ценами на другие модели на том же маркетплейсе, опять же не реклама:</p>
  <figure id="pnBQ" class="m_column">
    <img src="https://img2.teletype.in/files/16/2f/162f3954-4a8e-41c5-9496-7cad859f3172.png" width="2268" />
    <figcaption>Цены на безымянном (популярном) маркетплейсе на Macbook Neo - от 66к руб с учетом пошлины</figcaption>
  </figure>
  <figure id="E9Xj" class="m_column">
    <img src="https://img4.teletype.in/files/b8/32/b832a1ec-d3a0-420d-a268-01144b1c17ea.png" width="2318" />
    <figcaption>Цены на том же маркетплейсе на Macbook Air M4 - от 89к руб с учетом пошлины</figcaption>
  </figure>
  <p id="Ajp4"><strong>Важная деталь</strong>: это сравнение цен на новые ноутбуки, т.е. не б/у, не паленые, а прямо новые.</p>
  <p id="CFKc"><strong>Air m4</strong> дороже на <strong>23к</strong> руб (на этом маркетплейсе), но и по большинству параметров он намного лучше.</p>
  <p id="9vhE">У мелких перекупщиков на других популярных сайтах можно найти тот же <strong>air m4</strong> за <strong>69к</strong>.</p>
  <p id="hHov"><u>Внимательный</u> читатель может спросить: &quot;<em>Зачем покупать дорогой air m4, если можно купить новый модный и молодежный neo дешевле?</em>&quot;.</p>
  <p id="jsVf">На это я <em>отвечу</em> внимательному читателю: &quot;<em>Если не получается найти разницу при беглом сравнении параметров обеих моделей под свои потребности, то не нужно платить больше</em>&quot;.</p>
  <p id="n66a">Потом <u>сообразительный</u> читатель может спросить: &quot;<em>Зачем вообще покупать макбук в 2026 году за такие деньги не для ios-разработки, когда можно купить ноутбук на виндовс под любые потребности и по любой цене, в том числе от 15к и до 60к?</em>&quot;</p>
  <p id="GInl">На что я отвечу сообразительному читателю: &quot;<em>Отличный вопрос, мой дорогой читатель! Нет никакого смысла тратить деньги на макбук, если нет конкретных задач, для которых нужен именно макбук, и с которыми не справятся другие ноутбуки</em>&quot;.</p>
  <p id="eLf5">И пока другие блогеры и СМИ активно пиарят новый <strong>macbook neo</strong> как убийцу доступных ноутбуков на винде, мы с вами как самые сообразительные понимаем, что это пустой трёп, который нацелен только на отработку рекламного ТЗ, или на сбор хайпа (или и то, и другое разом).</p>
  <figure id="fIlM" class="m_column">
    <img src="https://img2.teletype.in/files/92/09/920960e0-5641-4551-bb55-18da1fbb2857.png" width="2266" />
    <figcaption>Вот пример реальных убийц macbook neo на том же маркетплейсе дешевле 25к руб. В карточках товара кринж, но это не так важно.</figcaption>
  </figure>
  <p id="gY7l">И прежде чем набегут желающие защитить божественный бренд Apple от сравнения с безымянными ноутбуками, сначала вспомним, что этот раздел был не для слабонервных и не для фанатов Apple!</p>
  <p id="srx3">А теперь можно вместе подумать, что еще можно купить на оставшиеся 45к руб после покупки ноутбука на винде:</p>
  <ul id="0rEu">
    <li id="xx5Z">Отличный новый андроид-смартфон</li>
    <li id="2qoL">Два хороших новых андроид-смартфона</li>
    <li id="oxUm">Хороший андроид-смартфон и еще один ноутбук на винде</li>
    <li id="nnbN">Два дополнительных ноутбука на винде</li>
    <li id="1ABD">45 пицц по 1000 рублей каждая</li>
  </ul>
  <p id="z2uI">Ну вы поняли, надеюсь 😉</p>
  <h2 id="oBfa">Заключение</h2>
  <p id="AYFS">Итоговый выбор как всегда зависит от ваших конкретных задач (но не покупайте, пожалуйста, <strong>macbook neo</strong>), для которых выбирается макбук, и в большинстве случаев для разработки подойдет любая модель с чипом <strong>m4</strong>-<strong>m5</strong>.</p>
  <p id="gfix">Удачных покупок!</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@easy_dev991/baZfVYrGA5H</guid><link>https://teletype.in/@easy_dev991/baZfVYrGA5H?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991</link><comments>https://teletype.in/@easy_dev991/baZfVYrGA5H?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991#comments</comments><dc:creator>easy_dev991</dc:creator><title>Загружаем фото в андроид-эмулятор</title><pubDate>Fri, 20 Mar 2026 19:18:24 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/1e/19/1e199205-20aa-4772-9b17-0e91fcd114fa.png"></media:content><description><![CDATA[<img src="https://img2.teletype.in/files/51/55/5155b125-26a8-44dc-8192-92da8d2af2a6.png"></img>В процессе разработки андроид-приложения бывает нужно протестировать функционал добавления фотографий из галереи в приложение.]]></description><content:encoded><![CDATA[
  <p id="DZbY">В процессе разработки андроид-приложения бывает нужно протестировать функционал добавления фотографий из галереи в приложение.</p>
  <p id="uvnt">Есть разные способы загрузки фоток в эмулятор, например:</p>
  <ul id="qJML">
    <li id="iMIh">перетащить файлы с фотками на экран эмулятора</li>
    <li id="mVuf">перетащить файлы с фотками в открытое приложение &quot;файлы&quot; на экране эмулятора</li>
    <li id="xIBT">открыть Device Explorer и добавить фотки в папку sdcard/DCIM</li>
  </ul>
  <p id="LOpN">Но что бы вы ни сделали, все эти способы могут не привести к желаемому результату...</p>
  <p id="SB53">А вот кстати и желаемый результат:</p>
  <figure id="1xXu" class="m_column">
    <img src="https://img2.teletype.in/files/51/55/5155b125-26a8-44dc-8192-92da8d2af2a6.png" width="1080" />
    <figcaption>В приложении можно открыть галерею фотографий</figcaption>
  </figure>
  <figure id="ddko" class="m_column">
    <img src="https://img1.teletype.in/files/0e/a8/0ea83121-a09d-4551-ad5c-df2c3961292e.png" width="1080" />
    <figcaption>Фото из галереи можно выгрузить в приложение</figcaption>
  </figure>
  <h2 id="KUrD">Проблема</h2>
  <p id="8TnF">Все перечисленные ранее способы не сработали:</p>
  <figure id="pslV" class="m_column">
    <img src="https://img1.teletype.in/files/0f/95/0f9536df-c57a-4c4c-bac2-35b650a96af1.png" width="1080" />
    <figcaption>Фотографии добавлены в эмулятор каждым из перечисленных ранее способов, но приложение их не видит (как и сам эмулятор)</figcaption>
  </figure>
  <h2 id="9Tk4">Решение</h2>
  <p id="cRx5">После копирования фотографий на эмулятор любым из ранее перечисленных способов выключаем эмулятор и жмем <code>Cold Boot</code>:</p>
  <figure id="qK93" class="m_column">
    <img src="https://img4.teletype.in/files/fb/e3/fbe3b5a9-9508-4f3a-bb0e-e78475f5c01c.png" width="1080" />
    <figcaption>Чудо-кнопка, решающая проблему с обнаружением фотографий в эмуляторе</figcaption>
  </figure>
  <h2 id="AMrX">Заключение</h2>
  <p id="lQAa">Перед написанием этой статьи я пробовал добавить фотографии в эмулятор всеми перечисленными способами, даже стирал данные эмулятора и перезагружал его - ничего не помогало.</p>
  <p id="VheN">И только чудо-кнопка <code>Cold Boot</code> помогла.</p>
  <p id="Ox3a">Будет здорово, если кому-то эта статья поможет сэкономить время в подобной ситуации </p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@easy_dev991/BJryrCj0DMr</guid><link>https://teletype.in/@easy_dev991/BJryrCj0DMr?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991</link><comments>https://teletype.in/@easy_dev991/BJryrCj0DMr?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991#comments</comments><dc:creator>easy_dev991</dc:creator><title>Xcode 26.3 Coding Assistant</title><pubDate>Sat, 28 Feb 2026 09:19:07 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/db/6f/db6f9ffb-be0f-404b-96bb-c8b0f1216126.png"></media:content><category>iOS</category><description><![CDATA[<img src="https://img2.teletype.in/files/53/eb/53ebfbf1-2440-4b77-ae0e-5e1cf7028697.png"></img>Вчера активно тестировал режим агента в Xcode 26.3, чтобы вам не пришлось]]></description><content:encoded><![CDATA[
  <p id="pk0p">Вчера активно тестировал режим агента в <code>Xcode 26.3</code>, чтобы вам не пришлось  </p>
  <h2 id="O4du">С чем имеем дело</h2>
  <p id="B51q">В <code>Xcode 26.3</code> нельзя подключить любую свою модель кроме предусмотренных из коробки</p>
  <figure id="Uxb2" class="m_column">
    <img src="https://img2.teletype.in/files/53/eb/53ebfbf1-2440-4b77-ae0e-5e1cf7028697.png" width="1644" />
    <figcaption>Это обман, не ведитесь на него зря!</figcaption>
  </figure>
  <p id="bZ2Y">Да, есть настройка для добавления кастомного провайдера моделей, но она не совместима с <code>Z.AI</code> (провайдер <code>GLM</code>-моделей), например.</p>
  <p id="SYFt">Чтобы подключить своего провайдера, нужно подключать специальный прокси (при желании можно даже найти бесплатные варианты), так я и сделал.</p>
  <p id="xmwv">Я не использовал <code>ChatGPT</code>/<code>Claude</code> в <code>Xcode</code>, потому что у меня куплены другие подписки (<code>cursor</code> + <code>Z.AI</code>).</p>
  <h2 id="mmgr">Сразу к результатам</h2>
  <h3 id="5vCg">Положительные моменты</h3>
  <ul id="K28j">
    <li id="a52i">Чат работает? - чек</li>
    <li id="FphR">Анимации в чате работают? - чек</li>
    <li id="hSAc">Градиенты в <code>Xcode</code> отображаются? - чек</li>
    <li id="kGZk">Код генерируется? - чек</li>
  </ul>
  <h3 id="xsXv">Отрицательные моменты</h3>
  <ul id="PNE7">
    <li id="cBnr">Модель может выполнять команды в терминале? - нет</li>
    <li id="YGTd">Модель может писать код в разных файлах, которые не открыты в <code>Xcode</code>? - нет</li>
    <li id="3GXZ">При включении/выключении прокси <code>Xcode</code> обновляет статус в окне с чатом? - нет</li>
    <li id="WCpn">Модель может взаимодействовать с тестами или симулятором? - нет</li>
    <li id="rCx9">При начале нового диалога <code>Xcode</code> запоминает выбранную ранее модель? - нет</li>
  </ul>
  <figure id="pgbc" class="m_column">
    <img src="https://img4.teletype.in/files/77/a8/77a86ba0-ff2c-40fe-9c11-857754776a7c.png" width="784" />
    <figcaption>Стрём, который мы заслужили от Apple (1)</figcaption>
  </figure>
  <figure id="Mw6g" class="m_column">
    <img src="https://img2.teletype.in/files/d4/72/d472a71a-aeba-43ac-abf2-34aee293323e.png" width="758" />
    <figcaption>Стрём, который мы заслужили от Apple (2)</figcaption>
  </figure>
  <figure id="sd1q" class="m_column">
    <img src="https://img3.teletype.in/files/2a/07/2a078e47-98fc-4477-8899-7b2dfaa511da.png" width="934" />
    <figcaption>Пруф, что тесты в проекте есть</figcaption>
  </figure>
  <p id="7bdC">Больше всего меня удивил момент, когда модель написал код в одном из двух нужных файлов, а вместо изменения второго файла создала новый, что привело к ошибке компиляции, и эту ошибку комплияции она не смогла исправить, потому что не удалось, видите ли, сделать изменения в нужном файле.</p>
  <p id="mxAF">Полагаю, дело в объеме второго файла - там больше тысячи строк кода, но в других инструментах (<code>qwen-code</code>, <code>opencode</code>, <code>cursor</code>) такой проблемы ни разу не было.</p>
  <h2 id="hzOt">Сравнение с Android Studio</h2>
  <figure id="4bSw" class="m_column">
    <img src="https://img3.teletype.in/files/ab/03/ab0316ba-58f8-4cf6-b18b-9a8f54ee0594.png" width="3402" />
    <figcaption>Все отлично - любую модель можно открыть в терминале и все будет отображаться в одном окне, очень удобно</figcaption>
  </figure>
  <h2 id="rTAr">Вывод</h2>
  <p id="uZUe">То, что Apple нам рекламируют, по уже сложившейся традиции оказывается полной ерундой.</p>
  <p id="VfE8">Нет никакого оправдания, почему в 2026 году <code>Xcode</code> не может дать доступ к терминалу подключенным нейросетям, или хотя бы дать дефолтный <code>mcp</code> для работы с тестами и симулятором.</p>
  <p id="wKMs">Ну и отсутствие терминала уже ни в какие рамки не идет. Хоть я и не знаю почти никаких команд в терминале, и вообще не фанат модных движений типа TUI, но отсутствие терминала в <code>Xcode</code> - это показатель их провала.</p>
  <figure id="Ene0" class="m_column">
    <img src="https://img2.teletype.in/files/52/c1/52c1eb17-e02b-4297-8dd9-bd64a1690a34.png" width="1022" />
    <figcaption>Да, тоггл есть, но толку от него нет. Так держать, Apple.</figcaption>
  </figure>
  <p id="19wc">Но вдруг у меня слишком высокие требования к инструментам?</p>
  <p id="12XD">Если вам нравится использовать <code>Coding Assistant</code> в <code>Xcode</code>, поделитесь обратной связью в комментариях, пожалуйста. Вдруг я чего-то не знаю, и там все отлично работает, если использовать это по-другому.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@easy_dev991/zlfw_fKJvh9</guid><link>https://teletype.in/@easy_dev991/zlfw_fKJvh9?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991</link><comments>https://teletype.in/@easy_dev991/zlfw_fKJvh9?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991#comments</comments><dc:creator>easy_dev991</dc:creator><title>Приложение для Android с нуля до релиза за 3 недели</title><pubDate>Sun, 25 Jan 2026 15:06:37 GMT</pubDate><category>Android</category><description><![CDATA[<img src="https://img3.teletype.in/files/64/51/6451d1a1-8c87-4c66-b17e-922425af20e2.png"></img>Все больше классных смартфонов на андроиде выпускают, все сильнее стирается граница между iOS/Android, в особенности из-за санкций и ограничений на важные и нужные для жизни сервисы, и я решил сделать андроид-версию своего счётчика дней в новогодние праздники (которые у меня объединились с отпуском).]]></description><content:encoded><![CDATA[
  <p id="t5Ee">Все больше классных смартфонов на андроиде выпускают, все сильнее стирается граница между iOS/Android, в особенности из-за санкций и ограничений на важные и нужные для жизни сервисы, и я решил сделать андроид-версию своего <a href="https://github.com/easydev991/SwiftUI-Days" target="_blank">счётчика дней</a> в новогодние праздники (которые у меня объединились с отпуском).</p>
  <h2 id="OIx6">Предыстория и о чем статья</h2>
  <p id="lSZ1">Чуть больше двух лет назад я начинал изучать верстку с использованием Jetpack Compose, потому что это похожая штука на SwiftUI, и даже успел за пару месяцев сверстать большинство компонентов дизайн-системы для приложения с площадками для тренировок (<a href="https://github.com/easydev991/SwiftUI-WorkoutApp" target="_blank">вот iOS-версия</a>), но потом все же забросил это дело, потому что:</p>
  <ul id="VOCw">
    <li id="zeMi">хотелось развивать iOS-приложения, и я это успешно делал</li>
    <li id="N4e8">не хотелось тратить время на изучение андроид-разработки с нуля, когда я уже умею делать приложения для iOS</li>
    <li id="cbKV">в то время мне еще совсем не казалось, что есть смысл смотреть в сторону смены айфона на андроид (а теперь ситуация постепенно меняется)</li>
    <li id="glL7">не пользовался нейросетями для разработки </li>
  </ul>
  <p id="582s">Насчёт счётчика дней - именно с идеи этого приложения я и начинал свою карьеру в iOS-разработке, при этом сам же пользуюсь этим приложением на айфоне, и поэтому решил именно с него начать обучение андроид-разработке в этот раз.</p>
  <p id="EaKB">В этой статье расскажу самое интересное и главное на мой взгляд про андроид-разработку с нуля и до публикации в русторе. И пара слов будет про рустор.</p>
  <h2 id="rDWF">Что нужно знать и уметь</h2>
  <h3 id="60ya">Язык программирования</h3>
  <p id="V3Gk">Как ни странно, не нужно знать язык программирования, чтобы сделать андроид-приложение. Т.е. ни Kotlin, ни Java мне не пригодились. Первый я могу очень поверхностно читать (он похож на Swift), а второй вообще не знаю и не хочу знать . В разработке мне это не понадобилось совсем.</p>
  <h3 id="FUzC">Софты</h3>
  <p id="wude">Нужно уметь формулировать свои мысли в слова, чтобы писать запросы в нейросеть.</p>
  <p id="fGFK">Нужно уметь излагать свои мысли простым и понятным языком, и в достаточной степени детализировать постановку задачи, чтобы получить результат как можно ближе к желаемому.</p>
  <h3 id="8Lxh">Планирование</h3>
  <p id="VWoM">Для разработки андроид-приложения мне пригодились навыки планирования и деления задач на подзадачи (декомпозиция).</p>
  <p id="qe4V">Абсолютно все задачи для нейросети я провожу через планирование. Нельзя просто сказать &quot;Хочу такое приложение, чтобы в нем можно было запоминать события и считать дни&quot; - получится полная фигня, хотя что-то может быть даже будет работать </p>
  <p id="AT8B">Нужно уметь составить план по разработке приложения для начала у себя в голове, а потом перенести его в текстовый формат для последующей обработки нейросетью.</p>
  <h3 id="urFz">Принципы разработки</h3>
  <p id="I9HT">В процессе разработки iOS-приложений я привык обращать внимание на разные моменты, а отдельно хочу выделить эти:</p>
  <ol id="Z8Oi">
    <li id="wWHc">Пользовательский опыт (<strong>UX</strong>) и интерфейс (<strong>UI</strong>): дизайн должен быть интуитивным, простым и удобным для использования одной рукой; важна консистентность элементов управления и предсказуемое поведение.</li>
    <li id="VRA6"><strong>Адаптивность:</strong> интерфейс обязан корректно отображаться на экранах разного размера и разрешения</li>
    <li id="iwZp"><strong>Следование гайдлайнам:</strong> использование принципов <u>Material Design</u> для Android и <u>Human Interface Guidelines</u> для iOS для обеспечения привычного пользователям опыта</li>
  </ol>
  <p id="P4KI">Формулировки взял из интернета, надеюсь что звучат понятно.</p>
  <p id="H3NL">Это все не означает, что я знаю наизусть гайдлайны Android/iOS, или что я всегда планирую разработку для смартфонов и планшетов сразу, но я осознанно стремлюсь делать приложение так, чтобы пользователю было удобно и привычно, и чтобы пользователю не пришлось сталкиваться с ошибками, которые портят впечатление от использования приложения.</p>
  <h3 id="oXkP">Система контроля версий (Git)</h3>
  <p id="qt4Q">Про гит я писал в отдельной статье (<a href="https://telegra.ph/Pro-rabotu-s-Git-02-23" target="_blank">ссылка</a>), и если вам эта статья пригодилась, то призываю поставить лайк в <a href="https://t.me/easy_dev991/79" target="_blank">телеграм</a>.</p>
  <p id="sV5O">Гит позволяет вести разработку с высокой степенью уверенности в сохранении результатов работы. Поскольку нейросеть регулярно ломает код, то при помощи гита можно вернуться на предыдущий этап, когда все работало.</p>
  <h2 id="py7l">Инструменты</h2>
  <p id="QnvP">Я заранее определил идею для приложения, у меня был готовый дизайн в iOS-приложении, поэтому переходим к выбору инструментов.</p>
  <h3 id="r9xL"><strong>Android Studio</strong></h3>
  <p id="Ig07">Тут запускаю приложение на эмуляторе и нажимаю иногда кнопку для форматирования кода, фича кстати классная, вот демо:</p>
  <figure id="PJVW" class="m_column">
    <img src="https://img3.teletype.in/files/eb/41/eb4178a1-2e13-4d83-8d93-30bbc86cc3d3.png" width="1364" />
    <figcaption>Правык клик на папке с файлами -&gt; Reformat Code</figcaption>
  </figure>
  <figure id="MGRu" class="m_column">
    <img src="https://img2.teletype.in/files/18/75/18750d7f-de41-4a4b-991d-053dc700a5fd.png" width="1516" />
    <figcaption>Ставлю три галки и жму &quot;Run&quot;</figcaption>
  </figure>
  <h3 id="fwKm">Cursor</h3>
  <p id="5Erf">Тут происходит основная разработка.</p>
  <p id="8V4Q">Часто после генерации кода в курсоре мне приходится вручную исправлять пропущенные импорты в Android Studio для каких-нибудь кнопок или отступов, но это не требует знания языка программирования:</p>
  <figure id="pF3w" class="m_column">
    <img src="https://img3.teletype.in/files/28/6d/286dd40e-0707-4b0a-b9f8-db51c96091ea.png" width="1524" />
    <figcaption>Навожу курсор на слово выделенное красным, жду пока появится всплывающее окно со словами Unresolved reference, и жму на кнопку Import class ...</figcaption>
  </figure>
  <h3 id="Nom6">Qwen Code CLI</h3>
  <p id="W7pr">Крутой бесплатный (на данный момент) инструмент для разработки (и не только), всем рекомендую попробовать, <a href="https://github.com/QwenLM/qwen-code" target="_blank">вот ссылка на гитхаб</a>.</p>
  <p id="1tH7">Использовал для составления планов, исправления мелких ошибок, больших (но простых) рутинных задач типа перемещения файлов в проекте и т.д. - может работать часами и не упирается в лимит, в отличие от курсора. Ну и отвечает менее качественно, так что компромисс.</p>
  <h3 id="u7Qr">Perplexity</h3>
  <p id="2RJM">Бывает, что ни курсор, ни квен не дают нормальный ответ и предлагают плохие решения, которые очевидно не работают (судя по поведению приложения в эмуляторе), и я закидываю вопрос в перплексити, где внезапно получаю рабочее решение - просто пересылаю это решение в курсор/квен и говорю применить. Иногда помогает сразу, иногда нужно поискать обходные решения, но это тоже крутой инструмент.</p>
  <h2 id="sENU">Сроки</h2>
  <p id="DTWx">Начал разработку 29 декабря 2025 года, релиз в русторе опубликовал через 19 дней - 17 января 2026 года. Код приложения в <a href="https://github.com/easydev991/Jetpack-Days" target="_blank">гитхабе</a> - там можно посмотреть историю коммитов (первый коммит и коммит с тегом 1.0).</p>
  <p id="m9L8">19 дней я округлил до трех недель просто для красоты. Если считать прямо по 8 часов разработки, то выйдет около 12 дней. В отпуске я сделал 90% работы, а после отпуска за несколько вечеров все доделал и выпустил релиз.</p>
  <h2 id="P4sE">Что мне понравилось</h2>
  <h3 id="VaND">Работа с Android Studio</h3>
  <p id="A7o5">После Xcode это показалось просто прорывом во многих аспектах, кроме разве что тестов. Запускать тесты в студии очень неудобно, ну или я еще не научился делать это удобно: в Xcode есть одно меню для запуска нужных тестов, а в студии я такое не нашел 🫣</p>
  <p id="1M3Y">Больше всего понравилось:</p>
  <ul id="ntfN">
    <li id="CgBm">форматирование кода, о котором раньше уже написал и показал скриншоты</li>
    <li id="F6Qt">превью Jetpack Compose (запускается быстро, никаких крашей в фоне, удобнее чем в Xcode)</li>
    <li id="YHXh">наличие терминала и плагинов, например, для работы с Makefile</li>
  </ul>
  <p id="0avA">В терминале я и квен запускал, и команды из Makefile (пока не нашел плагин для работы с Makefile) - этого очень не хватает в Xcode:</p>
  <figure id="Bjpp" class="m_column">
    <img src="https://img4.teletype.in/files/fe/49/fe49045f-7ce0-4fce-ac48-01e9dc3fa0e6.png" width="1344" />
    <figcaption>Кнопки для терминала и плагина Makefile</figcaption>
  </figure>
  <figure id="dmTE" class="m_column">
    <img src="https://img4.teletype.in/files/f2/0c/f20c1634-5edc-4125-8556-49fefecb8da6.png" width="3504" />
    <figcaption>Слева код, справа тесты в терминале, и все это в одном месте (Android Studio) - чудеса!</figcaption>
  </figure>
  <figure id="g5T3" class="m_column">
    <img src="https://img4.teletype.in/files/30/4d/304dd16f-0ef5-4191-bb5d-2c3e445ce02f.png" width="820" />
    <figcaption>Плагин для Makefile в действии - все команды из Makefile можно запускать прямо по кнопке &quot;play&quot;</figcaption>
  </figure>
  <h3 id="dewW">Вес приложения</h3>
  <p id="x7x6">Андроид приложение значительно легче iOS-версии. Опубликованное в русторе приложение весит всего <strong>2.6 мб</strong> (AAB-файл), APK в гитхабе весит всего <strong>3.85 мб</strong>, тогда как iOS-версия весит <strong>5.9 мб</strong>, причем около 40% веса добавилось из-за перехода с Xcode 16.4 на Xcode 26 </p>
  <h3 id="LE0T">Минимальная версия Android</h3>
  <p id="0BKU">Приложение работает на версии Android 8 и выше, релиз этой версии андроид был в 2017 году. Вся верстка на Jetpack Compose, стабильная версия которого вышла в 2021 году!</p>
  <p id="Mhhu">iOS-приложение работает на версии iOS 17 и выше, релиз этой версии iOS был в 2023 году. Эту версию системы я выбрал, чтобы использовать упрощенный инструмент для хранения данных приложения (<a href="https://developer.apple.com/documentation/swiftdata" target="_blank">Swift Data</a>). Вся верстка на SwiftUI, который появился в 2019 году и был настолько дырявым, что его мало кто использовал в первые 2 года после релиза.</p>
  <p id="NGGh">В общем, андроид ушел в большой отрыв в плане удобства верстки.</p>
  <h2 id="extO">Что мне не понравилось</h2>
  <h3 id="wz1E">Навигация в Jetpack Compose</h3>
  <p id="ncLg">В iOS есть стандартные (нативные) инструменты для создания навигации любой сложности, но главное - есть простой и понятный способ как сделать простую навигацию по приложению.</p>
  <p id="JMMn">В андроиде с навигацией намного сложнее, как и со многими другими штуками - для навигации надо подключать специальную зависимость (<a href="https://developer.android.com/jetpack/androidx/releases/navigation" target="_blank">ссылка на документацию</a>), и с ней пришлось немало повозиться, чтобы настроить безопасную зону (фронтальная камера, нижняя панель навигации) и передачу данных между экранами.</p>
  <h3 id="fzik">Gradle</h3>
  <p id="gl3a">Если бы это было моей работой, я бы разобрался в этом.</p>
  <p id="Bg8N">Но поскольку это не работа (а хобби), то ну его нафиг, потому что выглядит это нифига не весело, и ни разу не очевидно 🤪</p>
  <p id="uF6S">Нахожу решения проблем, включая обходные решения, и на этом заканчиваю разбор. </p>
  <h3 id="MO6m">Обилие инструментов</h3>
  <p id="9I0O">После Xcode, в котором объективно почти ничего лишнего нет (как и некоторых нужных вещей), в студии просто дофига всего, что мне не нужно, и непонятно. И это уже после их редизайна, в котором неплохо так изменили UI и спрятали многие кнопки.</p>
  <p id="Jlkx">В том числе было непросто настроить линтер и форматирование кода - выбрал два инструмента (ktlint + detekt), которые конечно же конфликтуют в некоторых настройках </p>
  <p id="1lLw">Выбирал не по своему опыту (т.к. у меня его нет), а по предложению гугла и нейросети, но без линтера и форматирования кода можно намесить знатную кашу, поэтому лучше уж с ними, особенно если код пишу не сам.</p>
  <h3 id="o92I">Эмуляторы</h3>
  <p id="EoO1">Они издают неприятные системные звуки при смене языка в настройках и вообще при запуске приложения (и еще в разных ситуациях). Похоже, пришло время погуглить как это отключить (если это вообще возможно) </p>
  <h2 id="GsMR">Рустор</h2>
  <p id="vA9W">Специально пишу на русском языке название этого магазина приложений, потому что, блин, это специальный магазин приложений конкретно для РФ. Нафига его называть английскими буквами?)</p>
  <h3 id="TWvX">Требования к приложению</h3>
  <p id="COF4"><strong>5 звезд</strong> за требования к приложению перед релизом - минимум полей для настройки приложения перед отправкой на модерацию.</p>
  <p id="41kY">В AppStore просто миллион полей, еще и разбросаны по разным местам на сайте.</p>
  <h3 id="tpV3">Проверка приложения</h3>
  <p id="ALXg"><strong>5 звезд</strong> за скорость проверки приложения на релизе - меньше часа с момента <strong>первой</strong> отправки приложения до одобрения и публикации.</p>
  <p id="bk8s">В iOS у меня был рекорд по скорости публикации одного из релизов - <a href="https://t.me/easy_dev991/204" target="_blank">меньше часа</a>, но это был не первый релиз, а это важно. <strong>Первый</strong> релиз в iOS у меня был долгим для каждого из трех приложений: модератор задает одни и те же бесполезные вопросы про монетизацию, целевую аудиторию и т.д., хотя в приложении не подключена монетизация, есть описание приложения на русском и английском языках, и заполнены все миллион обязательных полей в кабинете разработчика.</p>
  <h3 id="evjQ">UX в русторе</h3>
  <p id="6ER4"><strong>3 звезды</strong> - слишком часто выдает бессмысленные ошибки в стиле &quot;что-то пошло не так&quot; и просит заново авторизоваться. Пишу в поддержку - они просят скриншоты. Присылаю скриншоты - они пишут, что нужны другие скриншоты, и что я что-то делаю не так.</p>
  <h3 id="iQbs">Аналитика по приложению</h3>
  <figure id="JjCc" class="m_column">
    <img src="https://img3.teletype.in/files/6c/b5/6cb5814c-0ad8-46ab-819f-e6d9669d1c8b.png" width="2234" />
    <figcaption>Вот и вся доступная аналитика</figcaption>
  </figure>
  <p id="ayHM"><strong>1 звезда</strong> - почти никакой аналитики нет, и это плохо. Даже в AppStore есть стандартная аналитика, включая падения приложения.</p>
  <p id="ycQF">Поддержка рустора сказала, что можно установить их инструмент для сбора аналитики в качестве отдельной зависимости, но я решил, что пока рановато, и подключил Firebase для отслеживания падений приложения. К слову, пока падений нет, как и пользователей </p>
  <h2 id="8ZAE">Заключение</h2>
  <p id="2Hr7">Вот такой врыв в андроид я совершил неожиданно для самого себя в конце 2025 года, и считаю это успешным результатом. Еще раз <a href="https://github.com/easydev991/Jetpack-Days" target="_blank">ссылка на код</a>, и <a href="https://www.rustore.ru/catalog/app/com.dayscounter" target="_blank">ссылка на приложение в русторе</a> - буду признателен за отзывы и тестирование </p>
  <p id="0VBG">Приложение делал, чтобы проверить, насколько это вообще реально без знания языка, ну и всей специфики андроида, и чтобы набить руку.</p>
  <p id="s8OL">Сейчас в свободное время делаю приложение с площадками для тренировок (аналог <a href="https://github.com/easydev991/SwiftUI-WorkoutApp" target="_blank">iOS-версии</a>), и там уже будет полный фарш с точки зрения логики и количества фичей, потому что в приложении больше 50 запросов к серверу и куча экранов, это будет интересно </p>
  <p id="hcrC">Отдельно в платном канале напишу больше подробностей и практических примеров разработки в андроиде, включая планирование, тесты и правила/навыки ... для нейросети.</p>
  <p id="TsnU">Спасибо за внимание!</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@easy_dev991/0f3ypt50ZJT</guid><link>https://teletype.in/@easy_dev991/0f3ypt50ZJT?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991</link><comments>https://teletype.in/@easy_dev991/0f3ypt50ZJT?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991#comments</comments><dc:creator>easy_dev991</dc:creator><title>135. Тестируем парсинг моделей</title><pubDate>Sun, 14 Dec 2025 09:55:02 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/a2/09/a209dad4-9415-4a2d-aa66-be82a023adb6.png"></media:content><description><![CDATA[<img src="https://img3.teletype.in/files/62/6c/626ce370-cf46-4cd2-8e39-35978103aea9.png"></img>Недавно я выпустил в аппстор приложение для тренировок, предварительно провел много ручных тестов и написал больше тысячи автотестов. В этой статье расскажу про важность забытых мной тестов 😅]]></description><content:encoded><![CDATA[
  <p id="LxAA">Недавно я выпустил в аппстор <a href="https://apps.apple.com/app/id6753644091" target="_blank">приложение для тренировок</a>, предварительно провел много ручных тестов и написал больше тысячи автотестов. В этой статье расскажу про важность забытых мной тестов 😅</p>
  <h2 id="VPU3">Тесты в первом релизе</h2>
  <figure id="VTP1" class="m_original">
    <img src="https://img3.teletype.in/files/62/6c/626ce370-cf46-4cd2-8e39-35978103aea9.png" width="1404" />
    <figcaption>Автотесты на момент релиза первой версии приложения</figcaption>
  </figure>
  <p id="7igo">Вот обобщенный перечень тестов, которые были на момент первого релиза:</p>
  <ul id="kmEj">
    <li id="PACM">Тесты для экрана выполнения тренировки (настройка, завершение шагов, получение результата, работа с таймером, турбо-дни и т.д.)</li>
    <li id="e0O7">Тесты для экрана превью тренировки (построение активности дня, редактирование комментариев, обновление данных, сохранение тренировки и т.д.)</li>
    <li id="RH0k">Тесты для управления статусом программы (получение статуса, запуск, сброс, синхронизация, журнал синхронизации, интеграционные тесты и т.д.)</li>
    <li id="Pgar">Тесты для работы с дневником тренировок (базовые операции, комментарии, разрешение конфликтов, обработка ошибок, тренировки и т.д.)</li>
    <li id="z4UF">Тесты для работы с инфопостами (синхронизация, парсинг, управление ресурсами, работа с YouTube, избранное и т.д.)</li>
    <li id="ersE">Тесты для работы с прогрессом пользователя (базовые операции, синхронизация, работа с фотографиями, калькулятор прогресса и т.д.)</li>
    <li id="eDOK">Тесты для работы с пользовательскими упражнениями</li>
    <li id="d5s5">Тесты для настроек приложения</li>
    <li id="Le3o">Тесты для воспроизведения звука</li>
    <li id="RR2L">Тесты для обновления стран</li>
    <li id="qymy">Тесты для обработки изображений</li>
    <li id="OzRF">Тесты для управления ресурсами изображений</li>
    <li id="9zUI">Тесты для работы с YouTube видео</li>
    <li id="F7bP">Тесты для моделей тренировок</li>
    <li id="nwG3">Тесты для моделей дней</li>
    <li id="MJUK">Тесты для моделей прогресса</li>
    <li id="ZnOv">Тесты для моделей пользователя</li>
    <li id="PcpC">Тесты для моделей синхронизации</li>
    <li id="w7qA">Тесты для других моделей (HomeScreenModel, ShareAppURL, TimerSound)</li>
    <li id="VgOS">Тесты для создания программы тренировок (инициализация дня, работа с активностью дня, турбо-дни, пользовательские упражнения, типы выполнения и т.д.)</li>
  </ul>
  <p id="mWFS">У меня была цель <strong>протестировать всю возможную логику</strong>, чтобы было проще поддерживать и развивать приложение в любое время: хоть сейчас, хоть через год, когда я скорее всего забуду большинство мелочей, влияющих на ту же синхронизацию данных с сервером и т.д.</p>
  <h2 id="XRO7">Проблема первого релиза</h2>
  <p id="ODqc">Про релиз сделали пост на сайте, с которым синхронизируется приложение, и в их телеграм-чате - там я случайно увидел пост одного из пользователей:</p>
  <figure id="t1Lj" class="m_original">
    <img src="https://img3.teletype.in/files/e8/e2/e8e20d50-4743-47e1-a404-b0cb7d1d6cb2.jpeg" width="828" />
    <figcaption>Всем привет, была у кого такая проблема в приложении на IOS?</figcaption>
  </figure>
  <p id="Gu1O">Моя реакция, когда я увидел этот пост: 🤯🤦‍♂️</p>
  <p id="lYN4">Для разработчика проблема очевидна: не получилось спарсить (обработать) какой-то из ответов сервера, а судя по скриншоту, это вообще происходит при первой авторизации, т.к. на главном экране ничего нет кроме алерта.</p>
  <p id="S7If">Это был <strong>единственный момент, который я не протестировал</strong>: зарегистрировать нового пользователя на сайте, а потом авторизоваться в iOS-приложении.</p>
  <p id="MEgF">Т.е. я тестировал только существующих пользователей, и для них все работает корректно. Вот это поворот 🙂</p>
  <p id="YUin">С пользователем мне повезло, он прислал мне в личку детали по воспроизведению проблемы и я сходу смог у себя это повторить.</p>
  <h3 id="lCfz">Причина проблемы</h3>
  <p id="Luk6">Проблема возникала при декодировании JSON ответа от сервера в структуру <code>CurrentRunResponse</code>.</p>
  <p id="afKY">Я использовал стандартные методы декодирования, которые не могли корректно обработать поле с датой: метод <code>decodeIfPresent</code> не мог правильно обработать случай, когда сервер возвращает <code>null</code> в JSON в поле с датой.</p>
  <p id="NT1a">При этом использовалась стратегия декодирования даты <code>iso8601</code>, которая не поддерживала формат с дробными секундами, который иногда возвращал сервер.</p>
  <h3 id="TkO4">Почему только для новых пользователей</h3>
  <p id="Jayg">Проблема возникала только для новых пользователей, которые еще не начинали тренироваться на сайте/в приложении, потому что сервер возвращал разные данные в зависимости от того, есть ли для пользователя запись на сервере о дате начала тренировок.</p>
  <p id="zGVw">У существующих пользователей, которые уже успели начать тренироваться до релиза этого приложения (в старом приложении или на сайте), проблема не возникала, потому что сервер возвращал валидные данные (не <code>null</code>) - тут стандартные методы декодирования работали корректно, потому что все поля имели ожидаемые типы данных.</p>
  <h3 id="Q05N">Исправление проблемы</h3>
  <p id="SKwL">Пришлось создать специальные методы декодирования, которые корректно обрабатывают все возможные случаи от сервера, и конечно на этот раз добавил тесты для этой логики, код можно посмотреть в <a href="https://github.com/easydev991/SwiftUI-SotkaApp/blob/main/SwiftUI-SotkaApp/Libraries/SWNetwork/Tests/SWNetworkTests/JSONDecoderExtensionTests.swift" target="_blank">гитхабе</a>, а тут просто перечислю сценарии:</p>
  <ul id="lEJ5">
    <li id="2j1s">Должен декодировать стандартный ISO8601 формат без дробных секунд</li>
    <li id="IH5r">Должен декодировать ISO8601 формат с таймзоной +03:00</li>
    <li id="jAdD">Должен декодировать ISO8601 формат с дробными секундами (одна цифра)</li>
    <li id="sWg8">Должен декодировать ISO8601 формат с дробными секундами (две цифры)</li>
    <li id="urXQ">Должен декодировать ISO8601 формат с дробными секундами (три цифры)</li>
    <li id="7qdy">Должен декодировать ISO8601 формат с дробными секундами (четыре цифры)</li>
    <li id="KbV3">Должен выбрасывать ошибку для невалидной даты</li>
    <li id="qiG2">Должен выбрасывать ошибку для пустой строки</li>
    <li id="FpqM">Должен выбрасывать ошибку для неправильного формата даты</li>
    <li id="5fC8">Должен декодировать опциональное поле Date? с валидной датой</li>
    <li id="9cCz">Должен декодировать опциональное поле Date? с null значением</li>
    <li id="ZSzQ">Должен декодировать опциональное поле Date? с отсутствующим ключом</li>
    <li id="jgKe">Должен декодировать server date time формат без часового пояса</li>
    <li id="ecn2">Должен декодировать опциональное поле Date? с server date time форматом</li>
    <li id="e8HJ">Должен декодировать минимальную дату</li>
    <li id="Mn06">Должен декодировать минимальную дату в server date time формате</li>
    <li id="udHu">Должен декодировать дату с максимальными дробными секундами</li>
    <li id="AE9x">Должен декодировать ISO short date формат</li>
    <li id="gwMg">Должен декодировать опциональное поле Date? с ISO short date форматом</li>
  </ul>
  <h2 id="be1h">Тесты во втором релизе</h2>
  <figure id="AGT7" class="m_original">
    <img src="https://img1.teletype.in/files/49/f9/49f92614-0687-42d2-89e6-540277ef5424.png" width="1410" />
    <figcaption>Автотесты на момент второго релиза приложения</figcaption>
  </figure>
  <p id="cd1Z">Все тесты генерировал с помощью нейросети с использованием нескольких простых правил, о чем напишу в другой раз, а на исправление проблемы вместе с написанием новых тестов потратил около часа.</p>
  <p id="PzyP">После этого оформил запрос на ускоренную проверку релиза и ... два дня разбирался с модераторами и отвечал на их вопросы 😁</p>
  <h2 id="19QT">Заключение</h2>
  <p id="Ap8L">Тестируем не только основную логику приложения, но и декодирование моделей, особенно в пет-проектах ☝️</p>
  <h3 id="7EfF">Бонус</h3>
  <p id="TOnO">Год назад еще можно было сказать, что автотесты - это дорого, сложно, непонятно, непривычно и так далее. Потому что многие еще не умели пользоваться или не знали про режим агента у популярных IDE типа cursor/windsurf.</p>
  <p id="TEHu">Сейчас нельзя сказать, что ты не умеешь писать тесты, или что это дорого и долго - подобные вещи будут просто наглым враньем.</p>
  <p id="YEHB">Если не хочется разбираться в новых инструментах - нужно так и говорить прямо, что все, пора на пенсию, потому что слишком сложно сказать нейросети написать тесты для логики или для модели.</p>
  <p id="EBlm">Предлагаю всем, кто еще не пробовал генерировать тесты с нейросетью - начните это делать наконец-то, чтобы не ковыряться во внезапных багах и не ломать уже существующий функционал наших классных приложений 😉</p>
  <p id="4Nmw">Если кто-то переживает, что у нас станет меньше работы при снижении количества багов, то это заблуждение - работы меньше не станет, как и багов, зато стабильности будет больше 👍</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@easy_dev991/d_eh9E72Sb4</guid><link>https://teletype.in/@easy_dev991/d_eh9E72Sb4?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991</link><comments>https://teletype.in/@easy_dev991/d_eh9E72Sb4?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991#comments</comments><dc:creator>easy_dev991</dc:creator><title>134. ViewThatFits + @State</title><pubDate>Sun, 23 Nov 2025 12:40:36 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/7d/9d/7d9d2cc9-70f6-4537-9724-7c7881a20f22.png"></media:content><description><![CDATA[<img src="https://img4.teletype.in/files/71/b6/71b6ff46-c0ba-4e08-b536-857d855d6f5d.gif"></img>Недавно чинил любопытный баг, который оказался очень простым на деле, но нейросеть исправить его не смогла]]></description><content:encoded><![CDATA[
  <p id="z9xy">Недавно чинил любопытный баг, который оказался очень простым на деле, но нейросеть исправить его не смогла </p>
  <h2 id="asTg">Постановка и демо</h2>
  <p id="X7ub">На главном экране в приложении 2 варианта UI: для широкого экрана (например, для iPad) и для узкого экрана (для iPhone). На этом экране можно открыть модальное окно с важной функциональностью, а для поддержки двух вариантов UI используется <a href="https://developer.apple.com/documentation/SwiftUI/ViewThatFits" target="_blank">ViewThatFits</a> (удобная штука для iOS 16+), но при повороте экрана на айфоне эта модалка закрывается сама:</p>
  <figure id="sWDP" class="m_original">
    <img src="https://img4.teletype.in/files/71/b6/71b6ff46-c0ba-4e08-b536-857d855d6f5d.gif" width="800" />
    <figcaption>Демонстрация бага: неприятно будет, если модалка сама закроется при повороте экрана</figcaption>
  </figure>
  <figure id="Ofcv" class="m_original">
    <img src="https://img4.teletype.in/files/ff/7c/ff7c8507-aa44-4860-936d-e41642366b17.gif" width="800" />
    <figcaption>Исправленный вариант: модалка не закрывается при повороте экрана, все ок.</figcaption>
  </figure>
  <h2 id="vsyY">Причина бага</h2>
  <p id="copG">Проблема возникает из-за хранения <code>@State</code>-свойства для модального окна внутри дочерней вьюхи, которая используется в обоих вариантах UI, а при повороте экрана <code>ViewThatFits</code> приводит к частичному сбросу состояний дочерних вьюх, которые исчезают.</p>
  <p id="DWb1"><strong>Почему сброс частичный?</strong> Потому что на девайсе при повороте экрана модалка закрывалась, а при повороте обратно - сама появлялась, но не каждый раз </p>
  <p id="tKuI">Код для этого случая можно посмотреть в <a href="https://github.com/easydev991/Shared-SwiftUI-Content/blob/main/Shared%20SwiftUI%20Content/134_SheetRotationBugDemo.swift" target="_blank">гитхабе</a>.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@easy_dev991/jX3syALAaMi</guid><link>https://teletype.in/@easy_dev991/jX3syALAaMi?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991</link><comments>https://teletype.in/@easy_dev991/jX3syALAaMi?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991#comments</comments><dc:creator>easy_dev991</dc:creator><title>Баг в тулбаре iOS 26</title><pubDate>Sun, 09 Nov 2025 13:26:00 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/5c/db/5cdb1d2a-6e59-4e94-bfc9-b8eaefff07b0.png"></media:content><description><![CDATA[<img src="https://img1.teletype.in/files/c8/dd/c8dd0f57-b590-4928-b531-428cd7a3e3f0.png"></img>Если нужно скрыть кнопку в тулбаре, то нужно закрывать ее проверкой на if/else, а применять opacity больше нельзя - в iOS 26 будет такая фиговина как на скриншоте.]]></description><content:encoded><![CDATA[
  <figure id="jbQz" class="m_original">
    <img src="https://img1.teletype.in/files/c8/dd/c8dd0f57-b590-4928-b531-428cd7a3e3f0.png" width="1170" />
    <figcaption>iOS 18.6</figcaption>
  </figure>
  <figure id="CJra" class="m_original">
    <img src="https://img3.teletype.in/files/61/04/61042124-a5ea-466e-8daa-57c1524f2694.png" width="1206" />
    <figcaption>iOS 26.0</figcaption>
  </figure>
  <p id="pq5o">Если нужно скрыть кнопку в тулбаре, то нужно закрывать ее проверкой на <code>if/else</code>, а применять <code>opacity</code> больше нельзя - в <code>iOS 26</code> будет такая фиговина как на скриншоте.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@easy_dev991/ViOqnhds2XA</guid><link>https://teletype.in/@easy_dev991/ViOqnhds2XA?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991</link><comments>https://teletype.in/@easy_dev991/ViOqnhds2XA?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991#comments</comments><dc:creator>easy_dev991</dc:creator><title>133. Чиним баг навигации в iOS 26</title><pubDate>Sun, 19 Oct 2025 10:06:48 GMT</pubDate><description><![CDATA[<img src="https://img2.teletype.in/files/5b/dc/5bdc10d2-6bb8-4fa8-94f9-2fcf4363c473.gif"></img>Недавно заметил в симуляторе iOS 26 сломавшуюся навигацию в нескольких экранах пет-проекта. Расскажу, в чем там было дело и как это исправляется.]]></description><content:encoded><![CDATA[
  <p id="2fXi">Недавно заметил в симуляторе iOS 26 сломавшуюся навигацию в нескольких экранах пет-проекта. Расскажу, в чем там было дело и как это исправляется.</p>
  <h2 id="MzM8">Демонстрация проблемы</h2>
  <figure id="jqj0" class="m_original">
    <img src="https://img2.teletype.in/files/5b/dc/5bdc10d2-6bb8-4fa8-94f9-2fcf4363c473.gif" width="500" />
    <figcaption>Баг №1: взаимодействие с menu закрывает экран</figcaption>
  </figure>
  <figure id="h8wG" class="m_original">
    <img src="https://img1.teletype.in/files/09/25/09258a97-9a75-4eb5-8ad2-2cdf31d96b35.gif" width="500" />
    <figcaption>Баг №2: взаимодействие с confirmationDialog закрывает экран</figcaption>
  </figure>
  <h2 id="fduY">Как должно быть</h2>
  <figure id="uNJN" class="m_original">
    <img src="https://img4.teletype.in/files/bd/ed/bded5263-a008-46ce-a103-a0ca8ee770f0.gif" width="512" />
    <figcaption>iOS 18.6, все работает корректно: и menu, и confirmationDialog</figcaption>
  </figure>
  <figure id="vnVy" class="m_original">
    <img src="https://img4.teletype.in/files/3b/37/3b3739ca-022e-4554-a500-5d721bc960fb.gif" width="502" />
    <figcaption>iOS 26, все работает корректно: и menu, и confirmationDialog (это видео записано после исправления &quot;ошибки&quot;)</figcaption>
  </figure>
  <h2 id="5hBP">Причина неправильного поведения для пользователей с iOS 26</h2>
  <p id="ridR">В проекте используется <a href="https://developer.apple.com/documentation/SwiftUI/NavigationView" target="_blank">NavigationView</a>, который все еще отлично работает сам по себе даже на iOS 26, а проблема возникает при отображении <code>NavigationView</code> внутри модального окна (<code>sheet</code>).</p>
  <h2 id="3YRm">Варианты исправления</h2>
  <h3 id="oXxN">Вариант 1: самый быстрый и &quot;очевидный&quot;</h3>
  <p id="EGIa">Можно заменить в проекте все места, где используется <code>NavigationView</code> на <code><a href="https://developer.apple.com/documentation/SwiftUI/NavigationStack" target="_blank">NavigationStack</a></code> ... и поднять минимальную версию iOS с 15 до 16. Таким образом можно сохранить исходное поведение и на старых версиях iOS, и на новых, но придется отказаться от пользователей на iOS 15*.</p>
  <p id="9y4w">Вы можете справедливо заметить, что на iOS &lt; 16 уже почти никого нет, и тем не менее там чуть больше 4% пользователей iPhone. Они не хотят или не могут обновиться, но ведь в нашем случае причина бага не в их девайсах, а где-то под капотом iOS 26, что вообще никак не касается других версий системы.</p>
  <h3 id="IPmd">Вариант 2: менее быстрый</h3>
  <p id="v4NN">Можно сделать то же самое, что в варианте 1, но без поднятия минимальной версии iOS - достаточно сделать свою обертку для навигации, где внутри будет проверка на iOS 16: если возможно, используем <code>NavigationStack</code>, а если нет, то <code>NavigationView</code>.</p>
  <p id="Tke2">Такой вариант я хотел применить еще на релизе iOS 18, но в то время это привело к крашам как раз в сценарии открытия <code>NavigationStack</code> внутри модального окна, причем именно на iOS 16+.</p>
  <h3 id="roas">Вариант 3: поменять способ открытия родительского окна</h3>
  <p id="92vM">Можно открывать экран с &quot;площадкой&quot; не в модальном окне, а в том же стеке навигации, что не требует изменения минимальной версии iOS и никаких проверок на версию iOS вообще не нужно добавлять, но немного поменяется подход к открытию некоторых экранов ... и все продолжит работать даже на iOS 26+, чудеса!</p>
  <h2 id="x0zX">Что я сделаю</h2>
  <p id="kWvE">Скорее всего, буду выбирать между первыми двумя вариантами. Все же это нишевое приложение, и там очень мало пользователей, и даже если кто-то сидит в приложении на iOS 15, то все основные функции там останутся доступными еще очень долго, пока на сервере никаких глобальных изменений не сделают (а там очень редко что-то меняется).</p>
  <h2 id="hRbd">Заключение</h2>
  <p id="Kyqt">Жидкое стекло принесло нам немало багов, и в том числе задело исправно работавшую навигацию на старом добром NavigationView.</p>
  <p id="KC52">Главное, что все еще есть решения таких проблем, в том числе и обходные.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@easy_dev991/Pereklyuchatel-dlya-podderzhki-temnoj-temy-09-21</guid><link>https://teletype.in/@easy_dev991/Pereklyuchatel-dlya-podderzhki-temnoj-temy-09-21?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991</link><comments>https://teletype.in/@easy_dev991/Pereklyuchatel-dlya-podderzhki-temnoj-temy-09-21?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991#comments</comments><dc:creator>easy_dev991</dc:creator><title>132. Переключатель для поддержки темной темы</title><pubDate>Sun, 19 Oct 2025 10:09:27 GMT</pubDate><description><![CDATA[Возьмем рабочий проект, который поддерживает только светлую тему, и добавим туда возможность включить/выключить поддержку темной темы - не пикер темы, а именно переключатель для поддержки фичи.]]></description><content:encoded><![CDATA[
  <p id="lLo1">Возьмем рабочий проект, который поддерживает только светлую тему, и добавим туда возможность включить/выключить поддержку темной темы - не пикер темы, а именно переключатель для поддержки фичи.</p>
  <h3 id="Больше-деталей">Больше деталей</h3>
  <p id="a5Z0">Есть iOS-приложение с жизненным циклом на <code>UIKit</code> (<code>AppDelegate</code>), которое никто раньше не адаптировал для темной темы.</p>
  <p id="D84D">В <code>Info.plist</code> есть ключ <code>UIUserInterfaceStyle</code> со значением <code>Light</code>, что по умолчанию включает всегда светлую тему, даже если на девайсе активна темная тема.</p>
  <p id="NWfs">Пользователи давно просят добавить поддержку темной темы, и пришло время это реализовать. Процесс займет неопределенное время, в ходе которого будет сделано несколько сборок для тестирования.</p>
  <p id="cECk">Чтобы было удобно переключаться между светлой и темной темами, добавим переключатель в дебаг-меню для тестовых сборок, куда нет доступа у пользователей - вот об этом и будет статья.</p>
  <h3 id="План">План</h3>
  <ol id="HcCC">
    <li id="4C9i">Удаляем из <code>Info.plist</code> неактуальный ключ <code>UIUserInterfaceStyle</code></li>
    <li id="pCKG">Добавляем тоггл в дебаг-меню</li>
    <li id="mLj7">Складываем логику в отдельный сервис</li>
    <li id="rIDO">Дорабатываем <code>AppDelegate</code></li>
  </ol>
  <p id="PEpD">Первые 2 пункта не очень интересные: удалить ключ из <code>Info.plist</code> очень легко, а добавить тоггл в дебаг-меню - по-разному, в зависимости от проекта.</p>
  <p id="ANbf">В нашем случае добавить тоггл было легко, поэтому перехожу к третьему шагу.</p>
  <h4 id="Складываем-логику-в-отдельный-сервис">Складываем логику в отдельный сервис</h4>
  <p id="JGeK">Управление поддержкой темной темы - это новая фича, которую хочется покрыть тестами, поэтому сделаем небольшой сервис, который будет заниматься этой логикой, и позже напишем тесты.</p>
  <p id="PQNE">Для управления поддержкой темной темы нам нужна ссылка на <code>window</code>, которая в нашем случае есть в <code>AppDelegate</code>, и значение тоггла из дебаг-меню:</p>
  <pre id="Z3lS">import UIKit

enum DarkModeSwitcher {
  @discardableResult
  static func applyIfNeeded(window: UIWindow?, isDarkModeEnabled: Bool) -&gt; Bool {
    guard let window else {
      return false
    }
    let desired: UIUserInterfaceStyle = isDarkModeEnabled ? .unspecified : .light
    guard window.overrideUserInterfaceStyle != desired else {
      return false
    }
    window.overrideUserInterfaceStyle = desired
    return true
  }
}
</pre>
  <p id="pgV6">Вот сценарии для юнит-тестов:</p>
  <ol id="XPrX">
    <li id="7HFA">Включение <code>darkMode</code> включает <code>.unspecified</code>, если ранее было <code>.light</code> и возвращает <code>true</code></li>
    <li id="l14k">Отключение <code>darkMode</code> устанавливает <code>.light</code>, если ранее было <code>.unspecified</code> и возвращает <code>true</code></li>
    <li id="mAKe">Повторный вызов с тем же значением не меняет стиль и возвращает <code>false</code></li>
    <li id="yzOK">Вызов с <code>nil</code> окном безопасен и возвращает <code>false</code></li>
  </ol>
  <h4 id="Дорабатываем-AppDelegate">Дорабатываем AppDelegate</h4>
  <p id="PXbp">Первым делом дорабатываем метод <a href="https://developer.apple.com/documentation/uikit/uiapplicationdelegate/application(_:didfinishlaunchingwithoptions:)" target="_blank">application(_:didFinishLaunchingWithOptions:)</a>, который вызывается при запуске приложения:</p>
  <pre id="cGRM">func application(
 _ application: UIApplication,
 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -&gt; Bool {
 // ...
 DarkModeSwitcher.applyIfNeeded(
  window: window,
  isDarkModeEnabled: isDarkModeEnabled // &lt;- ссылка на тоггл в дебаг-меню
 )
 // ...
 return true
}
</pre>
  <p id="f52l">Чтобы тестировать было удобнее, вызовем <code>DarkModeSwitcher</code> еще и при &quot;разворачивании&quot; приложения, т.е. когда оно переходит в активное состояние - таким образом можно будет сразу после переключения тоггла в дебаг-меню свернуть и развернуть приложение и сразу же увидеть темную тему (если она активна на устройстве):</p>
  <pre id="IbrE">func applicationDidBecomeActive(_: UIApplication) {
  DarkModeSwitcher.applyIfNeeded(
    window: window,
    isDarkModeEnabled: isDarkModeEnabled
  )
}
</pre>
  <p id="qQhO">Готово! Теперь можно продолжать процесс переезда на темную тему и не волноваться о том, что пользователи могут увидеть промежуточный результат, ну и не &quot;солить&quot; код в отдельной ветке.</p>
  <h3 id="Бонус:-SwiftUI">Бонус: SwiftUI</h3>
  <p id="QxDS">Как уже можно было догадаться, если жизненный цикл вашего приложения использует SwiftUI, то достаточно модификатора <a href="https://developer.apple.com/documentation/swiftui/view/preferredcolorscheme(_:)" target="_blank"><code>preferredColorScheme</code></a> - в него нужно передать значение тоггла из дебаг-меню и ... готово!</p>
  <h3 id="Заключение">Заключение</h3>
  <p id="Wic8">Код сервиса-переключателя для темной темы вместе с тестами можно посмотреть в <a href="https://gist.github.com/easydev991/184cf0434fa6cdd63517f3d490f091eb" target="_blank">гитхабе</a>. Если интересно почитать про план адаптации темной темы в этом приложении с нуля, ставьте лайк 👍</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@easy_dev991/131-Onbording-v-avtotesty-na-Python-09-06</guid><link>https://teletype.in/@easy_dev991/131-Onbording-v-avtotesty-na-Python-09-06?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991</link><comments>https://teletype.in/@easy_dev991/131-Onbording-v-avtotesty-na-Python-09-06?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=easy_dev991#comments</comments><dc:creator>easy_dev991</dc:creator><title>131. Онбординг в автотесты на Python</title><pubDate>Sun, 19 Oct 2025 10:18:55 GMT</pubDate><description><![CDATA[Вчера я организовал себе онбординг в автотесты на Python в рабочем проекте - расскажу кому это может пригодиться и как все прошло.]]></description><content:encoded><![CDATA[
  <p id="pc9N">Вчера я организовал себе онбординг в автотесты на <code>Python</code> в рабочем проекте - расскажу кому это может пригодиться и как все прошло.</p>
  <h3 id="Кому-будет-полезно">Кому будет полезно</h3>
  <ul id="iU8P">
    <li id="EO85">Новичкам, кто хочет научиться делать автотесты на <code>Python</code> (популярные языки мало отличаются в этом плане - дальше в статье все расскажу)</li>
    <li id="Nz0l">Действующим специалистам в этой теме - если ты уже пишешь автотесты самостоятельно, то эта статья может быть особенно полезной</li>
    <li id="HNib">Менеджерам, которые хотят снизить нагрузку на ручных тестировщиков (автотесты работают намного быстрее, чем живые люди - это факт)</li>
    <li id="IcVy">Всем желающим, кто умеет читать и нажимать на кнопки в этом нашем &quot;айти&quot;</li>
  </ul>
  <h3 id="Прежде-чем-начинать">Прежде чем начинать</h3>
  <p id="4vqU">Ключевые моменты, влияющие на успех при изучении автотестов:</p>
  <ol id="KNNA">
    <li id="g3OD">Мотивация - если не хочется узнавать новое и учиться, можно закрывать статью</li>
    <li id="0YAk">Отсутствие страхов: если страшно, если есть сомнения типа &quot;а вдруг не получится&quot;, &quot;а что если нужно уметь программировать&quot;, &quot;да я не знаю математику&quot;, &quot;да я не знаю Python&quot; - лучше сразу закрыть статью, чтобы не страдать</li>
  </ol>
  <h3 id="Мой-опыт-в-Python">Мой опыт в Python</h3>
  <p id="JcJU">В 2022 году я изучал <code>Python</code> в каком-то бесплатном мобильном приложении, потратил на это около 3-4 дней и не стал углубляться в детали, т.к. не использую этот язык в работе никогда, и вообще не использую его нигде.</p>
  <p id="HKWY">Просто было любопытно посмотреть на один из самых популярных языков программирования, вот и все.</p>
  <h3 id="Как-я-пришел-к-автотестам-в-рабочем-проекте">Как я пришел к автотестам в рабочем проекте</h3>
  <p id="S2eX">Сначала я начал активно писать (генерировать) автотесты в iOS-приложении, и после пары сотен тестов на <code>Swift</code> поинтересовался у наших тестировщиков, как они пишут свои тесты.</p>
  <p id="vdHd">Свои тесты я пишу почти целиком с использованием курсора, о котором я уже писал <a href="/125-Otzyv-na-Cursor-v-2025-godu-06-29">тут</a> и <a href="/127-Auto-Run-Mode-cursor-07-12">тут</a>, поэтому предложил поделиться опытом, чтобы упростить их работу.</p>
  <p id="VIvA">Оказалось, что ребята не пользуются курсором, и вообще автотестам нужно учиться, а это небыстро и т.д.</p>
  <p id="cuVr">Я решил разобраться в этой теме.</p>
  <h4 id="Шаг-1">Шаг 1</h4>
  <p id="UsD0">Первым делом запросил <strong>доступ к репозиториям</strong> с автотестами. Репозиторий - место, где хранится код проекта. Например, <a href="https://github.com/easydev991?tab=repositories" target="_blank">тут список</a> моих репозиториев в гитхабе.</p>
  <h4 id="Шаг-2">Шаг 2</h4>
  <p id="LeIr">Дальше нужно их клонировать, т.е. <strong>скачать себе на комп</strong> - для этого я использую <a href="https://desktop.github.com/download/" target="_blank">GitHub Desktop</a>, но можно пользоваться любыми инструментами, которые вам нравятся, даже просто командами в терминале (я их не заучивал, поэтому не использую).</p>
  <h4 id="Шаг-3">Шаг 3</h4>
  <p id="gZiL">Когда все загружено, осталось <strong>открыть проект в курсоре</strong> и приступать к делу - вот тут и начинается самое интересное и веселое.</p>
  <h3 id="Разбираемся-вместе-с-курсором">Разбираемся вместе с курсором</h3>
  <p id="7xip">Открываем проект в курсоре и что мы видим? Как всегда - список папок с файлами, и просто файлы.</p>
  <p id="r6IW">Если в проекте есть документация, хотя бы <code>README.md</code>, нужно обязательно туда заглянуть - вдруг документ не забросили и поддерживают в актуальном состоянии. Примеры ридми-файлов: <a href="https://github.com/easydev991/WrappingHStackLimited/blob/main/README.md" target="_blank">первый</a>, <a href="https://github.com/easydev991/SwiftUI-Days/blob/main/README.md" target="_blank">второй</a>, <a href="https://github.com/easydev991/SwiftUI-WorkoutApp/blob/main/README.md" target="_blank">третий</a>.</p>
  <p id="2QPS">Поскольку я вообще не представлял, как все работает в этом проекте, то я сказал курсору:</p>
  <blockquote id="C0ZA">изучи этот проект и скажи, нужно ли мне что-то установить для работы с этим проектом и для написания/запуска тестов, или можно сразу писать тесты и запускать их? если можешь, попробуй запустить тесты</blockquote>
  <p id="kxmF">Курсор подумал, что-то сделал, и <strong>через пару-тройку</strong> минут выдал ответ:</p>
  <p id="1CJv"><em>Я подготовил окружение и проверил запуск части тестов.</em></p>
  <ul id="BPzN">
    <li id="xQmB"><em>Что нужно установить/сделать:</em></li>
    <li id="AbC4"><em>Установить Python 3.11+.</em></li>
    <li id="nuOv"><em>Создать venv и поставить зависимости из requirements.txt (там свой PyPI-индекс, работает).</em></li>
    <li id="Csf0"><em>Создать /.env.local рядом с config.py с переменными: BASE_URL, USER_PASSWORD, TESTERDAV_PASSWORD.</em></li>
    <li id="DFfy"><em>Сгенерировать tests/_generated/actions.py.</em></li>
  </ul>
  <p id="bwFy"><em>Я уже выполнил:</em></p>
  <ul id="8ALp">
    <li id="mJ89"><em>Установил Python 3.11 через Homebrew.</em></li>
    <li id="Z6Cj"><em>Создал venv на 3.11, установил зависимости.</em></li>
    <li id="tA7E"><em>Сгенерировал tests/_generated/actions.py.</em></li>
    <li id="npVi"><em>Скопировал .env в .env.local (для локального прогона; значения лучше заменить на корректные).</em></li>
    <li id="UIbE"><em>Запустил безопасный тест без авторизации: 3 passed, 1 deselected.</em></li>
    <li id="SIZ5"><em>Тест с логином упал из‑за отсутствия валидных данных для логина (пустой токен).</em></li>
  </ul>
  <p id="SJ66"><em>Можно ли сразу писать и запускать тесты?</em></p>
  <ul id="QLhG">
    <li id="glzS"><em>Да, если у вас установлен Python 3.11+ и заполнен /.env.local. Тесты без авторизации уже успешно проходят. Для тестов с авторизацией нужно корректно заполнить логин/пароль в /.env.local.</em></li>
  </ul>
  <p id="BP3h"><em>1-2 предложения о прогрессе:</em></p>
  <p id="Az5u"><em>Я собрал окружение на Python 3.11, поставил зависимости, сгенерировал actions и запустил тесты: без авторизации — успех, авторизационные падают из‑за отсутствия валидных учётных данных. Готов подключить реальные значения в .env.local и прогнать полный набор.</em></p>
  <p id="0Kch">Знаю ли я про окружения в <code>Python</code> - совсем нет. Как их настраивать? Без понятия.</p>
  <p id="jFGp">При этом у меня в <code>GitHub Desktop</code> отобразилось несколько сотен новых файлов, что меня немного напрягло, и я написал в курсор:</p>
  <blockquote id="1418">проверь список изменений, которые мы не закоммитили - нужно ли что-то из этого добавить в гитигнор, чтобы не ломать работу другим коллегам в этом репозитории? и нужно ли нам оставлять тесты, которые ты создал в @actions/ , или это были просто пробные тесты и их можно удалить? если можно удалить, сделай это</blockquote>
  <p id="AtLe">Ответ курсора через 20 секунд:</p>
  <ul id="wiuh">
    <li id="t7vd"><em>Проверил незакоммиченные изменения: изменены только .gitignore и README.md. Новых тестовых файлов я не создавал.</em></li>
    <li id="labd"><em>Добавил в .gitignore: /.venv/, **/__pycache__/, .DS_Store. Это исключит локальные артефакты macOS и Python, чтобы не мешали коллегам.</em></li>
    <li id="hg8n"><em>tests/_generated/actions.py уже игнорируется (правило /tests/_generated/* есть), оставляем как есть.</em></li>
    <li id="Gj6Q"><em>Про «пробные тесты» в tests/api/actions/: новых не добавлял — папка уже была в проекте; удалять нечего.</em></li>
    <li id="cBCY"><em>Итого:В гит добавлять больше ничего не нужно, кроме уже внесённого .gitignore и обновлённого README.md.</em></li>
    <li id="uQQa"><em>Тесты в @actions/ не мной созданы и удалять их не нужно.</em></li>
  </ul>
  <p id="bKQy">Все новые файлы исчезли из списка в гите после обновления <code>gitignore</code> курсором, все четко ✅</p>
  <p id="vSMX">Дальше я сказал курсору создать <code>Makefile</code> с командами для настройки проекта и запуска тестов, чтобы коллегам в будущем было проще все настроить одной командой на <code>macOS</code>/<code>linux</code>, и проверить эти команды на работоспособность.</p>
  <p id="Y45y">Про <code>Makefile</code> я писал <a href="/122-Avtomatiziruem-rutinu-v-iOS-proekte-06-05">тут</a> и <a href="https://t.me/easy_dev991/221" target="_blank">тут</a>.</p>
  <p id="QU0N">Примерно через 25 минут, когда я сам проверил команды из <code>Makefile</code> и убедился, что все работает, я попросил курсор написать новый автотест по аналогии с существующими - он написал тест и запустил, все ок ✅</p>
  <h4 id="Обновляем-документацию">Обновляем документацию</h4>
  <p id="JTBr"><u>Пишу курсору:</u></p>
  <blockquote id="N2qA">теперь обнови ридми, чтобы там было краткое и понятное объяснение как запускать тесты и через стандартные команды (pytest), и через make-команды, и приведи по одному примеру для каждого варианта запуска тестов</blockquote>
  <p id="6Vb3">Он обновил ридми, я внес пару корректировок, а потом <u>задал пару уточняющих вопросов</u> про окружения в <code>Python</code>:</p>
  <blockquote id="PuhR">получается, что для запуска тестов через команду pytest не нужно предварительно активировать окружение? или я неправильно понял? если нужно активировать окружение, то прямо в примере запуска тестов с pytest напиши это. а то сейчас путаница возникает, как будто при использовании pytest не нужно делать предварительно source .venv/bin/activate</blockquote>
  <p id="XsTa"><u>Ответ:</u></p>
  <p id="5Ajf"><em>Я обновлю раздел в README, чтобы в примере для pytest явно сначала шёл source .venv/bin/activate, а альтернативный вариант без активации был выделен отдельно, чтобы не было путаницы. Затем вернусь с кратким резюме.</em></p>
  <p id="55YW"><em>Уточнил формулировку в README:</em></p>
  <ul id="9w8v">
    <li id="gwKS"><em>Для обычного вызова pytest через оболочку нужно активировать окружение. В примере теперь явно:</em></li>
    <li id="O5Sv"><em>сначала source .venv/bin/activate</em></li>
    <li id="jvFv"><em>затем pytest tests/... -q</em></li>
    <li id="wQSh"><em>Отдельной строкой оставил альтернативу без активации: ./.venv/bin/pytest ...</em></li>
  </ul>
  <p id="1DRF"><em>Так путаницы не будет: либо source + pytest, либо прямой вызов бинарника из .venv.</em></p>
  <blockquote id="hyty">проверь, есть ли в проекте какой-то пример оформления тестов? или инструкция по написанию тестов? я хочу добавить такой раздел в ридми, чтобы проще было начинать работать новым членам команды</blockquote>
  <p id="98oF">В результате в ридми появился разделы &quot;Запуск тестов на macOS&quot; и &quot;Как писать тесты&quot; ✅</p>
  <h4 id="Выводы-по-этому-проекту">Выводы по этому проекту</h4>
  <ol id="XBkU">
    <li id="fwV7">Я потратил около 30 минут на полную настройку проекта на своем ноуте, но в следующий раз другим ребятам (у которых <code>macOS</code>/<code>linux</code>) это можно будет сделать за пару минут, просто выполнив одну команду в терминале</li>
    <li id="lGYd">Разобрался как запускать существующие тесты и увидел как они проходят/падают</li>
    <li id="nQGu">Написал свой первый тест на <code>Python</code> за 1 запрос в курсоре</li>
  </ol>
  <h4 id="Что-было-дальше">Что было дальше</h4>
  <p id="jtjo">Дальше я повторил все с самого начала во втором проекте с UI-тестами для сайта, и это заняло в сумме около полутора часов, потому что:</p>
  <ol id="1AXB">
    <li id="fN7Y">Долго разбирался с кредами для тестовых учетных записей (логины, пароли, адреса почты), в итоге все нашлось, спасибо коллегам</li>
    <li id="3uYw">Сгенерировал неправильную автоматизацию в <code>Makefile</code> сначала</li>
  </ol>
  <p id="akmV">В первом проекте ребята использовали другой файл для хранения секретов, а поскольку у меня вообще не было никакого опыта работы с этими проектами, то я даже не думал, что надо где-то это проверить.</p>
  <p id="g7W8">В общем, есть такой файл <code>config.py</code>, где есть строка с явным указанием названия файла с кредами, например:</p>
  <p id="aTZ0"><code>load_dotenv(find_dotenv(filename=&quot;.env&quot;), override=True)</code></p>
  <p id="GDPQ"><strong>Как я в этом разобрался?</strong></p>
  <p id="1msn">Написал курсору, чтобы он запустил любой тест, где есть авторизация, и поправил ошибки.</p>
  <p id="Hf7b">Он запустил первый тест с авторизацией, тест упал, потом курсор за минуту нашел проблему и поправил ✅</p>
  <h3 id="Заключение">Заключение</h3>
  <p id="hXIB">Я хотел показать, что нет необходимости предварительно учить язык программирования, или покупать и проходить курсы по &quot;тестированию с нуля&quot;, или просто платить кому-то деньги за обучение новому в &quot;айти&quot;, прежде чем начинать заниматься делом. <strong>Можно сразу начинать разбираться в рабочих задачах.</strong></p>
  <p id="hSt1"><strong>Хочется </strong>чему-то<strong> научиться </strong>илиразобраться в новой теме? Открываем бесплатные инструменты типа перплексити и <strong>просим составить план</strong> обучения. Есть еще российские аналоги, но я ими ни разу не пользовался, поэтому не могу ничего полезного сказать про них.</p>
  <p id="vB0b">Нет уверенности в правильности ответов нейросетей? Живые <strong>люди</strong> <strong>тоже</strong> <strong>ошибаются</strong> очень часто, в том числе менторы, блогеры и эксперты на платформах, где продаются курсы.</p>
  <p id="i5aS">Боишься, что не получится? Я старался писать простым языком и делать акцент на том, что не нужно иметь опыт, чтобы его получить. Да, не нужен опыт работы, чтобы начать работать. Не нужно получать высшее образование, чтобы начать зарабатывать деньги.</p>
  <p id="6uJU"><strong>У тебя все получится</strong>, главное взять на себя ответственность за свое обучение и терпеливо решать возникающие задачи (проблемы) 🚀</p>

]]></content:encoded></item></channel></rss>