Swift Testing Framework สัมภาษณ์ 2026: มาโคร #expect และ #require เทียบกับ XCTest
เรียนรู้ Swift Testing Framework ใหม่สำหรับการสัมภาษณ์ iOS: มาโคร #expect และ #require การย้ายจาก XCTest แพทเทิร์นขั้นสูงและข้อผิดพลาดที่พบบ่อย

เปิดตัวที่ WWDC 2024 และมาพร้อม Swift 6 และ Xcode 16 Swift Testing คือการคิดใหม่อย่างสมบูรณ์เกี่ยวกับวิธีการทำงานของการทดสอบใน Swift เฟรมเวิร์กนี้แทนที่ assertion มากกว่า 40 รายการของ XCTest ด้วยมาโครเพียงสองตัว: #expect และ #require ปัจจุบันผู้สัมภาษณ์มักทดสอบความรู้นี้ในการสัมภาษณ์ทางเทคนิค iOS อย่างสม่ำเสมอ
แต่ละส่วนสะท้อนคำถามสัมภาษณ์ทางเทคนิคพร้อมคำตอบโดยละเอียดและโค้ดที่ใช้งานได้ ลำดับเริ่มจากแนวคิดพื้นฐานไปสู่แพทเทิร์นขั้นสูง
พื้นฐานของ Swift Testing
คำถามที่ 1: ความแตกต่างหลักระหว่าง Swift Testing และ XCTest คืออะไร
Swift Testing นำมาซึ่งการเปลี่ยนแปลงพื้นฐานห้าประการเมื่อเทียบกับ XCTest:
- ไวยากรณ์เชิงประกาศ: แอททริบิวต์
@Testแทนคำนำหน้าtest - มาโครสากลสองตัว:
#expectและ#requireแทนที่ assertion มากกว่า 40 รายการ - ขนานเป็นค่าเริ่มต้น: การทดสอบทั้งหมดทำงานพร้อมกัน
- รองรับ async แบบเนทีฟ: ผสานรวมเต็มรูปแบบกับ Swift Concurrency
- ข้ามแพลตฟอร์ม: ทำงานบนแพลตฟอร์ม Apple, Linux และ Windows
import XCTest
import Testing
// ❌ Legacy XCTest pattern
class UserServiceXCTests: XCTestCase {
// Must start with "test"
func testUserCreation() {
let user = User(name: "Alice", age: 25)
// Multiple verbose assertions
XCTAssertNotNil(user)
XCTAssertEqual(user.name, "Alice")
XCTAssertGreaterThan(user.age, 18)
XCTAssertTrue(user.isValid)
}
}
// ✅ Modern Swift Testing pattern
@Test("User creation with valid data")
func userCreation() {
let user = User(name: "Alice", age: 25)
// Single macro for all verifications
#expect(user.name == "Alice")
#expect(user.age > 18)
#expect(user.isValid)
}ความแตกต่างที่สำคัญอยู่ที่ความสามารถในการแสดงออก: Swift Testing ใช้นิพจน์ Swift มาตรฐานแทน assertion เฉพาะทาง ทำให้การทดสอบอ่านง่ายขึ้นและข้อความข้อผิดพลาดให้ข้อมูลมากขึ้น
คำถามที่ 2: มาโคร #expect ทำงานอย่างไร
มาโคร #expect ตรวจสอบว่านิพจน์บูลีนเป็นจริง โดยจับค่าที่ประเมินแล้วโดยอัตโนมัติเพื่อให้ข้อความข้อผิดพลาดที่ละเอียด ต่างจาก XCTAssert ตรงที่ใช้ไวยากรณ์ Swift แบบเนทีฟ
import Testing
@Test func basicExpectations() {
let numbers = [1, 2, 3, 4, 5]
let user = User(name: "Bob", email: "bob@example.com")
// Simple comparisons - standard Swift expression
#expect(numbers.count == 5)
#expect(user.name == "Bob")
// Comparisons with operators
#expect(numbers.first! < numbers.last!)
#expect(user.email.contains("@"))
// Nil checking
#expect(numbers.first != nil)
// Collection verification
#expect(numbers.contains(3))
#expect(!numbers.isEmpty)
}
@Test func expectWithCustomMessage() {
let balance = 150.0
let withdrawAmount = 200.0
// Custom message to clarify intent
#expect(
balance >= withdrawAmount,
"Insufficient funds: balance \(balance) < withdrawal \(withdrawAmount)"
)
}เมื่อ #expect ล้มเหลว การทดสอบจะดำเนินการต่อไป คุณสมบัตินี้ช่วยให้สามารถรวบรวมข้อผิดพลาดหลายรายการในการรันครั้งเดียว ทำให้การวินิจฉัยง่ายขึ้น
คำถามที่ 3: ความแตกต่างระหว่าง #expect และ #require คืออะไร
ความแตกต่างพื้นฐานเกี่ยวข้องกับพฤติกรรมหลังจากความล้มเหลว:
#expect: บันทึกความล้มเหลวและดำเนินการต่อ#require: บันทึกความล้มเหลวและหยุดการทดสอบทันที
#require ต้องใช้กับ try เสมอเพราะสามารถ throw ข้อผิดพลาดได้
import Testing
struct ApiResponse {
let data: Data?
let items: [Item]?
}
@Test func demonstrateExpectContinues() {
let values = [1, 2, 3]
// First #expect fails but test continues
#expect(values.count == 10) // ❌ Failure recorded
// These verifications still execute
#expect(values.first == 1) // ✅ Success
#expect(values.last == 3) // ✅ Success
// Result: 1 failure, 2 successes in the same test
}
@Test func demonstrateRequireStops() throws {
let response = ApiResponse(data: nil, items: nil)
// #require stops immediately if condition fails
let data = try #require(response.data) // ❌ Failure and STOP
// This code NEVER executes if data is nil
let json = try JSONDecoder().decode(User.self, from: data)
#expect(json.name == "Alice")
}ในทางปฏิบัติ #require แทนที่ XCTUnwrap ได้อย่างสมบูรณ์สำหรับการ unwrap optional อย่างปลอดภัย
ใช้ #require เมื่อขั้นตอนต่อไปขึ้นอยู่กับผลลัพธ์ (การ unwrap, เงื่อนไขเบื้องต้น) ใช้ #expect สำหรับการตรวจสอบที่เป็นอิสระซึ่งสามารถล้มเหลวได้โดยไม่บล็อกส่วนที่เหลือของการทดสอบ
แพทเทิร์นขั้นสูงด้วย #require
คำถามที่ 4: ใช้ #require สำหรับ unwrap optional อย่างไร
#require โดดเด่นในการ unwrap optional โดยส่งคืนค่าที่ไม่ใช่ optional หากมีอยู่ หรือทำให้การทดสอบล้มเหลวทันทีหากเป็น nil
import Testing
struct UserProfile {
let id: Int
let name: String
let settings: Settings?
}
struct Settings {
let theme: String
let notifications: Bool
}
@Test func unwrapOptionalChain() throws {
let profile = UserProfile(
id: 1,
name: "Alice",
settings: Settings(theme: "dark", notifications: true)
)
// Safe unwrap - stops if nil
let settings = try #require(profile.settings)
// Now settings is no longer optional
#expect(settings.theme == "dark")
#expect(settings.notifications == true)
}
@Test func unwrapArrayElement() throws {
let users = ["Alice", "Bob", "Charlie"]
// Unwrap first element
let firstUser = try #require(users.first)
#expect(firstUser == "Alice")
// Unwrap with safe index
let secondUser = try #require(users[safe: 1])
#expect(secondUser == "Bob")
}
@Test func unwrapDictionaryValue() throws {
let config: [String: Any] = [
"apiUrl": "https://api.example.com",
"timeout": 30,
"retryCount": 3
]
// Unwrap and cast in a single operation
let apiUrl = try #require(config["apiUrl"] as? String)
let timeout = try #require(config["timeout"] as? Int)
#expect(apiUrl.contains("https"))
#expect(timeout > 0)
}วิธีการนี้ช่วยกำจัดพีระมิด guard let และทำให้โค้ดทดสอบเป็นเส้นตรงและอ่านง่าย
คำถามที่ 5: ทดสอบว่าฟังก์ชัน throw ข้อผิดพลาดอย่างไร
Swift Testing ให้ #expect(throws:) เพื่อตรวจสอบว่าฟังก์ชัน throw ข้อผิดพลาดเฉพาะ
import Testing
enum ValidationError: Error, Equatable {
case emptyName
case invalidEmail
case underAge(minimum: Int)
}
struct Validator {
static func validateUser(name: String, email: String, age: Int) throws {
guard !name.isEmpty else {
throw ValidationError.emptyName
}
guard email.contains("@") else {
throw ValidationError.invalidEmail
}
guard age >= 18 else {
throw ValidationError.underAge(minimum: 18)
}
}
}
@Test func testThrowsSpecificError() {
// Verify a specific error is thrown
#expect(throws: ValidationError.emptyName) {
try Validator.validateUser(name: "", email: "test@mail.com", age: 25)
}
#expect(throws: ValidationError.invalidEmail) {
try Validator.validateUser(name: "Alice", email: "invalid", age: 25)
}
}
@Test func testThrowsErrorType() {
// Verify error type without specific value
#expect(throws: ValidationError.self) {
try Validator.validateUser(name: "Bob", email: "bob@mail.com", age: 15)
}
}
@Test func testThrowsWithInspection() throws {
// Capture error for detailed inspection
let error = try #require(
throws: ValidationError.self
) {
try Validator.validateUser(name: "Charlie", email: "charlie@mail.com", age: 16)
}
// Verify error details
if case .underAge(let minimum) = error {
#expect(minimum == 18)
}
}ไวยากรณ์นี้แทนที่ XCTAssertThrowsError ด้วย API ที่ชัดเจนกว่าและปลอดภัยทางประเภท
พร้อมที่จะพิชิตการสัมภาษณ์ iOS แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
การจัดระเบียบการทดสอบด้วย @Test และ @Suite
คำถามที่ 6: จัดระเบียบการทดสอบด้วย @Suite อย่างไร
@Suite จัดกลุ่มการทดสอบที่เกี่ยวข้องอย่างมีตรรกะ ต่างจาก XCTestCase ตรงที่ไม่ต้องการการสืบทอดคลาส
import Testing
// Suite for authentication tests
@Suite("Authentication Tests")
struct AuthenticationTests {
// Shared property for all tests in the suite
let authService = AuthService()
@Test("Login with valid credentials succeeds")
func loginWithValidCredentials() async throws {
let result = try await authService.login(
email: "user@example.com",
password: "validPass123"
)
#expect(result.isSuccess)
let token = try #require(result.token)
#expect(!token.isEmpty)
}
@Test("Login with invalid password fails")
func loginWithInvalidPassword() async {
let result = await authService.login(
email: "user@example.com",
password: "wrong"
)
#expect(!result.isSuccess)
#expect(result.error == .invalidCredentials)
}
}
// Nested suites for hierarchical organization
@Suite("User Management")
struct UserManagementTests {
@Suite("Creation")
struct CreationTests {
@Test func createUserWithValidData() {
// Creation test
}
@Test func createUserWithDuplicateEmail() {
// Duplicate error test
}
}
@Suite("Deletion")
struct DeletionTests {
@Test func deleteExistingUser() {
// Deletion test
}
@Test func deleteNonExistentUser() {
// Error test
}
}
}Suites อนุญาตให้รันชุดย่อยของการทดสอบและจัดระเบียบรายงานในรูปแบบที่อ่านง่าย
คำถามที่ 7: ใช้ trait เพื่อกำหนดค่าการทดสอบอย่างไร
Traits ปรับเปลี่ยนพฤติกรรมการทดสอบ: เงื่อนไขการดำเนินการ แท็ก timeout เป็นต้น
import Testing
@Suite("API Integration Tests")
struct APITests {
// Temporarily disabled test
@Test(.disabled("Backend under maintenance"))
func fetchUserProfile() async {
// Does not execute
}
// Conditional test based on platform
@Test
@available(iOS 17, *)
func useNewAPIFeature() {
// Executes only on iOS 17+
}
// Test with tags for filtering
@Test(.tags(.critical, .network))
func criticalNetworkOperation() async throws {
// Tagged test for filtering
}
// Test with custom timeout
@Test(.timeLimit(.minutes(2)))
func longRunningOperation() async {
// Must complete within 2 minutes
}
// Trait combination
@Test(
"Complex data sync",
.tags(.slow),
.timeLimit(.minutes(5)),
.bug("JIRA-1234", "Flaky on CI")
)
func complexDataSync() async throws {
// Test documented with known bug
}
}
// Custom tag definitions
extension Tag {
@Tag static var critical: Self
@Tag static var network: Self
@Tag static var slow: Self
}Traits ทำให้การทดสอบเป็นเอกสารในตัวเองและอนุญาตการดำเนินการแบบเลือกผ่านบรรทัดคำสั่ง
การทดสอบแบบมีพารามิเตอร์
คำถามที่ 8: สร้างการทดสอบแบบมีพารามิเตอร์อย่างไร
Swift Testing อนุญาตให้รันการทดสอบเดียวกันด้วยอินพุตที่แตกต่างกันผ่านพารามิเตอร์
import Testing
struct EmailValidator {
static func isValid(_ email: String) -> Bool {
let pattern = #"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"#
return email.range(of: pattern, options: .regularExpression) != nil
}
}
// Parameterized test with a collection
@Test(arguments: [
"user@example.com",
"test.name@domain.org",
"contact@company.co.uk"
])
func validEmailsAreAccepted(email: String) {
#expect(EmailValidator.isValid(email))
}
@Test(arguments: [
"invalid",
"@nodomain.com",
"no@tld",
"spaces in@email.com"
])
func invalidEmailsAreRejected(email: String) {
#expect(!EmailValidator.isValid(email))
}
// Test with tuples for input/output cases
@Test(arguments: [
("hello", "HELLO"),
("World", "WORLD"),
("Swift", "SWIFT")
])
func uppercaseConversion(input: String, expected: String) {
#expect(input.uppercased() == expected)
}
// Test with Cartesian product of two collections
@Test(arguments: [1, 2, 3], ["a", "b"])
func combinationTest(number: Int, letter: String) {
// Runs for (1,"a"), (1,"b"), (2,"a"), (2,"b"), (3,"a"), (3,"b")
let combined = "\(number)\(letter)"
#expect(combined.count == 2)
}การรวมพารามิเตอร์แต่ละชุดสร้างการทดสอบอิสระ ทำให้ระบุกรณีที่ล้มเหลวได้ง่าย
คำถามที่ 9: ทดสอบโค้ด asynchronous อย่างไร
Swift Testing ผสาน async/await แบบเนทีฟ ทำให้การทดสอบ asynchronous ง่ายขึ้นอย่างมาก
import Testing
actor DataStore {
private var items: [String] = []
func add(_ item: String) {
items.append(item)
}
func getAll() -> [String] {
items
}
func clear() {
items.removeAll()
}
}
@Suite("Async Operations")
struct AsyncTests {
@Test func basicAsyncOperation() async {
// No need for expectation or wait
let result = await fetchData()
#expect(!result.isEmpty)
}
@Test func asyncWithTimeout() async throws {
// Use Task.sleep to simulate delay
try await Task.sleep(for: .milliseconds(100))
let data = await loadConfiguration()
let config = try #require(data)
#expect(config.isValid)
}
@Test func testActorIsolation() async {
let store = DataStore()
// Operations on actor sequentially
await store.add("Item 1")
await store.add("Item 2")
let items = await store.getAll()
#expect(items.count == 2)
#expect(items.contains("Item 1"))
}
@Test func testConcurrentOperations() async {
let store = DataStore()
// Concurrent execution with TaskGroup
await withTaskGroup(of: Void.self) { group in
for i in 1...10 {
group.addTask {
await store.add("Item \(i)")
}
}
}
let items = await store.getAll()
#expect(items.count == 10)
}
}
// Async helpers for tests
func fetchData() async -> [String] {
try? await Task.sleep(for: .milliseconds(50))
return ["data1", "data2"]
}
func loadConfiguration() async -> Configuration? {
Configuration(isValid: true)
}
struct Configuration {
let isValid: Bool
}Swift Testing รันการทดสอบขนานเป็นค่าเริ่มต้น สำหรับการทดสอบที่แก้ไขสถานะที่แชร์ ใช้ .serialized บน suite หรือแยกสถานะด้วย actor
การย้ายจาก XCTest ไป Swift Testing
คำถามที่ 10: ย้ายจาก XCTest อย่างค่อยเป็นค่อยไปอย่างไร
เฟรมเวิร์กทั้งสองอยู่ร่วมกันในโปรเจกต์เดียวกัน แนะนำให้ย้ายแบบค่อยเป็นค่อยไป
import XCTest
import Testing
// ⚠️ CRITICAL RULE: Never mix frameworks in the same test
// ❌ INCORRECT - Mixing forbidden
class BadMixedTest: XCTestCase {
func testMixed() {
#expect(true) // Does not work in XCTestCase
}
}
// ✅ CORRECT - Pure XCTest for existing tests
class LegacyUserTests: XCTestCase {
func testUserCreation() {
let user = User(name: "Test")
XCTAssertNotNil(user)
XCTAssertEqual(user.name, "Test")
}
}
// ✅ CORRECT - Swift Testing for new tests
@Suite("User Tests - Modern")
struct ModernUserTests {
@Test func userCreation() {
let user = User(name: "Test")
#expect(user.name == "Test")
}
}
// Migration strategy by file
// 1. Identify tests to migrate (start with simplest)
// 2. Create new file with @Suite
// 3. Rewrite tests one by one
// 4. Delete old XCTest file once validated| XCTest | Swift Testing |
|--------|---------------|
| XCTAssertTrue(x) | #expect(x) |
| XCTAssertFalse(x) | #expect(!x) |
| XCTAssertEqual(a, b) | #expect(a == b) |
| XCTAssertNil(x) | #expect(x == nil) |
| XCTAssertNotNil(x) | #expect(x != nil) |
| XCTUnwrap(x) | try #require(x) |
| XCTAssertThrowsError | #expect(throws:) |
คำถามที่ 11: ฟีเจอร์ใดของ XCTest ที่ยังไม่มีใน Swift Testing
Swift Testing (Swift 6) ยังไม่ครอบคลุมทุกกรณีการใช้งานของ XCTest
// ❌ NOT SUPPORTED: Performance tests
// Stick with XCTest for measuring performance
class PerformanceTests: XCTestCase {
func testPerformance() {
measure {
// Code to measure
_ = (0..<1000).map { $0 * 2 }
}
}
}
// ❌ NOT SUPPORTED: UI tests (XCUITest)
// Continue using XCUITest for interface tests
class UITests: XCTestCase {
func testLoginFlow() {
let app = XCUIApplication()
app.launch()
// UI tests...
}
}
// ✅ SUPPORTED: Async integration tests
@Test func integrationTest() async throws {
let api = APIClient()
let response = try await api.fetchUsers()
#expect(!response.isEmpty)
}
// ✅ SUPPORTED: Mocking with protocols
@Test func mockingWithProtocols() async {
let mockService = MockUserService()
let viewModel = UserViewModel(service: mockService)
await viewModel.loadUser(id: 1)
#expect(viewModel.user?.name == "Mock User")
}สำหรับโปรเจกต์ที่มีการทดสอบ UI หรือประสิทธิภาพ ให้ใช้ XCTest ต่อไปสำหรับกรณีเฉพาะเหล่านั้น
พร้อมที่จะพิชิตการสัมภาษณ์ iOS แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
คำถามสัมภาษณ์ที่หลอกลวง
คำถามที่ 12: ทำไม #require ต้องการ try แต่ #expect ไม่ต้อง
#require สามารถ throw ข้อผิดพลาดหากเงื่อนไขล้มเหลวเพราะต้องขัดจังหวะการทดสอบ #expect เพียงบันทึกความล้มเหลวและดำเนินการต่อ จึงไม่ throw อะไร
import Testing
@Test func explainTryRequirement() throws {
let optionalValue: String? = nil
// #expect returns Void - no error thrown
// Test continues even if it fails
#expect(optionalValue != nil) // No try
// #require can throw ExpectationFailedError
// Test stops if it fails
// Must be marked with try
let value = try #require(optionalValue) // Requires try
// If we reach here, optionalValue was not nil
#expect(!value.isEmpty)
}
// The internal signature resembles:
// func #expect(_ condition: Bool) -> Void
// func #require<T>(_ value: T?) throws -> Tความแตกต่างทางสถาปัตยกรรมนี้ช่วยให้คอมไพเลอร์รับประกันการจัดการข้อผิดพลาดที่ถูกต้อง
คำถามที่ 13: Swift Testing จัดการการทำงานขนานอย่างไร
โดยค่าเริ่มต้น การทดสอบทั้งหมดทำงานขนาน ซึ่งช่วยเร่ง suites แต่ต้องการการแยกสถานะที่ถูกต้อง
import Testing
// ❌ PROBLEM: Mutable shared state
var sharedCounter = 0 // Dangerous in parallel!
@Suite("Problematic Parallel Tests")
struct ProblematicTests {
@Test func incrementCounter1() {
sharedCounter += 1 // Race condition!
}
@Test func incrementCounter2() {
sharedCounter += 1 // Race condition!
}
}
// ✅ SOLUTION 1: Force sequential execution
@Suite("Sequential Tests", .serialized)
struct SequentialTests {
static var counter = 0
@Test func first() {
Self.counter += 1
#expect(Self.counter == 1)
}
@Test func second() {
Self.counter += 1
#expect(Self.counter == 2)
}
}
// ✅ SOLUTION 2: Isolate state per test
@Suite("Isolated Tests")
struct IsolatedTests {
@Test func independentTest1() {
var localCounter = 0
localCounter += 1
#expect(localCounter == 1)
}
@Test func independentTest2() {
var localCounter = 0
localCounter += 1
#expect(localCounter == 1)
}
}
// ✅ SOLUTION 3: Use actor for shared state
actor TestState {
var value = 0
func increment() -> Int {
value += 1
return value
}
}
@Suite("Actor-based Tests")
struct ActorTests {
let state = TestState()
@Test func safeIncrement() async {
let result = await state.increment()
#expect(result > 0)
}
}trait .serialized รับประกันการดำเนินการตามลำดับของ suite ทั้งหมด
คำถามที่ 14: ใช้ confirmation สำหรับ callback อย่างไร
สำหรับ API ที่มี callback (ไม่ใช่ async) Swift Testing เสนอ confirmation
import Testing
// Legacy service with callback
class LegacyService {
func fetchData(completion: @escaping (Result<String, Error>) -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 0.1) {
completion(.success("Data loaded"))
}
}
}
@Test func testCallbackWithConfirmation() async {
let service = LegacyService()
// confirmation waits for it to be called
await confirmation("Data callback received") { confirm in
service.fetchData { result in
if case .success(let data) = result {
#expect(data == "Data loaded")
confirm() // Signals callback was executed
}
}
}
}
// For callbacks called multiple times
@Test func testMultipleCallbacks() async {
let publisher = EventPublisher()
// expectedCount specifies expected call count
await confirmation("Events received", expectedCount: 3) { confirm in
publisher.onEvent = { event in
#expect(!event.isEmpty)
confirm() // Called 3 times
}
publisher.emit("Event 1")
publisher.emit("Event 2")
publisher.emit("Event 3")
}
}
class EventPublisher {
var onEvent: ((String) -> Void)?
func emit(_ event: String) {
DispatchQueue.global().async {
self.onEvent?(event)
}
}
}confirmation แทนที่ XCTestExpectation และ wait(for:timeout:) อย่างสง่างาม
บทสรุป
Swift Testing เป็นตัวแทนของอนาคตของการทดสอบบนแพลตฟอร์ม Apple มาโครทั้งสอง #expect และ #require ทำให้การเขียนการทดสอบง่ายขึ้นอย่างมากในขณะที่ปรับปรุงคุณภาพของข้อความข้อผิดพลาด
จุดสำคัญที่ต้องจำสำหรับการสัมภาษณ์:
- ✅
#expectดำเนินการต่อหลังความล้มเหลว#requireหยุดทันที - ✅
#requireต้องการtryเพราะสามารถ throw ข้อผิดพลาด - ✅ Swift Testing ทำงานขนานเป็นค่าเริ่มต้น
- ✅ เฟรมเวิร์กทั้งสองอยู่ร่วมกันแต่ไม่ควรผสมในการทดสอบเดียวกัน
- ✅ XCTest ยังคงจำเป็นสำหรับการทดสอบ UI และประสิทธิภาพ
- ✅ Traits อนุญาตการกำหนดค่าพฤติกรรมการทดสอบอย่างละเอียด
- ✅ การทดสอบแบบมีพารามิเตอร์หลีกเลี่ยงการซ้ำซ้อนของโค้ด
การย้ายไปยัง Swift Testing สามารถทำได้แบบค่อยเป็นค่อยไป ทีละไฟล์ เริ่มจากการทดสอบที่ง่ายที่สุด
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
แท็ก
แชร์
บทความที่เกี่ยวข้อง

การสัมภาษณ์ StoreKit 2: การจัดการการสมัครสมาชิกและการตรวจสอบใบเสร็จ
เชี่ยวชาญคำถามสัมภาษณ์ iOS เกี่ยวกับ StoreKit 2 การจัดการการสมัครสมาชิก การตรวจสอบใบเสร็จ และการนำการซื้อในแอปไปใช้ พร้อมตัวอย่างโค้ด Swift ที่ใช้งานได้จริง

สัมภาษณ์ iOS Push Notifications 2026: APNs, โทเคน และ troubleshooting
คู่มือเตรียมสัมภาษณ์ iOS อย่างครบถ้วนเกี่ยวกับ Push Notifications, APNs, การจัดการโทเคน และ troubleshooting พร้อมคำถามยอดนิยมและคำตอบโดยละเอียด

25 คำถามสัมภาษณ์ Swift ยอดนิยมสำหรับนักพัฒนา iOS
เตรียมตัวสำหรับการสัมภาษณ์ iOS ด้วย 25 คำถาม Swift ที่พบบ่อยที่สุด: optionals, closures, ARC, protocols, async/await และ pattern ขั้นสูง