前言
在 上篇文章 中,我们介绍了如何使用 jest 测试自己的 screeps 代码,并实现了一个简单 screeps mock 环境。但是假的总归是假的,在测试复杂行为时(如多个房间的资源共享)这个 mock 环境多少会显得有些力不从心,而本文就来介绍一个测试利器:screeps-server-mockup。
简单来说,screeps-server-mockup(下文简称为 mockup)就是一个真正的 screeps 服务器。他使用了 screeps 私服的核心库自己封装了一套接口,让我们可以更加轻易的进行测试。接下来我们要介绍的内容,就是如何使用 mockup 来运行自己的代码,并对其进行测试。按照惯例,我们先来看一下它的优缺点:
引入 server-mockup 的优缺点
优点:
- 真实的环境模拟:不需要再手动进行什么模拟,我们完全可以将其当作是真正的 screeps 环境,只需要把自己的代码丢进去然后启动 mockup 即可。
- 功能测试:之前的单元测试只能保证最小单元的代码执行可靠性,但是代码可靠不代表功能可靠。在引入 mockup 之后,我们就可以使用它来进行功能测试,例如 pb 采集、新房间占领、lab 集群的运行等等,这些场景通常会持续数十乃至上百 tick。
- 快速收集数据:由于 mockup 的执行成本足够低(高速运行、不需要人看着、可以自动记录日志 ),所以我们多次运行 bot 并记录运行时数据,通过对比不同启动参数时的 bot 发展情况来进行调优。并且还可以通过这些运行指标量化 bot 的能力,例如多少 tick 能造好 tower、多少时间能升到 RCL 4 等等。
缺点:
- 花费更多精力:和单元测试一样,你同样需要花费时间在完善测试用例上,并且由于测试场景变的更加复杂(创建房间、放置建筑、放置地形、填充资源......),完成一个测试用例需要的时间会变得更长。
- 更长的测试时间:由于集成测试动不动就要跑几百 tick(几千甚至上万 tick 也有可能),所以每次集成测试的时间可能会持续十几分钟乃至数个小时。
- 不够直观:由于 mockup 是完全无界面的,我们只能通过运行日志查看 bot 行为,所以那些异常但是不影响正常运行的代码逻辑很难被发现。因此,mockup 不能完全代替手动运行测试。
- async / await:js 中的异步一直是比较令人头痛的事情,在本文中,我们将第一次在 screeps 中接触到异步操作。本文不会对其进行过多介绍,如果你不太了解的话,推荐在阅读本文时参照 MDN - async 函数。
本文使用 Screeps 使用 Jest 添加单元测试 中所述项目进行升级,请确保你至少读过这篇文章。
依赖安装
话不多说,首先还是安装依赖,执行如下命令来进行安装:
npm install --save-dev screeps-server-mockup fs-extra
如果你安装时出现了问题,那么可能是没有安装 c++ 编译环境导致的,可以参阅 node-gyp build-on-windows 所述来安装 VS 工具包。如下图,在 下载 vs buildTools 之后选中 c++ 生成工具并安装即可。
环境配置
安装完之后我们打开 package.json
来配置一会要用的命令:
"scripts": {
"test-unit": "jest src/",
"test-integration": "jest --runInBand test/integration/",
"test-cover": "jest --coverage src/"
}
其中 test-unit 和 test-cover 就是 Screeps 使用 Jest 添加单元测试 中的 test 和 test-c 命令,这里为了统一进行了更名。而 test-integration(集成测试)就是我们今天要介绍的内容。
需要注意的是,我们通过给 jest 传入一个路径参数来限制了测试用例的查找范围,这样可以让单测和集测分开执行。
而且,由于 jest 会默认 并行 执行所有测试套件,而 screeps-server-mockup 只会通过进程方式启动一个数据库实例。所以当并行执行测试时,多个服务器操作同一个数据库会导致出现问题。因此,我们在集成测试中指定了 --runInBand
参数来让 jest 同步执行测试套件。
接下来,我们来简单了解一下如何用 screeps-server-mockup 来执行测试。
测试接入 screeps-server-mockup
首先,我们新建 test/integration/
目录,我们之后所有的集成测试都会放在这个目录中,然后在其中新建 mockup.test.ts
并填入如下内容:
import { ScreepsServer } from 'screeps-server-mockup'
it('mockup 上手', async () => {
// 初始化服务器
const server = new ScreepsServer()
// 重设服务器
await server.world.reset()
// 创建一个基础的 9 房间世界,包含 source 和 controller
await server.world.stubWorld()
// 设置 bot 的代码
const modules = {
main: `module.exports.loop = function() {
console.log('Tick!', Game.time)
}`
}
// 设置 bot 的名称和初始位置
const bot = await server.world.addBot({
username: 'bot',
room: 'W0N1', x: 25, y: 25,
modules
})
// 监控 bot 控制台并打印输出
bot.on('console', logs => logs.forEach(console.log))
// 启动服务器并运行两个 tick
await server.start()
await server.tick()
await server.tick()
// 关闭服务器
await server.stop()
}, 10000)
然后执行 npm run test-integration
就可以看到以下输出:
可以看到测试中成功输出了 bot 控制台的信息(以及一段报错 )。上面的测试用例里已经包含了详细的注释,接下来我们就提一下几个比较重要的点:
bot 添加:在测试用例中我们新建了一个
modules
对象,对象的键就是“文件名”,而其值就是“对应文件的代码内容”。控制台监听:screeps 中的每个 bot 都运行在一个独立的 js 沙盒中,bot 调用的是沙盒中的 console 对象,而不是我们测试环境中的 console。因此,如果想查看 bot 的控制台输出,就要监听 bot 的 console 事件,并将输出“转发”过来。并且也是由于这个原因,代码中的 debugger 会被 js 沙盒吞掉,所以非常的悲催,我们没法在 mockup 中使用断点调试。
运行单个 tick:在 mockup 中,我们可以使用
server.tick()
方法来执行单个 tick,这样就可以对其进行更细粒度的检查,注意 server.tick 之前一定要加await
。修改超时限制:可以看到我们给 it 函数的第三个参数传入了一个值
10000
,这个就代表该测试的最大运行时限是 10 秒(默认为 5 秒),由于继承测试经常要运行很久,默认 5 秒的话 jest 就会认为是任务没有完成。你还可以通过jest.setTimeout(60 * 1000)
来设置全局超时限制。服务器关闭问题:你应该已经发现了,测试结束时出现了一个报错,这个错误提示是由于
@screeps/common
与游戏数据库连接的 socket 处理不充分导致的,并不会对测试造成影响,后面我们会将其隐藏掉。
我在 ts 中调用 screeps-server-mockup 为什么没有类型补全?
如果你自己开始写测试用例的话就会发现这个问题,实际上 mockup 是有类型声明文件的,但是由于其发布问题导致 tsc 无法发现其类型定义,你可以通过在
node_modules\screeps-server-mockup\package.json
的末尾添加"types": "dist/src/main.d.ts"
字段然后重启代码编辑器来解决这个问题。
server-mockup 基本使用
现在我们来简单介绍一下 mockup 的用法,这一小节一共包含三部分,分别是:服务器操作、设置游戏世界操作以及读取游戏世界。记住,以下 api 都是异步函数,所以不要忘了带上 await
。
1、服务器操作
我们通过 new ScreepsServer()
得到的 server 就是服务器实例,我们要用到的服务器操作都暴露在这个对象上,一共只有三个:
-
server.start
:启动服务器,需要在 tick 方法之前调用 -
server.tick
:运行单个 tick -
server.stop
:关闭服务器,若不调用将导致测试进程无法退出
这个很简单对吧,看名字就很清楚了,要注意的是 start 方法每个 server 实例只能调用一次,多次调用会导致无法正确释放服务器连接。
2、设置游戏世界
在 server 实例上还有一个名为 world 的属性,我们就可以通过这个对象对游戏世界进行设置,这个对象暴露了以下常用操作:
-
server.world.addRoom
:新增一个房间,房间名为接受的参数,注意该方法只会创建一个完全空白的房间,没有地形数据、没有任何 controller 之类的游戏对象。 -
server.world.setTerrain
:给一个房间设置地形数据,我们可以使用 mockup 导出的 TerrainMatrix 来设置一个地形,用法可以在 这里 里找到。 -
server.world.addRoomObject
:在指定房间中新增一个游戏对象,诸如 controller、source、creep、spawn 等等任何物体,我们搭建测试环境主要靠的就是这个方法,用法可以在 这里 里找到。 -
server.world.addBot
:添加一个 bot,说是 bot,其实就是一段可以执行的代码。我们可以用它设置 bot 的名字、出生点位置、代码内容等,并且返回的 bot 实例也可以访问其状态。
3、读取游戏世界
在 server.world 和 bot 实例上同样也暴露了很多接口用于对游戏世界进行查询,下面列举一些比较常见的:
-
server.world.gameTime
:获取当前的游戏 tick -
server.world.getTerrain
:获取指定房间的地形数据 -
server.world.roomObjects
:获取指定房间的游戏对象 -
bot.memory / .username / .cpu / .rooms
:获取对应 bot 的内存、用户名、cpu 或者可见的 room。
如果你足够了解 screeps 的话,那么应该记得 screeps 默认使用 lokijs 作为数据库。在 mockup 中,我们可以通过如下形式获取到游戏的实际数据库,然后就可以使用这个数据库实例对游戏内容进行更为全面的读写:
// db 就是实际的 lokijs 数据库
const { db } = await server.world.load();
至于如何使用这里就不在赘述,可以参考 lokijs - 官方示例 或者 screeps-server-mockup - addBot 源码(这个文件里还有很多使用 loki 的地方 )。
对 server-mockup 的介绍就到这里,上面只列举了一些主要操作,你可以从 screeps-server-mockup 官方示例 了解到更多用法,或者打开你的项目,node_modules\screeps-server-mockup\dist\src\main.d.ts
这个文件就是 server-mockup 的声明文件,并没有多少 api,ctrl 一路点过去大致读一遍即可掌握其用法。
通过这些方法,我们可以构建出任何我们想要进行测试的场景,例如两个 creep 战斗、多个房间中的 terminal 共享、factory 的生产线合作等等。但是...还有一个问题,我们怎么获取到自己的代码呢,总不能编译完手动复制过来吧,没有关系,下一小节我们将借助 node 的能力来实现这个功能。
构建并在 mockup 中执行自己的 bot
首先我们来改造一下咱们的 npm 命令,如下:
"test-integration": "npm run build && jest test/integration/",
我们在执行集测之前先执行 npm run build 来进行代码构建,由此来保证执行集测时 dist/
目录下肯定存在已经编译好的代码,接下来在 test/
中新建 moduleUtils.ts
并填入如下内容:
import { readFile } from 'fs'
/* addBot 需要的 bot module */
interface BotModule {
/* 主代码文件 */
'main': string
/* sourceMap 文件 */
"main.js.map": string
}
/**
* async 版本的 readFile
*
* @param path 要读取的文件
* @returns 该文件的字符串内容
*/
const readCode = async function (path: string): Promise {
return new Promise((resolve, reject) => {
readFile(path, (err, data) => {
if (err) return reject(err);
resolve(data.toString())
})
})
}
/**
* 全局唯一的 dist 代码模块
*/
let myCode: BotModule
/**
* 获取自己的全量代码模块
*/
export const getMyCode = async function (): Promise {
if (myCode) return myCode
const [ main, map ] = await Promise.all([
readCode('dist/main.js'),
readCode('dist/main.js.map.js')
])
myCode = { 'main': main, 'main.js.map': map }
return myCode
}
这里我们使用 fs.readFile
去读取并缓存 dist 目录下的文件,然后将其转换成字符串。这样,我们只需要调用 getMyCode
就可以直接获取 server.world.addBot
中需要的 modules,如下:
import { getMyCode } from '../moduleUtils';
it('执行实际代码', async () => {
const server = new ScreepsServer()
// 一系列场景搭建操作
// 获取我的代码,并添加到 bot 中
onst modules = await getMyCode()
await server.world.addBot({ username: '战斗测试 A 单位', room: 'W0N1', x: 25, y: 25, modules });
// 测试内容...
})
复用 server 实例
如果你之前有服务器开发经验的话,你就会敏锐的察觉到在每个测试用例里都新建一个 mockup server 实例并不优雅,是的,我们完全可以通过 单例模式让不同的测试复用同一个 server 实例。本小节将介绍如何进行复用并减少测试用例中的代码量:
首先在 test/
目录中新建 serverUtils.ts
并填入如下内容:
import { ScreepsServer } from 'screeps-server-mockup'
/**
* 全局唯一的 server 实例
*/
let server: ScreepsServer
/**
* 获取可用的 server 实例
* @returns server 实例
*/
export const getServer = async function () {
if (!server) {
server = new ScreepsServer()
await server.start()
}
return server
}
/**
* 重置 server
*/
export const resetServer = async function () {
if (!server) return
await server.world.reset()
}
/**
* 停止 server
*/
export const stopServer = async function () {
if (!server) return
// monkey-patch 用于屏蔽 screeps storage 的退出提示
const error = global.console.error
global.console.error = (...arg) => !arg[0].match(/Storage connection lost/i) && error.apply(this, arg)
await server.stop()
}
可以看到我们封装了三个方法,分别用于获取、重置和关闭服务器。
然后我们打开 test/setup.ts
,来把服务器的重置和关闭配置到 jest 测试流程中:
import { refreshGlobalMock } from './mock'
import { resetServer, stopServer } from './serverUtils'
// 之前的单测环境 mock
refreshGlobalMock()
beforeEach(refreshGlobalMock)
// 每次测试用例执行完后重置服务器
afterEach(resetServer)
// 所有测试完成后关闭服务器
afterAll(stopServer)
这样配置好后,我们就不需要再关心服务器的释放问题了,下面就是一个简单的使用示例:
import { getServer } from '../serverUtils'
it('封装后的 server 运行流程', async () => {
const server = await getServer()
await server.world.stubWorld()
await server.tick()
})
我们可以把这个测试用例复制两遍后执行一下 npm run integration
,可以看到第二次复用了 server,所以测试速度大幅提升,并且结尾的报错也清理掉了。
在完成了上面的流程之后,另外一个严峻的问题摆在我们面前,怎么查看 server 中 bot 的运行情况?把所有的东西都打印在控制台上?不太好吧,如果出现问题了再打印,感觉能获取到的信息比较少,但是如果提前把所有信息都打印出来,那控制台就会变得乱糟糟的,还有可能因为缓冲区的上限导致新 log 很快就把之前的 log 顶掉了。在下个小节,我们就来解决这个问题。
使用 json 记录 bot 运行情况
由于我们是要记录代码的运行,所以可以在每个 tick 开始时监听 bot 的控制台和内存,并在测试结束后将其写入到 json 里。这样不仅可以让控制台保持清爽,还可以非常全面的记录运行情况。
我们在 test/
目录下新建 logRecorder.ts
并填入如下内容:
import { resolve } from 'path'
import { outputJson } from 'fs-extra'
import { ScreepsServer } from 'screeps-server-mockup'
import User from 'screeps-server-mockup/dist/src/user'
/** tick 日志 */
interface TickLog {
/** 该 tick 的控制台输出 */
console?: string[]
/** 该 tick 的通知信息 */
notifications?: string[]
/** 该 tick 的 Memory 完整拷贝 */
memory?: AnyObject
}
const now = new Date()
/**
* 本次测试日志要保持到的 log 文件夹路径
* 形如:项目根目录/server/logs/2021-3-15 17-58-45
*/
const LOG_DIR = resolve(__dirname, `../server/logs/${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()} ${now.getHours()}-${now.getMinutes()}-${now.getSeconds()}`)
/**
* 日志记录者
* 用于记录指定 bot 的运行日志
*/
export default class LogRecorder {
/**
* 保存到的日志文件名称
*/
readonly name: string
/**
* 日志内容
*/
logs: { [tick: number]: TickLog } = {}
/**
* 实例化日志记录
*
* @param name 日志要保存到的文件名称
* @param server 日志记录所在的游戏服务器
* @param bot 要进行记录的 bot 实例
*/
constructor(name: string, server: ScreepsServer, bot: User) {
this.name = name
// 记录控制台输出
const consoleListener = async logs => {
this.record(await server.world.gameTime, { console: logs });
}
// 记录 Memory 和通知信息
const tickListener = async () => this.record(await server.world.gameTime, {
memory: JSON.parse(await bot.memory),
notifications: (await bot.newNotifications).map(({ message }) => message)
})
// 服务器重置时代表记录完成,取消回调并保存日志到本地
const resetListener = () => {
this.save()
server.removeListener('tickStart', tickListener)
bot.removeListener('console', consoleListener)
server.removeListener('reset', resetListener)
}
// 注册回调
server.on('tickStart', tickListener)
bot.on('console', consoleListener);
server.on('reset', resetListener)
}
/**
* 记录新的日志
*
* @param tick 要保存到的 tick
* @param newLog 日志内容
*/
record(tick: number, newLog: TickLog) {
if (!this.logs[tick]) this.logs[tick] = newLog
else {
Object.keys(newLog).forEach(key => {
if (key in this.logs[tick]) return
this.logs[tick][key] = newLog[key]
})
}
}
/**
* 保存日志到本地
*/
async save() {
await outputJson(resolve(LOG_DIR, `${this.name}.json`), this.logs, {
spaces: 4
})
// 主动移除内存中的日志,加速垃圾回收
this.logs = []
}
}
稍微有点长,不过不用担心,我们简单介绍一下,这个文件里的 LogRecorder
可以监听一个 bot,并在 tick 开始和 bot 控制台输出内容时进行记录,最后在服务器重置时将日志写入 项目根目录/server/logs/测试执行日期/bot名称.json
中。
但是现在它还没法正常使用,因为 server-mockup 并没有提供诸如 tick 开始,服务器重置这些事件,所以接下来我们就着手升级一下 mockup:
打开 serverUtils.ts
,首先在开头引入我们的日志记录器:
import LogRecorder from './logRecorder'
然后添加升级函数:
/**
* 升级 server 实例
* 添加更多的事件发射,并添加自动日志记录
*
* @param server 要进行升级的 server 实例
* @returns 升级后的 server 实例
*/
const upgradeServer = function (server: ScreepsServer) {
const { tick } = server
// 发射 tick 开始事件
server.tick = async function () {
server.emit('tickStart')
return tick.apply(this)
}
const { reset, addBot } = server.world
// 发射服务器重置事件
server.world.reset = async function () {
server.emit('reset')
return reset.apply(this)
}
// 在添加 bot 时同步启动日志记录实例
// 会在 server reset 时自动保存并释放
server.world.addBot = async function (...args) {
const [ addBotOptions ] = args
const { username } = addBotOptions
const bot = await addBot.apply(this, args)
new LogRecorder(username, server, bot)
return bot
}
return server
}
最后在新建 server 时调用升级函数:
export const getServer = async function () {
if (!server) {
server = upgradeServer(new ScreepsServer()) // <== 看这里
await server.start()
}
// ...
}
你或许发现了,我们在 upgradeServer
中也改造了 server.world.addBot
方法,并在其中实例化了日志记录。这样,我们在写测试用例时不需要做任何事情,日志记录就会在 bot 添加好后开始,并在测试用例结束(服务器重置)时自动保存。
接下来我们测试一下,执行如下测试用例:
it('日志记录测试', async () => {
const server = await getServer()
await server.world.stubWorld()
const modules = {
main: `module.exports.loop = function() {
console.log('Tick!',Game.time);
Memory[Game.time] = Game.time
};`
};
await server.world.addBot({ username: 'bot 启动', room: 'W0N1', x: 25, y: 25, modules });
for (let i = 0; i < 10; i++) {
await server.tick()
}
})
npm run test-integration
执行成功之后你就可以在 server/logs/测试执行日期/
中找到这个 bot 的执行日志,其中每个键值对都是一个 tick,memory 字段为当时 Memory 的完整拷贝,而 notifications 则是使用 Game.notify
发送的通知:
由于日志记录是和 bot 创建绑定的,所以哪怕你在一个测试用例里添加多个 bot 也不会影响日志的正常记录。不过这里我们只保存 bot 内部的信息(内存、控制台、通知)而没有保存周围环境(房间参数、source、controller 状态等),你可以通过在 bot 内部添加统计模块来将环境状态记录到内存,或者直接读取服务器数据库来将状态写入到日志。
我把服务器日志打印到了控制台,但是格式太乱了,怎么解决?
由于 jest 会自动的对 console 模块进行改造,由此来提供更多的调试信息,所以当我们在控制台打印信息时会显得更加的“啰嗦”:
我们可以通过在
test/setup.ts
里添加如下代码的形式来还原 console:import { log } from 'console' global.console.log = log
但是请记住,在测试时应尽量少的在控制台打印无关内容,防止出现问题时影响原因的查找。
看到这里,我们已经介绍了 server-mockup 的绝大多数内容,但是还是存在一个问题,如果我只想测试单个模块呢?例如一个比较后期的多房间资源共享。装进服务器的代码可是一个完整的 bot,我总不能等他慢慢发展到后期吧(又或者我的 bot 根本不具备冷启动能力 )。而且由于所有模块都包含在里边,万一问题是其他模块导致的,这不就让问题溯源更加复杂了么。
那么,能不能只把要测试的模块装进服务器然后进行测试呢?答案是可以的,这也就是我们接下来要讲的内容。
在 server-mockup 中进行功能测试
想要进行功能测试(又称行为测试 ),最重要的是进行局部编译,由于 mockup 只能接受纯 js 代码,所以我们需要把要测试的模块单独拿出来进行编译,一提到编译,怎么少得了我们的老朋友 rollup 呢?复习一下,rollup 可以接受一个入口文件,然后将其依赖按照指定的配置编译成纯 js 代码,这简直完美契合我们的需求。
rollup 除了命令行调用之外(我们之前编译 src 目录下的代码就是使用的命令行调用 )还支持 api 调用,这种调用方式更加的灵活,我们接下来就用这种方式来实现 编译指定文件并获取其 js 代码字符串。
要进行的修改也很简单,我们打开 test/moduleUtils.ts
并在其中添加如下代码:
import resolve from '@rollup/plugin-node-resolve'
import { InputOptions, OutputOptions, RollupBuild } from 'rollup'
const { rollup } = require('rollup')
const commonjs = require('@rollup/plugin-commonjs')
const typescript = require('rollup-plugin-typescript2')
/**
* 构建流程
*/
const plugins = [
// 打包依赖
resolve(),
// 模块化依赖
commonjs(),
// 编译 ts
typescript({ tsconfig: 'tsconfig.json' })
]
/**
* 执行模块构建
*
* @param inputPath 构建的入口文件
* @returns 可以直接用在 bot 中的 module
*/
export const build = async function (input: string): Promise {
const inputOptions: InputOptions = { input, plugins }
const outputOptions: OutputOptions = { format: 'cjs', sourcemap: true }
// 构建模块代码
const bundle: RollupBuild = await rollup(inputOptions);
const { output: [targetChunk] } = await bundle.generate(outputOptions);
// 组装并返回构建成果
return {
'main': targetChunk.code,
'main.js.map': `module.exports = ${targetChunk.map};`
}
}
我们要执行编译的话,只需要调用 build
方法并传入要构建入口文件的路径即可。仔细观察一下就能发现,这个构建流程几乎和我们在 rollup.config.js
中配置的一模一样。
接下来新建 test/behavior
目录,我们所有的功能测试都将写在这个目录下。测试文件的结构是这样的,每个功能测试都是一个文件夹,其中包含了编译入口文件 main.ts
和测试文件 main.test.ts
:
其中,编译入口文件的作用和我们的 src/main.ts
一样,都导出了一个 loop 函数,我们会把要进行测试的模块单独引入进来并调用。然后我们将在 main.test.ts
中用刚才的 build 方法从这个入口文件进行构建,最终得到可以在 mockup 中可以执行的代码对象。
接下来我们就按照刚才的步骤进行测试,假设我们在 src/modules/getRoom.ts
中有如下方法:
export const getRoom = function () {
const roomNames = Object.keys(Game.rooms)
console.log(roomNames.join(','))
}
这个“模块”的作用很简单,就是获取所有可见的房间。接下来我们就要单独对它进行测试,首先新建 test/behavior/getRoom/main.ts
并添加如下代码:
// 路径别名 @ 的用法详见 https://www.jianshu.com/p/f81e2d6092a1
import { getRoom } from '@/modules/getRoom'
export const loop = () => {
getRoom()
}
然后在新建 test/behavior/getRoom/main.test.ts
并添加如下代码,这段代码会新建房间并把 bot 防止在 W1N1
房间,然后监听控制台是否会输出正确的内容:
import { resolve } from 'path'
import { getServer } from '@test/serverUtils'
import { build } from '@test/moduleUtils'
it('getRoom 可以输出房间名到控制台', async () => {
const server = await getServer()
await server.world.stubWorld()
const spawnRoomName = 'W1N1'
// 从入口文件构建并添加进 bot
const modules = await build(resolve(__dirname, './main.ts'))
const bot = await server.world.addBot({ username: 'getRoom 测试', room: spawnRoomName, x: 25, y: 25, modules })
// 断言 console 输出并跑 tick
bot.on('console', logs => expect(logs).toEqual([spawnRoomName]))
await server.tick()
}, 10000)
现在我们就可以来执行功能测试了,打开 package.json
,在 scripts 中新增如下命令:
"scripts": {
"test-behavior": "jest --runInBand test/behavior/",
},
然后执行 npm run test-behavior
即可看到测试通过:
由此,我们就通过指定入口文件的方式来对要测试的功能进行单独编译,下面留几个思考题,大家可以尝试一下:
- 执行集成测试时是通过执行
npm run build
来获取代码的,能否通过上面的 build 函数进行生成? - build 函数中的构建流程和
rollup.config.js
中的流程类似,这两者能否进行复用?
总结
本文介绍了如何使用 screeps-server-mockup
来进行真实测试,并对 mockup 的调用和日志记录进行了一定程度的封装。最后通过调用 rollup 进行局部构建来实现功能测试。由此,我们就可以对 screeps 的游戏代码进行更加真实的测试来确保其功能真实可用。
至此,搭建 screeps 开发环境系列教程已全部结束了,想要查看更多教程?欢迎访问 《Screeps 中文教程》!