Re[8]: Менеджер про хаскель в продакшне
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 27.10.13 10:52
Оценка:
Здравствуйте, Abyx, Вы писали:

ГВ>>Из этого следует, что тестовое покрытие было недостаточным. Что вполне естественно — если язык допускает "Not implemented"-методы, то тесты должны покрывать *все* случаи, где такие методы могут быть задействованы. Очевидно, что этого сделано не было.


A>ты сам себе противоречишь.

A>сначала ты говоришь "*Все* ошибки не выловят ни тесты, ни наилучшие языки.", а потом "тестовое покрытие было недостаточным, тесты должны покрывать *все* случаи".

Ты бы ещё буквы в словах переставил. Я написал: "*все* случаи, где такие методы могут быть задействованы". Как ты понимаешь, это не "все" ошибки вообще.

A>кстати с статье говорится не про "Not implemented"-методы, а про "Null Pointer Dereference" (Object of type 'NoneType' has no method), но это не важно.


И правда. Но принципиально это ничего не меняет.

A>давай может возьмем конкретный пример?


A>
A>def call_foo(obj):
A>    obj.foo()
A>

A>вызовов call_foo много и они равномерно размазаны по всему коду

A>расскажи пожалуйста какие тесты ты напишешь чтобы покрыть все случаи вызова call_foo с любыми obj


Здесь надо тестировать не саму call_foo, а соблюдение контракта её вызова. Откуда берётся входной obj?
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re[9]: Менеджер про хаскель в продакшне
От: Abyx Россия  
Дата: 27.10.13 11:46
Оценка:
Здравствуйте, Геннадий Васильев, Вы писали:

A>>давай может возьмем конкретный пример?


A>>
A>>def call_foo(obj):
A>>    obj.foo()
A>>

A>>вызовов call_foo много и они равномерно размазаны по всему коду

A>>расскажи пожалуйста какие тесты ты напишешь чтобы покрыть все случаи вызова call_foo с любыми obj


ГВ>Здесь надо тестировать не саму call_foo, а соблюдение контракта её вызова.


а какой у нее контракт вызова? аргумент не None?
значит для каждого места вызова call_foo надо внедрить mock_call_foo которая проверит аргумент?

но внезапно оказывается что у 99% функций такой контракт, и их все надо проверять?

представь что у тебя в коде параметрами функций могут быть только указатели,
т.е. int* add(int* a, int* b) вместо int add(int a, int b)
и ты серьезно говоришь что "тесты должны покрывать *все* случаи, где такие методы функции могут быть задействованы"?
т.е. *каждый* вызов *каждой* функции?

ГВ>Откуда берётся входной obj?

в каждом из 100500 вызовов call_foo — по разному.
аргумент другой функции, поле класса, элемент коллекции — откуда угодно.
In Zen We Trust
Re[10]: Менеджер про хаскель в продакшне
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 27.10.13 12:34
Оценка: -1
Здравствуйте, Abyx, Вы писали:

A>>>расскажи пожалуйста какие тесты ты напишешь чтобы покрыть все случаи вызова call_foo с любыми obj

ГВ>>Здесь надо тестировать не саму call_foo, а соблюдение контракта её вызова.

A>а какой у нее контракт вызова? аргумент не None?


Я так понимаю, что это объект, у которого имеется метод foo. Не None, да.

A>значит для каждого места вызова call_foo надо внедрить mock_call_foo которая проверит аргумент?


Не знаю. Соблюдение контракта может отслеживаться и с более высоких уровней вызова. Зависит от программы.

A>но внезапно оказывается что у 99% функций такой контракт, и их все надо проверять?


Вполне может оказаться и так. Равно как может и оказаться, что программу придётся перепроектировать для "тестопригодности".

A>представь что у тебя в коде параметрами функций могут быть только указатели,

A>т.е. int* add(int* a, int* b) вместо int add(int a, int b)
A>и ты серьезно говоришь что "тесты должны покрывать *все* случаи, где такие методы функции могут быть задействованы"?
A>т.е. *каждый* вызов *каждой* функции?

Да, разумеется. Как правило, задача сильно упрощается, если тестировать не отдельные функции, а цельные блоки.

ГВ>>Откуда берётся входной obj?

A>в каждом из 100500 вызовов call_foo — по разному.
A>аргумент другой функции, поле класса, элемент коллекции — откуда угодно.

А откуда берутся аргументы другой функции, поле класса и элемент коллекции? Самозарождаются?

Я одного не пойму: что ты мне хочешь доказать? Что ты сможешь выдумать пример, который при тех или иных допущениях нельзя будет толком протестировать? Так я в этом, в общем-то, не сомневаюсь (в твоих способностях). Только это никак не опровергнет тезисов о том, что: а) тестирование было недостаточным, б) оно вполне могло быть достаточным.

В защиту тезиса б). Насколько я понимаю, под загадочной формулировкой "динамические типы входных данных" имеются в виду такие, которые при преобразовании к некоему ожидавшемуся типу давали None. ИМХО, такую ситуацию, в общем, легко предсказать и не так уж и сложно протестировать. Возможно, придётся поломать голову кое-где, но задача-то уже ясна.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re[11]: Менеджер про хаскель в продакшне
От: Abyx Россия  
Дата: 27.10.13 13:08
Оценка: -1
Здравствуйте, Геннадий Васильев, Вы писали:

A>>а какой у нее контракт вызова? аргумент не None?

A>>но внезапно оказывается что у 99% функций такой контракт, и их все надо проверять?

ГВ>Вполне может оказаться и так. Равно как может и оказаться, что программу придётся перепроектировать для "тестопригодности".

что значит "Вполне может оказаться и так" и "перепроектировать"? это свойство языка

ГВ>>>Откуда берётся входной obj?

A>>в каждом из 100500 вызовов call_foo — по разному.
A>>аргумент другой функции, поле класса, элемент коллекции — откуда угодно.

ГВ>А откуда берутся аргументы другой функции, поле класса и элемент коллекции? Самозарождаются?

сорока на хвосте приносит. third-party код выдает.

ГВ>Насколько я понимаю, под загадочной формулировкой "динамические типы входных данных" имеются в виду такие, которые при преобразовании к некоему ожидавшемуся типу давали None.

под этой формулировкой, в контексте языка программирования с динамической типизацией (dynamic typing), понимаются динамические типы (dynamically-typed) входных данных.

ГВ>Я одного не пойму: что ты мне хочешь доказать?

то что ты не разбираешься в ЯП с динамической типизацией, не разбираешься в тестировании кода таких ЯП, но при этом за компанию с eao197 пытаешься критиковать программистов на этих языках.

я тебе уже говорил, если не знаешь Python, возьми свой любимый кусок кода на Си или С++, замени все типы на указатели, и попробуй написать тесты которые покроют все разыменования указателей — тогда может поймешь почему в Python есть проблема с NoneType у аргументов.
или возьми C# и замени все типы на Nullable<T>.
In Zen We Trust
Re[12]: Менеджер про хаскель в продакшне
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 27.10.13 13:43
Оценка: +1
Здравствуйте, Abyx, Вы писали:

ГВ>>>>Откуда берётся входной obj?

A>>>в каждом из 100500 вызовов call_foo — по разному.
A>>>аргумент другой функции, поле класса, элемент коллекции — откуда угодно.

ГВ>>А откуда берутся аргументы другой функции, поле класса и элемент коллекции? Самозарождаются?

A>сорока на хвосте приносит. third-party код выдает.

Отлично. А что же с ними происходит дальше? Мы эти данные принимаем от third-party и исполненные уверенности, что они никогда-никогда не ошибаются? Допустим. Но вменяемые люди так поступают ровно до первой ситуации, когда thrid-party выдаёт нечто недопустимое. После этого всё, поступившее оттуда начинают проверять.

ГВ>>Насколько я понимаю, под загадочной формулировкой "динамические типы входных данных" имеются в виду такие, которые при преобразовании к некоему ожидавшемуся типу давали None.

A>под этой формулировкой, в контексте языка программирования с динамической типизацией (dynamic typing), понимаются динамические типы (dynamically-typed) входных данных.

ГВ>>Я одного не пойму: что ты мне хочешь доказать?

A>то что ты не разбираешься в ЯП с динамической типизацией, не разбираешься в тестировании кода таких ЯП, но при этом за компанию с eao197 пытаешься критиковать программистов на этих языках.

Если ты не заметил, то критикую я в основном менеджмент, а не программистов, поскольку задачи и граничные условия их решений ставит именно он. И коль скоро я в чём-то так невероятно не разбираюсь, просвети уж меня, будь любезен. Можно без подробностей, а как-нибудь попроще. Например, мне очень интересно узнать, какие непреодолимые проблемы мешают протестировать Python-программу на заведомо невалидных входных данных?

A>я тебе уже говорил, если не знаешь Python, возьми свой любимый кусок кода на Си или С++, замени все типы на указатели, и попробуй написать тесты которые покроют все разыменования указателей — тогда может поймешь почему в Python есть проблема с NoneType у аргументов.


Что-то я не понимаю, я очень давно не испытываю проблем с указателями на C++... Так что, лучше обойдёмся без аналогий, ты уж прямо расскажи, что за проблема у Python с NoneType.

A>или возьми C# и замени все типы на Nullable<T>.


А... Начинаю понимать. Любые данные в Python могут быть NoneType? И что теперь? А в LISP любой параметр может быть NIL, но программы же как-то работают, притом работают очень неплохо.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re[13]: Менеджер про хаскель в продакшне
От: Abyx Россия  
Дата: 27.10.13 18:28
Оценка:
Здравствуйте, Геннадий Васильев, Вы писали:

ГВ>Если ты не заметил, то критикую я в основном менеджмент, а не программистов, поскольку задачи и граничные условия их решений ставит именно он.


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

ГВ>Например, мне очень интересно узнать, какие непреодолимые проблемы мешают протестировать Python-программу на заведомо невалидных входных данных?


что ты имеешь ввиду под "невалидными" данными?

ГВ>А... Начинаю понимать. Любые данные в Python могут быть NoneType?

да.
In Zen We Trust
Re[14]: Менеджер про хаскель в продакшне
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 27.10.13 23:21
Оценка:
Здравствуйте, Abyx, Вы писали:

ГВ>>Если ты не заметил, то критикую я в основном менеджмент, а не программистов, поскольку задачи и граничные условия их решений ставит именно он.

A>я заметил что ты говоришь что у них было плохое покрытие юнит-тестами, и я хз при чем тут менеджмент который не пишет юнит-тесты.

Менеджмент ставит задачу сделать такое покрытие. Если менеджменту плевать на стабильность работы софта (или он уповает на какую-нибудь чепуху, типа "синергии", которая "сама" должна привести к нужному результату) — он такой задачи может и не поставить. А если ему не плевать — он её поставит, выделит ресурсы и потребует выполнения.

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

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

В пользу предположения о том, что руководство не озабочивалось таким тестированием, свидетельствует вот эта цитата:

Тесты, которые «покрывают весь код», к сожалению, совсем не покрывают «все возможные типы входных данных» (которые, внезапно, динамические) и совсем не спасают от ошибок типизации.


Понимаешь? "Внезапно"! А виноват в этом кто? Ясное дело — язык программирования.

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

ГВ>>Например, мне очень интересно узнать, какие непреодолимые проблемы мешают протестировать Python-программу на заведомо невалидных входных данных?


A>что ты имеешь ввиду под "невалидными" данными?


Вот псевдокод (я почти уверен, что он моделирует ситуацию из исходной статьи):

var data = inputStream.read();
SomeType typedData = castToSomeType(data);


"Валидными" мы назовём такие данные, которые, будучи прочитаны в переменную data могут быть преобразованы в объект типа SomeType. "Невалидными" — такие, что вызовут... М-м-м... Скажем так, вызовут неожиданности в этом преобразовании. Например, если у нас отключено выбрасывание исключений, typedData примет значение "пусто" (NULL, NIL, nul, None, Empty...), хотя формально, какие-то данные были прочитаны на первом шаге. Это самое "пусто" далее покатится туда, где его не ждут, генерируя разнообразные ошибки.

Насколько я понимаю, именно подобные неожиданности стали причиной многочисленных отказов Python-программ. По крайней мере, цитата, что я привёл выше, говорит именно о таком сценарии.

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

Да, говоря о "проверке типа" я имею в виду любую проверку структуры полученных данных, не только какое-нибудь: "String != Int". Например, на входе может быть XML, где в каких-то узлах мы ждём "целые", а оказываются "строки". Схема остаётся прежней.

ГВ>>А... Начинаю понимать. Любые данные в Python могут быть NoneType?

A>да.

Отлично. И как это должно повлиять на мои предыдущие рассуждения?
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re[15]: Менеджер про хаскель в продакшне
От: Abyx Россия  
Дата: 28.10.13 00:09
Оценка:
Здравствуйте, Геннадий Васильев, Вы писали:

ГВ>В пользу предположения о том, что руководство не озабочивалось таким тестированием, свидетельствует вот эта цитата:


ГВ>

Тесты, которые «покрывают весь код», к сожалению, совсем не покрывают «все возможные типы входных данных» (которые, внезапно, динамические) и совсем не спасают от ошибок типизации.


ГВ>Понимаешь? "Внезапно"! А виноват в этом кто? Ясное дело — язык программирования.


да. виноват язык программирования, а конкретно динамическая типизация.

ГВ>Вот псевдокод (я почти уверен, что он моделирует ситуацию из исходной статьи):


ГВ>
ГВ>var data = inputStream.read();
ГВ>SomeType typedData = castToSomeType(data);
ГВ>


нет. ты до сих пор не понял что такое "динамическая типизация" и о каких "входных данных" речь.

если тебе близок C#, то код моделирующий проблему выглядит так

void f(dynamic x)
{
    x.m();
}


под "входными данными" понимаются любые входные данные функций (unit'ов) — параметры функций, глобальные переменные — все что unit (класс/функция) получает на вход.
в этом коде "входные данные" это "x".

еще раз, речь не о данных от юзера, или других данных на входе программы.
речь о данных на входе каждого unit'a кода.

и вот автор статьи говорит:
"Тесты, которые «покрывают весь код»" — т.е. тесты покрывающие код f (и вообще любого другого юнита кода),
"не покрывают «все возможные типы входных данных»" — потому что аргументом f может оказаться что угодно,
потому что в ЯП динамические типы данных.

я хз как еще это объяснить %)
все типы аргументов, членов классов, вообще все типы — это "dynamic", и юнит-тесты никак не могут отловить ошибки типизации.
интеграционные тесты для N юнитов не могут отловить ошибки типизации, если система из этих юнитов сводится к f(dynamic x).
только когда в тестируемой системе будут все юниты которые создают все объекты, у нас не будет входных данных с динамическими типами, и мы можем ее протестировать. только такая система будет слишком большой и потому нетестируемой.




ГВ>
ГВ>SomeType typedData = castToSomeType(data);
ГВ>

SomeType это *статический* тип. в языках с динамической типизацией нету статических типов. *вообще нету*. и кастов-к-статическому-типу нету, потому что (внезапно) нету статических типов.
в языках с динамической типизацией у объектов нету статического набора методов. и нельзя сказать "у SomeType всегда есть метод foo".
объект может иметь тип SomeType, но может ему не добавили метод foo, или у него удалили метод foo.
class SomeType: # создали тип
    pass

x = SomeType() # создали объект
x.foo = some_foo # добавили метод
del x.foo # удалили метод
In Zen We Trust
Re[16]: Менеджер про хаскель в продакшне
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 28.10.13 03:28
Оценка: +1
Здравствуйте, Abyx, Вы писали:

A>нет. ты до сих пор не понял что такое "динамическая типизация" и о каких "входных данных" речь.


A>если тебе близок C#, то код моделирующий проблему выглядит так


A>
A>void f(dynamic x)
A>{
A>    x.m();
A>}
A>


A>под "входными данными" понимаются любые входные данные функций (unit'ов) — параметры функций, глобальные переменные — все что unit (класс/функция) получает на вход.


Вот те, нате. Я-то по-старинке привык называть такие вещи "формальными параметрами", "фактическими параметрами" и да, "глобальными переменными". Термин "входные данные" обычно употребляется после того, как уточнён контекст. Ну в общем, ясно. Тут ещё и с терминологией полный бардак.

A>в этом коде "входные данные" это "x".


В этом коде "x" — формальный параметр.

A>еще раз, речь не о данных от юзера, или других данных на входе программы.

A>речь о данных на входе каждого unit'a кода.

A>и вот автор статьи говорит:

A>"Тесты, которые «покрывают весь код»" — т.е. тесты покрывающие код f (и вообще любого другого юнита кода),
A>"не покрывают «все возможные типы входных данных»" — потому что аргументом f может оказаться что угодно,
A>потому что в ЯП динамические типы данных.

Стоп. С этого места я попрошу тебя перестать гнать пургу. "Что угодно" никогда нигде не оказывается, вне зависимости от того, какая типизация используется в данном языке программирования. И вменяемые люди никогда не полагаются на "что угодно". Вот смотри, на вскидку взятый кусок кода из питоновской библиотеки. Этот код ориентирован на враждебный внешний мир, подсовывающий неизвестно, что:

def start_new_thread(function, args, kwargs={}):
    ...
    if type(args) != type(tuple()):
        raise TypeError("2nd arg must be a tuple")
    if type(kwargs) != type(dict()):
        raise TypeError("3rd arg must be a dict")
    ...


То есть, формально говоря, на вход start_new_thread можно передать что угодно. Но она отсечёт это "что угодно" с исключениями. Ничего неожиданного. Точно такой же подход можно применить и в программе пользователя. Можно проверить содержимое атрибутов фактического параметра, можно сократить эти проверки до проверки типа и т.п. Короче говоря, всё как обычно. И тестирование таких функцией, в общем, ничем не отличается от тестирования программ на C, использующих указатели: тестами мы доказываем работоспособность программы и в том числе, её "умение" определять недопустимые комбинации входных данных.

A>я хз как еще это объяснить %)

A>все типы аргументов, членов классов, вообще все типы — это "dynamic", и юнит-тесты никак не могут отловить ошибки типизации.

Могут. И должны. Потому что если к типу объекта никаких требований не предъявляется, то мы им и не пользуемся и он находится за рамками нашей программы. А если пользуемся (методы там вызываем, атрибуты дёргаем), то нам нужно проверять его характеристики. То есть проверять имена типов, наличие нужных атрибутов у этих типов и т.п. Для статически типизированного языка это делает компилятор, для динамически типизированного — руки программиста. И если мы этого не делаем, то виноват не язык, а... Я уже писал, кто виноват.

То же самое и с тестами. Должны быть как тесты, имитирующие некорректные входные данные, так и тесты, которые проверяют структуру выходных данных.

A>интеграционные тесты для N юнитов не могут отловить ошибки типизации, если система из этих юнитов сводится к f(dynamic x).


А вот тут я снова чего-то не понимаю. Разве можно создать объект типа dynamic (за исключением Object или его аналога, который, само собой, почти никем не может использоваться)? Именно создать объект, а не объявить формальный параметр? Если нельзя, то система unit-тестов (и интеграционных) всё равно строится на каких-то вполне определённых типах и снова возникает противоречие: "тип любой, но с определёнными характеристиками".

A>только когда в тестируемой системе будут все юниты которые создают все объекты, у нас не будет входных данных с динамическими типами, и мы можем ее протестировать.


"Динамическими" эти типы по-прежнему останутся, во всяком случае, для функций. Просто мы ограничим набор типов, с которыми допускается работа нашей программы. Или, вместо проверки имени типа будем проверять наличие определённых атрибутов. И таким образом превратим программу из сферического УГ в обычную программу.

A>только такая система будет слишком большой и потому нетестируемой.


Да нет, такая система будет обыкновенной.

A>


ГВ>>
ГВ>>SomeType typedData = castToSomeType(data);
ГВ>>

A>SomeType это *статический* тип. в языках с динамической типизацией нету статических типов. *вообще нету*. и кастов-к-статическому-типу нету, потому что (внезапно) нету статических типов.

За-ши-бись! То есть в Python конструкция type(obj) есть, а типов — нет. Ж..а есть, а слова нет. Ну, где у вас там ещё 0==1?

A>в языках с динамической типизацией у объектов нету статического набора методов. и нельзя сказать "у SomeType всегда есть метод foo".


Зато всегда можно проверить, имеется ли нужный атрибут (слот, метод или как-он-здесь-называется). Вуаля. Мы изящно возвращаемся к проблеме, которую я сформулировал по незнанию глубин Python и акцентов, что расставляют питоноописатели:

... если язык допускает "Not implemented"-методы, то тесты должны покрывать *все* случаи, где такие методы могут быть задействованы.


По сути не имеет никакого значения, "Not Implemented"-метод или "объект, у которого отсутствует искомый метод".

A>объект может иметь тип SomeType, но может ему не добавили метод foo, или у него удалили метод foo.

A>
A>class SomeType: # создали тип
A>    pass

A>x = SomeType() # создали объект
A>x.foo = some_foo # добавили метод
A>del x.foo # удалили метод
A>


Кстати, а разве в Python можно аналогично удалять методы класса (а не объекта)?

class SomeType: # создали тип
    def foo(self):
        print("SomeType.foo")

x = SomeType() # создали объект
del x.foo # ???
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re[17]: Менеджер про хаскель в продакшне
От: Abyx Россия  
Дата: 28.10.13 12:30
Оценка:
Здравствуйте, Геннадий Васильев, Вы писали:

A>>еще раз, речь не о данных от юзера, или других данных на входе программы.

A>>речь о данных на входе каждого unit'a кода.

A>>и вот автор статьи говорит:

A>>"Тесты, которые «покрывают весь код»" — т.е. тесты покрывающие код f (и вообще любого другого юнита кода),
A>>"не покрывают «все возможные типы входных данных»" — потому что аргументом f может оказаться что угодно,
A>>потому что в ЯП динамические типы данных.

ГВ>Стоп. С этого места я попрошу тебя перестать гнать пургу. "Что угодно" никогда нигде не оказывается, вне зависимости от того, какая типизация используется в данном языке программирования. И вменяемые люди никогда не полагаются на "что угодно". Вот смотри, на вскидку взятый кусок кода из питоновской библиотеки. Этот код ориентирован на враждебный внешний мир, подсовывающий неизвестно, что:


ГВ>
ГВ>def start_new_thread(function, args, kwargs={}):
ГВ>    ...
ГВ>    if type(args) != type(tuple()):
ГВ>        raise TypeError("2nd arg must be a tuple")
ГВ>    if type(kwargs) != type(dict()):
ГВ>        raise TypeError("3rd arg must be a dict")
ГВ>    ...
ГВ>


ГВ>То есть, формально говоря, на вход start_new_thread можно передать что угодно. Но она отсечёт это "что угодно" с исключениями. Ничего неожиданного.


это неудачный пример. в start_new_thread эти проверки только для того чтобы получить красивое сообщение о нарушении контракта, потому что в документации сказано "args must be a tuple".
заметь, что там нет проверки типа параметра "function"
в реальном коде никто не проверяет типы аргументов. (если для этого нет каких-то особых причин)

т.е. никто не пишет код типа
def f(x):
    if not hasattr(x, 'm'): raise AttributeError('...')
    x.m()

потому что эта проверка дублирует работу виртуальной машины Pyhthon'а

ГВ>Точно такой же подход можно применить и в программе пользователя. Можно проверить содержимое атрибутов фактического параметра, можно сократить эти проверки до проверки типа и т.п. Короче говоря, всё как обычно. И тестирование таких функцией, в общем, ничем не отличается от тестирования программ на C, использующих указатели: тестами мы доказываем работоспособность программы и в том числе, её "умение" определять недопустимые комбинации входных данных.


этот подход не дает ничего. Python сам делает все проверки типов и выбрасывает исключения, только немного с другим текстом, типа "NoneType has no attribute m".
написание проверок типов руками позволяет добиться только user-friendly сообщений об ошибках, не более того.

A>>я хз как еще это объяснить %)

A>>все типы аргументов, членов классов, вообще все типы — это "dynamic", и юнит-тесты никак не могут отловить ошибки типизации.

ГВ>Могут. И должны. Потому что если к типу объекта никаких требований не предъявляется, то мы им и не пользуемся и он находится за рамками нашей программы. А если пользуемся (методы там вызываем, атрибуты дёргаем), то нам нужно проверять его характеристики. То есть проверять имена типов, наличие нужных атрибутов у этих типов и т.п. Для статически типизированного языка это делает компилятор, для динамически типизированного — руки программиста. И если мы этого не делаем, то виноват не язык, а... Я уже писал, кто виноват.

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

ГВ>То же самое и с тестами. Должны быть как тесты, имитирующие некорректные входные данные, так и тесты, которые проверяют структуру выходных данных.


тесты *чего*?
юнита? — они не помогают.
всей программы размером в 100500 LOC? — такие тесты всегда не покрывают все случаи. их вообще непонятно как писать %)

A>>интеграционные тесты для N юнитов не могут отловить ошибки типизации, если система из этих юнитов сводится к f(dynamic x).


ГВ>А вот тут я снова чего-то не понимаю. Разве можно создать объект типа dynamic


dynamic x = new System.Dynamic.ExpandoObject();
x.y = 1;
return x.y;


ГВ>Кстати, а разве в Python можно аналогично удалять методы класса (а не объекта)?


можно. "del SomeType.foo"
In Zen We Trust
Re[18]: Менеджер про хаскель в продакшне
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 28.10.13 14:36
Оценка: +1
Здравствуйте, Abyx, Вы писали:

A>это неудачный пример. в start_new_thread эти проверки только для того чтобы получить красивое сообщение о нарушении контракта, потому что в документации сказано "args must be a tuple".

A>заметь, что там нет проверки типа параметра "function"

Есть, хотя и неявная:

    try:
        function(*args, **kwargs)
    except SystemExit:
        pass
    except:
        import traceback
        traceback.print_exc()


Не только проверка, но ещё и печать стека.

A>в реальном коде никто не проверяет типы аргументов. (если для этого нет каких-то особых причин)


A>т.е. никто не пишет код типа

A>
A>def f(x):
A>    if not hasattr(x, 'm'): raise AttributeError('...')
A>    x.m()
A>

A>потому что эта проверка дублирует работу виртуальной машины Pyhthon'а

Ничего подобного. Эта проверка выполняется до обращения к атрибуту, а не в момент оного, поэтому функционально она не дублирует проверки Python VM.

ГВ>>Точно такой же подход можно применить и в программе пользователя. Можно проверить содержимое атрибутов фактического параметра, можно сократить эти проверки до проверки типа и т.п. Короче говоря, всё как обычно. И тестирование таких функцией, в общем, ничем не отличается от тестирования программ на C, использующих указатели: тестами мы доказываем работоспособность программы и в том числе, её "умение" определять недопустимые комбинации входных данных.


A>этот подход не дает ничего. Python сам делает все проверки типов и выбрасывает исключения, только немного с другим текстом, типа "NoneType has no attribute m".


Да ты что? Правда? То есть ранняя диагностика некорректных данных не даёт ничего? Блин, а мужики-то и не знают.

Перефразируя: этот подход ничего не даёт для C, потому что ОС сама предотвратит обращение по заведомо некорректным адресам, только немного с другим текстом, типа "General Protection Fault".

A>написание проверок типов руками позволяет добиться только user-friendly сообщений об ошибках, не более того.


Это уже очень сильно не "ничего". Как минимум, мы выигрываем время на поиске причин ошибок. И плюс к тому, можем быстрее и проще написать тесты.

ГВ>>То же самое и с тестами. Должны быть как тесты, имитирующие некорректные входные данные, так и тесты, которые проверяют структуру выходных данных.


A>тесты *чего*?

A>юнита? — они не помогают.

Откуда я знаю, тесты чего именно? Нужно смотреть по программе. Обычный подход — выделяют некие модули, фиксируют их контракты и тестируют соответствие этому контракту. Какие именно модули нужно выделять — вопрос сугубо индивидуальный, наивные советы в духе "класс = unit" или "функция = unit" тут помогают слабо.

A>всей программы размером в 100500 LOC? — такие тесты всегда не покрывают все случаи. их вообще непонятно как писать %)


Значит, нужно в этом вопросе разобраться и синтезировать соответствующее решение. Я не говорю, что это легко, но и как правило, не невозможно.

Или признать одно из двух: либо Python (и другие языки с динамической типизацией) принципиально УГ; либо у кого-то "руки не из того места растут". Третьего не дано.

A>>>интеграционные тесты для N юнитов не могут отловить ошибки типизации, если система из этих юнитов сводится к f(dynamic x).


ГВ>>А вот тут я снова чего-то не понимаю. Разве можно создать объект типа dynamic


A>
A>dynamic x = new System.Dynamic.ExpandoObject();
A>x.y = 1;
A>return x.y;
A>


Ясно, был не прав, хотя в общем — тот же Object, только в профиль. Однако, принципиально это снова ничего не меняет. Потому что дальше к dynamic-объекту всё равно предъявляются вполне определённые требования, например, наличия тех или иных атрибутов. Следовательно, технически, объект может быть dynamic, но функционально всё равно есть некий контракт, которому он должен удовлетворять.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re[19]: Менеджер про хаскель в продакшне
От: Sinclair Россия https://github.com/evilguest/
Дата: 29.10.13 05:33
Оценка:
Здравствуйте, Геннадий Васильев, Вы писали:

ГВ>Ясно, был не прав, хотя в общем — тот же Object, только в профиль. Однако, принципиально это снова ничего не меняет. Потому что дальше к dynamic-объекту всё равно предъявляются вполне определённые требования, например, наличия тех или иных атрибутов. Следовательно, технически, объект может быть dynamic, но функционально всё равно есть некий контракт, которому он должен удовлетворять.

Осталось понять
а) как это формализовать
б) как это протестировать, не заглядывая внутрь функции.

Вот у меня функция dynamic getValueOrDefault(this dynamic o, string name, dynamic defaultValue). Она должна возвращать значение по имени из более-менее любого контейнера. Какие тесты будем для неё писать?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[20]: Менеджер про хаскель в продакшне
От: Sinix  
Дата: 29.10.13 06:20
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Вот у меня функция dynamic getValueOrDefault(this dynamic o, string name, dynamic defaultValue). Она должна возвращать значение по имени из более-менее любого контейнера. Какие тесты будем для неё писать?


По-моему, тут проблема не столько в тестах, сколько в самой функции.

Если без динамика никак — остаются тесты по заранее оговорённым вариантам использования, они же документация: что можем обрабатывать — обрабатываем, остальное — на свой страх и риск. Плюс обязательный ассерт, который собирает в лог неподдерживаемые контейнеры.

Масштабируется конечно отвратно, если подобных велосипедов не один и не два — проблемы будут вылезать постоянно.
Re[20]: Менеджер про хаскель в продакшне
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 29.10.13 08:50
Оценка:
Здравствуйте, Sinclair, Вы писали:

ГВ>>Ясно, был не прав, хотя в общем — тот же Object, только в профиль. Однако, принципиально это снова ничего не меняет. Потому что дальше к dynamic-объекту всё равно предъявляются вполне определённые требования, например, наличия тех или иных атрибутов. Следовательно, технически, объект может быть dynamic, но функционально всё равно есть некий контракт, которому он должен удовлетворять.

S>Осталось понять
S>а) как это формализовать

Извини, что вопросом на вопрос: а как ты формализуешь требования к любым другим функциям/классам/модулям? Ну, вот так же и здесь.

S>б) как это протестировать, не заглядывая внутрь функции.


Тестирование по методу чёрного ящика.

S>Вот у меня функция dynamic getValueOrDefault(this dynamic o, string name, dynamic defaultValue). Она должна возвращать значение по имени из более-менее любого контейнера. Какие тесты будем для неё писать?


Что такое: "более-менее любого"? Так любого или "более-менее"?

И кстати, "this dynamic" первым параметром загнала меня в ступор. Не покажешь объявление поподробней, просто интересно.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re[19]: Недостающее третье
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 29.10.13 08:57
Оценка:
A>>всей программы размером в 100500 LOC? — такие тесты всегда не покрывают все случаи. их вообще непонятно как писать %)

ГВ>Значит, нужно в этом вопросе разобраться и синтезировать соответствующее решение. Я не говорю, что это легко, но и как правило, не невозможно.


ГВ>Или признать одно из двух: либо Python (и другие языки с динамической типизацией) принципиально УГ; либо у кого-то "руки не из того места растут". Третьего не дано.


Тут я, разумеется, загнул в полемическом задоре. "Третий" вывод есть, и выглядит он так: "у нас не хватает времени, чтобы разобраться в проблеме и написать нужные тесты". И этот самое третье автоматически переводит внимание с программистов и языков программирования на менеджмент. Дальнейшие рассуждения сводятся к тем, что были сформулированы eao197.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re[21]: Менеджер про хаскель в продакшне
От: Sinclair Россия https://github.com/evilguest/
Дата: 29.10.13 11:02
Оценка:
Здравствуйте, Геннадий Васильев, Вы писали:

ГВ>Извини, что вопросом на вопрос: а как ты формализуешь требования к любым другим функциям/классам/модулям?

При помощи сигнатуры. Ваш К.О.
ГВ>Ну, вот так же и здесь.
А здесь нет "сигнатуры". Ваш К.О.

ГВ>Тестирование по методу чёрного ящика.

А конкретнее?

ГВ>Что такое: "более-менее любого"? Так любого или "более-менее"?

У нас есть только любой. Напомню: типов нет.
Это в статике я бы взял аргументом что-то типа IDictionary<string, T>, и всё сразу стало бы понятно.
А тут может быть что угодно.

ГВ>И кстати, "this dynamic" первым параметром загнала меня в ступор. Не покажешь объявление поподробней, просто интересно.

Это из головы. В реальности не работает — заекстендить любой динамик к счастью нельзя.
На суть не влияет — убери this.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[21]: Менеджер про хаскель в продакшне
От: Sinclair Россия https://github.com/evilguest/
Дата: 29.10.13 11:04
Оценка:
Здравствуйте, Sinix, Вы писали:


S>По-моему, тут проблема не столько в тестах, сколько в самой функции.

Тут — да.
S>Если без динамика никак — остаются тесты по заранее оговорённым вариантам использования, они же документация: что можем обрабатывать — обрабатываем, остальное — на свой страх и риск. Плюс обязательный ассерт, который собирает в лог неподдерживаемые контейнеры.
Это вы так думаете, потому что я сигнатуру на шарпе написал. А если это джаваскрипт? Там нет способа вместо динамика написать что-то более специфичное, или написать перегрузок по типу аргумента.


S>Масштабируется конечно отвратно, если подобных велосипедов не один и не два — проблемы будут вылезать постоянно.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[22]: Менеджер про хаскель в продакшне
От: Sinix  
Дата: 29.10.13 12:07
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>>Если без динамика никак — остаются тесты по заранее оговорённым вариантам использования, они же документация: что можем обрабатывать — обрабатываем, остальное — на свой страх и риск. Плюс обязательный ассерт, который собирает в лог неподдерживаемые контейнеры.


S>Это вы так думаете, потому что я сигнатуру на шарпе написал. А если это джаваскрипт? Там нет способа вместо динамика написать что-то более специфичное, или написать перегрузок по типу аргумента.

А тогда ничего не подскажу, наоборот, присоединяюсь к вопросу

Какие ещё варианты могут быть, кроме переезда на typescript?
Re[22]: Менеджер про хаскель в продакшне
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 29.10.13 14:23
Оценка:
Здравствуйте, Sinclair, Вы писали:

ГВ>>Извини, что вопросом на вопрос: а как ты формализуешь требования к любым другим функциям/классам/модулям?

S>При помощи сигнатуры. Ваш К.О.
ГВ>>Ну, вот так же и здесь.
S>А здесь нет "сигнатуры". Ваш К.О.

O'rly? Проверим.

ГВ>>Тестирование по методу чёрного ящика.

S>А конкретнее?

Щас увидим.

ГВ>>Что такое: "более-менее любого"? Так любого или "более-менее"?

S>У нас есть только любой. Напомню: типов нет.

Отлично. Как ты будешь извлекать объект по имени из контейнера ArrayList<T>?

S>Это в статике я бы взял аргументом что-то типа IDictionary<string, T>, и всё сразу стало бы понятно.

S>А тут может быть что угодно.

ArrayList<T>. Извлеки элемент по имени.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re[23]: Менеджер про хаскель в продакшне
От: Sinclair Россия https://github.com/evilguest/
Дата: 30.10.13 04:15
Оценка:
Здравствуйте, Геннадий Васильев, Вы писали:

ГВ>ArrayList<T>. Извлеки элемент по имени.

Вас не затруднит определиться, о какой системе типов идёт речь?
В типичных динамиках нет никаких ArrayList<T>. Есть просто массивы. Вот вам функция на JavaScript, которая извлекает то, что мне нужно:
function getValueByName(o, name)
{
  if(Object.prototype.toString.call(o) === '[object Array]' ) {
    for(var i=0;i<o.length;i++)
      if (('name' in o[i]) && (o[i].name == name))
        return o[i];
  return null;
}
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.