JavaScript / 笑话:如何仅在测试失败时显示测试用例的日志?

JavaScript / Jest: How to show logs from test case only when test fails?

我正在使用 selenium 开发端到端测试套件,其中测试用例是使用 jest 测试运行器以 JavaScript 编写的。

我的问题是,selenium 通常会在某些东西不起作用时毫不客气地失败,而很少解释它失败的地方。不用说,调试这样的测试是相当困难的。

我正在寻找一种方法来记录每个测试用例,以便我知道测试失败的地方,但如果测试确实失败了,则只在测试输出中显示这些日志(为了不污染控制台输出测试有很多不必要的日志)。

所以我想做类似的事情:

describe(() => {
    it('case 1', async () => {
        // this log should only be printed on the console if this test fails
        logger.info('message from case 1');
        // ...
    });

    it('case 2', () => {
        logger.info('message from case 2');
        // ...
    });
});

因此,如果 case 1 中的测试失败而 case 2 没有失败,我会在控制台输出中看到 message from case 1(理想情况下就在该测试用例的错误之前) 不是message from case 2

开玩笑这可能吗?为此,我可以自由使用任何日志记录库。

有一些方法可以让您的 expect 调用变得棘手,让您知道哪里发生了故障。这样的东西有用吗?

const location = "case 1 failed";
const result = someFunction();  
expect({result: result, location}).toEqual({result: "hello", location});

现在,如果 someFunction() returns 不是 "hello",它会告诉你位置值,因为它会抱怨预期的结果。

这真的只有在您遇到 Jest 错误时才有用,但没有从正常的 expect 失败消息中获得足够的信息并且您需要更多详细信息。

我遇到了同样的问题,但找不到确定的解决方案。似乎 it's low on Facebook's to do list 所以这里有一个解决方法。 它使用了我发现的代码片段 here and here。 这个想法是,在每个笑话 运行 之前,您设置一个消息存储并全局覆盖控制台以将所有日志记录转移到该控制台。 每次测试后,然后检查该测试是否失败,如果失败,则打印出隐藏的消息。

package.json:

"jest": {
    ...
    "verbose": true,
    "setupFilesAfterEnv": ["<rootDir>/test/setup.js"],
    ...
  }

setup.js:

const util = require('util')

global.consoleMessages = []

// get information about the current test
jasmine.getEnv().addReporter({
    specStarted: result => jasmine.currentTest = result,
    specDone: result => jasmine.currentTest = result,
})

function squirrelAway(text, logger) {
    // use error to get stack trace
    try {
        throw new Error('stacktrace')
    } catch (err) {
        let trace = err.stack.split('\n')
        trace.shift()   // removes Error: stacktrace
        trace.shift()   // removes squirrelAway() call from the "throw" command
        trace.shift()   // removes console logger call in the console override
        consoleMessages.push({logger: logger, payload: text, stacktrace: trace.join('\n')})
    }
}

const orig = console
global.console = {...console,
    // use jest.fn() to silence, comment out to leave as it is
    log: (text => squirrelAway(text, orig.log)),
    error: (text => squirrelAway(text, orig.error)),
    warn: (text => squirrelAway(text, orig.warn)),
    info: (text => squirrelAway(text, orig.info)),
    debug: (text => squirrelAway(text, orig.debug))
}

global.afterEach(() => {
    // this includes tests that got aborted, ran into errors etc.
    let failed = (jasmine && jasmine.currentTest
                  && Array.isArray(jasmine.currentTest.failedExpectations)) ?
                 jasmine.currentTest.failedExpectations.length>0 : true
    //orig.log(`test "${jasmine.currentTest.fullName}" finished. failed? ${failed}`)
    if (failed) {
        //orig.log(`Logging for "${jasmine.currentTest.fullName}" start`)
        consoleMessages.forEach(msg => {
            if (typeof msg.payload === 'object' || typeof msg.payload === 'function') {
                msg.payload = util.inspect(msg.payload, false, null, true)
            }
            msg.logger.call(msg.logger, msg.payload + '\n' + msg.stacktrace)
        })
        //orig.log(`Logging for "${jasmine.currentTest.fullName}" end`)
    }
    consoleMessages = []
})

如果您需要进行任何其他清理,您可以在您的测试文件中 "extend" afterEach,如下所示:

some.test.js

const globalAfterEach = afterEach
afterEach((globalAfterEach) => {
    // do things here
    globalAfterEach()
    // or do things here
})

缺点:

  • 因为我们覆盖了控制台,日志记录的堆栈跟踪丢失了,当调用控制台时,jest 使用的堆栈跟踪只包含覆盖的堆栈 console.log(/debug/error/ ...) 在安装文件中调用。因此,为了获得原始堆栈跟踪,我们抛出一个错误。然后可以将其附加到正在记录的文本中。不是特别漂亮,但工作。

测试环境处理得很好。它缓冲所有控制台消息,按测试对它们进行分组,并且仅在测试失败时才显示它们。环境与设置文件或记者的好处在于,它可以有选择地应用于特定测试,让所有其他人单独使用全局控制台。

./tests/testEnvironment.js

const NodeEnvironment = require('jest-environment-node');
const colors = require('colors');

class TestEnvironment extends NodeEnvironment {
    constructor(config, context) {
        super(config, context);
    }

    async setup() {
        await super.setup();

        this.global.consoleItems = [];

        this.global.console = { ...console,
            original: console,
            log: ((message) => this.global.consoleItems.push({type: 'log', message })),
            error: ((message) => this.global.consoleItems.push({type: 'error', message })),
            warn: ((message) => this.global.consoleItems.push({type: 'warn', message })),
            info: ((message) => this.global.consoleItems.push({type: 'info', message })),
            debug: ((message) => this.global.consoleItems.push({type: 'debug', message })),
        };
    }

    async teardown() {
        this.global.console = this.global.console.original;
        await super.teardown();
    }

    async handleTestEvent(event, state) {
        if (event.name === 'test_done' && event.test.errors.length > 0) {
            let test = event.test;
            let fullTestName = event.test.name;
            while (test.parent != null && test.parent.name !== 'ROOT_DESCRIBE_BLOCK') {
                fullTestName = test.parent.name + ' › ' + fullTestName;
                test = test.parent;
            }
            this.global.console.original.log(colors.bold.red('Console messages for failed test ' + fullTestName));
            this.global.consoleItems.forEach((item) => {
                if (item.type === 'log') {
                    this.global.console.original.log('    - ' + item.message);
                } else if (item.type === 'error') {
                    this.global.console.original.error('    - ' + item.message);
                } else if (item.type === 'warn') {
                    this.global.console.original.warn('    - ' + item.message);
                } else if (item.type === 'info') {
                    this.global.console.original.info('    - ' + item.message);
                } else if (item.type === 'debug') {
                    this.global.console.original.debug('    - ' + item.message);
                }
            });
            this.global.console.original.log('\n');
        }
        if (event.name === 'test_done') {
            this.global.consoleItems = [];
        }
    }

}

module.exports = TestEnvironment;

对于每个测试套件,需要以下注释才能使用此环境:

/**
 * @jest-environment ./tests/testEnvironment
 */

这可能是不好的做法,但我注意到expect().toBe()或者如果它不相等会抛出错误,你可以先捕获它并记录它.

这是我尝试测试每条短信都有翻译语言时的示例代码。

test('All message has translate', () => {

  allAvailableText.forEach((key) => {

    const languagesOnTheKey = Object.keys(text[key]).sort()
    try {
      expect(languagesOnTheKey).toStrictEqual(languages)
    } catch (error) {
      console.log(key, languagesOnTheKey)
    }
    expect(languagesOnTheKey).toStrictEqual(languages)

  })
})

这是最快的方法,对我来说也是最容易阅读的,因为选择的答案让我很难理解。