Bắt đầu phát triển iOS Apps với Swift part 5: Định nghĩa Data Model và viết Unit Test
Bài đăng này đã không được cập nhật trong 3 năm
Đây là phần 5 trong series Bắt đầu phát triển iOS Apps với Swift
Các phần trước các bạn có thể xem ở đây nhé.
part 1: Xây dựng Basic UI
part 2: Kết nối UI và Source Code
part 3: Làm việc với View Controller
part4: Tự tạo một Custom Control
Trong phần 5 này chúng ta sẽ cùng nhau định nghĩa và test data model
cho app FoodTracker. data model
là model thể hiện cấu trúc của thông tin được lưu trong app.
Chúng ta sẽ hoc được những kiến thức sau khi hoàn thiện các bước thực hành của bài ngày hôm nay.
- Tạo được một
data model
- Viết một hàm khởi tạo có thể bị thất bại
- Chứng minh và hiểu sự khác biệt của hàm khởi tạo có thể thất bại và hàm khởi tạo đảm bảo không có lỗi.
- Test
data model
bằng cách viết và chạy Unit Tests
Nào, chúng ta cùng nhau bắt đầu
I. Tạo Data Model
Hiện tại chúng ta đang muốn tạo data model
để lưu thông tin của các món ăn trong màn hình list các món ăn. Để làm điều này chúng ta tạo một class đơn giản với các thông tin như tên, ảnh và giá trị đánh giá món ăn.
Để tạo một data model class
- Chọn File > New > File hoặc nhấn tổ hợp phím Command-N
- Chọn iOS ở trên dialog vừa xuất hiện.
- Chọn Swift File và click Next
Chúng ta đang tạo một class mới không theo cách trước đó chúng ta đã tạo
RatingControl
(iOS > Source > Cocoa Touch Class). Lý do bởi vì chúng ta đang tạobase class
cho data model, nghĩa là nó không cần thiết phải kế thừa từ bất kì class nào khác. - Ở trường Save As, gõ Meal
- Lưu file được tạo default trong folder của project. Ở Group option để default tên app, FoodTracker Ở Targets section, app được chọn vào test your app không để tick.
- Để các mục con lại với giá trị default của nó và click Create
Định nghĩa data model
cho món ăn
- Chuyển sang chế độ Standard Editor, mở
Meal.swift
- Chuyển import Foundation thành import UIKit
import UIKit
Xcode default khai báo Foundation framework để giúp chúng ta sử dụng được cấu trúc dữ liệu định nghĩa bởi Foundation. Tuy nhiên chúng ta sẽ cần làm việc với class từ UIKit framework nữa, mà khi import UIKit rồi thì chúng ta có thể truy cập vào Foundation framework một cách bình thường nên chúng ta xoá đi phần import Foundation thừa đi. 3. Thêm đoạn code khai báo properties của các món ăn
class Meal {
//MARK: Properties
var name: String
var photo: UIImage?
var rating: Int
}
- Định nghĩa hàm khởi tạo
//MARK: Initialization
init(name: String, photo: UIImage?, rating: Int) {
}
- Set các giá trị khởi tạo bằng các parameter được truyền vào.
// Initialize stored properties.
self.name = name
self.photo = photo
self.rating = rating
Nhưng điều gì sẽ xảy ra nếu các name bị trống hay rating có giá trị là số âm? Khi đó chúng ta không thể khởi tạo được dữ liệu cho món ăn. Các bạn phải trả về giá trị nil
để chỉ ra rằng không có item nào được tạo ra.
6. Thêm đoạn code này trước đoạn set giá trị cho các properties.
// Initialization should fail if there is no name or if the rating is negative.
if name.isEmpty || rating < 0 {
return nil
}
- Click vào icon thông báo lỗi để fix
- Click đúp vào để sửa hàm khởi tạo. Hàm khởi tạo đúng sẽ có dạng dưới đây:
init?(name: String, photo: UIImage?, rating: Int) {
Hàm khởi tạo có thể thất bại bắt buộc phải bắt đầu bằng init?
hoặc init!
. Hàm này trả về giá trị Optional hay giá trị implicitly unwrapped optional . Optionals có thể là giá trị có nội dung hoặc nil
. Vì vậy bạn phải kiểm tra giá trị của chúng hoặc unwrap cẩn thận trước khi sử dụng. Trong trường hợp này thì hàm khởi tạo của chúng ta sẽ trả về một optional object Meal?
Hàm khởi tạo bây giờ của chúng ta có dạng:
init?(name: String, photo: UIImage?, rating: Int) {
// Initialization should fail if there is no name or if the rating is negative.
if name.isEmpty || rating < 0 {
return nil
}
// Initialize stored properties.
self.name = name
self.photo = photo
self.rating = rating
}
II. Test Data Model
Đến thời điểm này mặc dù bạn đã code xong, nhưng chúng ta chưa tích hợp model này vào trong app một cách hoàn toàn. Chúng ta chưa thể biết nó hoạt động đúng hay sai; những giá trị ngoại lệ chúng ta muốn control liệu đã đủ hay chưa.
Nếu để tới lúc tích hợp hoàn toàn vào để chạy thì sẽ rất lâu, hơn nữa chạy máy ảo cũng rất tốn tài nguyên máy và thời gian.
Thật may chúng ta có giải pháp cho vấn đề này. Đó là viết Unit Test
để test những phần code nhỏ, hoàn chỉnh về mặt chức năng để đảm bảo các chức năng của nó hoạt động đúng như mong đợi.
Xcode đã tạo sẵn unit test file cho chúng ta rồi.
Cùng điểm qua nội dung của file đó
- Mở
FoodTrackerTests
folder và chọnFoodTrackerTests.swift
file - Mở
FoodTrackerTests.swift
import XCTest
@testable import FoodTracker
class FoodTrackerTests: XCTestCase {
override func setUp() {
super.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.
super.tearDown()
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}
Một vài điểm cần vọc từ file này như: @testable: Attribute này cho phép tests có thể truy cập vào các internal elements của code trong app. XCTest: Viết tắt của Xcode’s testing framework
Test cases đơn giản là các method mà hệ thống sẽ tự động chạy như là một phần của Unit Tests các bạn viết. Để tạo test case bạn tạo method với prefix là test
.
Cách viết unit test cho hàm khởi tạo Meal
- Xoá tất cả các template method sẵn có ở
FoodTrackerTests.swift
đi. Chúng ta chỉ để lại các khác báo cơ bản.
import XCTest
@testable import FoodTracker
class FoodTrackerTests: XCTestCase {
}
- Thêm MARK
//MARK: Meal Class Tests
- Thêm một test case mới.
// Confirm that the Meal initializer returns a Meal object when passed valid parameters.
func testMealInitializationSucceeds() {
}
Hệ thống sẽ tự động chạy test case khi mà unit tests được chạy. 4. Thêm nội dung test để kiểm tra trường hợp rating 0 và rating max 5
// Zero rating
let zeroRatingMeal = Meal.init(name: "Zero", photo: nil, rating: 0)
XCTAssertNotNil(zeroRatingMeal)
// Highest positive rating
let positiveRatingMeal = Meal.init(name: "Positive", photo: nil, rating: 5)
XCTAssertNotNil(positiveRatingMeal)
Nếu hàm khởi tạo hoạt động đúng, thì với các giá trị truyền vào như test ta sẽ có các object Meal
không bị nil
5. Tiếp theo đó chúng ta viết test case khi mà hàm khởi tạo bị fail.
// Confirm that the Meal initialier returns nil when passed a negative rating or an empty name.
func testMealInitializationFails() {
}
- Thêm vào các trường hợp mà tham số sai.
// Negative rating
let negativeRatingMeal = Meal.init(name: "Negative", photo: nil, rating: -1)
XCTAssertNil(negativeRatingMeal)
// Empty String
let emptyStringMeal = Meal.init(name: "", photo: nil, rating: 0)
XCTAssertNil(emptyStringMeal)
Nếu hàm khởi tạo hoạt động đúng chúng ta sẽ thấy nó trả về nil
.
7. Tiếp theo chúng ta sẽ thêm vào một đoạn code mà test thất bại với giá trị rating là 6.
// Rating exceeds maximum
let largeRatingMeal = Meal.init(name: "Large", photo: nil, rating: 6)
XCTAssertNil(largeRatingMeal)
Kiểm tra: Chạy Unit Test bằng cách chọn Product > Test hoặc tổ hợp phím Command-U
Kết quả testMealInitializationFails()
thất bại.
Cùng sửa lỗi đó nào
- Vào file
Meal.swift
, tìm hàminit?
- Thay đoạn code
// Initialization should fail if there is no name or if the rating is negative.
if name.isEmpty || rating < 0 {
return nil
}
Bằng
// The name must not be empty
guard !name.isEmpty else {
return nil
}
// The rating must be between 0 and 5 inclusively
guard (rating >= 0) && (rating <= 5) else {
return nil
}
Đoạn code này đảm bảo cho rating nằm trong khoảng cho phép [0;5] Hàm khởi tạo của chúng ta bây giờ sẽ có dạng
init?(name: String, photo: UIImage?, rating: Int) {
// The name must not be empty
guard !name.isEmpty else {
return nil
}
// The rating must be between 0 and 5 inclusively
guard (rating >= 0) && (rating <= 5) else {
return nil
}
// Initialize stored properties.
self.name = name
self.photo = photo
self.rating = rating
}
Kiểm tra: Đến đây ta chạy lại test và kiểm tra kết quả Tất cả các test đã pass!
All rights reserved