Уголок программиста (1 онлайн

pse

Active Member
21 Сен 2003
911
179
43
44
Белгород, Алматы
А посоветуйте, что почитать по написанию тестов? Чет у меня мозги в это сторону пока плохо работают.
Не, я понимаю, что все водится к достаточно простой схеме: Взять тестируемый код и впихнуть в него данные, которые должны к нему попасть.
Но когда пытаюсь это делать в реальном проекте - вообще сообразить не могу, что делать. То ли архитектура дурацкая, то ли лыжи не едут.
Если изначально код не писали по схеме юниттесты->основной код, то и скорее всего архитектура будет не приспособлена под юниттестирование со всеми вытекающими (никто не ждет тестовых данных на вход и т.п.). Обычно в такой архитектуре еще нарушенными бывают и принципы SOLID. Сначала нужно писать тесты, потом к ним код, который будет выполнять все тесты на 100%.

Например (привожу пример из своей работы, код не везде такой, просто проект очень большой, очень много легаси кода старого), очень часто нарушается принцип единственной ответственности, что однозначно будет мешать написать юниттесты правильно и красиво. Допустим, есть желание написать юниттест к уже существующему коду.

Открываешь функцию, а в ней черт ногу сломит - овер 1000 строк кода, делаются запросы к пользователю (привет и пока юниттест), делаются запросы в базу данных(какие-то там настроечные параметры, ищутся другие связанные сущности, расположенные также в базе ), состояние базы на момент запуска юниттеста может быть любое, данные по итогу сохраняются напрямую в базу в самом конце (это хорошо если в самом конце, а могут и по ходу этой простыни из 1000+ строк текста местах так в 10-30). За 15 лет существования этой функции в него допиливали какие-то костыли и грабли, которые размывают вообще общую картину что она делала изначально, когда ее только написали маленькой, и что сейчас.

И думаешь - ну и как этот бред протестировать юниттестами - вывод приходит один - никак. Или сидеть и изменять/рефакторить. А в большом проекте более менее важные куски особо не по изменяешь - меняешь строчку в одном месте - вылезает в совершенно другом))))

Очень хорошая книга по программированию Роберт Мартин "Чистый код. Создание, анализ и рефакторинг". Книга затрагивает многие аспекты разработки программного обеспечения. TDD в книге посвещена отдельная глава.
 
  • Like
Реакции: PianoIst

PianoIst

Well-Known Member
19 Май 2010
4.091
4.142
113
29
Kirchberg, kreis Zwickau
soundcloud.com
@pse, вопрос был как раз "на берегу", т.к. я начинал монстродвигло. Собственно, понял, что язык изначально не позволяет этого делать, что собственно вылелось в интересный академический (в смысле, лично для себя рисеч) проект, породивший недавний вопрос про "кучу" :)
Объяснять что к чему рано, и получится многобукаф, думаю, через пару месяцев (если повезет) закончу, оформлю в статью и положу на хабр)
 

pse

Active Member
21 Сен 2003
911
179
43
44
Белгород, Алматы
Ну а целом, по TDD могу рассказать на примере на пальцах как делаются юниттесты.
Не претендую на истину в последней инстанции. Расскажу как я понимаю это дело (на примере из своей жизни так сказать).
Стоит задача в техзадании - написать вычислялку для арифметических выражений. Т.е. на вход подается строка с выражением, на выходе получаем число. Примерно как в реальной жизни: Вход string("2+2") Выход decimal 4. Ну и все простые операции должны поддерживаться.
Основной код пока не пишем. Первым делом смотрим в ТЗ и выписываем все возможные варианты операций оттуда. Будет примерно следующее:
2+2 = 4
4 - 2 = 2
2*3 = 6
8/2 = 4
8/2-2 = 2

Потом делаем юниттестт (один или много, зависит от архитектуры юниттестирования. Как я понимаю смысл во всех один, а реализация немного может отличаться в разных языках и/или проектах).
var s = new solver().
Utest.Assrt(s.calculate("2+2") == 4).
Utest.Assrt(s.calculate("4-2") == 2).
Utest.Assrt(s.calculate("2*3") == 6).
Utest.Assrt(s.calculate("8/2") == 4).
Utest.Assrt(s.calculate("8/2-2") == 2).
В общем забиваем в таком виде все возможные (разумные) комбинации, которые можно вытянуть из техзадания.
Сначала юниттест не компилится, т.к. у нас нет еще класса.
Затем создаем класс solver и пустую реализацию метода calculate, которая возвращает всегда 0.
Юниттест начинает компилится, но при выполнении валится на первом же s.calculate("2+2") == 4.
Ну и начинаем потихоньку реализовывать внутренности calculate доводя их до того состояния, чтобы все тесты проходили.
Как только все тесты проходят - мы добились реализации. Останавливаемся, поднимаем куда следует код и пользуемся классом дальше.
Теперь, при внесении изменений в последующих доработках calculate кем-либо еще и внесении им ошибок при реализации, которые будут валить уже существующие юниттесты, то вы это сразу же увидите при прогоне юниттестов.
Вот такие вот дела с TDD.
[DOUBLEPOST=1524339054][/DOUBLEPOST]
т.к. я начинал монстродвигло.
меня пугает слово "монстродвигло")))настолько все монструозное?)
 
  • Like
Реакции: PianoIst

PianoIst

Well-Known Member
19 Май 2010
4.091
4.142
113
29
Kirchberg, kreis Zwickau
soundcloud.com
@pse, спасибо за подробный ответ, но хочу обратить внимание, что исходное сообщение от 3 декабря) С тех пор даже есть более свежее, в котором я уже радуюсь, как хорошо отлавливаются автоматом регрессии, то бишь, с TDD вопрос в "ознакомительном плане" закрыт. Дальше уже полезут грабли, которые в двух постах ликбеза не затронешь)
 

PianoIst

Well-Known Member
19 Май 2010
4.091
4.142
113
29
Kirchberg, kreis Zwickau
soundcloud.com
Vue.js это, условно, пользователь
https://github.com/vuejs/vue-loader - конкретный репозиторий конкретной утилиты loader, ее можно клонировать на комп (либо через гит, либо архивом) и использовать, в соответствии с лицензией (это не всегда бесплатно и не всегда можно использовать в небеспланых программах, но vue.js, если не ошибаюсь - вполне норм и для того, и для того)
[DOUBLEPOST=1524350800][/DOUBLEPOST]А поговорим об архитектуре?

Как бы так переписать, чтоб избежать самоповторов, и сохранить API, То есть, чтобы пользователь использовал-таки классы как
Код:
a = Int(1)
b = Real(1.5)
c = Str("string")
Python:
class KspNative:

    def __init__(self, value, reg):
        self.value = value
        self.reg = reg

    def _get_str(self):
        if self.reg:
            return self.reg
        else:
            return self.type_arr_str + self.idx_str()

    def idx_str(self):
        return '[' + str(self.idx) + ']'


class Int(KspNative):

    count = 0

    def __init__(self, value, reg=None):
        super(Int, self).__init__(value, reg)
        if not isinstance(value, int):
            raise TypeError("has to be int, passed " + str(type(value)))
        self.type_arr_str = 'init_int'
        if not reg:
            self.idx = Int.count
            Int.count += 1


class Str(KspNative):

    count = 0

    def __init__(self, string, reg=None):
        super(Str, self).__init__(string, reg)
        if not isinstance(string, str):
            raise TypeError("has to be str, passed " + str(type(string)))
        self.type_arr_str = 'init_str'
        if not reg:
            self.idx = Str.count
            Str.count += 1


class Real(KspNative):

    count = 0

    def __init__(self, value, reg=None):
        super(Real, self).__init__(value, reg)
        if not isinstance(value, float):
            raise TypeError(
                "has to be float(real), passed " + str(type(value)))
        self.type_arr_str = 'init_real'
        if not reg:
            self.idx = Real.count
            Real.count += 1
 
  • Like
Реакции: SuperDroid

PianoIst

Well-Known Member
19 Май 2010
4.091
4.142
113
29
Kirchberg, kreis Zwickau
soundcloud.com
о, сделал)
Код:
class KspNative:

    count = dict()

    def __init__(self, typ, typ_str, typ_prefix, typ_prefix_reg, value, reg=None):
        self.value = value
        if not isinstance(value, type(typ)):
            raise TypeError("has to be %s, passed %s" %
                            (typ_str, str(type(value))))
        if not reg:
            if typ_str not in KspNative.count:
                KspNative.count[typ_str] = 0
            self.idx = KspNative.count[typ_str]
            self.str = '%sinit_%s[%d]' % (
                typ_prefix, typ_str, KspNative.count[typ_str])
            KspNative.count[typ_str] += 1
        else:
            self.str = typ_prefix_reg + reg

    def _get_str(self):
        return self.str

    def idx_str(self):
        return '[' + str(self.idx) + ']'


class Int(KspNative):

    def __init__(self, value, reg=None):
        super(Int, self).__init__(int(), 'int', '%', '$', value, reg)


class Str(KspNative):

    def __init__(self, string, reg=None):
        super(Str, self).__init__(str(), 'str', '!', '@', string, reg)


class Real(KspNative):

    def __init__(self, value, reg=None):
        super(Real, self).__init__(float(), 'real', '?', '~', value, reg)
 

pse

Active Member
21 Сен 2003
911
179
43
44
Белгород, Алматы
Ну вот видишь, все само получается)))) Я, например, всегда сомневаюсь при продумывании дизайна классов. Например, где оставить код в суперклассе или в наследниках, как его использовать с максимальной выгодой. Приходится учитывать, насколько сам код может быть переиспользован. Ведь когда наследников суперкласса десятки, то не всегда во всех них может использоваться одинаковый код в суперклассе.

Ооп дает дает широкие возможности переиспользовать код. Но есть всегда компромисс между сложностью кода, сложностью поддержки этого кода, запутанностью, читаемостью. Если ты пишешь один для себя - это одно дело. Если пишешь большой проект для команды, то это совершенно другое дело. Код должен быть максимально прост и понятен. Чтобы любой программист, который будет прикасаться к нему мог сразу понять что к чему. Но это красивые слова. На деле получается не всегда так. Ооп кстати не единственный способ переиспользования кода(некоторые зависят от языка).
Советую ознакомиться с принципами SOLID и хотя не сразу врубаешься в них(я вот до сих пор не во все врубаюсь в полной мере), со временем начинаешь понимать зачем они нужны и как соблюдение этих правил улучшает качество кода.
Отдельная тема - это соблюдение стандартов написания кода. Для большого проекта просто необходимо соблюдать стандарты, чтобы код проекта был в едином стиле. Самому проще в нем ориентироваться и другие спасибо скажут. Еще одна тема - выбор имен для обозначения сущностей.
[DOUBLEPOST=1524374544][/DOUBLEPOST]Ну и еще имхо, но это не только мое мнение. Просто со временем осознаешь всю необходимость этого.
Код должен быть максимально самодокументированным.
Еще одно зло глобальный контекст. Стараться избегать глобальный контекст. Все, что нужно для работы класса, должно быть передано ему при настройке в конструктор, либо после конструктора. Очень неплохо для сложных классов пользоваться паттернами строитель и различнымм вариаетами фабрик.
 

pse

Active Member
21 Сен 2003
911
179
43
44
Белгород, Алматы
Забыть в коде классов писать new и везде в качестве входных параметров передавать не классы, а интерфейсы. Написали new - Это автоматически не даст возможности проведения нормального юниттестирования(к вопросу tdd). Если у класса есть необходимость использовать экземпляр другого класса, то этот экземпляр нужно передать ему в конструктор, используя интерфейсы, а не пользоваться операторами new прям внутри класса. Такой финт даст возможность для юниттеста передать мок класс, предназначенный специально для юниттеста.
 

Alex Longard

Well-Known Member
10 Май 2015
1.941
1.232
113
36
München->Deutschland, Київ->Україна
@PianoIst, кроме кучки кодов для dsp ничего ненашел, хотя лет пять назад когда увлекался парсерами и трансляторами было много чего на питоне.
Сегодня погуглю по ресурсам на которых раньше бомжевал инфу.

По вышеописаному коду:
Лучьше больше классов под определенные задачи, чем один суперкласс с кучей методов, тогда легче читается, и это один из принципов питона!
У вас как раз с этим все нормально. Мне попадался код который я переваривал две недели из за двух десятков методов в одном классе и все обработчики вызывались из предыдущих методов без наследования. Но это уже грабли в ООП реализации питона.
 

pse

Active Member
21 Сен 2003
911
179
43
44
Белгород, Алматы
Если таких экземпляров(зависимость одного класса от другого) окажется слишком много и в конструктор нужно будет передавать огромное число входных параметров, то стоит пересмотреть дизайн класса с целью уменьшения зависимостей и разбивки класса на несколько. Скорее всего, такой класс еще будет нарушать принцип единственной обязанности в Solid.
 

Alex Longard

Well-Known Member
10 Май 2015
1.941
1.232
113
36
München->Deutschland, Київ->Україна
@pse, вы приводили пример теста на JS?
PianoIst пишет на питоне и в нем предусмотрены готовые модули для юниттестов, реализация выглядит совершенно не так как вы выше написали пример.
В питоне предусмотрены методы в которые можно скормить имя класса или отдельной функции и делать что-то вроде трассировки с подстановкой данных.
 

pse

Active Member
21 Сен 2003
911
179
43
44
Белгород, Алматы
Это как один из вариантов. И нет, это не js. Пусть у питона будут другие варианты тестирования, я питон не изучал, к сожалению. Прогресс не стоит на месте. И я не спорю по конкретному языку, я написал один из примеров как можно пользоваться юниттестами. В том проекте, где работаю я, нет встроенных средств юниттестирования, но это не помешало нашей команде их разработать и внедрить. У нас если нет юниттестов, в продакшен(в систему клнтроля версии) не поднимешь такой код, тупо не даст поднять. И это все было написано внутри команды для команды.
 

Alex Longard

Well-Known Member
10 Май 2015
1.941
1.232
113
36
München->Deutschland, Київ->Україна
@pse, дык я не возмущаюсь))) Просто вы написали простенький пример той реализации как вы пользуетесь, а зачем Пианисту изобретать велосипед когда уже есть готовые))))
Когда-то во времена учебы в институте попадалась статья "тестирование ПО для чайников", там был похожий пример, но для каждой функции приходилось писать отдельный тест.
Я с этим к моей радости не сталкивался так как стараюсь продумать функцию так чтобы ошибочные данные игнорились или хотя бы высвечивался assert с ошибкой.
 

pse

Active Member
21 Сен 2003
911
179
43
44
Белгород, Алматы
Я старался максимально не использовать семантики каких то конкретных языков
[DOUBLEPOST=1524376766][/DOUBLEPOST]Как правило они все сводятся к одним и тем же принципам
[DOUBLEPOST=1524377066][/DOUBLEPOST]У нас язык совмещает не только ооп, но и процедурное программирование. Отсюда юниттесты не только классов, но и процедур и функций в библиотеках. Но принцип остается похожим.
 

pse

Active Member
21 Сен 2003
911
179
43
44
Белгород, Алматы
Сам хочу питон начать изучать, но как-то не подворачивается случай начать. Нет стимула чтоли) А самому писать hello world неинтересно).
 

PianoIst

Well-Known Member
19 Май 2010
4.091
4.142
113
29
Kirchberg, kreis Zwickau
soundcloud.com
Ой, не успеваю я за потоком сознаания в теме :)
Давайте, вместо множественного цитирования я буду вести дневник, а обсуждать будет там, где удобно. Тем более, что лично мне интересно поговорить предметно о том, что получается
https://vk.com/@kazantsevpianist-ksp-prevraschaetsya-ili-izuchenie-python-boem-chast-1-sistem
Прошу прощения у законопослушных жителей украины, а также у тех, кто не любит большого брата, но контактовские статьи - пока что для меня самый удобный хостинг мыслей в формате rich text...
 

buncker

=)
26 Ноя 2002
10.297
4.756
113
44
МО
@PianoIst, я может очевидное скажу - но по осторожнее с динамической памятью в риалтайм аудио процессах
 
  • Like
Реакции: PianoIst

PianoIst

Well-Known Member
19 Май 2010
4.091
4.142
113
29
Kirchberg, kreis Zwickau
soundcloud.com
@buncker, справедливо, но лучше иметь, чем не иметь) По существу, связные списки и доступ к атрибутам на этапе чтения будут отжирать на 1-2 процедуры больше, чем просто итерация по массиву, что все равно быстрее, чем встроенные функции, типа (find_group("name"))
А вот их создание - да, лучше делать не в риалтайм-каллбеках.
 

buncker

=)
26 Ноя 2002
10.297
4.756
113
44
МО
@PianoIst, ну- в риалтайм коде выделять и удалять память вообще ни-ни (ибо будешь потом искать почему у тебя плагин\инструмент в полнолуние крякает а на новый год не крякает)
Есть еще такие вещщи как использованеи уже готовых классов в которых память может реаллоцироваться (под те же строчки)
Можно встрять - причем по закону подлости у юзеров будет сплошной треск в звуке а утебя все будет ровно и красиво
 

PianoIst

Well-Known Member
19 Май 2010
4.091
4.142
113
29
Kirchberg, kreis Zwickau
soundcloud.com
@buncker, Это ж KSP) Он не имеет доступа к машине. "Память" потому и в кавычках, что ничего никуда не выделяется в риалтайме, а выделяется на этапе инициалиации инструмента в виде статического(их) массива
 

PianoIst

Well-Known Member
19 Май 2010
4.091
4.142
113
29
Kirchberg, kreis Zwickau
soundcloud.com
@buncker, По сути - да. Просто на данный момент все прогают с помощью препроцессора, который сначала регулярками разворачивает код средней степени тяжести проекта до >100к строк, потом раз 20 проходит по ним сначала до конца регулярками, применяя плагины расширенного синтаксиса, потом строит ast, и только потом оптимизирует. Каким бы образом в него не вклиниваться - страдает время компиляции фатально, а рахитектура все равно не появляется, тесты не реализуешь толком.

Другое дело - препроцессор делать чистым питоном и чистой датой без раздувания в макросы и регулярок, используя все, что доступно языку с 30летней историей. А потом уже из результата генерировать вполне чистый код, который дополнительно проверится текущим препроцессором.

На самом деле, жопа глубже, но как и написал, там текста - на отдельную статью
 
  • Like
Реакции: buncker

buncker

=)
26 Ноя 2002
10.297
4.756
113
44
МО
@buncker, По сути - да. Просто на данный момент все прогают с помощью препроцессора, который сначала регулярками разворачивает код средней степени тяжести проекта до >100к строк, потом раз 20 проходит по ним сначала до конца регулярками, применяя плагины расширенного синтаксиса, потом строит ast, и только потом оптимизирует. Каким бы образом в него не вклиниваться - страдает время компиляции фатально, а рахитектура все равно не появляется, тесты не реализуешь толком.

Другое дело - препроцессор делать чистым питоном и чистой датой без раздувания в макросы и регулярок, используя все, что доступно языку с 30летней историей. А потом уже из результата генерировать вполне чистый код, который дополнительно проверится текущим препроцессором.

На самом деле, жопа глубже, но как и написал, там текста - на отдельную статью
звучит как стройка века =)
 

PianoIst

Well-Known Member
19 Май 2010
4.091
4.142
113
29
Kirchberg, kreis Zwickau
soundcloud.com
@buncker, так и есть, конкретно этой реализации уже два месяца вялотекущего научного тыка (на гитхабе исходники), а самой идее улучшения ситуации уже почти год, и два полноценных инсайдерских репозитория )

Вообще у меня в планах не только препроцессор, но и своего рода, IDE, с расширенными тестовыми классами, автоматической работой с файлами контакт-библиотеки и прочими плюхами)
 
Последнее редактирование:

Altschuller

New Member
21 Мар 2018
13
15
3
22
Для начинающих Гугл выпустил классное приложение Grasshopper. В игровой форме позволяет узнать базовые вещи JS. Как думаете, полезная штука?
 

PianoIst

Well-Known Member
19 Май 2010
4.091
4.142
113
29
Kirchberg, kreis Zwickau
soundcloud.com
@Altschuller, игровая форма всегда хорошо, да и Гугл по большей части не дурак) А реальную пользу можно ощутить только на себе) Мне вот advent of code нрава, было б на него еще время :)
 

Altschuller

New Member
21 Мар 2018
13
15
3
22
PianoIst, advent of code крутая вещь, но немного мне английского не хватает для лучшего усвоения.
 

SuperDroid

Well-Known Member
4 Май 2006
1.725
3.396
113
rmmedia.ru
Вопрос: а на парадигмы программирования существуют ли документация, спецификация и тд, более или менее официальная? Понятно, что та или иная парадигма описана в документации у каждого ЯП (тот же самый ООП, например), но ведь существуют некие общие правила у каждой парадигмы, которые можно было бы стандартизировать?
 

Сейчас онлайн (Пользователей: 0, Гостей: 1)