LINUX.ORG.RU

Unit тесты для рекурсивных методов

 , , , ,


0

1

Реквестирую в тред ссылки на best practices при написании юнит-тестов содержащих рекурсивные вызовы.

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

Best practices не рекомендуют писать рекурсивные функции вообще.

Goury ★★★★★ ()
Ответ на: комментарий от Goury

Кстати, а почему вот? Очень многие алгоритмы в рекурсивной форме коротко и понятно выглядят, когда в итеративном виде там фиг поймешь, что и зачем.

cherry-pick ()

у твоей рекурсивной функции вход и выход есть?

umren ★★★★★ ()
Ответ на: комментарий от EnterpriseMobility

В зависимости от числа рекурсий рискуешь вылететь за размер стека, поскольку TCO сейчас нет нигде, включая ноду. Или ты пользуешься фокусами с авторазворотом рекурсии по переполнению стека?

x3al ★★★★★ ()
Ответ на: комментарий от umren

Монга.

// Определяем кложуру (вложенную приватную фунцкию) 
// внутри тестируемой функции
var processNode = db.collection(..).find(..).toArray(function processChilds(err, childs) {
   // there are still some childs
   // Проверка, нужен ли рекурсивный вызов для детей
   if (childs && childs.length > 0) {
      // Это пока еще не покрыто тестами
      // Рекурсивный вызов кложуры
      processNode(childs.shift(), function (err) {
        processChilds(err, childs)
      })
} else {
   // Это уже покрыто тестами
   ...
}
})

// Вызов для корневого элемента
processNode(...)
EnterpriseMobility ()
Ответ на: комментарий от EnterpriseMobility

и что ты тут тестировать собрался? как работает мангуст? или что значение есть в базе?

p.s. рекурсивность в тестировании вообще не играет никакой роли

umren ★★★★★ ()
Ответ на: комментарий от umren

нужен тест-кейс с childs.

причем здесь мангуст? Все вызовы к монге заменяются стабами. Как и должно быть

Мне нужно протестировать, что processNode была вызвана для ВСЕХ детей.

EnterpriseMobility ()
Ответ на: комментарий от Goury

и, я конечно извиняюсь, как ты в моем случае заменишь рекурсивный вызов на что-то нерекурсивное?

И эти люди мне что говорили про деревья. Которые по своей природе рекурсивны.

EnterpriseMobility ()

В чем разница тестирования рекурсивной и нерекурсивной функции? В обоих случаях нужно проверить правильность возвращаемого значения и/или всех побочных эффектов, а как оно внутри работает и сколько раз себя вызывает - без разницы.

annulen ★★★★★ ()
Ответ на: комментарий от cherry-pick

1. По ссылке — поддержка TCO в javascript.

2. Единственное отличие между реализацией с рекурсией и без — в том, что в первом случае есть риск вылететь за предел стека. Всё. В остальном разницы нет и вопрос в ОП-посте становится бессмысленным.

x3al ★★★★★ ()
Ответ на: комментарий от EnterpriseMobility

Итак, господа, ни на processNode, ни на processChilds я не смогу поставить ни шпиона ни заменить его на стабы.

Выносить их определение за пределы за пределы тестируемой функции (чтобы можно было поиметь к ним доступ с помощью rewire) - не поможет.

EnterpriseMobility ()
Ответ на: комментарий от x3al

Чем это отличается от необходимости следить сколько ты выбираешь данных?

zz ★★★★ ()
Ответ на: комментарий от EnterpriseMobility

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

Goury ★★★★★ ()
Ответ на: комментарий от EnterpriseMobility

Любую рекурсивную функцию можно сделать нерекурсивной за счет явного стека/очереди. Наверное, «вон из профессии» нужно всё-таки тебе.

Deleted ()
Ответ на: комментарий от Deleted

и да, эта функция будет офигеть как понятна и поддерживаема.

Зы. с еще можно обойтись только циклом while и следованием.

if, for, foreach не нужны

EnterpriseMobility ()
Ответ на: комментарий от EnterpriseMobility

Конкретно эту? Как и все прочие: переписать в tail call и заюзать trampoline. Грубо говоря, если функция делает return recursiveCall() — всё будет хорошо.

Итак, господа, ни на processNode, ни на processChilds я не смогу поставить ни шпиона ни заменить его на стабы.

Выносить их определение за пределы за пределы тестируемой функции (чтобы можно было поиметь к ним доступ с помощью rewire) - не поможет.

Подожди, зачем тебе выносить их определение?

Но если ты реально хочешь убедиться, что оно обходит детей — заюзай getter'ы на детях и ими проверяй, обошли ли всех или нет. Можно было бы заюзать ES6 Proxy, но емнип нода сейчас это не умеет.

x3al ★★★★★ ()
Ответ на: комментарий от x3al

Но если ты реально хочешь убедиться, что оно обходит детей — заюзай getter'ы на детях и ими проверяй, обошли ли всех или нет

а вот здесь поподробнее

EnterpriseMobility ()
Ответ на: комментарий от EnterpriseMobility

И этот человек выше пишет «вон из профессии»... рукапика.

invy ★★★★★ ()
Ответ на: комментарий от Deleted

но ведь это нужно делать только там, где язык вообще не поддерживает локальные переменные, да? Только GOTO и GOSUB. И DIM. Как в GW-BASIC. Только там тебе нужно стек и очередь самому реализовывать.

EnterpriseMobility ()
Ответ на: комментарий от invy

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

Зы. Учти, что я тестирую сейчас чужой код. Проблема обхода дочерних элементов дерева решена рекурсивно. Вот ведь лох. Мог бы решить как ты предложил.

EnterpriseMobility ()
Ответ на: комментарий от EnterpriseMobility

У тебя в childs() — объекты ведь? И ты можешь их пре-обработать, сделав Object.defineProperty с геттером (и сеттером если ты меняешь дерево). Например, можно сделать fakeChilds = Array.map(childs, что_там) и класть в fakeChilds объекты, где у свойств есть get(), делающий что угодно — например, возвращающим оригинальное значение; set() если оно должно модифицировать дерево и т.д.

get()/set() гарантированно вызовется при доступе к свойству и ты без проблем сможешь убедиться, что прошлись по всему дереву.

Хотя я бы успокоился и на верном результате.

x3al ★★★★★ ()
Ответ на: комментарий от x3al

Впрочем, Object.defineProperty не обязательно, можно прямо в литерале писать return { ... get property() { тут_код_который_вызывается} ... }

x3al ★★★★★ ()

И так бывает с каждым, кто начинал программирование с PHP и JS.

anonymous ()
Ответ на: комментарий от x3al

но емнип нода сейчас это не умеет.

умеет. Там синтаксис немного другой. Proxy.create

fuckYankee ()
Ответ на: комментарий от x3al

В зависимости от числа рекурсий рискуешь вылететь за размер стека

Можно еще юзать асинхронные вызова, но это только если перформанс не нужен.

fuckYankee ()
Ответ на: комментарий от Deleted

Любую рекурсивную функцию можно сделать нерекурсивной за счет явного стека/очереди

А смысл этого? Будет то же самое, вид сбоку. Надо стараться на цикле переписать, тогда смысл есть.

fuckYankee ()
Ответ на: комментарий от Deleted

Ну а зачем тогда стек городить?

было так

fact1=function(n){return n==1||n*fact1(n-1)}
А стало, допустим, так:
fact2=function(n){var acc=1; while(n) acc*=n--; return acc}

fuckYankee ()
Ответ на: комментарий от fuckYankee

неправильно написанный факториал

Анонiмас, опять новый акк зарегал?

Deleted ()
Ответ на: комментарий от Deleted

неправильно написанный факториал

это как?

Ты, кстати, не отвлекайся от стековой темы.

fuckYankee ()
Ответ на: комментарий от fuckYankee

Факториал нуля - единица. Забанься обратно плз.

Deleted ()
Ответ на: комментарий от x3al

В зависимости от числа рекурсий рискуешь вылететь за размер стека, поскольку TCO сейчас нет нигде, включая ноду. Или ты пользуешься фокусами с авторазворотом рекурсии по переполнению стека?

это, кста, не единственная причина, и даже не основная, а основная — это жор памяти.

fuckYankee ()
Ответ на: комментарий от Deleted

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

fact=function(n){return (n<2&&1)||n*fact(n-1)}

это был просто пример на рекурсию.

а на вопрос ты так и не ответил.

fuckYankee ()
Ответ на: комментарий от fuckYankee

ананимас обосрался и пытается бессильно увиливать

ahaha

Между прочим, нерекурсивная версия у тебя правильно выдаст единицу, а значит, не является эквивалентной твоему рекурсивному калу. top kek

Deleted ()
Ответ на: комментарий от Deleted

да это понятно все, но я не ожидал, что ты настолько низко опустился, что обсуждаешь такую туфту.

fuckYankee ()
Ответ на: комментарий от EnterpriseMobility

Мне нужно протестировать, что processNode была вызвана для ВСЕХ детей.

А как бы тестировал это если б вместо рекурсивного вызова там был цикл?

ya-betmen ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.