Тестирование функции js с помощью Jest возвращает неверное значение в цикле for - PullRequest
1 голос
/ 19 июня 2020

Это мое первое использование Jest, и я пытаюсь протестировать функцию со многими многими значениями, такими как:

const convertConfig = require('../tools/convertNumWord/config');
const numToWordConstructor = require('../tools/convertNumWord/num2words/numToWords');
const wordToNumConstructor = require('../tools/convertNumWord/words2num/wordsToNum');

describe('EN-CONFIG', () => {
  const config = convertConfig['en'];  // It returns an object with configuration values for the language
  const numToWord = numToWordConstructor(config); // It return a function builds with config object 
  const wordToNum = wordToNumConstructor(config);

  for (let i = 0; i <= 4; i++) {
    it(`Test for value ${i}`, () => {
      expect(wordToNum(numToWord(i))).toBe(Number(i));
    });
  }
});

Jest возвращает эту ошибку:

 ● ENGLISH CONFIG › Test for value 2

    expect(received).toBe(expected) // Object.is equality

    Expected: 2
    Received: 1

      69 |   for (let i = 0; i <= 4; i++) {
      70 |     it(`Test for value ${i}`, () => {
    > 71 |       expect(wordToNum(numToWord(i))).toBe(Number(i));
         |                                       ^
      72 |     });
      73 |   }
      74 | });

      at Object.<anonymous> (__tests__/loopConvertNum.test.js:71:39)

  ● ENGLISH CONFIG › Test for value 3

    expect(received).toBe(expected) // Object.is equality

    Expected: 3
    Received: 1

      69 |   for (let i = 0; i <= 4; i++) {
      70 |     it(`Test for value ${i}`, () => {
    > 71 |       expect(wordToNum(numToWord(i))).toBe(Number(i));
         |                                       ^
      72 |     });
      73 |   }
      74 | });

      at Object.<anonymous> (__tests__/loopConvertNum.test.js:71:39)

  ● ENGLISH CONFIG › Test for value 4

    expect(received).toBe(expected) // Object.is equality

    Expected: 4
    Received: 1

      69 |   for (let i = 0; i <= 4; i++) {
      70 |     it(`Test for value ${i}`, () => {
    > 71 |       expect(wordToNum(numToWord(i))).toBe(Number(i));
         |                                       ^
      72 |     });
      73 |   }
      74 | });

      at Object.<anonymous> (__tests__/loopConvertNum.test.js:71:39)

Test Suites: 1 failed, 2 passed, 3 total
Tests:       3 failed, 1 todo, 3 passed, 7 total
Snapshots:   0 total
Time:        1.773s
Ran all test suites.

Мой первый тест работал, но в течение 2 дней Jest, похоже, не выполняет мою функцию для каждого раунда l oop, как если бы он сохранял значение в памяти (часто значение второго раунда l oop) и тест toBe в конечном итоге ложно. В некоторых случаях результат моих функций полностью несовместим (ноль или какое-то приращение от предыдущего результата). Когда я запускаю свою функцию с узлом с теми же аргументами, она работает хорошо.

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

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

Это - это функция, импортированная в numToWordConstructor :

    const config = require('../config');
const _ = require('lodash');

const numToWordsConstructor = config => {
  config = _.cloneDeep(config);
  const letters = {
    ..._.invertBy(config.oneDigits),
    ..._.invertBy(config.doubleDigits),
    ..._.invertBy(config.tripleDigits),
  };
  const hundredWords = config.hundredWords;
  const oneDigits = config.oneDigits;
  const doubleDigits = config.doubleDigits;
  const tripleDigits = config.isEachHundredsWords ? config.tripleDigits : null;
  const separators = config.separators;

  let words = [];

  // Convertion function
  const num2Words = number => {
    let result = '';

    const numbersWords = Object.keys(letters);
    const nbString = number.toString();
    const nb = parseFloat(nbString.replace(/ /gi, ''));

    if (nb > 999999999999)
      throw new Error(
        'Function numToWords for number',
        number,
        ': Number to big',
      );
    if (isNaN(nb))
      throw new Error(
        'Function numToWords for number',
        number,
        'Not a valid number',
      );

    if (Math.ceil(nb) != nb) {
      separateFloatNumbers = nbString.split('.');
      return (
        num2Words(separateFloatNumbers[0]) +
        ' virgule ' +
        num2Words(separateFloatNumbers[1])
      );
    }

    if (Number(number) === 0) return oneDigits[0][0];

    // Build numByThousand object to split number to 3 digits arrays
    let numByThousand = [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]];

    if (numbersWords.includes(nbString)) {
      result = letters[nbString][0];
    } else {
      const nbReversedDigits = nbString
        .split('')
        .map(digit => parseInt(digit))
        .reverse();

      numByThousand = numByThousand
        .map(thousand => {
          if (nbReversedDigits.length >= 3) {
            return (thousand = nbReversedDigits.splice(0, 3).reverse());
          } else {
            return (thousand = [0, 0, 0]
              .map(
                defaultDigit =>
                  nbReversedDigits.splice(0, 1)[0] || defaultDigit,
              )
              .reverse());
          }
        })
        .reverse();
    }

    // Build array of words depending of numByThousand
    const convertNumByThousandToWords = numByThousand => {
      const hundred = numByThousand[0] * 100;
      const ten = numByThousand[1] * 10 + numByThousand[2];
      const newWords = [];

      if (numByThousand[0] > 0) {
        if (config.isEachHundredsWords) {
          newWords.push(tripleDigits[hundred][0]);
        } else if (numByThousand[0] === 1 && !config.isOneBeforeOnlyOne[0]) {
          newWords.push(config.hundredWords[0]);
        } else {
          newWords.push(oneDigits[numByThousand[0]][0]);
          newWords.push(config.hundredWords[0]);
        }
      }
      if (ten > 0) {
        if (Object.keys(doubleDigits).includes(ten.toString())) {
          newWords.push(doubleDigits[ten][0]);
          return newWords;
        } else {
          if (numByThousand[1] > 0)
            newWords.push(doubleDigits[numByThousand[1] * 10][0]);
          if (numByThousand[2] > 0)
            newWords.push(oneDigits[numByThousand[2]][0]);
          return newWords;
        }
      }
      return newWords;
    };

    if (config.noMoreMillionSeparator) separators.push(config.separators[0]);
    numByThousand.map((thousand, i) => {
      if (thousand.reduce((acc, cur) => acc + cur) > 0) {
        words = _.concat(words, convertNumByThousandToWords(thousand));
        // Handle thousand separators
        separators[config.separators.length - i - 1] &&
          words.push(separators[config.separators.length - i - 1][0]);
      }
    });

    //Handle specifics spelling rules
    words = config.spellingRules(words, oneDigits, doubleDigits);

    // console.log('words : ', words.join(' '));
    return words.join(' ');
  };

  return num2Words;
};

module.exports = numToWordsConstructor;

Это функция, импортированная в wordToNumConstructor :

    const _ = require('lodash');

    const arrayAwareInvert = obj => {
      return _.reduce(
        obj,
        (result, values, key) => {
          return _.reduce(
            values,
            (result, value) => {
              result[value] = key;
              return result;
            },
            result,
          );
        },
        {},
      );
    };

    const word2numConstructor = config => {
      config = _.cloneDeep(config);
      const separators = config.separators;
      const hundredWords = config.hundredWords;
      const oneDigits = arrayAwareInvert(config.oneDigits);
      const doubleDigits = arrayAwareInvert(config.doubleDigits);
      const tripleDigits = config.isEachHundredsWords
        ? arrayAwareInvert(config.tripleDigits)
        : null;
      const oneDigitsWords = Object.values(config.oneDigits).reduce(
        (acc, cur) => acc.concat(cur),
        [],
      );
      const doubleDigitsWords = Object.values(config.doubleDigits).reduce(
        (acc, cur) => acc.concat(cur),
        [],
      );
      const tripleDigitsWords = Object.values(config.tripleDigits).reduce(
        (acc, cur) => acc.concat(cur),
        [],
      );

      return stringNumber => {
        separators.forEach(thousandSeparators =>
          thousandSeparators.forEach(separator => {
            // Remove spaces from separator's word in sentence to not split it with split(' ') later
            const regex = new RegExp(`${separator}`, 'gi');
            stringNumber = stringNumber.replace(
              regex,
              separator.split(' ').join(''),
            );
            // Remove spaces from separator's word
            return separator.split(' ').join('');
          }),
        );

        let thousandsWords = [];
        if (config.noMoreMillionSeparator) {
          thousandsWords = separators[0];
        } else {
          thousandsWords = _.flatten(separators);
        }

        stringNumber.trim();
        // Apply on stringNumber the rules of locale key of config object
        stringNumber = config.rulesOnSentence(stringNumber);

        // Apply lowerCase on stringNumber and split words at each spaces
        let words = stringNumber.toLowerCase().split(' ');
        // Apply on each words the rules of locale key of config object
        words = config.rulesOnWords(words);

        const convertWordsByThousandToNumber = wordsByThousand => {
          return wordsByThousand.map(values => {
            if (values.length === 0) return 0;
            let thousand = null;
            let temp;
            // Function to handle ten and unit if temp is hundred value
            const handleTen_Unit = (i, coef = 100) => {
              if (!values[i] || thousandsWords.includes(values[i])) {
                thousand = temp * coef;
              } else if (oneDigitsWords.includes(values[i])) {
                thousand = temp * coef + Number(oneDigits[values[i]]);
              } else if (doubleDigitsWords.includes(values[i])) {
                if (!values[i + 1] || thousandsWords.includes(values[i + 1])) {
                  thousand = temp * coef + Number(doubleDigits[values[i]]);
                } else if (oneDigitsWords.includes(values[i + 1])) {
                  thousand =
                    temp * coef +
                    Number(doubleDigits[values[i]]) +
                    Number(oneDigits[values[i + 1]]);
                }
              }
            };

            //Convert values
            if (values.length === 1 && thousandsWords.includes(values[0])) {
              thousand = 1;
            } else if (hundredWords.includes(values[0])) {
              temp = 1;
              handleTen_Unit(1);
            } else if (doubleDigitsWords.includes(values[0])) {
              temp = Number(doubleDigits[values[0]]);
              if (!values[1] || thousandsWords.includes(values[1])) {
                thousand = temp;
              } else if (oneDigitsWords.includes(values[1])) {
                thousand = temp + Number(oneDigits[values[1]]);
              }
            } else if (oneDigitsWords.includes(values[0])) {
              temp = Number(oneDigits[values[0]]);
              if (!values[1] || thousandsWords.includes(values[1])) {
                thousand = temp;
              } else if (hundredWords.includes(values[1])) {
                handleTen_Unit(2);
              } else {
                thousand = temp;
              }
            } else if (tripleDigitsWords.includes(values[0])) {
              temp = Number(tripleDigits[values[0]]);
              if (!values[1] || thousandsWords.includes(values[1])) {
                thousand = temp;
              } else {
                handleTen_Unit(1, 1);
              }
            }

            return thousand;
          });
        };

        const buildWordsByThousand = words => {
          const wordsByThousand = [];
          separators
            .slice(0) // Make a shallow copy
            .reverse()
            .map(thousandSeparators => {
              const index = _.findIndex(words, word =>
                thousandSeparators.includes(word),
              );
              index > -1
                ? wordsByThousand.push(words.splice(0, index + 1))
                : wordsByThousand.push([]);
            });
          wordsByThousand.push(words); // Push the rest of words for hundred's part
          return wordsByThousand;
        };

        let results = [];

        let indexOfMillionWords = -1;
        words.map((word, i) => {
          if (separators[1].includes(word)) indexOfMillionWords = i;
        });

        if (config.noMoreMillionSeparator && indexOfMillionWords >= 0) {
          const wordsAboveMillion = words.splice(indexOfMillionWords + 1);
          const wordsOverMillion = words.splice(0, words.length - 1);
          const wordsByThousandOverMillion = buildWordsByThousand(
            wordsOverMillion,
          ).splice(1); // Splice is necessary to remove array of million thousand part
          const wordsByThousandAboveMillion = buildWordsByThousand(
            wordsAboveMillion,
          ).splice(1); // Splice is necessary to remove array of million thousand part
          results = results.concat(
            convertWordsByThousandToNumber(wordsByThousandOverMillion),
          );
          results = results.concat(
            convertWordsByThousandToNumber(wordsByThousandAboveMillion),
          );
        } else {
          const wordsByThousand = buildWordsByThousand(words);
          results = results.concat(convertWordsByThousandToNumber(wordsByThousand));
        }

        results;
        return results
          .reverse()
          .reduce(
            (acc, cur, i) =>
              acc === null || cur === null ? null : acc + cur * Math.pow(1000, i),
            0,
          );
      };
    };

    module.exports = word2numConstructor;

И вот результат of const config = convertConfig ['en'] :

{
    rulesOnSentence: sentence => {
      // Write your rules's function to apply on sentence before splitted it
      return sentence
        .normalize('NFD')
        .replace(/[\u0300-\u036f]/g, '')
        .replace(/[\-]/g, ' ')
        .replace(/[^a-zA-Z ]/g, '');
    },
    rulesOnWords: words => {
      // Write your rules's function to apply on words array (after to have splitted sentence)
      return words.filter(word => word !== 'and');
    },
    spellingRules: (words, oneDigits, doubleDigits) => {
      const unit = _.flatten(Object.values(oneDigits).slice(1));
      const ten = _.flatten(
        [20, 30, 40, 50, 60, 70, 80, 90].map(num => doubleDigits[num]),
      );
      const newWords = [...words];
      let j = 0;
      words.forEach((word, i) => {
        // Hyphenate all compound numbers
        if (ten.includes(word) && unit.includes(words[i + 1])) {
          newWords.splice(i - j, 2, word + '-' + words[i + 1]);
          j++;
        }
      });
      return newWords;
    },
    noMoreMillionSeparator: false,
    separators: [
      ['thousand', 'thousands'],
      ['million', 'millions'],
      ['billion', 'billions'],
    ],
    hundredWords: ['hundred', 'hundreds'],
    isOneBeforeOnlyOne: [true, true, true, true],
    isEachHundredsWords: false,
    tripleDigits: {},
    oneDigits: {
      0: ['zero'],
      1: ['one'],
      2: ['two'],
      3: ['three'],
      4: ['four'],
      5: ['five'],
      6: ['six'],
      7: ['seven'],
      8: ['eight'],
      9: ['nine'],
    },
    doubleDigits: {
      10: ['ten'],
      11: ['eleven'],
      12: ['twelve'],
      13: ['thirteen'],
      14: ['fourteen'],
      15: ['fifteen'],
      16: ['sixteen'],
      17: ['seventeen'],
      18: ['eighteen'],
      19: ['nineteen'],
      20: ['twenty'],
      30: ['thirty'],
      40: ['forty'],
      50: ['fifty'],
      60: ['sixty'],
      70: ['seventy'],
      80: ['eighty'],
      90: ['ninety'],
    },
  }

Заранее благодарим вас за ваш ценный отзыв.

Ответы [ 2 ]

0 голосов
/ 19 июня 2020

Проблема заключалась в том, что мои функции отслеживают состояние и результаты второго теста перекрестно загрязнены первым тестом. Спасибо Estus Flask (подробности можно увидеть в комментариях).

Этот тест работает хорошо:

describe('ENGLISH CONFIG', () => {
  let config, numToWord, wordToNum;

  beforeEach(() => {
    config = convertConfig['en'];
    numToWord = numToWordConstructor(config);
    wordToNum = wordToNumConstructor(config);
  });

  for (let i = 0; i <= 10000; i++) {
    it(`Test for value `, () => {
      expect(wordToNum(numToWord(i))).toBe(i);
    });
  }
});

Я буду работать над своими функциями, чтобы они не изменяли переменные закрытия (в конструкторе) ?.

EDIT: лучше оставить мою первую шутливую функцию (быстрее) и просто поместить переменную words

let words = []; // line 17 of numToWords function

внутри возвращаемого функция конструктора ?:

// [...]
const tripleDigits = config.isEachHundredsWords ? config.tripleDigits : null;
const separators = config.separators;

// Convertion function
const num2Words = number => {
  let words = [];
  let result = '';
// [...]
0 голосов
/ 19 июня 2020

Можете ли вы попробовать поместить l oop внутрь блока it.

  it('convert range of 1..4 to word and back to number', () => {
    for (let i = 0; i <= 4; i++) {
      expect(wordToNum(numToWord(i))).toBe(i);
    }
  });

Также удалите Number() итератор i уже является числом.

Если это не работает, вам может потребоваться опубликовать тело функции wordToNum & numToWord.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...