TDD
Test Drive Development
- 有利于更加專注軟件設(shè)計(jì)歇竟;
- 清晰地了解軟件的需求;
- 很好的詮釋了代碼即文檔敏储。
TDD是一種相對(duì)于普通思維的方式來說觉鼻,比較極端的一種做法。我們一般能想到的是先編寫業(yè)務(wù)代碼棋凳,然后為其編寫測(cè)試代碼拦坠,用來驗(yàn)證產(chǎn)品方法是不是按照設(shè)計(jì)工作。而TDD的思想正好與之相反贫橙,在TDD的世界中贪婉,我們應(yīng)該首先根據(jù)需求或者接口情況編寫測(cè)試,然后再根據(jù)測(cè)試來編寫業(yè)務(wù)代碼卢肃,而這其實(shí)是違反傳統(tǒng)軟件開發(fā)中的先驗(yàn)認(rèn)知的.
我們可以舉一個(gè)生活中類似的例子來說明TDD的必要性:有經(jīng)驗(yàn)的砌磚師傅總是會(huì)先拉一條垂線疲迂,然后沿著線砌磚,因?yàn)橛兄本€的保證莫湘,因此可以做到筆直整齊尤蒿;而新入行的師傅往往二話不說直接開工,然后在一階段完成后再用直尺垂線之類的工具進(jìn)行測(cè)量和修補(bǔ)幅垮。
TDD的好處不言自明腰池,因?yàn)榭偸窍葴y(cè)試,再編碼,所以至少你的所有代碼的public部分都應(yīng)該含有必要的測(cè)試示弓。另外讳侨,因?yàn)闇y(cè)試代碼實(shí)際是要使用產(chǎn)品代碼的,因此在編寫產(chǎn)品代碼前你將有一次深入思考和實(shí)踐如何使用這些代碼的機(jī)會(huì)奏属,這對(duì)提高設(shè)計(jì)和可擴(kuò)展性有很好的幫助跨跨,試想一下你測(cè)試都很難寫的接口,別人(或者自己)用起來得多糾結(jié)囱皿。在測(cè)試的準(zhǔn)繩下勇婴,你可以有目的有方向地編碼;另外嘱腥,因?yàn)橛袦y(cè)試的保護(hù)耕渴,你可以放心對(duì)原有代碼進(jìn)行重構(gòu),而不必?fù)?dān)心破壞邏輯齿兔。這些其實(shí)都指向了一個(gè)最終的目的:讓我們快樂安心高效地工作橱脸。
BDD
Behavior Drive Development
XCTest(作者注:蘋果官方測(cè)試框架)是基于OCUnit的傳統(tǒng)測(cè)試框架,在書寫性和可讀性上都不太好愧驱。在測(cè)試用例太多的時(shí)候慰技,由于各個(gè)測(cè)試方法是割裂的,想在某個(gè)很長(zhǎng)的測(cè)試文件中找到特定的某個(gè)測(cè)試并搞明白這個(gè)測(cè)試是在做什么并不是很容易的事情组砚。所有的測(cè)試都是由斷言完成的,而很多時(shí)候斷言的意義并不是特別的明確掏颊,對(duì)于項(xiàng)目交付或者新的開發(fā)人員加入時(shí)糟红,往往要花上很大成本來進(jìn)行理解或者轉(zhuǎn)換。另外乌叶,每一個(gè)測(cè)試的描述都被寫在斷言之后盆偿,夾雜在代碼之中,難以尋找准浴。使用XCTest測(cè)試另外一個(gè)問題是難以進(jìn)行mock或者stub事扭,而這在測(cè)試中是非常重要的一部分。
行為驅(qū)動(dòng)開發(fā)(BDD)正是為了解決上述問題而生的乐横,作為第二代敏捷方法求橄,BDD提倡的是通過將測(cè)試語句轉(zhuǎn)換為類似自然語言的描述,開發(fā)人員可以使用更符合大眾語言的習(xí)慣來書寫測(cè)試葡公,這樣不論在項(xiàng)目交接/交付罐农,或者之后自己修改時(shí),都可以順利很多
如果說作為開發(fā)者的我們?nèi)粘9ぷ魇菍懘a催什,那么BDD其實(shí)就是在講故事涵亏。一個(gè)典型的BDD的測(cè)試用例包活完整的三段式上下文,測(cè)試大多可以翻譯為Given..When..Then的格式,讀起來輕松愜意气筋。BDD在其他語言中也已經(jīng)有一些框架拆内,包括最早的Java的JBehave和赫赫有名的Ruby的RSpec和Cucumber。而在objc社區(qū)中BDD框架也正在欣欣向榮地發(fā)展宠默,得益于objc的語法本來就非常接近自然語言麸恍,再加上C語言宏的威力,我們是有可能寫出漂亮優(yōu)美的測(cè)試的光稼。在objc中或南,現(xiàn)在比較流行的BDD框架有cedar,specta和Kiwi艾君。
Quick + Nimble In Swift
https://github.com/Quick/Quick
// Swift
import Quick
import Nimble
class TableOfContentsSpec: QuickSpec {
override func spec() {
describe("the 'Documentation' directory") {
it("has everything you need to get started") {
let sections = Directory("Documentation").sections
expect(sections).to(contain("Organized Tests with Quick Examples and Example Groups"))
expect(sections).to(contain("Installing Quick"))
}
context("if it doesn't have what you're looking for") {
it("needs to be updated") {
let you = You(awesome: true)
expect{you.submittedAnIssue}.toEventually(beTruthy())
}
}
}
}
}
Nimble
https://github.com/Quick/Nimble
!Swift
expect(1 + 1).to(equal(2))
expect(1.2).to(beCloseTo(1.1, within: 0.1))
expect(3) > 2
expect("seahorse").to(contain("sea"))
expect(["Atlantic", "Pacific"]).toNot(contain("Mississippi"))
expect(ocean.isClean).toEventually(beTruthy())
Specta
https://github.com/specta/specta
A light-weight TDD / BDD framework for Objective-C.
FEATURES
- An Objective-C RSpec-like BDD DSL
- Quick and easy set up
- Built on top of XCTest
- Excellent Xcode integration
Specta BDD DSL
- SpecBegin 聲明了一個(gè)測(cè)試類采够,SpecEnd 結(jié)束了類聲明
- describe (context) 塊聲明了一組實(shí)例
- it (example/specify) 是一個(gè)單一的例子
- beforeAll 是一個(gè)運(yùn)行于所有同級(jí)塊之前的塊,只運(yùn)行一次冰垄。afterAll 與beforeAll相反蹬癌,是在所有同級(jí)塊之后運(yùn)行的塊,只運(yùn)行一次虹茶。
- beforeEach/afterEach逝薪,在每個(gè)同級(jí)塊運(yùn)行的時(shí)候,都會(huì)運(yùn)行一次蝴罪,而beforeAll/afterAll只會(huì)運(yùn)行一次
- it/waitUntil/done()董济,異步調(diào)用,注意完成異步操作之后要门,必須調(diào)用done()函數(shù)虏肾,如下:
!Objc
it(@"should do some stuff asynchronously", ^{
waitUntil(^(DoneCallback done) {
// Async example blocks need to invoke done() callback.
done();
});
});
Expecta
https://github.com/specta/expecta
A matcher framework for Objective-C and Cocoa.
!Objc
waitUntil(^(DoneCallback done) {
//query
[manager getSedentaryDataModels:^(NSArray < DMSedentaryDataModel * > *modelList) {
tempModel = modelList.firstObject;
expect(modelList.count).to.equal(1);
done();
}];
});
expect(tempModel).notTo.beNil();
expect(tempModel.prKey).notTo.equal(0);
OCMock
http://ocmock.org/reference/#creating-mock-objects
- Creating mock objects
- Stubbing methods
- Verifying interactions
!Objc
__block id mockConnection = nil;
beforeAll(^{
});
afterAll(^{
});
beforeEach(^{
mockConnection = OCMClassMock([TwitterConnection class]);
});
afterEach(^{
[mockConnection stopMocking];
});
it(@"is should be success", ^{
TwitterViewController *controller = [[TwitterViewController alloc] init];
controller.connection = mockConnection;
//模擬fetchTweets方法返回預(yù)設(shè)值
Tweet *testTweet = [[Tweet alloc] init];
testTweet.userName = @"齊滇大圣";
Tweet *testTweet2 = [[Tweet alloc] init];
testTweet2.userName = @"美猴王";
NSArray *tweetArray = @[testTweet,testTweet2];
OCMStub([mockConnection fetchTweets]).andReturn(tweetArray);
//模擬出來一個(gè)view類
id mockView = OCMClassMock([TweetView class]);
controller.tweetView = mockView;
//這里執(zhí)行updateTweetView之后,[mockView addTweet:]加入了testTweet和testTweet2
[controller updateTweetView];
OCMVerify([mockView addTweet:testTweet]);
OCMVerify([mockView addTweet:testTweet2]);
OCMVerify([mockView addTweet:[OCMArg any]]);
Slather
https://github.com/SlatherOrg/slather
!Objc
source ~/.bash_profile
cd ${SRCROOT}
rm -rf ${SRCROOT}/slather-html
slather coverage --html --output-directory ${SRCROOT}/slather-html --scheme DeviceManager --workspace ${SRCROOT}/DeviceManager.xcworkspace ${SRCROOT}/DeviceManager.xcodeproj
open 'slather-html/index.html'