Jest: 期望的人类可读上下文

创建于 2016-10-21  ·  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);

但它与前一个选项几乎相同。

我认为这不是很有用的原因是因为工程师不想浪费时间编写测试。 我们所做的任何让他们更难的事情只会导致更糟糕的测试。

过去,最好的解决方案实际上是创建自定义匹配器。 我们将在 Jest 的下一个版本中引入expect.extend ,它将允许您轻松创建匹配器,例如:

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 vs 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 vs 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的第二个参数的替代建议。

感谢您继续进行对话。 请注意,您也不需要 describe 块,进一步使事情变得更小。 不幸的是, .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烦人的情况是当您建立状态时,例如在状态机中用于 sagas、epics 等。每个测试都需要先前的状态更改,隔离它们需要大量重复而没有任何增益 AFAIK。

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 }
  );
});

或者您甚至可以将其放入另一个 describe() 调用中。

你能详细说明一下吗? 嵌套描述调用 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功能。

请实施一个简单的方法来做到这一点。 我不经常使用它,但特别是对于更复杂的测试,无需去挖掘就可以准确地知道什么是失败的。

请不要说“重写你的测试套件更简单”。 工程师唯一比_writing_测试套件更讨厌的是_rewriting_测试套件。

我在某个地方看到的另一个建议,我忘记了它在同一个问题中的位置,并解释了为什么它不起作用。 我可能应该睡一觉了:)

我得到了一些简单的“原型”演示,我现在需要实现递归。 它是一个瘦包装器,在全局变量周围使用代理,然后在每个方法上使用。 但是,较旧的浏览器不支持代理,并且不能进行 polyfill,因此它可能不被 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 手动调试它或添加日志记录或任​​何 _modifications._ 这比仅查看测试运行结果要方便得多。
例如,在 Jasmine 中,我们能够打印一些上下文以更了解失败。
在 Java 最流行的测试框架 JUnit 中,我们也有完全相同的特性。

抱歉,如果我弄错了,但我在这里没有看到任何对此功能的 _technological_ 反驳论点。 诸如“不应该添加,因为它看起来不好”之类的东西简直是荒谬的。

我们可以重新开放吗? 即使是上面@aaronabramov建议的jest.debug()也会对我有所帮助。

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"}'
  });
});

有没有计划重新开放这个? 好像最近有重复这个问题。 我还希望在测试失败时显示自定义消息。

嗯..这也是我的愿望清单上的东西。 阅读此主题后,我可以理解有关在 Facebook 使用 Jest 的响应,并且不想影响您自己的工作流程,但有一些建议不会干扰现有测试,并且会添加其他几个人想要的功能有(包括我自己)。

第二个参数 expect() 或 since() 格式被接受为 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 您最好在 for 循环中用test包装期望,例如:

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

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

由于需要在预期期间提供自定义消息,我也有一个玩笑的问题。 将期望包装在test下似乎适用于不需要异步调用的情况。 但是由于 Jest 无法处理describe (#2235 ) 返回一个 promise,我们不能使用异步调用创建测试以及用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

异步描述是另一个问题(添加的代码框无济于事)

我想我不会使用异步描述,而是使用beforeEachbeforeAll

@kentcdodds您能否提供一个示例,如何使用beforeEachbeforeAll处理此问题? 如果您尝试在beforeEachbeforeAll中构建所有必要的异步调用结果,最终将强制您创建不允许的嵌套test

啊,我错过了你对那个异步调用所做的事情。 对此感到抱歉😅 是的,你不能这样做beforeEachbeforeAll来做到这一点。

@SimenB ,打印上下文已经有很大帮助,并解决了自定义消息的大部分问题。 谢谢你! 但是最好有可能将自定义消息明确地作为参数,因为它有助于在循环中使用期望等情况。

@tojuhaka试试这个: https ://github.com/negativetwelve/jest-plugins/tree/master/packages/jest-plugin-context

看看 OP 中的问题(“如果有一些人类可读的上下文可以立即清楚地表明哪个期望失败了”),我认为它现在已经解决了。

是的,这确实解决了原始问题,只要您在编译之前可以访问原始源,大多数 Jest 用户都将拥有。 与允许用户仅打印用户提供的失败消息相比,IMO 有点笨拙,但我想这已经足够了。

刚开始使用 Jest,我缺少这样的功能。 我的用例:
测试失败,我断言对象的属性是真实的。 如果我可以在断言失败的情况下记录对象,它将帮助我更快地理解失败。

您可以为此使用toHaveProperty

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

image

如果您只想检查它是否存在,请删除第二个参数。 如果您只想断言它具有 _some_ 值,则可以使用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)

之前用过jasmine,所以来这里发现不支持。
然而afik这些东西都是功能。 我们不能只做类似的事情:

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不错的解决方案。 由于 describe 和它是 jest 提供的魔法全局变量,我必须承认它们可能会让人感到晦涩难懂,我不确定在循环中编写 ìt` 是否可行。

链接另一个修饰符怎么样?

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

或者也许是一个函数

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

我认为另一个修饰符很快变得不可读。

2018-04-19 13:47 GMT+02:00 λ • Geovani de Souza [email protected]

链接另一个修饰符怎么样?

expect(foo).toEqual(bar).because('使用 %s 占位符的原因')

或者也许是一个功能

expect(foo).toEqual(bar).explainedBy((result) => 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)。

我认为我们需要了解的是:第二组中的选项比第一组中的选项更具吸引力吗? 第二组添加了什么可以证明我们提供的所有匹配器的核心维护是合理的(跨异步匹配器、非对称匹配器、间谍匹配器、抛出匹配器、承诺匹配器和自定义匹配器)?

你好,
你基本上有几个用例:

  • 多个断言测试(您需要在测试中断言多个事物)
  • 动态生成的断言上下文(您希望失败消息中的变量使其更清晰,例如用于打印失败对象的特定字段,因为您有很多测试)
  • 动态生成的断言(您创建一个生成断言的循环)

第一组选项主要用于第一个用例。 如果您遇到需要动态生成的断言,如建议的那样,您可以嵌套调用ittest ,以便测试可以在循环中生成新的测试。 问题是您生成测试而不是断言。 想象一下,我想在 1000 个元素数组的每个元素上声明一些东西,这会使测试摘要膨胀。

但是,由于这些动态用例仍然很少见,因此我们应该坚持使用维护人员需要最少工作的解决方案。 我个人喜欢because/since解决方案,因为它听起来很简单。 我猜这个实现主要是将expect包装在try/catch中,打印消息并返回它?
jest.debug听起来很奇怪,对我来说调试是打印一条消息,即使测试实际上通过了
“最后一个参数”选项也不错,但我不确定它是否可行,因为expect可以接受可变数量的参数?

我对任何选择都很好。 我只想要这个功能。 我对jest.debug API 也不是很感兴趣,但如果它是唯一有意义的 API,我可以接受,因为我只想要这个功能。

@kentcdodds现有的四个选项呢?

@eric-burel 您是否看到在 Jest 23 中添加了test.eachdescribe.each (可作为 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()

这些有什么问题? 提议的_new_ 解决方案似乎只比这些现有的解决方案稍微好一点。 与我们所拥有的东西相比,它们带来了哪些好处来证明维护成本是合理的?

@rickhanlonii很好,我不知道test.each ,这确实是一个很棒的功能,感谢您指出这一点。 我认为它解决了我的第三个用例的问题,即从数组动态生成的测试。

所以它留下了我列出的第二个:动态生成失败消息,这将使调试更快。 我现在没有太多用例,也许当您测试对象字段值时,您想在失败时打印整个对象。 这是合法的 imo,因为任何使编写测试更容易的东西,即使边缘或有点多余。 毕竟我们都可以写ittest ,除非有区别我不知道这主要是为了安慰。
正如此线程所示,它是微不足道的,但确实是用户所期望的(没有双关语)。

编辑:在另一个测试中使用ittest和动态生成的测试名称创建一个测试是一个有效的解决方案,但是当我的意思是创建一个断言时,我真的不喜欢创建一个测试. 如果没有在这个线程中给出解决方案,我永远不会猜到这是可能的。

这太疯狂了。 只需向 expect() 添加第二个可选参数。 我们这些想要使用它的人会(有选择地)使用它,而那些不想使用它的人则不会。

Mocha 一直在这样做......这是我多年前放弃 Jasmine 的原因之一(另一个是更好的计时器嘲笑。)如果我不必加入 React 潮流,我就不会使用 Jest 或任何其他茉莉花衍生物。

打印错误消息是许多其他测试框架的惯例,我很惊讶在 Jest 中没有看到它。 我在这个线程中找到了很多有用的例子(谢谢你),但是添加一个显式的方法来打印测试失败的自定义错误将是对 Jest 可用性的一个很好的补充。 这将使习惯于其他测试框架(包括非 JS 框架)的开发人员更容易使用 Jest。

@mattphillips您是否认为可以在这里做类似于 jest-chain 的事情来允许在用户空间中存在解决方案? 例如expect的第二个参数

老实说,这在大多数 JS 测试框架中是非常标准的。 很失望没有在 Jest 中找到它,因为我们使用自定义错误消息编写所有测试。

@SimenB对不起,我今天早上才注意到你的消息!

是的,这在用户空间中是可行的,我刚刚将它敲起来并发布为jest-expect-message https://github.com/mattphillips/jest-expect-message

欢迎反馈 :smile:

太棒了,谢谢你这样做!

@cpojer

我认为这不是很有用的原因是因为工程师不想浪费时间编写测试。 我们所做的任何让他们更难的事情只会导致更糟糕的测试。

两件事情:

  1. 向 expect() 添加可选的第二个参数几乎不会让开发人员更难。
  2. 开发人员最不想做的事情就是浪费时间调试导致测试失败的原因。 通常期望与接收是检查条件是否满足的好方法,但通常没有足够的上下文来说明导致它失败的原因。

在来 Jest 之前,我使用了 Mocha/Chai 以及磁带,这确实是一个交易破坏者。 我们必须做些什么才能让自定义消息支持成为期望?

告诉我们 expect.extend 以创建自定义匹配器听起来与您在第一个参数中试图避免的完全一样:“工程师不想浪费时间编写测试。”

我发现打开单元测试并查看行号很容易,因此 OP 中的用例不会打扰我。

困扰我的用例是当我在测试中有一个循环时,例如测试枚举的每个值,例如像这样:

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

如果失败了,那么我不知道哪个 wordType 值失败了。

我的解决方法是将其替换为包含测试结果的消息,并期望该消息包含预期的测试结果(即 true)。 如果失败,则 Jest 打印包含附加信息的消息。

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

开玩笑打印这个...

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检查参数化测试,这样每个 wordType 将是一个带有描述性标题的独立测试(基于值)

这在这样的测试中非常有用:

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 ,我知道您想降低 jest 的维护成本,但是您在评论中提供的选项会增加所有其他项目的单元测试的维护。 如果我想使用toEqual来解释一个断言,否则这已经足够完美了,你真的建议我引入一个自定义匹配器吗?!

虽然有重复测试的情况,其中it.each很有用,但必须为断言的简单注释添加不必要的代码会使这个测试框架更难使用。

关于jest-expect-message的讨论和出现让我想起了 Jasmine 中的beforeAll问题......

我可能在这里误解了 OP 的请求,但我试图解决并带我到这个问题线程的问题只是使用简单的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 等级