add integration testing support

This commit is contained in:
kaen
2018-11-10 15:49:43 -08:00
parent 1bf84c2443
commit f8507b632a
10 changed files with 219 additions and 4 deletions

3
.gitignore vendored
View File

@@ -17,6 +17,9 @@
# Screeps Config
screeps.json
# ScreepsServer data from integration tests
/server
# Numerous always-ignore extensions
*.diff
*.err

71
docs/in-depth/testing.md Normal file
View File

@@ -0,0 +1,71 @@
# Testing
Automated testing helps prevent regressions and reproduce complex failure
scenarios for bug fixing or feature implementation. This project comes with
support for both unit and integration testing with your Screeps code.
You can read more about [unit and integration testing on
Wikipedia](https://en.wikipedia.org/wiki/Test-driven_development).
This documentation will cover the testing setup for those already familiar with
the process of test driven design.
Tests are written via [Mocha](https://mochajs.org/) and executed as tests only
if they include `.test.ts` in their filename. If you have written a test file
but aren't seeing it executed, this is probably why. There are two separate test
commands and configurations, as unit tests don't need the complete Screeps
server run-time as integration tests do.
## Running Tests
The standard `npm test` will execute all unit and integration tests in sequence.
This is helpful for CI/CD and pre-publish checks, however during active
development it's better to run just a subset of interesting tests.
You can use `npm run test-unit` or `npm run test-integration` to run just one of
the test suites. Additionally you can supply Mocha options to these test
commands to further control the testing behavior. As an example, the following
command will only execute integration tests with the word `memory` in their
description:
```
npm run test-integration -- -g memory
```
Note that arguments after the initial `--` will be passed to `mocha` directly.
## Unit Testing
You can test code with simple run-time dependencies via the unit testing
support. Since unit testing is much faster than integration testing by orders of
magnitude, it is recommended to prefer unit tests wherever possible.
## Integration Testing
Integration testing is for code that depends heavily on having a full game
environment. Integration tests are completely representative of the real game
(in fact they run with an actual Screeps server). This comes at the cost of
performance and very involved setup when creating specific scenarios.
Server testing support is implmented via
[screeps-server-mockup](https://github.com/Hiryus/screeps-server-mockup). View
this repository for more information on the API.
By default the test helper will create a "stub" world with a 3x3 grid of rooms
with sources and controllers. Additionally it spawns a bot called "player"
running the compiled main.js file from this repository.
It falls on the user to properly set up preconditions using the
screeps-server-mockup API. Importantly, most methods exposed with this API are
asynchronous, so using them requires frequent use of the `await` keyword to get
a result and ensure order of execution. If you find that some of your
preconditions don't seem to take effect, or that you receive a Promise object
rather than an expected value, you're likely missing `await` on an API method.
Finally, please note that screeps-server-mockup, and this repo by extension,
come with a specific screeps server version at any given time. It's possible
that either your local package.json, or the screeps-server-mockup package itself
are out of date and pulling in an older version of the [screeps
server](https://github.com/screeps/screeps). If you notice that test environment
behavior differs from the MMO server, ensure that all of these dependencies are
correctly up to date.

View File

@@ -10,7 +10,9 @@
"push-main": "rollup -c --environment DEST:main",
"push-pserver": "rollup -c --environment DEST:pserver",
"push-sim": "rollup -c --environment DEST:sim",
"test": "rollup -c rollup.test-config.js && mocha dist/test.bundle.js",
"test": "npm run test-unit && npm run test-integration",
"test-unit": "rollup -c rollup.test-unit-config.js && mocha dist/test-unit.bundle.js",
"test-integration": "npm run build && rollup -c rollup.test-integration-config.js && mocha dist/test-integration.bundle.js",
"watch-main": "rollup -cw --environment DEST:main",
"watch-pserver": "rollup -cw --environment DEST:pserver",
"watch-sim": "rollup -cw --environment DEST:sim"
@@ -43,8 +45,10 @@
"rollup-plugin-commonjs": "^9.1.4",
"rollup-plugin-multi-entry": "^2.0.2",
"rollup-plugin-node-resolve": "^3.3.0",
"rollup-plugin-nodent": "^0.2.2",
"rollup-plugin-screeps": "^0.1.2",
"rollup-plugin-typescript2": "^0.16.1",
"screeps-server-mockup": "^1.4.3",
"sinon": "^6.3.5",
"sinon-chai": "^3.2.0",
"ts-node": "^7.0.1",

View File

@@ -0,0 +1,34 @@
"use strict";
import clear from "rollup-plugin-clear";
import resolve from "rollup-plugin-node-resolve";
import commonjs from "rollup-plugin-commonjs";
import typescript from "rollup-plugin-typescript2";
import buble from 'rollup-plugin-buble';
import multiEntry from 'rollup-plugin-multi-entry';
import nodent from 'rollup-plugin-nodent';
export default {
input: 'test/integration/**/*.test.ts',
output: {
file: 'dist/test-integration.bundle.js',
name: 'lib',
sourcemap: true,
format: 'iife',
globals: {
chai: 'chai',
it: 'it',
describe: 'describe'
}
},
external: ['chai', 'it', 'describe'],
plugins: [
clear({ targets: ["dist/test.bundle.js"] }),
resolve(),
commonjs(),
typescript({tsconfig: "./tsconfig.test-integration.json"}),
nodent(),
multiEntry(),
buble()
]
}

View File

@@ -1,5 +1,6 @@
"use strict";
import clear from "rollup-plugin-clear";
import resolve from "rollup-plugin-node-resolve";
import commonjs from "rollup-plugin-commonjs";
import typescript from "rollup-plugin-typescript2";
@@ -7,9 +8,9 @@ import buble from 'rollup-plugin-buble';
import multiEntry from 'rollup-plugin-multi-entry';
export default {
input: 'test/**/*.test.ts',
input: 'test/unit/**/*.test.ts',
output: {
file: 'dist/test.bundle.js',
file: 'dist/test-unit.bundle.js',
name: 'lib',
sourcemap: true,
format: 'iife',
@@ -21,6 +22,7 @@ export default {
},
external: ['chai', 'it', 'describe'],
plugins: [
clear({ targets: ["dist/test.bundle.js"] }),
resolve(),
commonjs(),
typescript({tsconfig: "./tsconfig.json"}),

View File

@@ -0,0 +1,63 @@
const { readFileSync } = require('fs');
const _ = require('lodash');
const { ScreepsServer, stdHooks } = require('screeps-server-mockup');
const DIST_MAIN_JS = 'dist/main.js';
/*
* Helper class for creating a ScreepsServer and resetting it between tests.
* See https://github.com/Hiryus/screeps-server-mockup for instructions on
* manipulating the terrain and game state.
*/
class IntegrationTestHelper {
private _server;
private _player;
get server() {
return this._server;
}
get player() {
return this._player;
}
async beforeEach() {
this._server = new ScreepsServer();
// reset world but add invaders and source keepers bots
await this._server.world.reset();
// create a stub world composed of 9 rooms with sources and controller
await this._server.world.stubWorld();
// add a player with the built dist/main.js file
const modules = {
main: readFileSync(DIST_MAIN_JS).toString(),
};
this._player = await this._server.world.addBot({ username: 'player', room: 'W0N1', x: 15, y: 15, modules });
// Start server
await this._server.start();
}
async afterEach() {
await this._server.stop();
}
}
beforeEach(async () => {
await helper.beforeEach();
});
afterEach(async () => {
await helper.afterEach();
});
before(() => {
stdHooks.hookWrite();
});
after(() => {
process.exit();
})
export const helper = new IntegrationTestHelper();

View File

@@ -0,0 +1,18 @@
import {assert} from "chai";
import {helper} from "./helper";
describe("main", () => {
it("runs a server and matches the game tick", async function () {
for (let i = 1; i < 10; i += 1) {
assert.equal(await helper.server.world.gameTime, i);
await helper.server.tick();
}
});
it("writes and reads to memory", async function () {
await helper.player.console(`Memory.foo = 'bar'`);
await helper.server.tick();
const memory = JSON.parse(await helper.player.memory);
assert.equal(memory.foo, 'bar');
});
});

View File

@@ -1,5 +1,5 @@
import {assert} from "chai";
import {loop} from "../src/main";
import {loop} from "../../src/main";
import {Game, Memory} from "./mock"
describe("main", () => {

View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"module": "esnext",
"lib": ["esnext"],
"target": "es5",
"moduleResolution": "Node",
"outDir": "dist",
"baseUrl": "src/",
"sourceMap": true,
"strict": true,
"experimentalDecorators": true,
"noImplicitReturns": true,
"noImplicitAny": false,
"allowSyntheticDefaultImports": true,
"allowUnreachableCode": false
},
"exclude": [
"node_modules"
]
}