Понимание функций randomGenerator для тестирования функций Javascript

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



Https://steemit.com/@mightypanda предоставил решение той же проблемы и выиграл награду. Я хотел протестировать решение для различных сценариев. Создание входных данных для крайних случаев было очень трудоемким. Я подумал об использовании случайных генераторов для тестирования того же самого. Итак, я начал немного копать.

Рандомизировать или нет

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

Давайте рандомизируем

Если вы читали мою предыдущую статью Понимание обещаний в Javascript, вы бы помните, что мы использовали функцию getRandomNumber, которая хорошо послужила нам для promiseGenerator функций, так что мы могли моделировать и изучать, как работают обещания. Я добавил заявление об отказе от ответственности, в котором говорится, что //works when both start,end are >=1 and end > start. Поскольку я буду использовать этот метод чаще, я подумал о том, чтобы немного его очистить.

function getRandomNumber(start = 1, end = 10) {
  //works when both start,end are >=1 and end > start
  return parseInt(Math.random() * end) % (end-start+1) + start;
}
// Lets add another function which just calls getRandomNumber //function and prints it.
function printRandomNumber(start = 1, end = 10) {
  console.log(getRandomNumber.apply(null, arguments));
}

Я хотел, чтобы он работал для положительных чисел, один из параметров мог быть нулем и даже для отрицательных чисел. Чтобы узнать, действительно ли он случайный (о, я имею в виду, недостаточно рандомизированный) и если крайние случаи обработаны, нам нужно будет запустить эту getRandomNumber функцию несколько раз. Я имею в виду, что если нам нужно быть уверенными, что начальное и конечное значения включены в генерируемые случайные числа, единственный путь вперед - это запустить функцию достаточное количество раз, пока сгенерированное случайное число не станет таким же, как start или end. Я бы сказал это как occurrence is the only proof that it will occur.

Повторить

Итак, давайте создадим функцию, которая может вызывать желаемую функцию желаемое количество раз. Я получил следующий пример из Stackoverflow.

const loop = (fn, times) => {
  if (!times) {
    return;
  }
  fn();
  loop(fn, times - 1);
};
//Format for invoking our loop function. Let us say we need to call // getRandomNumber function 20 times
loop(printRandomNumber, 20);

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

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

const loop = (fn, times = 5, params = []) => {
  if (!times) {
    return;
  }
  fn(...params);
  loop(fn, times - 1, params);
};
//Format for invoking our loop function. Let us say we need to call // getRandomNumber function 20 times with start and end values as 2 and 5
loop(printRandomNumber, 20, [2,5]);

Итак, мы добавили третий параметр к нашей функции цикла, который будет массивом параметров. При вызове желаемой функции мы используем spread operatorthree точки для передачи параметров функции. Нам просто нужно убедиться, что при передаче параметров в нужную функцию мы передаем ее как массив значений параметров. Если подумать, я думаю, нам следовало назвать эту функцию repeat вместо цикла.

Если мы сделаем times равным 100 или около того, нам будет трудно взглянуть на значения, равные единице. Итак, давайте просто создадим функцию конкатенации.

outputString = "";
// I know using global variables is bad. For now let us keep it this //way. Once explore the closures completely we can use it for this //scenario.
function concatenateRandomnumber(start = 1, end = 10) {
  outputString += getRandomNumber.apply(null, arguments) + ", ";
}
// This will add a trailing comma but who cares. Lets just call in //Stanford comma for now :P

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

var randomLimits = [0, 3];
loop(concatenateRandomnumber, 100, randomLimits);
console.log(...randomLimits);
console.log(outputString);

var randomLimits = [-3, 3];
loop(concatenateRandomnumber, 100, randomLimits);
console.log(...randomLimits);
console.log(outputString);

Лучше рандомизировать

Используя функцию loop, мы определили крайние случаи, когда наш getRandomNumber дает сбой. На этот раз я не гуглил. Поразмыслив, я понял, что все дело в том, чтобы получить диапазон. Итак, я изменил функцию следующим образом.

function getRandomNumber(start = 1, end = 10) {
  if (start > end) {
    [start, end] = [end, start];
  }
  let range = end - start + 1;
  return (parseInt(Math.random() * range) % range) + start;
}

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

Math.random дает случайные числа с плавающей запятой от 0 до 1. Таким образом, (parseInt(Math.random() * range) % range) даст нам случайное число между 0 and range.. Затем я смещаю его на start, чтобы число радома было сгенерировано между start и end.

Используйте этот подход для тестирования нашего сценария

Чтобы узнать подробности оформления задачи, обратитесь к разделу https://steemit.com/javascript/@gokulnk/nestify-a-flat-json-object

Для этой постановки задачи мы знаем, что у нас будет плоский json, а значение атрибута pos изменяется только на один шаг за раз. Таким образом, приращение происходит только в единицах -1,0, + 1. В решении, предоставленном mightypanda, getNestedJSON является основной функцией, а createChild используется для внутренних целей.

Давайте сначала определим функцию runTestforFixedValues. Это как статические входные данные для уже известных нам сценариев. Давайте проверим вывод.

function runTestforFixedValues() {
  var inputSets = [];
  var input = [
    { pos: 1, text: "Andy" },
    { pos: 1, text: "Harry" },
    { pos: 2, text: "David" },
    { pos: 3, text: "Dexter" },
    { pos: 2, text: "Edger" },
    { pos: 1, text: "Lisa" }
  ];
  inputSets.push(input);
  input = [
    { pos: 1, text: "Andy" },
    { pos: 2, text: "Harry" },
    { pos: 2, text: "David" },
    { pos: 1, text: "Dexter" },
    { pos: 2, text: "Edger" },
    { pos: 2, text: "Lisa" }
  ];
  inputSets.push(input);
  input = [
    { pos: 1, text: "Andy" },
    { pos: 2, text: "Harry" },
    { pos: 3, text: "David" },
    { pos: 4, text: "Dexter" },
    { pos: 5, text: "Edger" },
    { pos: 6, text: "Lisa" }
  ];
  inputSets.map(inputJSON => {
    getNestedJSON(inputJSON);
  });
}

Предоставленное решение работало для всех трех сценариев ввода. Вместо того, чтобы создавать больше подобных входов. Некоторое время я потратил на создание случайного генератора Flat JSON в нужном формате.

function runTestforRandom() {
  var inputArray = [];
  let alphabetsArray = [];
  for (i = "A".charCodeAt(0); i <= "Z".charCodeAt(0); i++) {
    alphabetsArray.push(String.fromCharCode(i));
  }
var maxNumberOfElements = getRandomNumber(3, 10);
  var inputObject = [];
// All we need is -1,0,-1 just change the number of occurences to //control the likelihood of the value being used. CRUDE //IMPLEMENTATION :P
  var incrementArray = [-1, -1, 0, 1, 1, 1, 1, 1];
  pos = 1;
  inputArray.push({ pos: pos, text: "A" });
  for (var i = 1; i < maxNumberOfElements; i++) {
    randomNumber = getRandomNumber(1, incrementArray.length) - 1;
    increment = incrementArray[randomNumber];
    tempValue = pos + increment;
    pos = tempValue > 0 ? tempValue : 1;
    var obj = new Object();
    obj.pos = pos;
    obj.text = alphabetsArray[i % 26];
    inputArray.push(obj);
  }
  getNestedJSON(inputArray);
}

Я создал alphabetsArray, содержащий алфавиты, которые мы будем использовать для свойства текста. maxNumberOfElements генерируется с использованием нашей функции генератора случайных чисел. Мы знаем, что между соседними объектами значение pos изменяется только на -1,0, + 1. Таким образом, incrementArray набивается этими значениями, и мы выбираем одно из этих значений случайным образом. Допустим, вы хотите создать глубоко вложенный объект, а затем увеличьте количество вхождений +1 в этом массиве. Мы также можем сделать поле text объекта случайным. Поскольку его значение не влияет на результат, мы последовательно назначаем алфавиты объекту text, чтобы было легче проверить, правильно ли получен результат. Взгляните на вывод ниже, чтобы понять, что я говорю. Проверить правильность вложенности почти так же просто, как прочитать алфавит. Даже если бы мы не подавали в суд на алфавиты последовательно, мы все равно могли бы подготовить свойство pos в этих объектах для проверки операции проверки.

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

function runTestforFixedValues() {
  var inputSets = [];
  var input = [
    { pos: 1, text: "Andy" },
    { pos: 1, text: "Harry" },
    { pos: 2, text: "David" },
    { pos: 3, text: "Dexter" },
    { pos: 2, text: "Edger" },
    { pos: 1, text: "Lisa" }
  ];
  inputSets.push(input);
  input = [
    { pos: 1, text: "Andy" },
    { pos: 2, text: "Harry" },
    { pos: 2, text: "David" },
    { pos: 1, text: "Dexter" },
    { pos: 2, text: "Edger" },
    { pos: 2, text: "Lisa" }
  ];
  inputSets.push(input);
  input = [
    { pos: 1, text: "Andy" },
    { pos: 2, text: "Harry" },
    { pos: 3, text: "David" },
    { pos: 4, text: "Dexter" },
    { pos: 5, text: "Edger" },
    { pos: 6, text: "Lisa" }
  ];
  // All those involving Alphabets were generated from the random function
  inputSets.push(input);
  input = [
    { pos: 1, text: "A" },
    { pos: 2, text: "B" },
    { pos: 2, text: "C" },
    { pos: 2, text: "D" },
    { pos: 1, text: "E" },
    { pos: 2, text: "F" },
    { pos: 2, text: "G" }
  ];
  inputSets.push(input);
  input = [
    { pos: 1, text: "A" },
    { pos: 2, text: "B" },
    { pos: 1, text: "C" },
    { pos: 2, text: "D" },
    { pos: 2, text: "E" },
    { pos: 3, text: "F" },
    { pos: 4, text: "G" },
    { pos: 5, text: "H" },
    { pos: 5, text: "I" },
    { pos: 4, text: "J" },
    { pos: 4, text: "K" },
    { pos: 5, text: "L" }
  ];
  inputSets.push(input);
  input = [
    { pos: 1, text: "A" },
    { pos: 1, text: "B" },
    { pos: 1, text: "C" },
    { pos: 2, text: "D" },
    { pos: 1, text: "E" },
    { pos: 1, text: "F" },
    { pos: 2, text: "G" },
    { pos: 1, text: "H" },
    { pos: 1, text: "I" },
    { pos: 2, text: "J" },
    { pos: 3, text: "K" },
    { pos: 3, text: "L" }
  ];
inputSets.push(input);
inputSets.map(inputJSON => {
    getNestedJSON(inputJSON);
  });
}

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

Итак, теперь мы можем протестировать код для различных статических входных значений. Каждый раз, когда мы запускаем тесты, мы также можем посмотреть на некоторые случайные входные данные и проверить, ожидается ли результат. Если вам нравится этот ввод или если вы считаете, что это крайний случай, который следует протестировать в целом, вы можете просто скопировать и вставить этот ввод в свой метод тестирования статических значений. Разве это не круто? По крайней мере, я так думаю. Я считаю это очень полезным, когда хочу проверить логику конкретной критической функции.

Если вы хотите просмотреть весь этот код в одном месте, проверьте

Вот репо для того же - https://github.com/nkgokul/flat-to-nested/blob/master/nestify.js

Подводя итоги

  1. Мы начали с getRandomNumber из предыдущей статьи.
  2. Мы использовали loop функцию от SO и проверили, как она реализована.
  3. Мы расширили loop, чтобы мы могли также передавать параметры нужной функции, которую нужно вызвать.
  4. Мы научились использовать оператор spread.
  5. Мы использовали наш loop, чтобы определить, где все наши getRandomNumber функции дают сбой.
  6. Мы улучшили логику getRandomNumber, чтобы она работала для всех диапазонов.
  7. Мы протестировали getRandomNumberand , убедились, что он работает на всех диапазонах.
  8. Мы написали runTestforFixedValues функцию, чтобы перечислить некоторые известные крайние случаи и понять природу входных данных.
  9. Мы создали runTestforRandom функцию, которая генерирует случайный плоский ввод json для нашего тестирования.
  10. Мы использовали loop(runTestforRandom, 10); для runTestforRandom пробега 10 раз.

Укажите, если мне здесь чего-то не хватает или что-то можно улучшить. Если вам понравились эти статьи и вы хотите прочитать похожие статьи, не забывайте аплодировать. На среднем уровне не более 50 хлопков в ладоши. Проверьте это;)

Вы также можете прочитать https://hackernoon.com/why-clapping-is-important-in-medium-ed78e016b50a, чтобы узнать, почему так важно хлопать в ладоши.

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



Понимание обещаний в Javascript
У меня было что-то вроде« любви и ненависти
к Javascript. Но, тем не менее, javascript всегда интригует… hackernoon.com »