TDDでじゃんけんアプリ作った!

初めまして!きゅうすけと申します!

iOSエンジニアの私が、自社の定例イベント 「TokyoUppersBoost」 で学んだ基礎の部分をまとめたものです!

メモ感覚ではありますが、これ違うぞってことがあったらぜひ教えてくださいー!(● ˃̶͈̀ロ˂̶͈́)੭ꠥ⁾⁾

今回は、「iOSにおけるunit testハンズオン」という企画でした。


概要


  • TDDについての座学まとめ

  • Unit Testハンズオンまとめ

早速座学のまとめからしていきます!


TDD とは

テスト駆動開発 (Test-Driben Deberopment)のことです。

普通の開発は

実装 → テストコードを書く → テスト

で進めていきます。

それに対して TDD

テストコードを書く → テストコードが通る実装とリファクタリング

普通の開発と逆の流れで進めていきます。


メリットとデメリット


メリット


  • テストコードがドキュメントとして残せる

  • 仕様を確認しやすい

  • リファクタリング時の心理的な負担がへる

  • ペアプロにもってこい(1人がテストコード、1人が実装)


デメリット

ほぼ二倍のコードを書くため、

2倍コードをかけるように、または1/2の量でコードをかけるようになる必要がある


実装サイクル

Red → Green → Refactor のサイクルで行う

・Red

絶対に失敗するテストコードを書く

テストが通らないものを確認する

└ 「このテストコードを書いて通ったらおかしい」を知る

・Green

テストの通る最低限の実装をする

・Refactor

テストをオールグリーンにした状態で、ソースコードを綺麗な状態にして見やすくする


Unit Testハンズオン開始!

今回のハンズオンで作ったのは、じゃんけんアプリです!

↓こちらのgithubをクローンして作成していきます!

https://github.com/mht-mikiya-okugawa/SampleJanken1.git


ハンズオンの流れ

テストかく (Red)



コンパイルエラーを解消する



テストが通る処理をかく (Green)



RedとGreenを繰り返す



Refactor



ViewControllerでViewの調整


Red / Greenテスト

以下テストのコードになります!オールグリーンの状態です!


SampleJanken1Tests.swift


import XCTest
@testable import SampleJanken1

class SampleJanken1Tests: XCTestCase {

override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
}

override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}

//パターンscissors
//あいこ
func testYouScissorsDrow() {
let yourHandScissors: Int = 1
let pcScissors: Int = 1
let janken = Janken()
let rps = janken.rockPeparScissors(pc: pcScissors, you: yourHandScissors)
XCTAssertEqual(rps, "あいこ")
}
//かち
func testYouScissorsWin() {
let yourHandScissors: Int = 1
let pcPaper = 2
let janken = Janken()
let rps = janken.rockPeparScissors(pc: pcPaper, you: yourHandScissors)
XCTAssertEqual(rps, "あなたの勝ち")
}
//負け
func testYouScissorsLoose() {
let yourHandScissors: Int = 1
let pcRock = 0
let janken = Janken()
let rps = janken.rockPeparScissors(pc: pcRock, you: yourHandScissors)
XCTAssertEqual(rps, "あなたの負け")
}

//パターンRock
func testYouRockDrow() {
let yourHandRock: Int = 0
let pcRock: Int = 0
let janken = Janken()
let rps = janken.rockPeparScissors(pc: pcRock, you: yourHandRock)
XCTAssertEqual(rps, "あいこ")
}
//かち
func testYouRockWin() {
let yourHandRock: Int = 0
let pcPaper: Int = 1
let janken = Janken()
let rps = janken.rockPeparScissors(pc: pcPaper, you: yourHandRock)
XCTAssertEqual(rps, "あなたの勝ち")
}

//負け
func testYouRockLoose() {
let yourHandRock: Int = 0
let pcScissors: Int = -1
let janken = Janken()
let rps = janken.rockPeparScissors(pc: pcScissors, you: yourHandRock)
XCTAssertEqual(rps, "あなたの負け")
}

//パターンぱー
//あいこ
func testYouPaperDrow() {
let yourHandPaper: Int = 2
let pcPaper: Int = 2
let janken = Janken()
let rps = janken.rockPeparScissors(pc: pcPaper, you: yourHandPaper)
XCTAssertEqual(rps, "あいこ")
}
//かち
func testYouPaperWin() {
let yourHandPaper: Int = 2
let pcRock: Int = 3
let janken = Janken()
let rps = janken.rockPeparScissors(pc: pcRock, you: yourHandPaper)
XCTAssertEqual(rps, "あなたの勝ち")
}
//負け
func testYouPaperLoose() {
let yourHandPaper: Int = 2
let pcScissors: Int = 1
let janken = Janken()
let rps = janken.rockPeparScissors(pc: pcScissors, you: yourHandPaper)
XCTAssertEqual(rps, "あなたの負け")
}

//ランダムにCPに手を出してもらう-1~3の間が帰ってきてほしい
func testRandomCpHand() {
let janken = Janken()
// 100回テストしてくれる → コンソールで結果確認
for num in 0...100 {
let result: Int = janken.randomHand()
print ("Result" + String(num) + "回目" + String(result))
XCTAssertEqualWithAccuracy(1.0, Float(result), accuracy: 2.0,"成功")
}
}
}



Jsnken.swift

import Foundation

open class Janken {
public func rockPeparScissors(pc: Int, you: Int) -> String {
let paper1: Int = -1
let rock1: Int = 0
let scissors1: Int = 1
let paper2: Int = 2
let rock2: Int = 3

if you == scissors1 {
if pc == scissors1 {
return "あいこ"
} else if pc == paper1 || pc == paper2 {
return "あなたの勝ち"
} else if pc == rock1 || pc == rock2 {
return "あなたの負け"
} else {
return ""
}
} else if you == rock1 {
if pc == rock1 || pc == rock2 {
return "あいこ"
} else if pc == scissors1 {
return "あなたの勝ち"
} else if pc == paper1 || pc == paper2 {
return "あなたの負け"
} else {
return ""
}
} else if you == paper2 {
if pc == paper2 || pc == paper1 {
return "あいこ"
} else if pc == rock1 || pc == rock2 {
return "あなたの勝ち"
} else if pc == scissors1 {
return "あなたの負け"
} else {
return ""
}
} else {
return ""
}
}

public func randomHand() -> Int {
// -1〜4よりも小さいメソッドを適当に返してくれる
return Int.random(in: -1..<4)
}
}



Refactor

次がJsnken.swiftを、じゃんけんの法則にそってリファクタしたコードです!

public func rockPeparScissors(pc: Int, you: Int) -> String {}の中身を書き換えます!


import Foundation

open class Janken {
// リファクタリング
public func rockPeparScissors(pc: Int, you: Int) -> String {
let jadge: Int = (you - pc + 3) % 3

switch jadge {
case 0:
return "あいこ"
case 1:
return "あなたの負け"
case 2:
return "あなたの勝ち"
default:
return ""
}
}

public func randomHand() -> Int {
// -1〜4よりも小さいメソッドを適当に返してくれる
return Int.random(in: -1..<4)
}
}


View調整

import UIKit

class ViewController: UIViewController {

@IBOutlet var pcHandImgView: UIImageView!
@IBOutlet var resultLabel: UILabel!
@IBOutlet var rockBtn: UIButton!

@IBOutlet var scissorsBtn: UIButton!
@IBOutlet var paperBtn: UIButton!
let rock: Int = 0
let scissors: Int = 1
let paper: Int = 2
let janken = Janken()

override func viewDidLoad() {
super.viewDidLoad()

rockBtn.addTarget(self, action: #selector(ViewController.youHandRock), for: .touchUpInside)
scissorsBtn.addTarget(self, action: #selector(ViewController.youHandScissors), for: .touchUpInside)
paperBtn.addTarget(self, action: #selector(ViewController.youHandPaper), for: .touchUpInside)

}

// グーを選んだ時
@objc func youHandRock() {
// テストで作ったランダムハンドのメソッドを呼ぶ
let pcHand: Int = janken.randomHand()
// ランダムハンドの結果を使って、画像を切り替える
pcImageChange(pcHand: pcHand)

// ボタンの背景色を変える
rockBtn.backgroundColor = UIColor.blue
scissorsBtn.backgroundColor = UIColor.white
paperBtn.backgroundColor = UIColor.white

// テストで作った、rockPeparScissorsけメソッドを呼んで、結果をresultLabelに入れる
resultLabel.text = janken.rockPeparScissors(pc: pcHand, you: rock)

}
// チョキを選んだ時
@objc func youHandScissors() {
// テストで作ったランダムハンドのメソッドを呼ぶ
let pcHand: Int = janken.randomHand()
// ランダムハンドの結果を使って、画像を切り替える
pcImageChange(pcHand: pcHand)

// ボタンの背景色を変える
rockBtn.backgroundColor = UIColor.white
scissorsBtn.backgroundColor = UIColor.blue
paperBtn.backgroundColor = UIColor.white

// テストで作った、rockPeparScissorsけメソッドを呼んで、結果をresultLabelに入れる
resultLabel.text = janken.rockPeparScissors(pc: pcHand, you: scissors)
}

// パーを選んだ時
@objc func youHandPaper() {
// テストで作ったランダムハンドのメソッドを呼ぶ
let pcHand: Int = janken.randomHand()
// ランダムハンドの結果を使って、画像を切り替える
pcImageChange(pcHand: pcHand)

// ボタンの背景色を変える
rockBtn.backgroundColor = UIColor.white
scissorsBtn.backgroundColor = UIColor.white
paperBtn.backgroundColor = UIColor.blue

// テストで作った、rockPeparScissorsけメソッドを呼んで、結果をresultLabelに入れる
resultLabel.text = janken.rockPeparScissors(pc: pcHand, you: paper)
}

// グーチョキパーを変えるメソッド
func pcImageChange(pcHand: Int) {
if pcHand == -1 || pcHand == 2 {
pcHandImgView.image = UIImage(named: "paper")
} else if pcHand == 0 || pcHand == 3 {
pcHandImgView.image = UIImage(named: "rock")
} else {
pcHandImgView.image = UIImage(named: "scissors")
}
}

}

これでアプリ完成です!!

色変えて遊んだり( ^ω^ )

スクリーンショット 2019-07-06 23.50.51.png

スクリーンショット 2019-07-06 23.50.58.png

スクリーンショット 2019-07-07 0.08.04.png




↓ 以下講師の方の記事を参考に作成しました!ぜひリポジトリをクローンしてやってみてください!

https://qiita.com/MHTcode_micky/private/bfbc80065d5e02cea849


終わりに

「このテストコードを書いて通ったらおかしい」を知るを把握することで

何かエラーが起きたときにも対処しやすいし、アプリの品質も担保できるというところがいいと思いました。

普通の開発のテストしかやったことがなかったので、今回できてよかったです!(*゚▽゚)ノ




私が参加している勉強会 「TokyoUppersBoost」への参加者さん大募集中です!!

一緒にiOSについて学びましょう・:*+.(( °ω° ))/.:+

↓私が作ったLPなのでよかったら見てください!٩( 'ω' )و

TokyoUppersBoostとは?