UNIT TESTING IN SWIFT
Bài đăng này đã không được cập nhật trong 3 năm
1. Unit testing là gì
Trong ngành phần mềm, thuật ngữ Unit Testing là một phương pháp dùng để kiểm tra tính đúng đắn của một đơn vị source code. Một Unit (đơn vị) source code là phần nhỏ nhất có thể test được của chương trình.
2. Lợi ích của Unit testing
Mục đích của Unit Test là cô lập từng phần của chương trình và đảm bảo những phần đó chạy đúng như yêu cầu. Unit test giúp bảo đảm tính chính xác của chương trình, nó giúp thiết lập những ràng buộc và những phần code của chúng ta phải thực hiện chính xác những ràng buộc đó. Kết quả là Unit Test đem lại rất nhiều lợi ích, nhưng rõ ràng nhất là nó giúp phát hiện lỗi và những vấn đề liên quan ngay từ những phase đầu tiên của quá trình phát triển phần mềm.
Trên lý thuyết, Unit Test cho phép lập trình viên refactor code và bảo đảm những gì anh ta viết vẫn chạy đúng sau khi code bị thay đổi. Để làm được điều này, người ta buộc phải viết các test case cho tất cả các function và methods và do đó bất cứ một thay đổi nào làm chương trình chạy sai sẽ bị phát hiện kịp thời và buộc người gây lỗi phải fix ngay.
Unit testing giúp lập trình viên thêm tự tin và dám thực hiện thay đổi hơn.
3. Unit testing và Swift
Mặc định, khi tạo 1 project mới từ template, ta đã có sẵn 1 class Tests có tên Tên_Project_Tests kế thừa từ XCTestCase.
Trong đó có sẵn các hàm:
override func setUp(): được chạy trước khi chạy hàm test trong class override func tearDown(): được chạy sau khi chạy hàm test trong class
Hàm test có cấu trúc tương tự như sau:
func testExample() {
// Arrange
var v = ViewController()
// Act
// do something
// Assert
XCTAssertNotNil(v, "View did not load")
}
Trong đó: Arrange : chuẩn bị Act: thực hiện Assert: kiểm tra
Hàm test trên được coi là pass khi hàm kiểm tra (Assert) XCTAssertNotNil được thỏa mãn tức là khi v khác nil.
Có 1 số hàm kiểm tra khác như:
XCTAssert XCTAssertGreaterThanOrEqual XCTAssertEqual XCTAssertFalse XCTAssertTrue
4. Xây dựng project demo
4.1. Tạo project kiểu Single View Application, ngôn ngữ Swift
Project này có các chức năng cơ bản: lấy danh sách người dùng
4.2. Tạo class UserDto lưu thông tin người dùng
import UIKit
class UserDto: NSObject {
var userName: String!
var email: String!
var fullName: String!
var birthDay: NSDate?
var address: String?
}
Target membership chọn thêm UnitTestingDemoTests
4.3. Tạo protocol cho UserService
import Foundation
protocol UserServiceProtocol {
func getUsers() -> [UserDto]
}
Target membership chọn thêm UnitTestingDemoTests
4.4. Tạo class UserService kế thừa UserServiceProtocol
import UIKit
class UserService: NSObject, UserServiceProtocol {
func getUsers() -> [UserDto] {
var user1 = UserDto()
user1.userName = "user1"
user1.email = "user1@framgia.com"
user1.fullName = "User 1"
var user2 = UserDto()
user2.userName = "user2"
user2.email = "user2@framgia.com"
user2.fullName = "User 2"
return [user1, user2]
}
}
Target membership chọn thêm UnitTestingDemoTests
4.5. Tạo class MainViewController kế thừa UITableViewController
Target membership chọn thêm UnitTestingDemoTests
4.6. Trên storyboard, tạo 1 UITableViewController
Đặt làm initial view controller, chọn class là MainViewController Cấu hình lại TableCell của Table View:
- Style: Subtitle
- Identifier: UserCell
4.7. Trong class MainViewController sửa các hàm như sau
import UIKit
class MainViewController: UITableViewController {
var userService : UserServiceProtocol!
var users : [UserDto]!
override func viewDidLoad() {
super.viewDidLoad()
users = userService.getUsers()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("UserCell", forIndexPath: indexPath) as UITableViewCell
let user = users[indexPath.row]
cell.textLabel.text = user.fullName
cell.detailTextLabel?.text = user.userName
return cell
}
}
4.8. Tạo unit test cho UserService
Tại group UserTestingDemoTests, tạo class UserServiceTests kế thừa XCTestCase
import UIKit
import XCTest
class UserServiceTests: 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 testGetUsers() {
// Arrange
var service = UserService()
// Act
var users = service.getUsers()
// Assert
XCTAssertTrue(users.count == 2, "User count incorrect!")
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measureBlock() {
// Put the code you want to measure the time of here.
}
}
}
Hàm testGetUsers test kết quả trả về của hàm getUsers của UserService có chính xác = 2 hay không, nếu sai thì đưa ra thông báo “User count incorrect!”
Nhấn Ctrl + U để chạy test, kết quả như hình dưới đây:
4.9. Tạo unit test cho MainViewController
class MainViewControllerTests: 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 testUserServiceNotNil() {
// Arrange
var controller = MainViewController()
// Act
controller.viewDidLoad()
// Assert
XCTAssertNotNil(controller.userService, "User service is nil")
}
func testUserCount() {
// Arrange
var controller = MainViewController()
// Act
controller.viewDidLoad()
var userCount = controller.userCount()
// Assert
XCTAssertTrue(userCount == 2, "User count is incorrect!")
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measureBlock() {
// Put the code you want to measure the time of here.
}
}
}
Hàm testUserCount test kết quả trả về của hàm userCount của MainViewController có chính xác = 2 hay không, nếu sai thì đưa ra thông báo “User count is incorrect!”
Hàm testUserServiceNotNil test khi không set UserService instance cho biến userService của MainViewController thì sau khi hàm viewDidLoad được chạy có tự động gán giá trị cho biến hay không? Nếu không sẽ đưa ra thông báo lỗi “User service is nil”
Nhấn Ctrl + U để chạy test, kết quả như hình dưới đây:
4.10. Sử dụng OCMock
Hiện tại OCMock chưa hỗ trợ swift hoàn toàn nên trong khuôn khổ bài viết này tôi không đề cập đến, các bạn có thể tham khảo tại http://ocmock.org/swift/
All rights reserved