# 自动化测试方案 如何测试一个 WebGL 应用渲染结果是否正确呢?常用的做法是进行像素比对,当然这也只能用于一些简单的判断例如渲染是否成功,整体的 Snapshot 比对开销很大。 但不管怎么说,这都意味着我们必须使用 WebGL API 进行真实的渲染。 以上过程在测试用例中描述如下: ```javascript // 1. 绘制 // 2. 读取像素 const pixels = new Uint8Array(width * height * 4); gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels); // 3. 判断某个像素点是否符合预期 ``` 在与测试框架结合时,常用的方案有: * Electron 除了 WebGL API,DOM API 等其他浏览器实现对于 WebGL 测试都是多余的,在 CI 时需要安装的依赖过大,所需的启动时间也很长。 * [node-webgl](https://github.com/mikeseven/node-webgl) 不同于 WebGL,可以直接调用 OpenGL 驱动,但同样包含了很多 WebGL 之外的特性。 这里我们选择 [headless-gl](https://github.com/stackgl/headless-gl),一个纯粹的 WebGL 1 规范的实现。并且能够很容易集成进现有的 [CI 流程](https://github.com/stackgl/headless-gl#how-can-i-use-headless-gl-with-a-continuous-integration-service)中,例如 [TravisCI](https://travis-ci.org/) 和 [AppVeyor](http://www.appveyor.com/)。 ## 测试框架 在配置测试框架前,我们必须解决一个 WebGL 项目中常见的问题。 ### 引入 GLSL 文件 如何在测试时正确引入 GLSL 文件是一个问题。目前各个 3D 引擎常用的做法有两种: * 以字符串形式直接写在 `.js` 文件中。`luma.gl/deck.gl` 使用[这种方式](https://github.com/uber/deck.gl/blob/7.1-release/modules/layers/src/arc-layer/arc-layer-fragment.glsl.js)。 * 使用 `.glsl` 编写,测试前使用构建脚本自动生成对应的 `.js` 文件。`Three.js`、`clay.gl` 使用[这种方式](https://github.com/pissang/claygl/blob/master/build/glsl2js.js)。 前者的好处是测试流程无需做过多修改,坏处则是无法享受编辑器对于 GLSL 的语法高亮,影响开发体验。而后者又需要编写额外的 `glsl2js` 的转换脚本。 我们显然希望如后者一样直接写 GLSL,但最好能让测试框架帮助我们完成自动转换的工作。 之前我们选择 `@babel/preset-typescript` 而非官方 `tsc` 的一大原因就是可以使用 `babel` 丰富的插件,`babel-plugin-inline-import` 就能完成类似 webpack 中 `raw-loader` 的功能,直接以字符串形式引入 GLSL 代码: ```javascript // 以字符串形式引入 GLSL 代码 import circleFrag from '../../shaders/circle_frag.glsl'; ``` 这样测试框架只需要使用同一套 babel 项目全局配置就行了。我们使用 Jest: ```javascript // jest.config.js module.exports = { transform: { '^.+\\.(ts|tsx)$': 'babel-jest', }, } ``` 下面就可以编写测试用例了。 ## 测试用例编写 我们将测试用例分成三类: * 内部服务的单元测试 * 渲染服务结果的 Snapshot 快照测试 * React 组件测试 将测试用例放在任意路径的 `__tests__` 文件夹下并以 `xxx.spec.ts` 命名就可以帮助框架发现并执行了。 使用 `yarn test` 运行所有测试用例: ![](./screenshots/jest.png) ### 单元测试 这类测试直接使用 Jest API 就好了,我们以 `@antv/l7-core` 模块的 `ShaderModuleService` 为例,编写一个简单的测试用例: ```typescript // services/shader/__test__/shader.spec.ts import 'reflect-metadata'; import IShaderModuleService from '../IShaderModuleService'; import ShaderModuleService from '../ShaderModuleService'; describe('ShaderService', () => { let shaderService: IShaderModuleService; beforeEach(() => { shaderService = new ShaderModuleService(); }); it('should register common module correctly and generate fragment/vertex shader code', () => { const rawShaderCode = ` #define PI 3.14 `; const commonModule = { fs: rawShaderCode, vs: rawShaderCode, }; shaderService.registerModule('common', commonModule); const { vs, fs } = shaderService.getModule('common'); expect(vs).toMatch(/3\.14/); expect(fs).toMatch(/3\.14/); }); }); ``` ### 渲染结果测试 得益于 L7 使用的基于 Inversify 的依赖注入方案,我们能够很轻易地将渲染服务替换为基于 headless-gl 的渲染服务。 具体到我们目前的渲染服务实现 `regl`,它能轻易做到这一点。事实上 regl 的[测试用例](https://github.com/regl-project/regl/blob/gh-pages/test/util/create-context.js#L28)也是这样使用的。 ### [WIP] React 组件测试 ### Coverage Report 我们使用 Coveralls.io: ```json // package.json "coveralls": "jest --coverage && cat ./tests/coverage/lcov.info | coveralls", ``` 运行 `yarn coveralls` 可以查看代码覆盖率,我们为分支、行覆盖率等指标设置了阈值: ```javascript // jest.config.js module.exports = { coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80, }, }, }; ``` ## TravisCI TravisCI 检测到 `yarn.lock` 就会默认安装 `yarn` 并使用它安装依赖,所以不需要[额外的配置](https://yarnpkg.com/en/docs/install-ci#travis-tab)。 TravisCI 配合之前的 Coveralls.io。