利用NodeSchool工具创建教程学习工具系列2

这是利用NodeSchool工具创建教程学习工具系列之二,第一部分见利用NodeSchool工具创建教程学习工具系列1

在第一部分我们简要介绍了NodeSchool的workshopper的创建工具,并说明了利用workshopper
创建学习教程的几个步骤,下面我们就详细讲解其中的答案文件、练习文件的具体作用。

来聊聊“答案”

按照第一部分说的文件夹结构,答案文件是放在每个练习目录下的solution/solution.js文件中的,当运行verify时,两个进程其中一个会寻找
该答案文件,然后计算出结果,而另一个进程会运行你编写的program.js文件,再将这两个结果进行比较。

在每个练习的目录中都有一个exercise.js文件,该文件是进行答案验证的必要文件,里面放置了该练习的具体构建和处理代码。

再来聊聊exercise.js

下面让我们来看看具体看下exercise.js文件:

  1. 引入需要的模块,比如workshopper-exerciseworkshopper-exercise/filecheck以及workshopper-exercise/execute等。
  2. 利用filecheck检查用户是否提交了答案
  3. 通过子进程spawn的方式并行执行答案文件和用户提交的文件
  4. 对比两个子进程执行的结果

上面就是exercise.js文件所执行的常见操作,不过可以通过addSetup函数来为3步骤执行时添加参数,其中this.solutionArgs
为执行答案文件进程的参数列表,而this.submissionArgs为执行用户提交的文件进程的参数列表。除此之外,你也可以在此添
加其他的处理逻辑。

除了addSetup的扩展,你也可以通过addProcessor来添加如果测试答案的逻辑,一般来说获得输出结果就足够了,这样就不需要扩展
addProcessor,不过有时需要做一些其他的处理来方便测试。
PS: 通过addProcessor添加的逻辑先于4步骤的结果对比,并且默认添加的processor是应用于runverify操作的,如果要单独添加,可以用
addVerifyProcessoraddRunProcessor

下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

// add a processor for both run and verify calls, added *before*
// the comparestdout processor so we can mess with the stdouts
exercise.addProcessor(function (mode, callback) {
this.submissionStdout.pipe(process.stdout)

// replace stdout with our own streams
this.submissionStdout = through2()
if (mode == 'verify')
this.solutionStdout = through2()

setTimeout(query.bind(this, mode), 500)

process.nextTick(function () {
callback(null, true)
});
});

// set up the data file to be passed to the submission
exercise.addSetup(function (mode, callback) {
this.submissionPort = rndport()
this.solutionPort = this.submissionPort + 1

this.submissionArgs.unshift(testFile)
this.submissionArgs.unshift(this.submissionPort)
this.solutionArgs.unshift(testFile)
this.solutionArgs.unshift(this.solutionPort)

fs.writeFile(testFile, rndtxt, 'utf8', callback)
})


// cleanup for both run and verify
exercise.addCleanup(function (mode, passed, callback) {
// mode == 'run' || 'verify'

rimraf(testFile, callback)
})


// delayed for 500ms to wait for servers to start so we can start
// playing with them
function query (mode) {
var exercise = this

function connect (port, stream) {
//TODO: introduce verification of content-type:text/plain and statusCode=200
var url = 'http://localhost:' + port
hyperquest.get(url)
.on('error', function (err) {
exercise.emit(
'fail'
, exercise.__('fail.connection', {address: url, message: err.message})
)
})
.pipe(stream)
}

connect(this.submissionPort, this.submissionStdout)

if (mode == 'verify')
connect(this.solutionPort, this.solutionStdout)
}


// add a processor only for 'verify' calls
exercise.addVerifyProcessor(function (callback) {
var exercise = this
, badCalls = Object.keys(exercise.wrapData.fsCalls).filter(function (m) {
exercise.emit('fail', exercise.__('fail.no_createReadStream', {method: 'fs.' + m + '()'}))
return !(/createReadStream/).test(m)
})

callback(null, badCalls.length === 0)
});

">未完待续,预告:实战起来,干干干。。。

微信