Swift Performance Test Using XCTest
Swift Unit Test
1. Measure time interval code snippet π©βπ»
κ°λ¨ν μ½λ ν μ€νΈ μ μ μ©ν λ°©λ²μΌλ‘, μ½λ μ€ν μ νμ μκ°μ°¨λ₯Ό μΆλ ₯νλ€.
func measureTimeInterval(closure: () -> ()) -> TimeInterval {
let start = CFAbsoluteTimeGetCurrent()
closure()
return CFAbsoluteTimeGetCurrent() - start
}
func testCode(_ closure: () -> ()) {
print("Execution time: \(measureTimeInterval(closure: closure))")
}
testCode {
_ = (1...10000).reduce(0) {
$0 + $1
}
}
Execution time: 0.057237982749938965
2. Perform Test using XCTest π©βπ»
1. Example Cases
Swift Playground
κ°μ κ³³μμ μ¬μ©νκΈ°μλ μ λ°©λ²μ΄ μ½κ³ νΈνλ€. νμ§λ§ μ λ°©λ²μ μ€μ λ‘ μ±μ κ°λ°νλ©΄μ μ¬μ©ν μ μλ
λ°©λ²μ μλλ€. μλ μ£Όμ¬μμ isEven
μ Computed Properties
λ‘ μ¬μ©ν λμ Methods
λ‘ μ¬μ©ν λ μ΄λ€ μ°¨μ΄κ° μλμ§
XCTest
λ₯Ό μ΄μ©ν΄ μ±λ₯ ν
μ€νΈλ₯Ό ν΄λ³΄λλ‘νμ.
struct Dice {
@OneToSix var currentDice: Int
var isEven: Bool {
currentDice.isMultiple(of: 2)
}
mutating func rollDice() {
currentDice = Int.random(in: 1...6)
}
func printDice() {
print("The current numer of dice is \(currentDice), then is \(isEven ? "even" : "odd") number.")
}
}
@propertyWrapper
struct OneToSix {
private var number = 1
var wrappedValue: Int {
get { number }
set {
switch newValue {
case 1...6: number = newValue
default: fatalError("Out of range.")
}
}
}
}
2. Make Sample Project
Playground
κ° μλ Xcode Project
λ₯Ό μμ±νλλ‘νλ€. μ΄λ νλ‘μ νΈλ₯Ό App
μΌλ‘ μμ±ν κ²½μ° μλμ κ°μ΄ Include Test
λ₯Ό
μ ννλ©΄ νλ‘μ νΈμ Tests Group
κ³Ό UITests Group
μ΄ κΈ°λ³ΈμΌλ‘ ν¬ν¨λλ―λ‘ 3. Add Test Target Groups λ‘ κ±΄λλ΄λ€.
νμ§λ§ νλ‘μ νΈλ₯Ό Command Line Tool
λ‘ μμ±ν κ²½μ° Include Tests
λ₯Ό μ ννλ κ²μ΄ λΆκ°λ₯νλ€. λν App
μΌλ‘ λ§λ νλ‘μ νΈλλΌλ
Tests
λ₯Ό ν¬ν¨νμ§ μκ³ μμ±ν νλ‘μ νΈμΌ κ²½μ°λ μ§μ Tests
λ₯Ό μΆκ°ν΄μ€μΌνλ€.
μλμ κ°μ΄ Command Line Tool
λ‘ νλ‘μ νΈλ₯Ό μ ννκ³ , μ΄λ¦μ Computed-Properties
λ‘ μν νλ‘μ νΈλ₯Ό μμ±νλ€.
κ·Έλ¦¬κ³ λ€μ μ½λλ₯Ό κ° Swift νμΌμ μμ±νλ€.
- main.swift
import Foundation
struct App {
func getValueFromComputedProperties() {
let dice = DiceOne()
(1...100000).forEach { _ in
_ = dice.isEven
}
}
func getValueFromClosures() {
let dice = DiceTwo()
(1...100000).forEach { _ in
_ = dice.isEven()
}
}
}
- DiceOne.swift
import Foundation
struct DiceOne {
@OneToSix var currentDice: Int
var isEven: Bool {
currentDice.isMultiple(of: 2)
}
mutating func rollDice() {
currentDice = Int.random(in: 1...6)
}
func printDice() {
print("The current numer of dice is \(currentDice), then is \(isEven ? "even" : "odd") number.")
}
}
@propertyWrapper
struct OneToSix {
private var number = 1
var wrappedValue: Int {
get { number }
set {
switch newValue {
case 1...6: number = newValue
case ..<1: number = 1
case 7...: number = 6
default: fatalError("Maybe \(newValue) is not an integer value.")
}
}
}
}
- DiceTwo.swift
import Foundation
struct DiceTwo {
@OneToSix var currentDice: Int
func isEven() -> Bool {
currentDice.isMultiple(of: 2)
}
mutating func rollDice() {
currentDice = Int.random(in: 1...6)
}
func printDice() {
print("The current numer of dice is \(currentDice), then is \(isEven() ? "even" : "odd") number.")
}
}
3. Add Test Target Groups
νλ‘μ νΈ μ€μ μμ +
λ₯Ό μ΄μ©ν΄ μΆκ°λ₯Ό νκ±°λ File-New-Target...
λ₯Ό μ΄μ©ν΄ μΆκ°λ₯Ό νλ€.
(νλ‘μ νΈ μ€μ μμ `+`λ₯Ό μ΄μ©ν΄ μΆκ°)
(`File-New-Target...`λ₯Ό μ΄μ©ν΄ μΆκ°)
Unit Testing Bundle
μ μ ν ν Computed-PropertiesTests
λΌλ μ΄λ¦μΌλ‘ μμ±νλ€.
μΌλ°μ μΌλ‘
Tests Groups
μ΄λ¦μ
Unit Testing Bundle
μProject Name
+Tests
λ‘ λ§λ€κ³ ,
UI Testing Bundle
μProject Name
+UITests
λ‘ λ§λ λ€.
μλμ κ°μ΄ Computed-PropertiesTests
λΌλ μ΄λ¦μ κ·Έλ£Ήκ³Ό XCTestCase
λ₯Ό μμν μν Class
κ° μμ±λλ€.
4. Edit Scheme for Tests
Product-Scheme-Edit Scheme...
λλ λ¨μΆν€ β <
λ₯Ό μ΄μ©ν΄ νλ‘μ νΈμ Scheme
μμ μ μ κ·Όνλ€.
νμ¬ Build
λ νκ²μ΄ μ‘νμμ§λ§
Test
λ νκ²μ΄ μ‘νμμ§ μμ κ²μ λ³Ό μ μλ€. μ΄κ²μ μ‘μμ€μΌνλ€.
μλ μΊ‘μ²λ₯Ό μ°Έκ³ ν΄ λ°©κΈ μμ±ν Computed-PropertiesTests
κ·Έλ£Ήμ νκ² κ·Έλ£ΉμΌλ‘ μΆκ°νλ€.
μ΄ μ€μ μ νμ§ μμΌλ©΄ ν μ€νΈ μ€ν μμ²΄κ° μ€ν¨λλ€.
5. Add Target Memberships
ν
μ€νΈμ νμν λͺ¨λ Swift νμΌμ Computed-PropertiesTests
μ Target Membership
μΌλ‘ λ±λ‘νλ€.
ν
μ€νΈμ νμν λͺ¨λ Swift νμΌμ μμ κ°μ΄ ν
μ€νΈ κ·Έλ£Ήμ Target Membership
μ 체ν¬ν΄ λ±λ‘ν΄μΌνλ€.
λ°λΌμ, DiceTwo.swift
νμΌλ λμΌν μμ
μ ν΄μ£Όλλ‘ νλ€.
μ κ²½μ°λ κΈ°μ‘΄μ νλ‘μ νΈκ° μ§νλλ μ€κ°μ ν
μ€νΈ κ·Έλ£Ήμ μΆκ°νκΈ° λλ¬Έμ΄κ³ , ν
μ€νΈ κ·Έλ£Ήμ μμ±ν μ΄ν μ Swift νμΌμ μμ±ν κ²½μ°,
μλμ κ°μ΄ Target Membership
μ 미리 μ€μ ν μ μλ€.
μ΄ μ€μ μ νμ§ μμΌλ©΄ ν μ€νΈλ μ€νλμ§λ§,
Computed-Properties
κ·Έλ£Ήμ Classes, Structures, Protocols κ°μ κ²λ€μ μ°Ύμ§ λͺ» νλ€(ν΄λΉ μ½λκ° ν¬ν¨λ Swift νμΌμ μ°Ύμ§ λͺ» νκΈ° λλ¬Έμ΄λ€).
6. Write Test Cases
- setUpWithError : ν΄λμ€ λ΄ κ° ν μ€νΈ λ©μλκ° μ€νλκΈ° μ μ νΈμΆν μ½λ
- tearDownWithError : ν΄λμ€ λ΄ κ° ν μ€νΈ λ©μλκ° μ€νλ ν νΈμΆν μ½λ
- testExample :
Unit Tests
ν μ½λλ₯Ό μμ±νλ€. νμtest
λΌλ prefix λ₯Ό λΆμ¬μΌ μ μμ μΈ ν μ€νΈλ‘ μΈμνλ€.- testPerformanceExample : μ±λ₯ μΈ‘μ μ μν λ©μλλ‘,
test
λΌλ prefix λ₯Ό λΆμ΄κ³ ,measure
Closures μμ μ½λλ₯Ό μμ±νλ€.
Unit Tests
λ
- Arrange : νμμ λ°λΌ ν μ€νΈμ νμν λ°μ΄ν°λ₯Ό μμ± λ° λμ΄νλ€.
- Act : ν μ€νΈλ₯Ό μννλ€.
- Assert : ν μ€νΈλ₯Ό κ²μ¦νλ€.
μμλ‘ μμ±νλ€.
- Test
func testInitialValue_Is_Number_One() throws {
// Arrange
let dice = DiceOne()
let currentDice:Int = dice.currentDice
// Act
// Assert
XCTAssertEqual(currentDice, 1, "The initial value of the 'currentDice' is should be 1.")
}
func testCurrentDice_Is_Greater_Than_Or_Equal_To_One() throws {
// Arrange
var dice = DiceOne()
// Act
dice.currentDice = 0
// Assert
XCTAssertEqual(dice.currentDice, 1, "The 'currentDice' is greater than or equal to 1.")
}
func testCurrentDice_Is_Less_Than_Or_Equal_To_Six() throws {
// Arrange
var dice = DiceOne()
// Act
dice.currentDice = 7
// Assert
XCTAssertEqual(dice.currentDice, 6, "The 'currentDice' is less than or equal to 6.")
}
- Test Performance
func testPerformance_GetValueFromComputedProperties() throws {
measure {
App().getValueFromComputedProperties()
}
}
func testPerformnace_GetValueFromClosures() throws {
measure {
App().getValueFromClosures()
}
}
λ₯Ό μΆκ°νλ€.
ν μ€νΈ μ½λλ μμ κ°μ΄ νΉμ μΌμ΄μ€μ λν΄ μ»€λλ₯Ό μ§μ μμ±ν΄ ν μ€νΈ ν μλ μκ³ , μ΄λ―Έ μ‘΄μ¬νλ ν¨μλ λ©μλμ
arguments
λ₯Ό μΌμ΄μ€ λ³λ‘ μμ±ν΄ βμ±κ³΅β/βμλ¬ μ²λ¦¬βλ₯Ό ν μ€νΈ ν μλ μλ€.
7. Run Tests
μ λ²νΌμ κΈΈκ² λλ¬ Test
λ₯Ό μ€ννκ±°λ, λ¨μΆν€ β U
λ₯Ό μ¬μ©ν΄ ν
μ€νΈλ₯Ό μ€ννλ€.
(1 Pass)
(100,000 Pass)
Show the Report navigator
νμ λλ₯΄κ³ ν
μ€νΈ κ²°κ³Όλ₯Ό νμΈνλ€.
μ μμ μ κ²½μ° 3κ°μ ν μ€νΈ μ½λκ° μ±κ³΅μ μΌλ‘ ν΅κ³Όνλ€.
- μ£Όμ¬μ μ΄κΉκ°μ 1
- μ£Όμ¬μ μ΅μκ°μ 1
- μ£Όμ¬μ μ΅λκ°μ 6
λν 2κ°μ μ±λ₯ ν μ€νΈκ° μ±κ³΅νλ€.
Time
μ 보면 λ°λ³΅μ νμ§ μμ κ²½μ°Closures
κ° κ·Όμνκ² λΉ λ₯΄μ§λ§,- κ° μ‘°νλ₯Ό 100,000λ² ν κ²½μ°
Computed Properties
κ° κ·Όμνκ² λΉ λ₯Έ κ²μ νμΈν μ μλ€.
(measure λ₯Ό μ¬μ©ν κ²½μ° Duration μ measure λ₯Ό μλνκΈ° μν λ³λμ λΉλ μκ°μ ν¬ν¨νλ λ― νλ€. λ©μλλ₯Ό κ°λ³μ μΌλ‘ λλ €λ³΄κ±°λ κ°λ³κ² λλ €λ΄λ νμ 5μ΄ κ°κΉμ΄ μκ°μ΄ λμ€λ©°, μ€μ λ‘ λ°λ³΅ νμμ μν₯μ λ°λ κ°μTime
μ΄λ€.)μ ν μ€νΈμ κ²½μ°
Closures
λλComputed Properties
μ κ³μ° μμ²΄κ° λ§€μ° κ°λ³κΈ° λλ¬Έμ ν° μ°¨μ΄κ° μμ§λ§ λ¬΄κ±°μ΄ κ³μ°μ΄ ν¬ν¨λ κ²½μ° μμ 보λ€λ μ κ·Ό νμκ° λ§λ€λ©΄Closures
보λ€λComputed Properties
λ₯Ό μ¬μ©νλ κ²μ΄ μΊμ±μΌλ‘ μΈν΄ μ±λ₯μ μ΄μ μ κ°μ§ κ²μ΄λ€.
ν μ€νΈ μ€ν μμ²΄κ° μ€ν¨νλ κ²½μ° 4. Edit Scheme for Tests λ₯Ό νμΈνκ³ ,
ν μ€νΈλ μ€νλμ§λ§ Classes, Structures, Protocols κ°μ κ²λ€μ μ°Ύμ§ λͺ» νλ κ²½μ° 5. Add Target Memberships λ₯Ό νμΈνλ€.
3. Summary π©βπ»
κ°μ λ³κ²½μ΄ μμ΄λ λ§€λ² κ³μ°νλ Closures
μ κ²½μ° O(N)+
μ 볡μ‘λλ₯Ό κ°μ§λ§, Computed Properties
λ₯Ό μ¬μ©νλ κ²½μ°
μΊμ±μ μν΄ O(1)
μ 볡μ‘λλ₯Ό κ°κΈ° λλ¬Έμ λ³κ²½μ΄ μ κ³ μ‘°νκ° λ§μ κ°μΌ λ μ 리νλ€.
λ¨, λ€μ μΌμ΄μ€λ Computed Properties
μ¬μ©μ λμ μλ‘ νΌνλλ‘ νλ€.
- Random values
- todayβs date
- a value from another object or singleton
- formatting a date
- fetching from server
Reference
- Aaina, jain. βFunctions vs Computed property β What to use?.β Medium, last modified Nov. 22, 2018, Functions vs Computed property.
- βUni testing best practices.β Microsoft Learn .NET, Nov. 4, 2022, Uni testing best practices.