Skip to main content

Testing and Debugging

Unit Testing

XCTest Framework

import XCTest

class Calculator {
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
}

class CalculatorTests: XCTestCase {
var calculator: Calculator!

override func setUp() {
super.setUp()
calculator = Calculator()
}

func testAddition() {
let result = calculator.add(2, 3)
XCTAssertEqual(result, 5, "Addition result should be 5")
}
}

Test-Driven Development (TDD)

Writing Tests First

protocol UserService {
func fetchUser(id: String) async throws -> User
}

class MockUserService: UserService {
var mockUser: User?
var error: Error?

func fetchUser(id: String) async throws -> User {
if let error = error {
throw error
}
guard let user = mockUser else {
throw NSError(domain: "Test", code: -1)
}
return user
}
}

class UserServiceTests: XCTestCase {
var mockService: MockUserService!

override func setUp() {
super.setUp()
mockService = MockUserService()
}

func testFetchUser() async throws {
let expectedUser = User(id: "123", name: "John")
mockService.mockUser = expectedUser

let user = try await mockService.fetchUser(id: "123")
XCTAssertEqual(user.id, expectedUser.id)
XCTAssertEqual(user.name, expectedUser.name)
}
}

Debugging Techniques

1. Print Debugging

func processData(_ data: Data?) {
print("[DEBUG] Received data: \(String(describing: data))")
guard let data = data else {
print("[ERROR] Data is nil")
return
}
// Process data
}

2. Breakpoints and LLDB

class DebuggingExample {
func complexOperation() {
let numbers = [1, 2, 3, 4, 5]

// Set a breakpoint here
for number in numbers {
let result = process(number)
// Use LLDB commands:
// po result
// p number
// bt
}
}

private func process(_ number: Int) -> Int {
return number * 2
}
}

Performance Testing

Measuring Performance

class PerformanceTests: XCTestCase {
func testArraySortPerformance() {
var numbers = (1...1000).map { _ in Int.random(in: 1...1000) }

measure {
numbers.sort()
}
}

func testComplexOperation() {
measureMetrics([.wallClockTime], automaticallyStartMeasuring: false) {
// Setup code here
startMeasuring()
// Code to measure
stopMeasuring()
// Cleanup code here
}
}
}

Best Practices

1. Test Organization

class UserTests: XCTestCase {
// Given-When-Then pattern
func testUserAuthentication() {
// Given
let user = User(username: "test", password: "password")
let auth = Authenticator()

// When
let result = auth.authenticate(user)

// Then
XCTAssertTrue(result.isSuccess)
}
}

2. Mocking and Stubbing

protocol NetworkClient {
func fetch(from url: URL) async throws -> Data
}

class MockNetworkClient: NetworkClient {
var mockData: Data?
var mockError: Error?

func fetch(from url: URL) async throws -> Data {
if let error = mockError {
throw error
}
return mockData ?? Data()
}
}

Common Debugging Scenarios

1. Memory Leaks

  • Using Instruments
  • Memory Graph Debugger
  • Leak Detection

2. Crash Analysis

  • Reading Crash Logs
  • Symbolication
  • Exception Breakpoints

Next Steps

  1. Review Swift Features
  2. Explore Memory Management