add integration testing support
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -17,6 +17,9 @@
|
|||||||
# Screeps Config
|
# Screeps Config
|
||||||
screeps.json
|
screeps.json
|
||||||
|
|
||||||
|
# ScreepsServer data from integration tests
|
||||||
|
/server
|
||||||
|
|
||||||
# Numerous always-ignore extensions
|
# Numerous always-ignore extensions
|
||||||
*.diff
|
*.diff
|
||||||
*.err
|
*.err
|
||||||
|
|||||||
71
docs/in-depth/testing.md
Normal file
71
docs/in-depth/testing.md
Normal 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.
|
||||||
@@ -10,7 +10,9 @@
|
|||||||
"push-main": "rollup -c --environment DEST:main",
|
"push-main": "rollup -c --environment DEST:main",
|
||||||
"push-pserver": "rollup -c --environment DEST:pserver",
|
"push-pserver": "rollup -c --environment DEST:pserver",
|
||||||
"push-sim": "rollup -c --environment DEST:sim",
|
"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-main": "rollup -cw --environment DEST:main",
|
||||||
"watch-pserver": "rollup -cw --environment DEST:pserver",
|
"watch-pserver": "rollup -cw --environment DEST:pserver",
|
||||||
"watch-sim": "rollup -cw --environment DEST:sim"
|
"watch-sim": "rollup -cw --environment DEST:sim"
|
||||||
@@ -43,8 +45,10 @@
|
|||||||
"rollup-plugin-commonjs": "^9.1.4",
|
"rollup-plugin-commonjs": "^9.1.4",
|
||||||
"rollup-plugin-multi-entry": "^2.0.2",
|
"rollup-plugin-multi-entry": "^2.0.2",
|
||||||
"rollup-plugin-node-resolve": "^3.3.0",
|
"rollup-plugin-node-resolve": "^3.3.0",
|
||||||
|
"rollup-plugin-nodent": "^0.2.2",
|
||||||
"rollup-plugin-screeps": "^0.1.2",
|
"rollup-plugin-screeps": "^0.1.2",
|
||||||
"rollup-plugin-typescript2": "^0.16.1",
|
"rollup-plugin-typescript2": "^0.16.1",
|
||||||
|
"screeps-server-mockup": "^1.4.3",
|
||||||
"sinon": "^6.3.5",
|
"sinon": "^6.3.5",
|
||||||
"sinon-chai": "^3.2.0",
|
"sinon-chai": "^3.2.0",
|
||||||
"ts-node": "^7.0.1",
|
"ts-node": "^7.0.1",
|
||||||
|
|||||||
34
rollup.test-integration-config.js
Normal file
34
rollup.test-integration-config.js
Normal 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()
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
import clear from "rollup-plugin-clear";
|
||||||
import resolve from "rollup-plugin-node-resolve";
|
import resolve from "rollup-plugin-node-resolve";
|
||||||
import commonjs from "rollup-plugin-commonjs";
|
import commonjs from "rollup-plugin-commonjs";
|
||||||
import typescript from "rollup-plugin-typescript2";
|
import typescript from "rollup-plugin-typescript2";
|
||||||
@@ -7,9 +8,9 @@ import buble from 'rollup-plugin-buble';
|
|||||||
import multiEntry from 'rollup-plugin-multi-entry';
|
import multiEntry from 'rollup-plugin-multi-entry';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
input: 'test/**/*.test.ts',
|
input: 'test/unit/**/*.test.ts',
|
||||||
output: {
|
output: {
|
||||||
file: 'dist/test.bundle.js',
|
file: 'dist/test-unit.bundle.js',
|
||||||
name: 'lib',
|
name: 'lib',
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
format: 'iife',
|
format: 'iife',
|
||||||
@@ -21,6 +22,7 @@ export default {
|
|||||||
},
|
},
|
||||||
external: ['chai', 'it', 'describe'],
|
external: ['chai', 'it', 'describe'],
|
||||||
plugins: [
|
plugins: [
|
||||||
|
clear({ targets: ["dist/test.bundle.js"] }),
|
||||||
resolve(),
|
resolve(),
|
||||||
commonjs(),
|
commonjs(),
|
||||||
typescript({tsconfig: "./tsconfig.json"}),
|
typescript({tsconfig: "./tsconfig.json"}),
|
||||||
63
test/integration/helper.ts
Normal file
63
test/integration/helper.ts
Normal 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();
|
||||||
18
test/integration/integration.test.ts
Normal file
18
test/integration/integration.test.ts
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import {assert} from "chai";
|
import {assert} from "chai";
|
||||||
import {loop} from "../src/main";
|
import {loop} from "../../src/main";
|
||||||
import {Game, Memory} from "./mock"
|
import {Game, Memory} from "./mock"
|
||||||
|
|
||||||
describe("main", () => {
|
describe("main", () => {
|
||||||
20
tsconfig.test-integration.json
Normal file
20
tsconfig.test-integration.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user