本系列编号基本对应stanford CS193P的课程编号,可能有一两节课的误差:比如我标(1)就对应Lecture 1,但有时我做得快了就变成(1)对应lecture 1的全部和lecture 2的一部分。
正文:
本文包括三个类,PlayingCard和PlayingDeck,两个单元测试类,和两个单元测试的用到的工具类。
写完之后的感觉:只要写完view controller这个程序就能在iphone上有个用户界面可以点了,刚做出来的时候还是挺有成就感的。在看swift的电子书只看了一两章的时候我就想把这个做出来,但是没写对。
后来在网上一搜发现别人也有在做这个,而且也在问这个怎么写。然后我借鉴他贴的代码把这个写出来了。再后面把书又读了几章,才决定再次重新开始继续看这个CS193P的课程,这次总算能跟上进度了。
具体值得记录的点有:
1.每个类都可以选TargetMembership,单元测试类应该只选CardGameTest,待测类应该同时选CardGame和CardGameTest。CardGame是我的应用名字。
选对之后我就不用一直用默认提供的CardGameTests这个类了,我后面写了两个分开的单元测试类
2.PlayingCard类里,suit和rank做了合法值校验,比如rank不能大于13,suit不能取四种花色以外的值。
这点在CS193P里,OC下用的做法是重载了这两个变量的setter,我的做法是用了swift的did set功能来校验。
3.PlayingCardTests是我写来测试PlayingCard的。原CS193P的老外是不写单元测试的。但我现在主职业是做测试的,所以我写了单元测试。
思路是一点一点校验PlayingCard类提供的所有东西,包括suit和rank的默认值或最大值,单个变量成功的设置、不成功的设置时的校验、
4.PlayingCardDeckTests里我通过检查deck里所有元素来检查待测的init方法。
并且在其后做了2次抽牌,抽牌之前先记录当前卡组,做随机抽牌,抽牌之后先用正则表达式证明抽到的牌在抽牌之前在卡组中,再用正则表达式判断被抽出的牌在抽牌之后不再在卡组中。
5.DeckPrintHelper只是用来打印当前卡组中的所有牌和打印随机抽出的牌,为了减少重复代码用。Regex是我从网上找来的swift正则表达式封装好的工具类
代码:
首先是ViewController,里面只有一个IBOutlet和一个IBAction
1 // 2 // ViewController.swift 3 // CardGame 4 // 5 // Created by colin.zt on 14-6-6. 6 // Copyright (c) 2014 Ting. All rights reserved. 7 // 8 9 import UIKit 10 11 class ViewController: UIViewController { 12 13 14 @IBOutlet var flipCount : UILabel 15 16 var flipCountNum: Int = 0 17 override func viewDidLoad() { 18 super.viewDidLoad() 19 // Do any additional setup after loading the view, typically from a nib. 20 } 21 22 override func didReceiveMemoryWarning() { 23 super.didReceiveMemoryWarning() 24 25 // Dispose of any resources that can be recreated. 26 } 27 28 @IBAction func touchButton(sender : UIButton) { 29 //check the card title to know which side is upside 30 //then flip it by change the background image and title 31 if (sender.currentTitle.isEmpty){ 32 sender.setTitle("A♣?", forState: UIControlState.Normal) 33 sender.setBackgroundImage(UIImage(named:"CardFront"),forState: UIControlState.Normal) 34 } else { 35 sender.setTitle("", forState: UIControlState.Normal) 36 sender.setBackgroundImage(UIImage(named:"CardBack"),forState: UIControlState.Normal) 37 } 38 //increse the flip count number 39 flipCountNum++ 40 //update the flip count on screen 41 setFlipCount() 42 } 43 44 func setFlipCount(){ 45 // update the flip count by reset the text of the label 46 flipCount.text = "Flips:" + String(flipCountNum) 47 48 } 49 50 51 }
然后是PlayingCard类,他是Card类的子类
1 // 2 // PlayingCard.swift 3 // CardGame 4 // 5 // Created by colin.zt on 14-10-18. 6 // Copyright (c) 2014 Ting. All rights reserved. 7 // 8 9 import UIKit 10 11 class PlayingCard: Card { 12 //in did set, do a check and make it can‘t set to invalid value 13 var suit: String = "?" { 14 didSet { 15 //#####could improve or not: is there a simpler way to check if an element is in an array? 16 for it in PlayingCard.valid_suits{ 17 if it == suit{ 18 return 19 } 20 } 21 suit = oldValue 22 } 23 } 24 //in did set, do a check and make it can‘t set to invalid value 25 var rank: Int = 0 { 26 didSet { 27 if rank > PlayingCard.max_rank{ 28 rank = oldValue 29 } 30 31 } 32 } 33 34 //all valid ranks in this game,"?" means unset 35 class var rank_strings:String[] { 36 return ["?","A","2","3","4","5","6","7","8","9","10","J","Q","K"] 37 } 38 39 //define the max rank 40 class var max_rank:Int{ 41 return rank_strings.count - 1 42 } 43 44 //all valid suits 45 class var valid_suits:String[]{ 46 return ["♥?","♦?","♠?","♣?"] 47 } 48 49 init() {} 50 51 //just pirnt the rank and suit, and rank 11 will be rank "J",and so n 52 func contents() -> String{ 53 return PlayingCard.rank_strings[self.rank]+self.suit 54 } 55 56 57 }
接着是PlayingCardDeck类,他是Deck类的子类
1 // 2 // PlayingCardDeck.swift 3 // CardGame 4 // 5 // Created by colin.zt on 14-10-20. 6 // Copyright (c) 2014 Ting. All rights reserved. 7 // 8 9 import UIKit 10 11 class PlayingCardDeck: Deck { 12 //just init the deck with 52 cards 13 init(){ 14 super.init() 15 for suit in PlayingCard.valid_suits{ 16 for (var rank = 1; rank <= PlayingCard.max_rank; rank++){ 17 var card: PlayingCard = PlayingCard() 18 card.rank = rank 19 card.suit = suit 20 cards.append(card) 21 } 22 } 23 24 } 25 26 }
为了测试这两个类,写了两个单元测试:
PlayingCardTests
1 // 2 // PlayingCardTests.swift 3 // CardGame 4 // 5 // Created by colin.zt on 14-10-18. 6 // Copyright (c) 2014 Ting. All rights reserved. 7 // 8 9 import XCTest 10 11 class PlayingCardTests: XCTestCase { 12 13 override func setUp() { 14 super.setUp() 15 // Put setup code here. This method is called before the invocation of each test method in the class. 16 } 17 18 override func tearDown() { 19 // Put teardown code here. This method is called after the invocation of each test method in the class. 20 super.tearDown() 21 } 22 23 func testExample() { 24 // This is an example of a functional test case. 25 var p_card: PlayingCard = PlayingCard() 26 27 println(PlayingCard.valid_suits) 28 println(PlayingCard.rank_strings) 29 println(PlayingCard.max_rank) 30 31 32 XCTAssert(PlayingCard.valid_suits == ["♥?","♦?","♠?","♣?"], "Pass") 33 XCTAssert(PlayingCard.rank_strings == ["?","A","2","3","4","5","6","7","8","9","10","J","Q","K"], "Pass") 34 XCTAssert(PlayingCard.max_rank == 13, "Pass") 35 36 XCTAssert(p_card.contents == "??","Pass") 37 println(p_card.contents) 38 39 p_card.suit = "♥?" 40 p_card.rank = 11 41 println("this card:\(p_card.contents)") 42 XCTAssert(p_card.contents == "J♥?","Pass") 43 44 p_card.suit = "♀" // try to set an invalid value, it will be ignored because I do the check in didSet of the suit 45 println(p_card.contents) 46 XCTAssert(p_card.contents == "J♥?","Pass") 47 48 49 p_card.suit = "♣?" 50 println(p_card.contents) 51 XCTAssert(p_card.contents == "J♣?","Pass") 52 53 p_card.rank = 33 54 println(p_card.contents) 55 XCTAssert(p_card.contents == "J♣?","Pass") 56 57 p_card.rank = 13 58 println(p_card.contents) 59 XCTAssert(p_card.contents == "K♣?","Pass") 60 61 } 62 63 func testPerformanceExample() { 64 // This is an example of a performance test case. 65 self.measureBlock() { 66 // Put the code you want to measure the time of here. 67 } 68 } 69 70 }
PlayingCardDeckTests
1 // 2 // PlayingCardDeckTests.swift 3 // CardGame 4 // 5 // Created by colin.zt on 14-10-20. 6 // Copyright (c) 2014 Ting. All rights reserved. 7 // 8 9 import XCTest 10 11 class PlayingCardDeckTests: XCTestCase { 12 13 override func setUp() { 14 super.setUp() 15 // Put setup code here. This method is called before the invocation of each test method in the class. 16 } 17 18 override func tearDown() { 19 // Put teardown code here. This method is called after the invocation of each test method in the class. 20 super.tearDown() 21 } 22 23 func testExample() { 24 // This is an example of a functional test case. 25 26 //to test the init method, I create a new deck 27 var card_deck: PlayingCardDeck = PlayingCardDeck() 28 29 // the expected result 30 var expected_card_string: NSString = "A♥?,2♥?,3♥?,4♥?,5♥?,6♥?,7♥?,8♥?,9♥?,10♥?,J♥?,Q♥?,K♥?,A♦?,2♦?,3♦?,4♦?,5♦?,6♦?,7♦?,8♦?,9♦?,10♦?,J♦?,Q♦?,K♦?,A♠?,2♠?,3♠?,4♠?,5♠?,6♠?,7♠?,8♠?,9♠?,10♠?,J♠?,Q♠?,K♠?,A♣?,2♣?,3♣?,4♣?,5♣?,6♣?,7♣?,8♣?,9♣?,10♣?,J♣?,Q♣?,K♣?," 31 32 //print and check the deck, if it has all 52 cards 33 XCTAssert( DeckPrintHelper.printDeck(card_deck) == expected_card_string,"Pass" ) 34 35 //check the card number in the deck 36 XCTAssert(card_deck.cards.count == 52, "Pass") 37 38 //draw a card and check again 39 var draw_this_card = DeckPrintHelper.drawRandomCard(card_deck) 40 // Regex("K♥?").is_found_in(DeckPrintHelper.printDeck(card_deck)) 41 // Regex(",").is_found_in(DeckPrintHelper.printDeck(card_deck)) 42 XCTAssert( Regex(draw_this_card.contents).is_found_in(expected_card_string),"Pass" ) 43 // use regex to check if still contain this card 44 var after_draw_once = DeckPrintHelper.printDeck(card_deck) 45 XCTAssert( Regex(draw_this_card.contents).is_found_in(after_draw_once) == false,"Pass" ) 46 XCTAssert(card_deck.cards.count == 51, "Pass") 47 48 //do it again 49 draw_this_card = DeckPrintHelper.drawRandomCard(card_deck) 50 XCTAssert( Regex(draw_this_card.contents).is_found_in(after_draw_once),"Pass" ) 51 XCTAssert( Regex(draw_this_card.contents).is_found_in(DeckPrintHelper.printDeck(card_deck)) == false,"Pass" ) 52 XCTAssert(card_deck.cards.count == 50, "Pass") 53 54 55 } 56 57 58 59 func testPerformanceExample() { 60 // This is an example of a performance test case. 61 self.measureBlock() { 62 // Put the code you want to measure the time of here. 63 } 64 } 65 66 }
其中有写打印所有card的方法写在了工具类里
DeckPrintHelper
1 // 2 // DeckPrintHelper.swift 3 // CardGame 4 // 5 // Created by colin.zt on 14-10-20. 6 // Copyright (c) 2014 Ting. All rights reserved. 7 // 8 9 //a help class which contains some test logic for reuse 10 import UIKit 11 class DeckPrintHelper: NSObject { 12 13 // to print and return the deck string in my format 14 class func printDeck(deck: Deck) -> String{ 15 var card_string: String = "" 16 println("\n_______________________________________") 17 println("Now these cards in deck") 18 for card in deck.cards{ 19 card_string += ("\(card.contents),") 20 } 21 println(card_string) 22 println("_______________________________________") 23 24 return card_string 25 } 26 27 // to draw a random card while printing some comments 28 class func drawRandomCard(deck: Deck) -> Card{ 29 var random_card = deck.drawRandomCard() 30 println("Now draw this random card:") 31 println(random_card.contents) 32 return random_card 33 } 34 }
还有一个正则表达式的工具类
Regex
1 // 2 // Regec.swift 3 // CardGame 4 // 5 // Created by colin.zt on 14-10-20. 6 // Copyright (c) 2014年 Ting. All rights reserved. 7 // 8 9 import Foundation 10 11 class Regex: NSObject { 12 let internalExpression: NSRegularExpression 13 let pattern: String 14 15 init(_ pattern: String) { 16 self.pattern = pattern 17 var error: NSError? 18 self.internalExpression = NSRegularExpression(pattern: pattern, options: .CaseInsensitive, error: &error) 19 } 20 21 func is_found_in(input: String) -> Bool { 22 let matches = self.internalExpression.matchesInString(input, options: nil, range:NSMakeRange(0, countElements(input))) 23 var count = matches.count 24 return matches.count > 0 25 } 26 } 27 28 //Usage Example: Regex("\\w{4}").is_found_in("abdddcd")