Форум программистов, компьютерный форум, киберфорум
C# для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.61/18: Рейтинг темы: голосов - 18, средняя оценка - 4.61
1 / 1 / 0
Регистрация: 29.10.2013
Сообщений: 9

Виртуальные методы и юнит-тесты

29.10.2013, 15:00. Показов 3470. Ответов 7
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
В C# пришел недавно из Java, где все методы виртуальные. И сразу столкнулся с тем, что в C# по умолчанию методы невиртуальные, более того, обычно учебники по C# рекомендуют оставлять методы невиртуальными, поскольку это мол повышает их производительность.

Однако вот сейчас вычитал у Рихтера (Richter - CLR via C#, 4ed, pp. 163-164), что это вовсе не так, что нет никакого выигрыша в производительности. Разница в производительности достигается благодаря только тому, что не делается проверка объекта, на котором вызывается метод, на null. А это происходит только для статических методов. Для нестатических методов, неважно, виртуальных или невиртуальных, проверка на null производится, и производительность падает одинаково.

Вопрос этот для меня непраздный, ибо еще будучи в Java, завел себе привычку во время юнит-тестов при тестировании метода А, который вызывает метод Б, перегружать метод Б, заменяя заглушкой. Чтобы тестировать только логику метода А. В Java это все делается без раздумий - все же методы виртуальные, а вот по поводу использования такой техники в C# до сего дня были сомнения.

В общем, хотел спросить у опытного народа - как он относится к тому, чтобы делать в C# методы виртуальными только для того, чтобы использовать заглушки в тестах.
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
29.10.2013, 15:00
Ответы с готовыми решениями:

Требуется написать юнит-тесты данного метода по прохождению
public void buttonLogIn_Click(object sender, EventArgs e) { if (comboBoxUser.SelectedIndex == 0) { ...

Полиморфизм. Виртуальные методы
Здраствуйте уважаемые админы!!У меня проблемы:swoon: с некоторыми предметами,у меня к вам очень большая просьба помогите с лабой...

Виртуальные методы и наследование
Здраствуйте. Не могли бы вы помочь добавить к этой программе наследование и виртуальные методы? using System; using...

7
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
29.10.2013, 15:17
Программы, скомпилированные входящим в состав .NET компилятором, всегда используют виртуальный вызов для нестатических методов, потому вопрос виртуальный/не виртуальный метод уже стоит в области архитектуры, а не производительности.

Цитата Сообщение от toleg Посмотреть сообщение
при тестировании метода А, который вызывает метод Б, перегружать метод Б, заменяя заглушкой.
То есть, тем самым меняя реализацию метода A, т.к. результат работы метода Б может повлиять на его дальнейшее выполнение.
В результате вы тестируете не свое приложение, а что-то другое. А посему и результат теста не может вызывать доверия.

Цитата Сообщение от toleg Посмотреть сообщение
В общем, хотел спросить у опытного народа - как он относится к тому, чтобы делать в C# методы виртуальными только для того, чтобы использовать заглушки в тестах.
Отрицательно: архитектура приложения не должна подстраиваться под фреймворк, используемый при тестах.
0
1 / 1 / 0
Регистрация: 29.10.2013
Сообщений: 9
29.10.2013, 15:43  [ТС]
Цитата Сообщение от kolorotur Посмотреть сообщение
То есть, тем самым меняя реализацию метода A, т.к. результат работы метода Б может повлиять на его дальнейшее выполнение.
Вы ведь в курсе, что это типичный паттерн юнит-тестирования? Если Б уже протестирован, зачем его тестировать вторично? Особенно, если Б вызывает еще кучу других методов, выполняет запросы к БД и пр., а А - всего лишь метод с примитивной логикой? В том ведь наверное и суть юнит-тестов, чтобы изолировать логику, разве нет? В общем, я как-то с трудом представляю, как вы обходитесь в тестах без заглушек, моков и пр.

Цитата Сообщение от kolorotur Посмотреть сообщение
В результате вы тестируете не свое приложение, а что-то другое. А посему и результат теста не может вызывать доверия.
На этот случай есть интеграционные тесты, которые тестируют всю цепочку вызовов. Но обычно рекомендуют их использовать, когда все методы протестированы по одиночке.

Цитата Сообщение от kolorotur Посмотреть сообщение
Отрицательно: архитектура приложения не должна подстраиваться под фреймворк, используемый при тестах.
Ясно, что ж, спасибо за мнение. Только не пойму, почему быть таким категоричным. Помнится, кто-то говорил, что если в обычных машинах и механизмах есть специальные окошки для наблюдения, щупы для проверки уровня масла, то почему создание таких "лишних" навесок для кода, облегчающих его тестирование и мониторинг, должно вызывать проблемы?

Я думаю, что ваш подход оправдан для open-source приложений, где вы публикуете интерфейсы и не хотите открывать для перегрузки лишние методы. Но для приложений другого типа - какая разница, сколько методов можно перегрузить в подклассах?

P.S. Надеюсь вы не считаете данный пост дерзостью, учитывая что вам пишет новичок как форума, так и платформы .NET. Просто хотелось бы понять ваши мотивы касательно данных практик программирования.
0
52 / 45 / 4
Регистрация: 07.10.2010
Сообщений: 95
29.10.2013, 16:32
virtual означает, что метод открыт для изменений. И тут возникает вопрос - мы тестируем класс, или отдельный метод? А имет ли смысл тестировать отдельный метод? Скорее нет, чем да. Все равно прийдется тестировать все классы в которых опредленны пергрузки.
Не проще ли сделать нормальную архитетуру, разбить на уровни и использовать DI?
0
1 / 1 / 0
Регистрация: 29.10.2013
Сообщений: 9
29.10.2013, 17:11  [ТС]
Цитата Сообщение от serefa Посмотреть сообщение
И тут возникает вопрос - мы тестируем класс, или отдельный метод? А имет ли смысл тестировать отдельный метод? Скорее нет, чем да.
Почему нет? По-моему логично, если класс содержит несколько методов, тестировать их по отдельности, а не кучей. Некоторые IDE даже предлагают возможность сгенерировать тесты - для каждого метода в отдельности.

Цитата Сообщение от serefa Посмотреть сообщение
Все равно прийдется тестировать все классы в которых опредленны пергрузки
Конечно, тестировать перегружаемый метод придется столько раз, сколько реализаций он имеет.

Цитата Сообщение от serefa Посмотреть сообщение
Не проще ли сделать нормальную архитетуру, разбить на уровни и использовать DI?
Не пойму я, как уровни и DI может помочь избежать юнит-тестирования? Вообще, тестирование с привлечением DI-контейнеров обычно рассматривается как еще более поздний этап, после юнит- и интеграционных тестов. Потому что не нужно вносить заморочки DI-фреймворка для тестирования логики простых методов.


P.S. Я понимаю, что у каждого свои привычки тестирования. В силу того, что я, как правило, пишу приложения, связанные с математическими и научными расчетами, я, как вы уже заметили, имею крен к подробному тестированию, стремясь иногда к 100% покрытию. Поэтому и тестирую поведение отдельных методов - чтобы убедиться, что они во-первых, корректно работают при заданном наборе входных параметров и результатов работы других методов. А потом уж тестирую методы в комплексе.
0
52 / 45 / 4
Регистрация: 07.10.2010
Сообщений: 95
29.10.2013, 18:50
А не перегруженные методы? У нас есть какие то данные и методы манипулирующие ими. Мы меняем один метод. Может ли поменятся поведение других - да, конечно, поэтому мы должны и их тестировать. Поэтому мы тестируем не отдельные методы, а весь класс, в котором были сделанны перегрузки.

DI позволяет перенести всю сложную логику в классы, вместо реальных использовать моки. И по сути оставить для тестирования только скелет.
Опять же - тестировать ВСЕ методы не нужно. Можно обойтись пабликами. Ну в крайнем случае протектами.
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
29.10.2013, 19:24
Цитата Сообщение от toleg Посмотреть сообщение
Вы ведь в курсе, что это типичный паттерн юнит-тестирования?
Если вы про стабы (stubs), то они применяются при перекрестных ссылках с другими модулями, но я, честно говоря, не видел, чтобы они применялись для подмены метода, являющегося членом тестируемого модуля, то есть при внутренней зависимости.
Это же вся логика разом похериться может, не говоря о состоянии модуля.

Цитата Сообщение от toleg Посмотреть сообщение
Если Б уже протестирован, зачем его тестировать вторично?
Если у вас электричество в здании генерируется не от динамо-машины с педальным приводом к рабочим местам сотрудников, то в чем проблема?
Ну отработает тест чуть дольше, делов-то.

Цитата Сообщение от toleg Посмотреть сообщение
Особенно, если Б вызывает еще кучу других методов, выполняет запросы к БД и пр.
Проблема в том, что Б в процессе может поменять состояние объекта, которое потом скажется на дальнейшей работа А.
Куча методов — не проблема, а вот запрос к БД должен подменяться моком.

Цитата Сообщение от toleg Посмотреть сообщение
В том ведь наверное и суть юнит-тестов, чтобы изолировать логику, разве нет?
Да, изолировать логику отдельных методов. Но если Б вызывается внутри А, то он становится частью логики А, потому его так просто зачастую не выкинешь.

Цитата Сообщение от toleg Посмотреть сообщение
В общем, я как-то с трудом представляю, как вы обходитесь в тестах без заглушек, моков и пр.
Возможно мы оперируем разными понятиями, но в моем понимании моки и стабы — это замена перекрестных зависимостей, а не внутренних.
Конечно, не исключено, что я чего-то не знаю.

Цитата Сообщение от toleg Посмотреть сообщение
На этот случай есть интеграционные тесты, которые тестируют всю цепочку вызовов. Но обычно рекомендуют их использовать, когда все методы протестированы по одиночке.
Дык интеграционные тесты нужны для тестирования нескольких модулей в совокупности, а юнит тесты — это тестирование отдельных компонентов конкретного модуля.
Я как-то не встречал, чтобы специально для тестирования юнита изменялась его же логика.

Цитата Сообщение от toleg Посмотреть сообщение
Только не пойму, почему быть таким категоричным.
Да бог с вами, никакой категоричности нет и в помине.
Это ж буковки — в них интонацию и мимику не передашь. Неустранимый дефект общения по переписке.
Я просто не люблю тупые аббревиатуры типа "ИМХО".
У нас в академических кругах любой текст, не подкрепленный цитатой или ссылкой, считается личным мнением по-умолчанию.

Цитата Сообщение от toleg Посмотреть сообщение
Помнится, кто-то говорил, что если в обычных машинах и механизмах есть специальные окошки для наблюдения, щупы для проверки уровня масла, то почему создание таких "лишних" навесок для кода, облегчающих его тестирование и мониторинг, должно вызывать проблемы?
Не могу согласиться с примером. Щупы и окошки — это все-таки вещи для диагностики "в полевых условиях", то есть во время эксплуатации. Аналог в программах — логи.
А вот если бы машины выпускали с квадратными колесами, потому что так их проще тестировать на предмет устойчивости на дорожном полотне — это совсем другое дело!

Цитата Сообщение от toleg Посмотреть сообщение
Я думаю, что ваш подход оправдан для open-source приложений, где вы публикуете интерфейсы и не хотите открывать для перегрузки лишние методы. Но для приложений другого типа - какая разница, сколько методов можно перегрузить в подклассах?
Это вопрос больше архитектурный: если переопределение метода не имеет смысла с семантической точки зрения, то зачем его делать виртуальным? Бритва Оккама жеж.

Цитата Сообщение от toleg Посмотреть сообщение
Надеюсь вы не считаете данный пост дерзостью
Нет и надеюсь на полную взаимность: никакой дерзости, категоричности или грубости в свой текст не вкладываю.
Если что-то показалось или задело, то приношу извинения — такой цели не стояло.
0
1 / 1 / 0
Регистрация: 29.10.2013
Сообщений: 9
29.10.2013, 21:43  [ТС]
Цитата Сообщение от serefa Посмотреть сообщение
Мы меняем один метод. Может ли поменятся поведение других - да, конечно, поэтому мы должны и их тестировать.
Почему это другие поменяются? Если они не вызывают измененный метод, то логика-то у них останется прежняя.

Ну а если боитесь, что поменяются - так ведь для того и делаются регрессионные тесты, чтобы знать, что сломалось, если вы что-то меняете.

Поэтому мы тестируем не отдельные методы, а весь класс, в котором были сделанны перегрузки.
Что значит тестируете класс? Вы ведь все равно, тестируя класс, тестируете поведение его методов, или изолируя их друг от друга, или вызывая их все в куче.

DI позволяет перенести всю сложную логику в классы, вместо реальных использовать моки. И по сути оставить для тестирования только скелет.
В смысле? А без DI сложная логика где? В глобальных функциях что ли? ))

Я и сам не против DI, но пока осваиваю .NET и пока работаю над основами нового проекта, даже не заморачиваюсь освоением Spring.NET. Потому что на данном этапе простоты это не добавит. Хотя конечно, объекты все равно по привычке разрабатываю с прицелом на DI и с принципами POJO.

Опять же - тестировать ВСЕ методы не нужно. Можно обойтись пабликами. Ну в крайнем случае протектами.
Это зависит от того, насколько сложное и ответственное приложение (или его часть). Простые и неважные вещи, особенно GUI я конечно позволяю себе тестировать поверхностно. А те части, где черт ногу сломит, где можно легко повредить данные и где можно сутками напролет искать непонятный баг - те лучше предусмотрительно потестить. Так я думаю.

Тем более, что какой-нибудь там паблик, если его разобрать по ветвлениям кода, может легко дать несколько сотен вариантов исполнения кода. И как его тестировать? Создавать TestFixture с сотней-другой методов, отличающихся только входными и разными другими параметрами? Скорее всего вы создадите пару методов, которые описывают основные сценарии, а остальные баги будете ловить в продакшене. Для моего нынешнего приложения это не вариант.

Добавлено через 19 минут
Цитата Сообщение от serefa Посмотреть сообщение
Можно обойтись пабликами. Ну в крайнем случае протектами.
И еще одна мысль в догонку.

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

Естественно, тут нужно трезво оценивать - писать ли вообще для приватных методов тесты. В данном случае на помощь может придти арифметика с комбинаторикой. Предположим, что у меня есть цепочка из 5 методов, в каждом из которых есть 3 варианта развития событий (скажем, if-elseif-else). Сколько всего у вас вариантов развития событий? 3^5=243 - т.е. чтобы протестировать все варианты вам потребуется 243 теста.

Если же вы будете их тестировать порознь - тогда вам потребуется по 3 варианта на каждый метод - итого 5*3 = 15 штук. Ну и плюс еще десяток-другой перегруженных методов.

Так что вот в такой ситуации для меня очевидно, что лучше написать 30-40 простых методов, которые могут через месяц-другой отправиться в корзину, чем писать 243 "вечных" теста. Да и какие они вечные, если следующая версия может потребовать изменения самого паблик-метода?

Добавлено через 58 минут
Цитата Сообщение от kolorotur Посмотреть сообщение
Если вы про стабы (stubs), то они применяются при перекрестных ссылках с другими модулями, но я, честно говоря, не видел, чтобы они применялись для подмены метода, являющегося членом тестируемого модуля, то есть при внутренней зависимости.
Это же вся логика разом похериться может, не говоря о состоянии модуля.
Не вижу, в чем может быть проблема. Если вы корректно понимаете работу кода, который вы заменяете заглушкой, то почему эту заглушку не воткнуть? Тем более, что иная заглушка может создавать особые ситуации, которые так просто и не воспроизвести - ну например, разрыв соединения с БД или отказ жесткого диска.

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


Если у вас электричество в здании генерируется не от динамо-машины с педальным приводом к рабочим местам сотрудников, то в чем проблема?
Ну отработает тест чуть дольше, делов-то.
Дело не только в том, что это дольше и неэффективнее, хотя мне известны случаи, когда серьезные проекты тестируются часами.
Но и в том, что когда методы неизолированы, один какой-нить баг может уронить изрядный процент тестов. А если их у вас не одна сотня или тысяча - как прикажете искать в них код с багом?

Проблема в том, что Б в процессе может поменять состояние объекта, которое потом скажется на дальнейшей работа А.
Ну так если Б меняет состояние объекта или возвращает различные результаты, то по уму, нужно протестировать реакцию А на все эти варианты. В посте выше я писал, что таким путем может потребовать туева хуча тестов для А - чтобы и все варианты работы Б учесть, и В и Г и тд.

Куча методов — не проблема, а вот запрос к БД должен подменяться моком.
Куча методов - проблема. Потому что если падает А, придется проверять работу не только А, но и Б, В и тд. К тому же, как я уже говорил, при таком подходе все варианты развития событий не проверить.

Да, изолировать логику отдельных методов. Но если Б вызывается внутри А, то он становится частью логики А, потому его так просто зачастую не выкинешь.
Ну если и правда трудно выкинуть - то и не надо. Но если Б - это комплексный метод чтения данных, который вызывает В для чтения из файла и Г для валидации данных и Д для преобразования данных, то почему всю эту канитель не заменить на Б1, который будет тупо возвращать строку для успешного сценария и Б2, который будет бросать исключение?

Зачем тесту для А падать оттого, что метод Д я писал впопыхах среди ночи?

Возможно мы оперируем разными понятиями, но в моем понимании моки и стабы — это замена перекрестных зависимостей, а не внутренних.
Конечно, не исключено, что я чего-то не знаю.
Не обязательно это должны быть внешние зависимости. Если они помогают протестировать методы того же класса, то почему их не применить, хотя бы временами?

Дык интеграционные тесты нужны для тестирования нескольких модулей в совокупности, а юнит тесты — это тестирование отдельных компонентов конкретного модуля.
Я как-то не встречал, чтобы специально для тестирования юнита изменялась его же логика.
В идеале юнит тесты тестируют минимально возможную логику: от приватных методов до паблик-методов максимум.

Все что включает взаимодействие нескольких объектов, нескольких паблик-методов - уже интеграционные. Начиная от этой минимальной интеграции и вплоть до интеграции компонентов и подсистем - все интеграционные.

Когда вы выходите на тестирование целых вариантов использования (use cases), там уже начинается еще более высокий уровень - функциональные тесты.

У нас в академических кругах любой текст, не подкрепленный цитатой или ссылкой, считается личным мнением по-умолчанию.
Гут, ну значит я могу подкрепить свои слова цитатами. (Надеюсь, цитаты на англ. вас не утомят).

Не могу согласиться с примером. Щупы и окошки — это все-таки вещи для диагностики "в полевых условиях", то есть во время эксплуатации. Аналог в программах — логи.
А вот если бы машины выпускали с квадратными колесами, потому что так их проще тестировать на предмет устойчивости на дорожном полотне — это совсем другое дело!
Процитирую эту мысль - может я ее не очень ясно изложил...
"Should you worry about introducing trapdoors to make your code easier to test? Here’s how Extreme Programming guru Ron Jeffries explains it:
My car has a diagnostic port and an oil dipstick. There is an inspection port on the side of my furnace and on the front of my oven. My pen cartridges are transparent so I can see if there is ink left.
And if I find it useful to add a method to a class to enable me to test it, I do so. It happens once in a while, for example in classes with easy interfaces and complex inner function (probably starting to want an Extract Class).
I just give the class what I understand of what it wants, and keep an eye on it to see what it wants next."
(Tahchiev - JUnit in Actiun, 2ed)

Тестирование, конечно, своеобразная эксплуатация. Но ведь все-таки эксплуатация! И если вместо логов, она дает более точную информацию о работе системы - разве это плохо?
Конечно, встает вопрос - так ли уж нужно знать и видеть, как программа работает изнутри? Есть два мнения на это счет: тестирование, когда вы полностью раскрываете реализацию, называется тестированием прозрачного ящика. Напротив, когда вы учитываете только внешние интерфейсы - тестированием черного ящика. К примеру, у Тахчиева (Tahchiev - JUnit in Actiun, 2ed, p.62):
"Black box test—A black box test has no knowledge of the internal state or behavior of the system. The test relies solely on the external system interface to verify its correctness...
At the other end of the spectrum is white box testing, sometimes called glass box testing. In contrast to black box testing, we use detailed knowledge of the implementation to create tests and drive the testing process. Not only is knowledge of a component’s implementation required, but also of how it interacts with other components. For these reasons, the implementers are the best candidates to create white box tests."
Ну и дальше обсуждаются плюсы и минусы каждого подхода...

Это вопрос больше архитектурный: если переопределение метода не имеет смысла с семантической точки зрения, то зачем его делать виртуальным? Бритва Оккама жеж.
Для того, чтобы легче тестировать. См. выше.

Нет и надеюсь на полную взаимность: никакой дерзости, категоричности или грубости в свой текст не вкладываю.
Если что-то показалось или задело, то приношу извинения — такой цели не стояло.
ОК, спасибо на добром слове. Просто на иных форумах можно нарваться на ветеранов, которые на дух не переносят, когда им возражают, особенно личности, только появившиеся на форуме.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
29.10.2013, 21:43
Помогаю со студенческими работами здесь

Upcasting и виртуальные методы
Всем привет Догадываюсь что сабж баян, но все-таки, оооочень прошу знающих "разжевать" что именно происходит в коде: class...

Виртуальные статические методы
вычитал на стэковерфлоу что их не предусмотрено, т.к. противоречат принципам ооп и т.д.т.д. но например, есть класс, представляющий...

Позднее связывание и виртуальные методы
Добрый день. Возник вопрос по поводу позднее связывания в c#. В с++ п.с. можно организовать следующим образом: class A: { public: ...

Виртуальные методы,с комментариями пожалуйста
Родительский класс:Вектор(поле-название) Потомки: (Двумерный вектор(поля:компоненты вектора), Трехмерный вектор(поле размерность)). ...

Виртуальные методы vs. сокрытие имен
Доброго времени суток. Насколько я понял, виртуальный метод - это собственная вариация метода (аля перегрузка) производного класса. Но вот,...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
8
Ответ Создать тему
Новые блоги и статьи
http://iceja.net/ сервер решения полиномов
iceja 18.01.2026
Выкатила http:/ / iceja. net/ сервер решения полиномов (находит действительные корни полиномов методом Штурма). На сайте документация по API, но скажу прямо VPS слабенький и 200 000 полиномов. . .
Первый деплой
lagorue 16.01.2026
Не спеша развернул своё 1ое приложение в kubernetes. А дальше мне интересно создать 1фронтэнд приложения и 2 бэкэнд приложения развернуть 2 деплоя в кубере получится 2 сервиса и что-бы они. . .
Расчёт переходных процессов в цепи постоянного тока
igorrr37 16.01.2026
/ * Дана цепь постоянного тока с R, L, C, k(ключ), U, E, J. Программа составляет систему уравнений по 1 и 2 законам Кирхгофа, решает её и находит: токи, напряжения и их 1 и 2 производные при t = 0;. . .
Восстановить юзерскрипты Greasemonkey из бэкапа браузера
damix 15.01.2026
Если восстановить из бэкапа профиль Firefox после переустановки винды, то список юзерскриптов в Greasemonkey будет пустым. Но восстановить их можно так. Для этого понадобится консольная утилита. . .
Изучаю kubernetes
lagorue 13.01.2026
А пригодятся-ли мне знания kubernetes в России?
Сукцессия микоризы: основная теория в виде двух уравнений.
anaschu 11.01.2026
https:/ / rutube. ru/ video/ 7a537f578d808e67a3c6fd818a44a5c4/
WordPad для Windows 11
Jel 10.01.2026
WordPad для Windows 11 — это приложение, которое восстанавливает классический текстовый редактор WordPad в операционной системе Windows 11. После того как Microsoft исключила WordPad из. . .
Classic Notepad for Windows 11
Jel 10.01.2026
Old Classic Notepad for Windows 11 Приложение для Windows 11, позволяющее пользователям вернуть классическую версию текстового редактора «Блокнот» из Windows 10. Программа предоставляет более. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru