61

问题

我有几个测试在 mocha 中做同样的事情。对我来说,这是重复,当您希望系统可维护时,这是最糟糕的事情。

var exerciseIsPetitionActive = function (expected, dateNow) {
    var actual = sut.isPetitionActive(dateNow);
    chai.assert.equal(expected, actual);
};

test('test_isPetitionActive_calledWithDateUnderNumSeconds_returnTrue', function () {
    exerciseIsPetitionActive(true, new Date('2013-05-21 13:11:34'));
});

test('test_isPetitionActive_calledWithDateGreaterThanNumSeconds_returnFalse', function () {
    exerciseIsPetitionActive(false, new Date('2013-05-21 13:12:35'));
});

我需要什么

我需要一种将重复的 mocha 测试折叠成一个的方法。

例如,在 PhpUnit(和其他测试框架)中,您有dataProviders
在 phpUnit 中,dataProvider 以这种方式工作:

<?php class DataTest extends PHPUnit_Framework_TestCase {
    /**
     * @dataProvider provider
     */
    public function testAdd($a, $b, $c)
    {
        $this->assertEquals($c, $a + $b);
    }

    public function provider()
    {
        return array(
          array(0, 0, 0),
          array(0, 1, 1),
          array(1, 0, 1),
          array(1, 1, 3)
        );
    }
}

这里的provider给测试注入参数,测试执行所有的case。非常适合重复测试。

我想知道在 mocha 中是否有类似的东西,例如,像这样的东西:

var exerciseIsPetitionActive = function (expected, dateNow) {
    var actual = sut.isPetitionActive(dateNow);
    chai.assert.equal(expected, actual);
};

@usesDataProvider myDataProvider
test('test_isPetitionActive_calledWithParams_returnCorrectAnswer', function (expected, date) {
    exerciseIsPetitionActive(expected, date);
});

var myDataProvider = function() {
  return {
      {true, new Date(..)},
      {false, new Date(...)}
  };
};

我已经看过的

有一些技术称为Shared Behaviors。但它并没有直接用测试套件解决问题,它只是解决了具有重复测试的不同组件的问题。

问题

你知道在 mocha 中实现 dataProviders 的任何方法吗?

4

6 回答 6

36

使用不同数据运行相同测试的基本方法是在提供数据的循环中重复测试:

describe('my tests', function () {
  var runs = [
    {it: 'options1', options: {...}},
    {it: 'options2', options: {...}},
  ];

  before(function () {
    ...
  });

  runs.forEach(function (run) {
    it('does sth with ' + run.it, function () {
      ...
    });
  });
});

before在 a 中的所有its之前运行describe。如果您需要使用 中的某些选项before请不要将其包含在forEach循环中,因为 mocha 将首先运行 all befores 和 all its,这可能是不需要的。您可以将整体describe放入循环中:

var runs = [
  {it: 'options1', options: {...}},
  {it: 'options2', options: {...}},
];

runs.forEach(function (run) {
  describe('my tests with ' + run.it, function () {
    before(function () {
      ...
    });

    it('does sth with ' + run.it, function () {
      ...
    });
  });
});

如果你不想用多个describes 污染你的测试,你可以使用有争议的模块sinon来解决这个问题:

var sinon = require('sinon');

describe('my tests', function () {
  var runs = [
    {it: 'options1', options: {...}},
    {it: 'options2', options: {...}},
  ];

  // use a stub to return the proper configuration in `beforeEach`
  // otherwise `before` is called all times before all `it` calls
  var stub = sinon.stub();
  runs.forEach(function (run, idx) {
    stub.onCall(idx).returns(run);
  });

  beforeEach(function () {
    var run = stub();
    // do something with the particular `run.options`
  });

  runs.forEach(function (run, idx) {
    it('does sth with ' + run.it, function () {
      sinon.assert.callCount(stub, idx + 1);
      ...
    });
  });
});

诗乃感觉肮脏但有效。一些辅助模块(例如 leche)是基于 sinon 的,但可以说没有必要引入进一步的复杂性。

于 2016-09-02T07:23:57.997 回答
34

Mocha 没有为此提供工具,但自己很容易做到。您只需要在循环中运行测试并使用闭包将数据提供给测试函数:

suite("my test suite", function () {
    var data = ["foo", "bar", "buzz"];
    var testWithData = function (dataItem) {
        return function () {
            console.log(dataItem);
            //Here do your test.
        };
    };

    data.forEach(function (dataItem) {
        test("data_provider test", testWithData(dataItem));
    });
});
于 2013-06-26T09:26:18.150 回答
5

Leche将该功能添加到 Mocha。请参阅公告文档

这比简单地循环测试要好,因为如果测试失败,它会告诉您涉及哪个数据集。

更新:

我不喜欢 Leche 的设置,也没有设法让它与 Karma 一起使用,所以最终我将数据提供程序提取到一个单独的文件中。

如果你想使用它,只需获取源代码Leche 自述文件中提供了文档,您可以在文件本身中找到其他信息和使用提示。

于 2014-12-02T13:19:28.053 回答
2

根据@Kaizo 的回答,这是我为我的测试提出的(它是一个从请求中获取一些参数的控制器)来模拟 PHPUnit 中的数据提供者。该getParameters方法将接收来自 Express 的请求,然后用于req.param检查一些查询参数,例如GET /jobs/?page=1&per_page=5. 这也显示了如何存根 Express 请求对象。

希望它也可以帮助某人。

// Core modules.
var assert = require('assert');

// Public modules.
var express = require('express');
var sinon = require('sinon');

// Local modules.
var GetJobs = require(__base + '/resources/jobs/controllers/GetJobs');

/**
 * Test suite for the `GetJobs` controller class.
 */
module.exports = {
    'GetJobs.getParameters': {
        'should parse request parameters for various cases': function () {
            // Need to stub the request `param` method; see http://expressjs.com/3x/api.html#req.param
            var stub = sinon.stub(express.request, 'param');
            var seeds = [
                // Expected, page, perPage
                [{limit: 10, skip: 0}],
                [{limit: 5, skip: 10}, 3, 5]
            ];
            var controller = new GetJobs();

            var test = function (expected, page, perPage) {
                stub.withArgs('page').returns(page);
                stub.withArgs('per_page').returns(perPage);

                assert.deepEqual(controller.getParameters(express.request), expected);
            };

            seeds.forEach(function (seed) {
                test.apply({}, seed);
            });
        }
    }
};

唯一的缺点是 Mocha 不计算实际的断言(就像 PHPUnit 一样),它只是显示为一个测试。

于 2014-06-13T00:44:54.930 回答
1

下面使用mocha-testdata库描述了一个更简单的解决方案。

问题的示例解决方案。

import * as assert from assert;
import { givenAsync } from mocha-testdata;

suite('My async test suite', function () {
  given([0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 3]).test('sum to 6', function (a, b, c) {
    assert.strictEqual(a + b + c, 6);
  });
});

如果您需要测试 node.js 应用程序中最常见的异步函数调用,请改用 givenAsync。

import * as assert from assert;
import { givenAsync } from mocha-testdata;

suite('My async test suite', function () {
  givenAsync([1, 2, 3], [3, 2, 1]).test('sum to 6', function (done, a, b, c) {
    doSomethingAsync(function () {
        assert.strictEqual(a + b + c, 6);
        done();
    });
  });
});
于 2018-01-11T21:25:50.177 回答
0

我发现mocha-testcheck是最简单的工具。它生成各种数据。它将缩小导致测试失败的输入范围。

于 2018-02-08T02:31:03.113 回答