Skip to content

Instantly share code, notes, and snippets.

@cades
Last active May 27, 2016 08:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cades/38948999bca78342e8ab8e0a5ee6f397 to your computer and use it in GitHub Desktop.
Save cades/38948999bca78342e8ab8e0a5ee6f397 to your computer and use it in GitHub Desktop.
在 mocha 中模仿 rspec-given 語法
describe('App.login()', () => {
it('should give token if success', sync(function*() {
var subject = Object.create(App), result
subject.init({ authVerifier: fakeAuthVerifier })
result = yield subject.login('user', 'pass', resume())
assert(typeof result.token === 'string')
}))
it('should reject given no such user', sync(function*(){
var subject = Object.create(App), result, err
subject.init({ authVerifier: fakeAuthVerifier })
try { yield subject.login('no-such-user', 'pass', resume()) }
catch (e) { err = e }
assert(err.message === 'no such user')
}))
it('should reject given wrong password', sync(function*(){
var subject = Object.create(App), result, err
subject.init({ authVerifier: fakeAuthVerifier });
try { result = yield subject.login('user', 'wrong-pass', resume()) }
catch (e) { err = e }
assert(err.message === 'invalid password')
}))
})
var Given = before,
GivenEach = beforeEach,
When = beforeEach,
Then = it
describe('App.login()', () => {
var ctx = {}
afterEach(() => ctx = {})
GivenEach(() => ctx.subject = Object.create(App) )
GivenEach(() => ctx.subject.init({ authVerifier: fakeAuthVerifier }) )
When(sync(function*(){
try { ctx.result = yield ctx.subject.login(ctx.username, ctx.password, resume()) }
catch (e) { ctx.err = e }
}))
context('valid user & pass', () => {
Given(() => ctx.username = 'user' )
Given(() => ctx.password = 'pass' )
Then('issue a token', () => assert(typeof ctx.result.token === 'string'))
})
context('invalid user', () => {
Given(() => ctx.username = 'no-such-user' )
Given(() => ctx.password = 'pass' )
Then('is rejected', () => assert(ctx.err.message === 'no such user'))
})
context('wrong password', () => {
Given(() => ctx.username = 'user' )
Given(() => ctx.password = 'wrong-pass' )
Then('is rejected', () => assert(ctx.err.message === 'invalid password'))
})
})
describe('App.login() - back to mornal', () => {
var ctx = {}
afterEach(() => ctx = {})
beforeEach(() => ctx.subject = Object.create(App) )
beforeEach(() => ctx.subject.init({ authVerifier: fakeAuthVerifier }) )
beforeEach(sync(function*(){
try { ctx.result = yield ctx.subject.login(ctx.username, ctx.password, resume()) }
catch (e) { ctx.err = e }
}))
context('valid user & pass', () => {
before(() => ctx.username = 'user' )
before(() => ctx.password = 'pass' )
it('issue a token', () => assert(typeof ctx.result.token === 'string'))
})
context('invalid user', () => {
before(() => ctx.username = 'no-such-user' )
before(() => ctx.password = 'pass' )
it('is rejected', () => assert(ctx.err.message === 'no such user'))
})
context('wrong password', () => {
before(() => ctx.username = 'user' )
before(() => ctx.password = 'wrong-pass' )
it('is rejected', () => assert(ctx.err.message === 'invalid password'))
})
})

這個實驗展示了 test code 可讀性演進的過程. 被測物是一個 prototype 為 App 的 object, 我們會測試 .login() 執行成功與失敗的情況.
整個過程我們都採用 mocha 測試框架, 如果你對 mocha 不熟悉, 可以先看看它的文件.

1-first-attempt.js 結構清楚, 採用 AAA(Arrang-Act-Assert) 三段敘述, 但仔細看, 每個 it block 都有不少重複的 code.

2- mimic-rspec-using-alias.js 替 it/before/beforeEach 取別名, 成為 Given/When/Then (由於before與beforeEach的執行順序無法完全模仿 rspec-given, 這裡增加了一個 GivenEach). 使用 Given/When/Then 並且讓每一個敘述都保持一行, 程式變得簡潔許多. 每個 context 中「變動的參數」與「assert的條件」被突顯出來, 沒有被細節淹沒.

3-back-to-normal.js 則是把 alias 還原, 看看 mocha 原生 API 的表現. 好像不那麼自然了? 的確, 使用 before, it 這些詞彙時, 我們不會想這樣使用它們. 語言與詞彙會影響使用者的思考方式.

參考資料

  1. How to stop hating your tests (啟發這個實驗的片段在 8:30~11:00)
  2. Better Specs { rspec guidelines with ruby }
  3. rspec subject
  4. rspec shared example
  5. rspec-given
  6. RSpec-Given 與 RSpec-Spies
  7. lazy variables with mocha js
  8. bdd-lazy-var
  9. alias 的靈感來源
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment