javascript 是否有测试框架支持TypeScript中的内联测试?

ctrmrzij  于 2023-01-04  发布在  Java
关注(0)|答案(2)|浏览(107)

我发现Karma js测试的设置和编写有些麻烦,并且发现自己经常因此而忽略编写测试,所以我想知道是否存在更好的替代方案。
由于我使用 typescript ,我的梦想场景是如果我能写这样的东西:

module adder {
    export function add(a, b){
        return a + b;
    }
}
[Tests]
assert.equal(4, adder.add(2, 2));

我的测试是内联的,当当前文件发生更改时,将直接在我的编辑器中运行。由于typescript可以很容易地从最终输出中删除测试,我可以将我的测试放在与代码相同的文件中(在我看来,越接近越好)。任何测试框架都支持这一点吗?如果没有,需要什么来支持此场景?

piok6c0g

piok6c0g1#

只是一个学究式的注解-- Karma是一个测试运行器,而不是一个测试框架。然而,它可以使用Jasmine、Mocha、QUnit或自定义测试框架。
你可以使用装饰器语法来完成这种类型的行为。

@TestSuite
class AdderTests {
    @Test
    test1() {
        assert.equal(4, adder.add(2,2));
    }
    @Test
    test2() {
        assert.equal(0, adder.add(2,-2));
    }

}

尽管如此,测试代码的结构与Jasmine语法非常相似:

describe('AdderTests', () => {
    it('should add 2 and 2', () => {
        expect(adder.add(2,2)).toBe(4);
    }

});

您的理想,即:

[Test]
assert.equal( ... )

使用TypeScript装饰器,甚至泛型都是不可能的。您正在尝试将属性应用于任意代码行。这必须是一个用于启动的函数,以便正确使用JavaScript作用域规则:

[Test]
test1() { assert.equal( ... ) };
mspsb9vt

mspsb9vt2#

我认为任何声明测试为普通函数或对象的框架都可以支持内联测试。你可以像blorkfish的回答那样在被测试的代码旁边声明Jasmine语法的测试。但是我不认为框架是必要的。
值得记住的是,Type/JavaScript文件中的顶级语句是在导入时执行的(除非是仅类型导入)。因此,您可以将测试函数或对象添加到测试的全局列表中。一旦所有模块都已导入,您就可以运行测试了。
例如,假设我们有lib/test.ts

interface Suites {
  [index: string]: Tests;
};

interface Tests {
  [index: string]: () => Promise<void> | void;
};

let suites: Suites = {};

export function suite(name: string)
{
  const suite: Tests = {};

  suites[name] = suite;

  return suite;
}

export async function run(on_pass: (desc: string) => void,
                          on_fail: (desc: string, error: Error) => void)
{
  for (let suite in suites) {
    for (let test in suites[suite]) {
      const desc = `${suite}: ${test}`;

      try {
        // this will run the tests in serial
        await suites[suite][test]();
        on_pass(desc);
      } catch (e) {
        on_fail(desc, e as Error);
      }
    }
  }
}

然后我们可以定义这样的测试

import { strict as assert } from 'node:assert';
import { inspect } from 'node:util';
import { suite } from 'lib/test';

// Create a test suite/group
const test = suite('lib/google');

// Not important
export type DateTime = { dateTime: string, };
export type DateOnly = { date: string, };

export function json2date(dt: DateTime | DateOnly)
{
  // left as an exercise to the reader
}

// Add the test to the suite
test['json2date'] = () => {
  const dt = {
    dateTime: '2023-01-02T14:42:25.861Z',
  };
  const d = {
    date: '2026-01-01',
  };

  assert.deepEqual(json2date(dt), new Date(dt.dateTime));
  assert.deepEqual(json2date(d), new Date(2026, 0, 1));
};

test['An async test'] = async () => {
  // test some async code
};

然后,我们需要加载并执行所有测试,这是应用程序健康检查的一部分,也就是说,当向/sys/health发出GET请求时,将运行类似下面的代码

import { ... } from 'lib/google';
import { run as run_tests } from 'lib/test';

...

app.get('/sys/health', async () => {
  let passes: number = 0;
  let fails: number = 0;

  const on_pass = (desc: string) => {
    passes++;
    log.info(`Test PASS: ${desc}`);
  };
  const on_fail = (desc: string, err: Error) => {
    fails++;
    log.error(`Test FAIL: ${desc}: ${inspect(err)}`);
  };

  await run_tests(on_pass, on_fail);
  log.info(`Test summary: ${passes} passes, ${fails} fails, ${passes + fails} in total`);

  if (fails > 0)
    // return 500 to cause health checks to fail
});

类似的代码也可以用在客户端。如果用户遇到客户端错误,你可以在错误页面的后台自动运行客户端测试。运行的测试将取决于包含的模块。
这样做的缺点是测试代码将与普通代码一起包含。如果你在生产环境中运行单元测试作为健康检查,这不是问题。而且大多数捆绑包支持“defines”和死代码消除,所以你可以从生产代码中删除它们(例如https://esbuild.github.io/api/#define)。

相关问题