Jest: Удобочитаемый контекст для ожиданий

Созданный на 21 окт. 2016  ·  76Комментарии  ·  Источник: facebook/jest

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

test('api works', () => {
    expect(api()).toEqual([]) // api without magic provides no items
    expect(api(0)).toEqual([]) // api with zero magic also provides no items
    expect(api(true)).toEqual([1,2,3]) // api with magic enabled provides all items
})

Какое ожидание не оправдалось? Первый или второй?

image

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


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

Обратите внимание, что это также отодвигает удобочитаемый шум в конец строки в тестовом исходном коде, где вы все равно можете написать комментарий.

test('api works', t => {
  t.deepEquals(api(), [], 'api without magic provides no items')
  t.deepEquals(api(0), [], 'api with zero magic also provides no items')
  t.deepEquals(api(true), [1,2,3], 'api with magic enabled provides all items')
})

image


Кажется, единственный способ прикрепить удобочитаемую информацию к ошибкам с помощью jest — это обернуть все в дополнительный it , который является излишне подробным IMO.

describe('api works', () => {
  test('api without magic provides no items', () => {
    expect(api()).toEqual([])
  })
  test('api with zero magic also provides no items', () => {
    expect(api(0)).toEqual([])
  })
  test('api with magic enabled provides all items', () => {
    expect(api(true)).toEqual([1,2,3])
  })
})

В идеале можно было бы каким-то образом прикрепить некоторый удобочитаемый контекст к концу expect .

например

Контекстное сообщение как дополнительный необязательный параметр для методов утверждения:

test('api works', () => {
    expect(api()).toEqual([], 'api without magic provides no items')
    expect(api(0)).toEqual([], 'api with zero magic provides no items')
    expect(api(true)).toEqual([1,2,3], 'api with magic enabled provides all items')
})


Или контекстное сообщение в виде цепочки .because или .why или .comment или .t или что-то в этом роде:

test('api works', () => {
    expect(api()).toEqual([]).because('api without magic provides no items')
    expect(api(0)).toEqual([]).because('api with zero magic provides no items')
    expect(api(true)).toEqual([1,2,3]).because('api with magic enabled provides all items')
})

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

Самый полезный комментарий

Из этого обсуждения и этого репозитория я думаю, что хорошим и семантическим было бы:

it('has all the methods', () => {
  since('cookie is a method').expect(reply.cookie).toBeDefined();
  since('download is a method').expect(reply.download).toBeDefined();
  since('end is a method').expect(reply.end).toBeDefined();
  // ...
});

Использование аналогично because , но с точки зрения семантики оно имеет больше смысла.

Если вам это нравится, я мог бы разработать PR, добавив функциональность since .

Все 76 Комментарий

Привет! Таким образом, мы на самом деле использовали это в Jasmine, но обнаружили, что из тысяч тестовых файлов на FB никто не использовал его. Итак, пока мы печатаем красивое сообщение об ошибке с приблизительной информацией и трассировкой стека, которая приведет к ожиданию (как на вашем скриншоте). Я согласен, что мы могли бы напечатать строку, которая выдает исключение, но довольно часто утверждение состоит из нескольких строк:

expect(a).toEqual({
  …
});

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

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

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

@cpojer , значит, шаблон заключается в том, чтобы обернуть каждое утверждение в it ? и/или просто доверять номерам строк?

Возможно ли, что этот шаблон был принят меньше, потому что он лучше или хуже, а больше просто для соответствия существующим тестам? или, возможно, не зная, что эта функция существует? Я не знал, что это было в Жасмин.

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

Рефакторинг в одну строку может способствовать увеличению семантической информации в утверждении? Возможно?

const adminUser = {
  …
}
expect(a).toEqual(adminUser);

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

Приведенный выше пример показывает, что трудно определить, какое именно утверждение не удалось, если вы не добавите многословные (IMO) оболочки вокруг всего. Это особенно верно в транспилированной среде, где номера строк исходной карты не всегда точны. Я считаю, что быстро и точно понимание сломалось и где важно, как и лаконичные тесты.

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

Я сделал несколько предложений выше:

Вы ищете что-то более простое или другое?

привет @timoxley! мы уже думали о добавлении чего-то подобного.

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

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

expect(555).toBeCloseTo(111, 2, 'reason why');
expect(555).toBeCloseTo(111, 'reason why');

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

expect(1).toBe(2)/* will throw here */.because('reason');

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

expect(1).because('reason').toBe(2);
// or 
because('reason').expect(1).toBe(2);

но этот API выглядит не очень хорошо.

другим вариантом было бы добавить второй аргумент к expect

expect(1, 'just because').toBe(2);

но это почти то же самое, что и предыдущий вариант.

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

В прошлом лучшим решением было создание пользовательских сопоставителей. Мы представим expect.extend в следующей версии Jest, и это позволит вам легко создавать сопоставители, такие как:

expect(a).toEqualMySpecificThing(…)

что должно позволить вам писать более выразительные сообщения об ошибках. Я видел, как это часто используется в таких проектах, как Relay. Посмотреть все сопоставители: https://github.com/facebook/relay/blob/master/src/tools/__mocks__/RelayTestUtils.js#L281

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

@cpojer @dmitriiabramov извините за задержку.

Было бы здорово добавить второй аргумент к expect или связать причину с .because . Что нужно сделать, чтобы это произошло или нет?

инженеры не хотят тратить время на написание тестов

@cpojer Согласен! Помимо того, что я не хочу тратить время на отладку тестов, именно поэтому я считаю, что менее подробный API с более широким контекстом отказа был бы предпочтительнее.

Для некоторых конкретных чисел, используя простой пример из моего комментария выше, чтобы получить эквивалентные утверждения + контекст с лентой, Jest требует, чтобы программист написал почти вдвое больше церемониального шаблона:

  • В 1,8 раза больше строк (6 против 11)
  • 2x отступ (1 против 2)
  • 2x скобки/завитки (24 против 48) !

Это можно улучшить!

// tape
test('api works', t => {
  t.deepEquals(api(), [], 'api without magic provides no items')
  t.deepEquals(api(0), [], 'api with zero magic also provides no items')
  t.deepEquals(api(true), [1,2,3], 'api with magic enabled provides all items')
  t.end()
})

// jest
describe('api works', () => {
  test('api without magic provides no items', () => {
    expect(api()).toEqual([])
  })
  test('api with zero magic also provides no items', () => {
    expect(api(0)).toEqual([])
  })
  test('api with magic enabled provides all items', () => {
    expect(api(true)).toEqual([1,2,3])
  })
})

Обновление : я полагаю, вы могли бы написать шуточные тесты в одной строке со стрелками:

// jest
describe('api works', () => {
  test('api without magic provides no items', () => expect(api()).toEqual([]))
  test('api with zero magic also provides no items', () => expect(api(0)).toEqual([]))
  test('api with magic enabled provides all items', () => expect(api(true)).toEqual([1,2,3]))
})

Это удлиняет линии, но несколько улучшает статистику, которую мы сравнивали ранее:

  • В 0,8 раза больше строк (6 против 5)
  • 1x отступ (1 против 1)
  • В 1,75 раза больше скобок/завитков (24 против 42)

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

Разбирать код важнее, чем читать описание теста, которое, по сути, является просто прославленным комментарием. Вот почему никто не пишет комментарии в начале строки. Например, это было бы садомазохистским безумием:

/* api without magic provides no items */ expect(api()).toEqual([])
/* api with zero magic also provides no items */ expect(api(0)).toEqual([])
/* api with magic enabled provides all items */ expect(api(true)).toEqual([1,2,3])

В идеале весь код утверждения должен быть аккуратно выстроен в одну колонку, чтобы его легко анализировал человек. Исходя из этого, я бы настоятельно предпочел конечную форму .because вместо альтернативного предложения второго аргумента expect .

Спасибо, что поддерживаете разговор. Обратите внимание, что вам также не нужен блок описания, что еще больше уменьшит размер. К сожалению, .because не будет работать, потому что, когда сопоставитель выдает бросок (что происходит до вызова .because ), у нас не будет способа извлечь имя.

Тогда использовать последний аргумент каждой функции сопоставления?

Это сработает, только если мы добавим его в качестве второго аргумента expect .
Мы не можем добавить его в качестве последнего аргумента для каждой функции сопоставления из-за неоднозначности.
например

expect(obj).toHaveProperty('a.b.c', 'is that a reason or a value of the property?');

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

@cpojer неясно, был ли исключен жасминовый (и другие) способ expect(value).toBe(something, 'because message') ?

Одним из примеров является тестирование материала redux-saga/redux-observable, где вы тестируете конечный автомат . Очень полезно иметь описательное сообщение о том, в каком состоянии произошел сбой. Этот пример надуманный, поэтому описания тоже..

@jayphelps мы больше не используем жасминовый способ, так как мы переписали все жасминовые сопоставители

@dmitriiabramov извините, мой вопрос не был ясен. Был ли жасминовый _способ_ сделать это, было решено, чтобы он был добавлен обратно? Делать то же самое, что они позволяют.

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

expect(obj).toHaveProperty('a.b.c', 'is that a reason or a value of the property?');

и sing jest matchers могут быть расширены сторонними пакетами, я не думаю, что это хорошая идея возиться со списком аргументов

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

expect(123, 'jest because').toEqual(123);

я не уверен, что мы хотим перегрузить API. Мы почти никогда не использовали его в наборах тестов facebook, и для особых случаев, я думаю, проще просто определить новый тест:

beforeEach(someSharedSetup);
test('reason or description', () => expect(1).toBe(1));

это всего лишь несколько строк :)

Или вы даже можете поместить его в другой вызов describe() .

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

it('stuff', () => {
  const generator = incrementAsync();

  expect(generator.next().value).toBe(
    call(delay, 1000)
  );

  expect(generator.next().value).toBe(
    put({ type: 'INCREMENT' })
  );

  expect(generator.next()).toBe(
    { done: true, value: undefined }
  );
});

Или вы даже можете поместить его в другой вызов description().

Можете ли вы уточнить это? Вложенные вызовы описания AFAIK были только для разделения заголовков разделов, тесты по-прежнему выполняются одновременно, верно?

Наборы тестов (файлы) выполняются одновременно, вызовы test() — нет.

я начинаю думать, что что-то вроде

test('111' () => {
  jest.debug('write something only if it fails');
  expect(1).toBe(2);
});

может быть вещью

Из этого обсуждения и этого репозитория я думаю, что хорошим и семантическим было бы:

it('has all the methods', () => {
  since('cookie is a method').expect(reply.cookie).toBeDefined();
  since('download is a method').expect(reply.download).toBeDefined();
  since('end is a method').expect(reply.end).toBeDefined();
  // ...
});

Использование аналогично because , но с точки зрения семантики оно имеет больше смысла.

Если вам это нравится, я мог бы разработать PR, добавив функциональность since .

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

Пожалуйста, не говорите «перепишите свои наборы тестов, чтобы они были проще». Единственное, что инженеры ненавидят больше, чем _написание_ наборов тестов, — это _переписывание_ наборов тестов.

Еще одно предложение, которое я где-то видел, забыл, где оно было в этом же номере, с объяснением, почему оно не сработает. Я, пожалуй, посплю :)

У меня работает простая демонстрация «прототипа», теперь мне нужно реализовать рекурсию. Это тонкая оболочка, использующая прокси для глобальных переменных, а затем для каждого метода. Однако прокси- серверы не поддерживаются старыми браузерами и не могут быть заполнены полифилами, поэтому для Jest это может быть неприемлемо. Это общая структура оболочки:

const since = (text) => {
  return new Proxy(global, {
    get: (orig, key) => {
      return (...args) => {
        try {
          const stack = orig[key](...args);
          return new Proxy(stack, {
            get: (orig, key) => {
              return (...args) => {
                try {
                  const ret = orig[key](...args);

                  // ... implement recursion here

                } catch (err) {
                  console.log('2', key, text, err);
                  throw err;
                }
              }
            }
          });
        } catch (err) {
          console.log('1', key, text, err);
          throw err;
        }
      };
    }
  });
};

Есть три реальных варианта:

  • Этот способ приемлем, поэтому его следует добавить в основную библиотеку Jest. Я убираю это и создаю PR.
  • Копните глубже в Jest и измените основную библиотеку. Много работы, так что ничего не будет делать, пока кто-то из участников не скажет что-нибудь полуофициально по этому вопросу/направлению.
  • Закончите это так и опубликуйте как пакет. Нежелательно, так как его нелегко обнаружить.

Изменить: посмотреть в действии:

describe('Test', () => {
  it('works', () => {
    since('It fails!').expect('a').toEqual('b');
  });
});

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

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

Когда я что-то сломал и оно не работает, с помощью Jest мне приходится отлаживать это вручную или добавлять логирование или любые другие _модификации._ Что гораздо менее удобно, чем просто смотреть на результаты тестового прогона.
Например, в Jasmine у ​​нас есть возможность напечатать некоторый контекст, чтобы лучше понимать неудачу.
В самой популярной тестовой среде Java JUnit у нас есть точно такая же функция.

Извините, если я ошибаюсь, но я не вижу здесь никаких _технологических_ контраргументов против этой функции. А такие вещи, как «это не следует добавлять, потому что это не будет хорошо выглядеть», просто смешны.

Можем ли мы снова открыться? Даже jest.debug() , предложенный @aaronabramov выше, был бы мне полезен.

This:

it('has all the methods', () => {
    since('cookie is a method', () => expect(reply.cookie).toBeDefined());
});

can be supported by adding this:


// setupTestFrameworkScriptFile.js
// http://facebook.github.io/jest/docs/configuration.html#setuptestframeworkscriptfile-string
global.since = (explanation, fn) => {
    try {
        fn();
    } catch(e) {
        e.message = explanation + '\n' + e.message;
        throw e;
    }
};

Кроме того, jasmine-custom-message выглядит аналогично тому, что запрашивается:

describe('test', function() {
  it('should be ok', function() {
    since(function() {
      return {'tiger': 'kitty'};
    }).
    expect(3).toEqual(4); // => '{"tiger":"kitty"}'
  });
});

Есть ли планы возобновить это? Похоже, что недавно были дубликаты этой проблемы. Я также хочу отображать пользовательское сообщение при сбое теста.

Хм.. Это тоже есть в моем списке желаний. Прочитав эту ветку, я могу понять ответ, касающийся использования Jest в Facebook и не желающий влиять на ваш собственный рабочий процесс, но были некоторые предложения, которые не будут мешать существующим тестам и добавят функциональность, которую некоторые другие хотели бы добавить. есть (я в том числе).

Что потребуется для того, чтобы 2-й аргумент для expect() или формат as() был принят в качестве PR? Я готов пожертвовать некоторое время, чтобы помочь с этим.

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

import assert from 'assert'
import chalk from 'chalk'

test('api works', () => {
  assert.deepEqual(
    api(),
    [],
    chalk.red('api without magic provides no items')
  )
  assert.deepEqual(
    api(0),
    [],
    chalk.red('api with zero magic also provides no items')
  )
  assert.deepEqual(
    api(true),
    [1, 2, 3],
    chalk.red('api with magic enabled provides all items')
  )
})

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

Мне все равно, как это реализовано честно. Но вот альтернатива since просто для того, чтобы добавить что-то еще на случай, если другие предпочтут это:

const expectWithMessage = expect.withMessage(
  'api with magic enabled provides all items'
)
expectWithMessage(api(true)).toEqual([1, 2, 3])

// could be rewritten like
expect
  .withMessage('api with magic enabled provides all items')(api(true))
  .toEqual([1, 2, 3])

Я не уверен, что мне это нравится больше, чем since . У меня все хорошо, я просто очень хотел бы иметь это :)

О, и адрес комментария:

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

Я согласен, что мы не хотим усложнять написание тестов. Вот почему это будет дополнительным изменением. Таким образом, люди, которые не хотят «тратить время» на упрощение отладки своих тестов, могут просто пропустить полезные сообщения, но тогда они попадут в кодовую базу с такими полезными сообщениями, и тогда они будут благодарны инженер, который нашел время, чтобы немного объяснить утверждение :wink:

Привет , @cpojer, есть новости по этому поводу?

Jest отлично работает для меня, за исключением этой проблемы... В данный момент я изо всех сил пытаюсь исправить ошибочное утверждение в цикле for, например
expectationsArray.forEach(expectation => expect(...))

Трудно точно определить, какие ожидания терпят неудачу без пользовательского сообщения об ошибке (если я не делаю это неправильно..?)

Спасибо

@mj-airwallex, вы можете обернуть ожидания test в цикле for, например:

const expectationsArray = [[0, 'a'], [1, 'b']];

expectationsArray.forEach(([expectation, desc]) => {
  test(`test ${desc}`, () => {
    expect(expectation).toBeGreaterThanOrEqual(2);
  });
});

У меня также есть проблема с шуткой из-за необходимости предоставлять пользовательские сообщения во время ожидания. Обертывание ожиданий под test похоже работает в случаях, когда нет необходимости в асинхронных вызовах. Но так как Jest не может обработать describe (#2235 ), возвращающее промис, мы не можем создавать тесты с асинхронными вызовами вместе с обёрткой их test . И мы не можем иметь несколько вложенных test .

Вот пример, иллюстрирующий проблему:

async function getArray() {
  return [0,0,0,0,0,0]
}

describe('Custom messages with async', async () => {
  const array = await getArray();
  array.forEach((item) => {
    test(`test${item}`, () => {
      expect(item).toBe(0)
    });
  });
})

Любые идеи, как справиться с этим?

Глядя на проблему в OP («Было бы неплохо, если бы был какой-то удобочитаемый контекст, который сразу бы дал понять, какое ожидание не оправдалось»), я думаю, что теперь она решена. Начиная с Jest 22, мы печатаем контекст ошибочного утверждения. Дополнительное сообщение все еще необходимо? Если это _is_, это может быть комментарий к коду выше или сбоку от утверждения.

image

Асинхронное описание - еще одна проблема (которой не поможет добавленный кодовый кадр)

Я думаю, что не стал бы использовать асинхронное описание, а вместо этого использовал бы beforeEach или beforeAll

@kentcdodds , не могли бы вы привести пример, как справиться с этим с помощью beforeEach или beforeAll ? Если вы попытаетесь построить все необходимые асинхронные вызовы, результаты будут beforeEach и beforeAll , это в конце концов заставит вас создать вложенные test , что недопустимо.

Ах, я пропустил, что вы делали с этим асинхронным вызовом. Извините за это 😅 Да, вы не могли сделать beforeEach или beforeAll , чтобы сделать это.

@SimenB , печать контекста уже очень помогает и решает большинство проблем с пользовательскими сообщениями. Спасибо за это! Но было бы неплохо иметь возможность для пользовательских сообщений явно в качестве аргумента, поскольку это помогает в таких ситуациях, как использование ожиданий внутри циклов.

Глядя на проблему в OP («Было бы неплохо, если бы был какой-то удобочитаемый контекст, который сразу бы дал понять, какое ожидание не оправдалось»), я думаю, что теперь она решена.

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

Только начал использовать Jest, и мне не хватает такой функции. Мой вариант использования:
Тест не пройден, если я утверждаю, что свойство объекта истинно. Это помогло бы мне быстрее понять ошибку, если бы я мог регистрировать объект в случае сбоя утверждения.

Вы можете использовать toHaveProperty для этого.

test('property', () => {
  expect({foo: 'bar'}).toHaveProperty('baz', 'foobar');
});

image

Если вы просто хотите проверить, есть ли он, отбросьте второй аргумент. Если вы просто хотите подтвердить, что оно имеет _некоторое_ значение, вы можете использовать expect.anything() .
toMatchObject — еще одна альтернатива.

Вы также можете использовать assert , если хотите.

test('property', () => {
  const obj = {foo: 'bar'};
  assert.equal(obj.baz, 'foobar', JSON.stringify(obj));
});

image

Спасибо за совет. assert.equal(obj.baz, 'foobar', JSON.stringify(obj)); подойдет в моем конкретном случае.

@SimenB @mpseidel что такое утверждение? это какая-то сторонняя библиотека? Я ничего не могу найти в документах шутки.

@sharikovvladislav assert — модуль ядра узла https://nodejs.org/api/assert.html

@mpseidel ой! Я не знал. Спасибо. Оно работает.

Я использую следующий фрагмент кода, чтобы обойти это ограничение фреймворка (в TypeScript, но просто удалите аннотации типа для JS)
export const explain = (expectation: () => void, explanation: string) => { try { expectation(); } catch(e) { console.log(explanation) throw e; } }

Привет,
Я удивлен, что никто еще не упомянул циклы. Сообщение будет не просто строкой, а динамической строкой, зависящей от итерации цикла.
jest-plugin-context это хорошо, спасибо за это работает, но это немного тяжело, и первоначальная проблема все еще актуальна imo.
Посмотрите на этот тест

describe('MyStuff', () => {
    it('should render and contain relevant inputs', () => {
      const wrapper = shallowWrapped(<MyStuff />);
      const expectedKeys = ['love','jest','but','need','details','for','expect'];
      expectedKeys.forEach((key) => {
        expect(wrapper.find({ id: key }).length).toEqual(1);
      });
    });
  });

Удачи в поиске виновника. Прямо сейчас я должен добавить строку или протестировать объект вместо {len:.., key:..} , это не чисто и не удобно для пользователя.
Я думаю, что этот вариант использования актуален, например, для форм и проверки рендеринга элементов.
Синтаксис может быть таким простым, как toEqual(1).context("my message") или toEqual(1, "my message") (хотя, конечно, я знаю, что реализация всегда сложнее, и я уважаю вашу прекрасную работу с Jest).

Возможно, используйте тот же формат, что и chai , т.е. добавьте сообщение в качестве второго аргумента к ожидаемому вызову:

expect(foo, 'this detail').toEqual(2)

Т

Раньше использовал жасмин, поэтому пришел сюда и обнаружил, что он не поддерживается.
Однако все эти вещи являются функциями. Можем ли мы просто не сделать что-то вроде:

describe('MyStuff', () => {
    describe('should render and contain relevant inputs', () => {
      const wrapper = shallowWrapped(<MyStuff />);
      const expectedKeys = ['love','jest','but','need','details','for','expect'];

      expectedKeys.forEach((key) => {
        it(`contains key "${key}"`, () =>
          expect(wrapper.find({ id: key }).length).toEqual(1)
        )
      })
  });
});

2018-04-18-222246_646x390_scrot

@akkerman Хорошее решение. Так как description и it — это волшебные глобальные переменные, предоставленные jest, я должен признать, что они могут казаться неясными, я не был уверен, что запись «t» в цикле сработает.

Как насчет присоединения другого модификатора?

expect(foo).toEqual(bar).because('reason with %s placeholders')

Или, может быть, функция

expect(foo).toEqual(bar).explainedBy((result) => `Lorem ipsum ${result}`)

Я думаю, что другой модификатор быстро становится нечитаемым.
Т

19.04.2018 13:47 GMT+02:00 λ • Джовани де Соуза, уведомления@github.com :

Как насчет присоединения другого модификатора?

expect(foo).toEqual(bar).because('причина с %s заполнителями')

Или, может быть, функция

ожидаем(foo).toEqual(bar).explainedBy((результат) => Lorem ipsum ${result} )


Вы получаете это, потому что вы прокомментировали.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/facebook/jest/issues/1965#issuecomment-382705387 или отключить звук
нить
https://github.com/notifications/unsubscribe-auth/AAM5PwBCvET1KdEDeDEF7gGo708Naj8oks5tqHlSgaJpZM4Kc6Uu
.

--


Тарьей Хьюз
Мобильный: 920 63 413

Способ работы expect заключается в броске, так что это все равно не сработает.

Я думаю, что лучшими альтернативами являются expect(something, 'some helpful text on failure').toEqual(somethingElse) или expect.context(something, 'some helpful text on).toEqual(somethingElse) , но мне не очень нравится ни один из них.

Можно ли это снова открыть? Кажется, у Jest до сих пор нет хорошего решения для проверки того, как изменяется состояние при множественных взаимодействиях, например:

  • тестирование того, как контейнер React с отслеживанием состояния изменяется в ответ на серию событий
  • тестирование того, как веб-страница меняется в течение нескольких взаимодействий с помощью Puppeteer

Оба этих случая требуют выполнения ряда действий и подтверждения того, как состояние изменилось (или не изменилось) после каждого действия, поэтому иногда необходимы тесты с несколькими утверждениями. Не всегда возможно решить такую ​​проблему с помощью beforeEach .

Я продолжаю находить ситуации, когда это было бы действительно полезно. В частности, когда я запускаю несколько взаимодействий и утверждений, как объясняет @callumlocke .

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

Вот краткое изложение предлагаемых решений:

expect(api()).toEqual([]) // api without magic provides no items
it('api without magic provides no items', () => expect(api()).toEqual([]))
test('api without magic provides no items', () => expect(api()).toEqual([]))
expect(api()).toHaveNoItems()

expect(api(), 'api without magic provides no items').toEqual([])
expect(api()).because('api without magic provides no items').toEqual([])
since('api without magic provides no items').expect(api()).toEqual([]))
because('api without magic provides no items').expect(api()).toEqual([]))
jest.debug('api without magic provides no items'); expect(api()).toEqual([]))

Обратите внимание, что завершающий .because() невозможен, поэтому не включен в качестве опции.

Сегодня поддерживаются все четыре варианта из первой группы. Лично я считаю, что первый вариант (фрейм кода с комментарием) отлично работает. А еще лучше использовать пользовательский сопоставитель (вариант 4).

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

Привет,
В основном у вас есть несколько вариантов использования:

  • Множественные тесты утверждений (вам нужно утверждать несколько вещей в тесте)
  • Динамически генерируемый контекст утверждения (вы хотите, чтобы переменная в вашем сообщении об ошибке делала его более понятным, например, для печати определенного поля вашего объекта с ошибкой, потому что вы получили много тестов)
  • Динамически генерируемые утверждения (вы создаете цикл, который генерирует утверждения)

Варианты первой группы в основном предназначены для первого варианта использования. Если вы столкнулись с необходимостью динамически генерируемого утверждения, как предлагается, вы можете вложить вызовы в it и test , чтобы тест мог генерировать новые тесты в цикле. Проблема в том, что вы генерируете тесты, а не утверждения . Представьте, что я хочу что-то утверждать для каждого элемента массива из 1000 элементов, это раздует сводки тестов.

Поскольку эти динамические варианты использования все еще редки, мы должны придерживаться решения, которое требует минимальной работы со стороны сопровождающих. Лично мне нравится решение because/since , потому что оно звучит довольно просто. Я предполагаю, что реализация будет в основном обертывать expect в try/catch , который печатает сообщение и возвращает его?
jest.debug звучит странно, для меня отладка печатает сообщение, даже если тесты действительно проходят
Вариант «последний аргумент» также хорош, но я не уверен, что он выполним, поскольку expect может принимать переменное количество аргументов?

Меня устраивает любой вариант. Я просто хочу эту функцию. Я тоже не очень хорошо разбираюсь в API jest.debug , но если это единственное, что имеет смысл, меня это устраивает, потому что я просто хочу эту функцию.

@kentcdodds как насчет четырех существующих вариантов?

@eric-burel вы видели test.each и describe.each , добавленные в Jest 23 (доступны как отдельные для Jest <23)?

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

  1. expect(api(), 'api without magic provides no items').toEqual([])
  2. because('api without magic provides no items').expect(api()).toEqual([]))
  3. since('api without magic provides no items').expect(api()).toEqual([]))
  4. expect(api()).because('api without magic provides no items').toEqual([])
  5. jest.debug('api without magic provides no items'); expect(api()).toEqual([]))

(test|describe).each отлично, но не решает проблему, когда вы хотите иметь несколько действий/утверждений в одном тесте.

Эта функция существует сегодня с четырьмя вариантами:

expect(api()).toEqual([]) // api without magic provides no items
it('api without magic provides no items', () => expect(api()).toEqual([]))
test('api without magic provides no items', () => expect(api()).toEqual([]))
expect(api()).toHaveNoItems()

Что с ними не так? Предлагаемые _новые_ решения кажутся лишь незначительно лучшими, чем существующие решения. Какие преимущества они приносят по сравнению с тем, что есть у нас, что оправдывает затраты на обслуживание?

@rickhanlonii Хорошо , я не знал о test.each , это действительно отличная функция, спасибо, что указали на это. Я думаю, что это решает проблему для моего третьего варианта использования, динамически сгенерированного теста из массива.

Таким образом, он оставил второе, что я перечислил: иметь динамически генерируемое сообщение об ошибке, что ускорило бы отладку. У меня сейчас не так много вариантов использования, возможно, когда вы проверяете значение поля объекта, вы хотели бы напечатать весь объект в случае сбоя. Это законно, IMO, как и все, что упрощает написание теста, даже если оно незначительно или немного избыточно. В конце концов, мы оба можем написать it и test , если нет разницы, о которой я не знаю, это в основном для удобства.
Это незначительно, но на самом деле то, что пользователи ожидают (без каламбура), как показывает эта ветка.

Изменить: создание теста в другом тесте с it или test и динамически сгенерированное имя для теста является допустимым решением, но мне действительно не нравится создавать тест, когда я имею в виду создание утверждения . Я бы никогда не догадался, что это возможно, если бы решение не было дано в этой теме.

Это безумие. Просто добавьте второй необязательный параметр в expect(). Те из нас, кто хочет использовать его, будут (выборочно), а те, кто не хочет, не будут.

Mocha делал это всегда... это одна из причин, по которой я отказался от Jasmine несколько лет назад (другая — гораздо лучшее издевательство над таймером). другие производные жасмина.

Распечатка сообщения об ошибке является соглашением во многих других средах тестирования, и я был удивлен, не увидев его в Jest. Я нашел много полезных примеров в этой ветке (спасибо за них), но добавление явного способа печати пользовательской ошибки при сбое теста было бы хорошим дополнением к удобству использования Jest. Это облегчило бы разработчикам, которые привыкли к другим средам тестирования (в том числе не-JS), переходить на Jest.

@mattphillips , как вы думаете, можно ли здесь сделать что-то похожее на цепочку шуток, чтобы решение существовало в пространстве пользователя? Например, второй аргумент для expect

Честно говоря, это что-то очень стандартное в большинстве фреймворков для тестирования JS. Очень разочарован тем, что не нашел его в Jest, поскольку мы пишем все наши тесты с собственным сообщением об ошибке.

@SimenB извините, я заметил ваше сообщение только сегодня утром!

Да, это выполнимо в пользовательской среде, я только что сделал это и выпустил как jest-expect-message https://github.com/mattphillips/jest-expect-message

Приветствуется обратная связь :smile:

Круто, спасибо, что сделали!

@cpojer

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

Две вещи:

  1. Добавление необязательного второго аргумента к expect() вряд ли усложнит задачу разработчикам.
  2. Последнее, что хотят делать разработчики, — это тратить время на отладку того, что привело к сбою теста. Часто ожидает vs получено — это хороший способ проверить, было ли выполнено условие, но часто недостаточно контекста, чтобы понять, что вызвало его сбой.

Я использовал Mocha / Chai, а также ленту, прежде чем прийти в Jest, и это действительно нарушило условия сделки. Что нам нужно сделать, чтобы получить поддержку пользовательского сообщения в ожидании?

Сказать нам, что нужно expect.extend для создания пользовательского сопоставителя, звучит точно так же, как то, чего вы пытались избежать в своем первом аргументе: «инженеры не хотят тратить время на написание тестов».

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

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

it("Should contain at least one word of every wordType", () => {
  for (const wordType of wordTypes) {
    expect(words.find((word) => word.wordType === wordType)).toBeTruthy();
  }
});

Если это не удается, я не знаю, какое значение wordType не удалось.

Мой обходной путь заключался в том, чтобы заменить это сообщением, содержащим результат теста, и ожидать, что сообщение содержит ожидаемый результат теста (т.е. истину). Если это не удается, Jest печатает сообщение, содержащее дополнительную информацию.

    expect(`${wordType} ${!!words.find((word) => word.wordType === wordType)}`).toEqual(`${wordType} ${true}`);

Jest печатает это...

expect(received).toEqual(expected)

Difference:

- Expected
+ Received

- 6 true
+ 6 false

... что говорит мне, что wordType был 6, когда это не удалось.

Или более читабельно что-то вроде...

    if (!words.find((word) => word.wordType === wordType)) {
      expect(`Didn't find a word with wordType '${wordType}'`).toEqual(null);
    }

@cwellsx проверить параметризованные тесты с test.each , таким образом, каждый тип слова будет независимым тестом с описательным заголовком (на основе значения)

Это было бы невероятно полезно в таких тестах:

test("compare ArrayBufferCursors", () => {
    const orig: ArrayBufferCursor;
    const test: ArrayBufferCursor;

    expect(test.size).toBe(orig.size);

    while (orig.bytes_left) {
        expect(test.u8()).toBe(orig.u8());
    }
});

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

@rickhanlonii , я понимаю, что вы хотите сократить расходы на обслуживание шутки, но варианты, которые вы предлагаете в своем комментарии , увеличивают обслуживание модульных тестов всех других проектов. Если я хочу объяснить утверждение, используя toEqual , что в противном случае вполне достаточно, вы действительно предлагаете мне ввести собственный сопоставитель?!

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

Это обсуждение и появление jest-expect-message напоминает мне о проблеме с beforeAll в Jasmine...

Возможно, я неправильно понимаю запрос ОП здесь, но проблема, которую я пытался решить и привела меня в эту тему, была решена с помощью простого try / catch и оболочки вокруг jest.expect . Все, что я хотел сделать, это зарегистрировать все объекты, которые я ожидал, по сравнению с теми, которые я получил, + некоторые основные журналы, объясняющие значение. Конечно, это может быть расширено, чтобы делать все, что вы хотите.

Наиболее общая версия этого может выглядеть так:

in some test utility file...

const myExpect = (expectFn, errorCallback) => {
  try {
    expectFn();
  } catch (err) {
    errorCallback();
    throw new Error(err);
  }
};

// in the actual test suite...

const context = { hello: "world", results, expected };
myExpect(
    () => expect(results).toEqual(expected),
    () => console.error("[Error] -- context:", JSON.stringify(context))
);

+1
Привет, мне бы тоже очень понравилась эта функция. Это сделало бы мою жизнь намного проще.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги