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
- Review Swift Features
- Explore Memory Management