0

UNIT TESTING IN SWIFT

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 = "[email protected]"
        user1.fullName = "User 1"

        var user2 = UserDto()
        user2.userName = "user2"
        user2.email = "[email protected]"
        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:

Screen-Shot-2014-11-25-at-9.25.37-AM.png

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:

Screen-Shot-2014-11-25-at-9.28.14-AM.png

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/

Download project


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.