kb
07.01.2013 17:07
Кароч для тех, кому лень было читать (всем кроме меня) вот этот чувак самый умный оказался https://www.tbray.org/ongoing/When/201x/...
1. Statically-typed languages can make unit testing hard, so
2. People adopt dependency injection to work around this, and
3. In a sort of Stockholm-syndrome effect, people argue that DI is A Good Thing and over-use it, to harmful effect.
// дальше только сейчас читаю
> Statically-typed languages can make unit testing hard
не надо использовать те, которые "can make", йоу.
дык, собственно, я кстати и не знаю языков со статической типизацией, которые из коробки удобно дают тесты с моками удобно делать.
точнее не с моками а банально "развязывать" вызовы функций напрямую.
"из коробки" "ненужно".
Можно подробнее?
а как это?
всегда можно установить нужную библиотеку для некоторых статически-типизированных языков, и будут тесты.
Ну, блин, например на время теста подложить нужные результаты вызова http-запроса, вон по ссылке как раз чувак рассматривает такой пример с OAuth. То есть хотя бы на уровне "во время теста вот в этом модуле подменить импорт этого модуля на вот эту хрень" я не видел ЯП как-то (в Си люди извращаются с инклудами, что тоже вариант, конечно :).
Ээ. Да нет, вопрос не в том, чтоб они просто были, а в том, чтоб они по понятиям были, собственно для этого и DI.
для С/C++ DI можно реализовать на уровне линкера
Да, я знаю. И понимаю тех, кто этим не занимается :)
а, теперь понял, про что речь. В большинстве статически-типизированных языков программирования такого точно нет штатным образом. С одной стороны пичалька, факт.
(в окамле можно кое-как извратиться, но либо с изначальным учётом того, что конкретный модуль будет подменяться, либо внешними силами — скриптов, препроцессора. но подобные тесты всяко руками пишутся, поэтому подготовить для них почву — не западло.)
А вообще, почему бы не писать сразу правильно? Желательно, формальным образом доказанным образом!11
Вот и меня сильно расстраивает, что это не делают частью "фич языка". Я вот даже думал, мол, круто было бы на уровне системы типов давать возможность на время теста объявлять некоторый нечистый код чистым, таким образом система типов бы удостоверилась, что ты всю нечисть на время тестов подменил на свой чистый код. Отсюда и "покрытие нечисти" было бы. Короче, фантазии есть где разыграться.
Сразу писать правильно не умею, формально доказывать корректность тоже еще совершенно не знаю как.
> объявлять некоторый нечистый код чистым
я в рот ебал "чистоту кода", если чо. И х-ь в том числе.
Кроме того, #tioeof
Главное — чтобы работало. Функциональный стиль — в плюс к "работает".
Однако прекрасно понимаю, что "подсунуть модуль" может оказаться очень полезным. С другой стороны, чего подсовывать, если тесты таки нужны, и чего бы в этом случае не выделить тестируемое отдельно? Я не знаю, как в петоне с этим дела обстоят, однако.
Про "сразу правильно" — скорее, основываясь на типах, которые не дадут сделать плохого, это работает в языках с богатыми типами. Про "доказывать корректность" — это стоит делать не всегда, чаще типизация и тесты справляются лучше, но учитывать неплохо. Но петонокод доказывать не рекомендую.
c++, например. google://ubermock
java + invocation handler
В данном случае имеется в виду временное изменение типа с целью гарантии качественного тестирования, х-ль тут только потому что умеет так.
> С другой стороны, чего подсовывать, если тесты таки нужны, и чего бы в этом случае не выделить тестируемое отдельно?
Так об этом и вся речь. Отсюда появляются di головного мозга, и у тебя программа везде сплошь параметризируется как только можно, но на деле выходит пиздец нечитаемый (хотя многие привыкли).
Про питонокод не понял зачем приплетаешь, но в тестируемости у него все хорошо, и di ему тоже не нужен почти.
А можно хотя бы readme написать?
Ну и снова таки, разве он даёт подменять что-то на уровне импортов?
ну, у кого как, а у меня редко получалось что-то нечитаемое из-за параметризации. А польза от неё — ощутимая. Видимо, это субъективный вопрос, фор хум хау.
Питонокод приплетаю к вопросу "формально доказывать корректность", там выше по треду понятно, к чему это. Ну да мелочи.
оно написано же... :( ща, до работы доеду — сгенерю пдфку и выложу на файлопомойку
он умеет перехватывать все вызовы к интерфейсу
Ну да, вещь хорошая, из джавы почти динамический ЯП делает какой-то, но в случае с модулями и тестируемостью (и главным топиком), к сожалению, не поможет, потому придется снова фабрики, классы и проч. городить ради тестируемости.
Ну как тебе еще объяснить. Если бы в джава мире все писали не на джаве а на хаскеле (как буду знать окамл — напишу тебе на окамле :), то давно бы все писали вместо
foo :: X → Y
foo x = (f1 x) + (f2 x) — (f3 x)
Как-то так:
create_foo f1 f2 f3 = (\x → (f1 x) + (f2 x) — (f3 x))
и потом в рантайме делали бы
foo = create_foo f1 f2 f3
И кучу тестов бы понаписали, в каджом создавая foo заново чтоб со своими f1 f2 и f3 было. То есть фабрики чисто ради тестируемости делаются, а теперь еще приговаривается, что дикаплинг — это круто (даже когда он не нужен), и специальные фреймворки, позволяющие так повсеместно писать есть.
В общем, я лично считаю, что надо на уровне языка подобную подмену f1, f2 и f3 разрешать делать на время теста, а не делать язык настолько "динамичным".
1. обещанная документация: https://docs.google.com/open?id=0B6ttfyN... можно сразу переходить к третьей странице
2. тестилось только под x86, и то давно
3. сейчас не работает вообще, хз почему, видно что-то забыл поправить
4. чинить нет времени, так что на чтение документации ты впустую потратишь время
Ага, ну убермок крут, конечно, но я так понял всё равно всё пляшет от #include'ов, а затем перекрытия нужных функций :)
ну да, а как ты будешь перекрывать функцию не заинклудив её определение? (особенно с учётом extern C перед ней)
ну вон джава-программисты DI для этого делают повсеместный, у них не поинклудишь.
ну так это негодная параметризация какая-то! Хотя идею понял. Рад, что объяснил.
в окамле обычной "единицей абстракции апи" является модуль, и вот, для нужных модулей (в том числе из стандартной библиотеки, типа работы с файлами или сетью) вполне можно написать замену и линковать её для тестов. То есть, дело в том, что именно будем линковать. Или в реальных обстоятельствах это хуёвое решение? (про подмену f1, f2, f3 идею понял, но представим себе какой-нибудь другой путь, если интересно почесать репу. Если я заебал — забьём.)
Ну линковать-то можно почти везде, наверное и в джаве можно, но ведь:
1. это некоторый очень серьезный гемор сам по себе
2. нужно над этим гемором написать фреймворк, который из простого описания теста делал бы всю линковку за тебя
3. оно будет тормозить, т.к. на каждый тест свои линкования хитрые надо будет делать
То есть, я конечно не пробовал, но сдаётся мне, что с линкованием будет ну очень геморно.
То есть, чисто чтоб ты представлял как это происходит в питоне (без использования извращений в виде DI):
есть у тебя модуль psto.posts.bl (бизнес-логика) и в нём функция, скажем, ban_user.
def ban_user(user):
ok = deactivate_user(user)
if not ok:
return
notify_about_ban(user)
if user.age < 10:
user.status = "loh"
elif 10 < user.age < 20:
user.status = "mudak"
else:
user.status = "pots"
user.save()
Так вот, цель у тебя — протестировать очень сложный механизм с заданием статуса юзеру ("loh", "mudak" и "pots"), при этом ты не хочешь чтоб deactivate_user, notify_about_ban и прочие спец-эффекты (вроде бы их здесь нет) как-то на твое тестирование влияли, потому ты просто задашь их поведение явно.
В питоне это выглядело бы как-то так:
class TestBanUser(TestCase):
@patch("deactivate_user", return_value=True)
@patch("notify_about_ban", return_value=None)
def test_should_set_status_to_loh(self):
user = UserFactory.create()
user.age = 8
ban_user(user)
assert_equal(user.status, "loh")
То есть, видишь как красиво (на самом деле еще красивее) ты:
1. определяешь поведение нетестируемых частей (функций deactivate_user, notify_about_ban).
2. заменяешь их на "моки", таким образом они мгновенно отрабатывают (и не шлют имейлов, не лезут в БД и так далее).
Таким образом ты тестируешь только часть логики, которая работает с user.status, и знаешь что делает/возвращает всё остальное.
Как сделать подобного рода вещи только через линковки я не знаю.
читабельная паста
http://paste.ubuntu.com/1509615/
(да и так вполне читаемо было!)
Идею понял.
Да, тут либо патчить, либо параметризовывать.
А вот как мне нравится решать подобное в coq: https://gist.github.com/b3b1af7808e8bfbc... (там упрощение в плане сайд-эффектов и в плане if, но можно сделать и более-менее по-честному). Доказательство состоит из "взять все утверждения в контекст доказательств; поупрощать всё что можно; подобрать автоматически терм для очевидного факта" (этот очевидный факт — факт того, что какие-то actions таки могут быть равны тем actions, которые реально выдаёт ban_user).
Въехать скоро не обещаю, но попробую в ближайшее время.
лол
Ага, посмотрел. Ну выглядит красиво, конечно, жаль я понятия не имею как оно выводить это будет :) В будущем бы с удовольствием изучал такое.
а что тут выводить? Из вывода тут только "exists", которое говорит "есть какой-то список действий, не важно какой".
А так — рекомендую связку coq + ocaml для написания хитровыебанного кода, если нужно очень дохуя гарантий. Если чо, помогу, чем смогу. Вдобавок, хорошая книжка по coq находится тут: http://adam.chlipala.net/cpdt/ .
кстати вот, про "очень дохуя гарантий" это не шутка. Иногда их настолько дохуя, что заёбываешься. Когда я заёбываюсь с coq, беру окамл и говнокодю на нём. То есть, coq далеко не всегда нужен. Но знать о нём и немножко его уметь — это всяко плюс.
DI, моки… Действительно, чё с ними носятся, это ж всего-навсего higher-order functions & closures!
В функциональном программировании скорее всего вместо фабрик ты бы делал higher-order functions и замыкания, да (ну, еще, возможно, какой-нибудь message-passing чтоб описать одной функцией сразу целый интерфейс, чтоб заменить объект).
Но при чем здесь это? С замыканиями и higher-order functions проблема-то остаётся абсолютно той же, пример описан в /24
благодаря first-class functions /24 решается обыкновенным карированием
то есть, просто не вижу проблемы в этой твоей жалобе, всё работает и выглядит норм
каким карированием? что решается? что ты несёш вообще? при чем здесь это всё?
COCOCONSTRUTIVE DEBATE!!1
да вообще пиздец. обсуждаем одно, и тут врывается какой-то тип и на пример, приведённый для объяснения проблемы, отвечает фразой "это решается ...". что, блять, решается? здесь /0 решается, вообще-то.