Merge pull request #100 from bkconrad/feature/integration-testing
Feature/integration testing
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -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
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.
|
||||
18
package.json
18
package.json
@@ -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": "echo \"Error: no test specified\" && exit 1",
|
||||
"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"
|
||||
@@ -26,16 +28,30 @@
|
||||
},
|
||||
"homepage": "https://github.com/screepers/screeps-typescript-starter#readme",
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.1.6",
|
||||
"@types/lodash": "^3.10.1",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^10.5.5",
|
||||
"@types/screeps": "^2.4.0",
|
||||
"@types/sinon": "^5.0.5",
|
||||
"@types/sinon-chai": "^3.2.0",
|
||||
"chai": "^4.2.0",
|
||||
"lodash": "^4.17.11",
|
||||
"mocha": "^5.2.0",
|
||||
"prettier": "^1.14.0",
|
||||
"rollup": "^0.63.4",
|
||||
"rollup-plugin-buble": "^0.19.4",
|
||||
"rollup-plugin-clear": "^2.0.7",
|
||||
"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",
|
||||
"tslint": "^5.9.1",
|
||||
"tslint-config-prettier": "^1.14.0",
|
||||
"tslint-plugin-prettier": "^1.3.0",
|
||||
|
||||
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()
|
||||
]
|
||||
}
|
||||
32
rollup.test-unit-config.js
Normal file
32
rollup.test-unit-config.js
Normal file
@@ -0,0 +1,32 @@
|
||||
"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';
|
||||
|
||||
export default {
|
||||
input: 'test/unit/**/*.test.ts',
|
||||
output: {
|
||||
file: 'dist/test-unit.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.json"}),
|
||||
multiEntry(),
|
||||
buble()
|
||||
]
|
||||
}
|
||||
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');
|
||||
});
|
||||
});
|
||||
12
test/mocha.opts
Normal file
12
test/mocha.opts
Normal file
@@ -0,0 +1,12 @@
|
||||
--require test/setup-node.js
|
||||
--require ts-node/register
|
||||
--ui bdd
|
||||
|
||||
--reporter spec
|
||||
--bail
|
||||
--full-trace
|
||||
--watch-extensions tsx,ts
|
||||
--colors
|
||||
|
||||
--recursive
|
||||
--timeout 5000
|
||||
6
test/setup-node.js
Normal file
6
test/setup-node.js
Normal file
@@ -0,0 +1,6 @@
|
||||
//inject mocha globally to allow custom interface refer without direct import - bypass bundle issue
|
||||
global._ = require('lodash');
|
||||
global.mocha = require('mocha');
|
||||
global.chai = require('chai');
|
||||
global.sinon = require('sinon');
|
||||
global.chai.use(require('sinon-chai'));
|
||||
25
test/unit/main.test.ts
Normal file
25
test/unit/main.test.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import {assert} from "chai";
|
||||
import {loop} from "../../src/main";
|
||||
import {Game, Memory} from "./mock"
|
||||
|
||||
describe("main", () => {
|
||||
before(() => {
|
||||
// runs before all test in this block
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// runs before each test in this block
|
||||
// @ts-ignore : allow adding Game to global
|
||||
global.Game = _.clone(Game);
|
||||
// @ts-ignore : allow adding Memory to global
|
||||
global.Memory = _.clone(Memory);
|
||||
});
|
||||
|
||||
it("should export a loop function", () => {
|
||||
assert.isTrue(typeof loop === "function");
|
||||
});
|
||||
|
||||
it("should return void when called with no context", () => {
|
||||
assert.isUndefined(loop());
|
||||
});
|
||||
});
|
||||
10
test/unit/mock.ts
Normal file
10
test/unit/mock.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export const Game = {
|
||||
creeps: [],
|
||||
rooms: [],
|
||||
spawns: {},
|
||||
time: 12345
|
||||
};
|
||||
|
||||
export const Memory = {
|
||||
creeps: []
|
||||
};
|
||||
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