Tìm hiểu Unit Testing trên iOS
Bài đăng này đã không được cập nhật trong 3 năm
I. Giới thiệu
Khi mới làm quen với việc lập trình, thông thường các lập trình viên không chú ý nhiều tới các phương pháp test code của mình. Họ thường code các module, rồi chạy thử chương trình, break, debug,... để tìm và fix bug trong code. Tuy nhiên, việc test thủ công này trong nhiều trường hợp có thể mất rất nhiều thời gian để test. Hơn nữa, trong một số trường hợp, các module của chương trình quá phức tạp khiến cho lập trình viên rất khó khăn trong việc tìm bug, thậm chí là không tìm ra bug.
Vì vậy, việc áp dụng phương pháp test trong khi code trở thành một phần quan trọng trong quá trình code. Trong các phương pháp test, Unit testing được nhắc tới và sử dụng rộng rãi nhất. Unit testing là một phương pháp test mà các lập trình viên sẽ chia nhỏ việc test ra thành các unit nhỏ, test từng module, method của chương trình.
Unit testing mang lại rất nhiều lợi ích cho lập trình viên. Bằng việc test từng unit của chương trình, lập trình viên có thể chắc chắn những unit code đã được test của họ chắc chắn không có bug. Hơn nữa, để sử dụng hiệu quả unit testing, lập trình viên phải viết code thành các module, không để code của cả chương trình quá rắc rối, chồng chéo, phức tạp.
Trên Xcode 7, Apple hỗ trợ rất tốt chúng ta sử dụng unit testing. Trên Xcode, chúng ta được tích hợp sẵn các Framework, API,... và cả test tool để chúng ta test từng module mà không cần phải chạy cả project.
Sau đây, tôi xin tạo một project demo đơn giản sử dụng unit testing để chúng ta cùng tìm hiểu cách sử dụng unit testing trong project.
II. Unit testing - Demo project
1. Tạo project
Chúng ta mở Xcode, tạo new project -> iOS Application -> Single view application, chọn ngôn ngữ là swift, lưu ý tích chọn vào ô include Unit Tests và tạo project.
Project demo rất đơn giản. Chúng ta sẽ chỉ có 1 view controller với 2 ô text field để users điền vào 2 số, và 1 button để khi users bấm vào, chúng ta sẽ tính toán và hiển thị kết quả của phép nhân 2 số vừa nhập vào label bên dưới.
Đầu tiên, mở file storyboard và kéo vào view controller các elements như trong hình sau:
Mục tiêu của bài viết này là tìm hiểu về unit testing, vì vậy chúng ta sẽ không quan tâm nhiều đến giao diện. Các bạn có thể chỉnh lại giao diện, thêm màu mè tùy thích.
Tiếp theo, chúng ta mở assistant editor và kéo các outlet, action chúng ta vừa tạo từ storyboard vào view controller:
- firstNumTextField, secondNumTextField outlet cho 2 text field
- resultLabel outlet cho label hiển thị kết quả
- onButtonClicked action cho action khi button được bấm
Tiếp theo chúng ta implement button action, và thêm các hàm như sau:
@IBAction func onButtonClicked(sender: AnyObject) {
let number1 = firstNumTextField.text!
let number2 = secondNumTextField.text!
if number1.characters.count == 0 || number2.characters.count == 0 {
return
}
let result = multiply(Double(number1)!, num2: Double(number2)!)
showResult("\(result)")
}
func multiply(num1: Double, num2: Double) -> Double {
return (num1 * num2 / 2)
}
func showResult(result: String) {
resultLabel.text = result + "123"
}
Vậy là chúng ta đã hoàn thành việc code cho project, bây giờ chúng ta sẽ chuyển sang phần tiếp theo: Unit testing
2. Unit testing
Hãy để ý phần project navigator trên Xcode, ngoài các file của project, Xcode còn tạo cho chúng ta target UnitTestingTutorialTests với file UnitTestingTutorialTests.swift. Mở file UnitTestingTutorialTests.swift, các bạn có thể thấy class UnitTestingTutorialTests thừa kế từ XCTestCase class; trong class UnitTestingTutorialTests đã được viết sẵn 4 hàm setUp(), tearDown(), testExample() và testPerformanceExample():
- setUp(): Được gọi trước khi thực thi các hàm test
- tearDown(): Được gọi sau khi các hàm test được thực thi
- testExample() và testPerformanceExample(): 2 hàm example của Xcode
Hãy để ý 2 hàm testExample() và testPerformanceExample(), các bạn có thể thấy ở đầu của 2 hàm này có 2 ô hình thoi. Các ô này có thể click vào, và chúng chính là những ô để chúng ta test. Chúng ta hãy thử click vào 1 ô, các bạn sẽ thấy Xcode bắt đầu build, và sau đó sẽ có biểu tượng build successed, kèm theo đó là ô chúng ta vừa click vào sẽ chuyển sang màu xanh.
Ta đa, vậy là các bạn vừa thực hiện việc build unit test trên hàm example của Xcode, bây giờ chúng ta sẽ tự tạo unit test của chúng ta.
Đầu tiên, khai báo property viewController trong class UnitTestingTutorialTests:
var viewController: ViewController!
Sau đó, trong hàm setUp(), gán giá trị cho viewController
override func setUp() {
super.setUp()
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
viewController = storyboard.instantiateInitialViewController() as! ViewController
}
Bây giờ chúng ta sẽ tạo hàm cho unit test. Vẫn trong UnitTestingTutorialTests, chúng ta thêm function sau:
func testMultiplyFunction() {
}
Hãy để ý hàm chúng ta vừa tạo, để có thể thực hiện unit test, hàm chúng ta tạo ra cần phải bắt đầu với tiền tố test
Các bạn hãy xem lại file ViewController.swift, bây giờ chúng ta sẽ implement hàm testMultiplyFunction() để test hàm multiply() của class này:
func testMultiplyFunction() {
let multi = viewController.multiply(12, num2: 10)
XCTAssert(multi == 120)
}
Ở đây, chúng ta tạo một biến multi với giá trị là kết quả trả về của hàm multiply() của instance viewcontroller chúng ta khởi tạo bên trên.
Hãy để ý hàm XCTAssert(), hàm này nhận giá trị đầu vào kiểu Bool, kết quả của việc test phụ thuộc vào giá trị chúng ta truyền vào hàm này. Khi chúng ta truyền vào giá trị true, hàm test của chúng ta trả về success và ngược lại, failed với giá trị false được truyền vào.
Cụ thể trong trường hợp này, chúng ta muốn test xem kết quả của hàm multiply() có chính xác hay không, nên chúng ta đã truyền vào hàm này 2 số 12 và 10. Chúng ta đều biết 12 nhân 10 phải ra 120 là chính xác, nên theo suy đoán của chúng ta, giá trị của multi phải là 120. Chính vì vậy, chúng ta truyền giá trị Bool "multi == 120" vào hàm XCTAssert(). Khi chúng ta chạy hàm test này, kết quả ô hình thoi ở đầu hàm testMultiplyFunction() chuyển màu xanh và Xcode thông báo "test success" có nghĩa là hàm multiply() của chúng ta là chính xác.
OK, test thôi, bấm vào ô hình thoi ở đầu hàm testMultiplyFunction() nào. Oops,... kết quả ra "test failed", và ô này chuyển sang màu đỏ.
Vậy là hàm multiply() của chúng ta đã có lỗi, chúng ta cùng xem lại nào. Hiện tại hàm multiply của chúng ta đang là thế này:
func multiply(num1: Double, num2: Double) -> Double {
return (num1 * num2 / 2)
}
Ngó qua cũng thấy hàm viết sai, nhân 2 số mà lại đi chia cho 2 nữa làm gì, học sinh cấp 1 cũng thấy chỗ sai. Tất nhiên là hàm này phải viết sai thì mới có cái mà nói, chứ viết đúng thì chúng ta đã không có lỗi để mà nói =))
Chúng ta cùng sửa lại hàm cho đúng và test lại thôi nào:
func multiply(num1: Double, num2: Double) -> Double {
return num1 * num2
}
Kết quả:
Great, vậy là chúng ta đã hoàn thành unit testing cho hàm multiply(), giờ chúng ta có thể chắc chắn hàm này là đúng, nếu code của chúng ta lỗi thì hàm này không liên can
Ừmmmm, chạy thử project thôi, xem thành quả của chúng ta nào. build project, điền vào giá trị cho 2 ô textfield là 12 và 10, và giá trị chuẩn 120 luôn
Oops, kết quả lại là 120.0123 chứ không phải 120.0 như chúng ta chờ đợi. Phép nhân của chúng ta chính xác cơ mà, tại sao khi hiện ra thì giá trị lại không như chúng ta mong muốn nhỉ :-?. OK, tiếp tục viết unit test để debug để tìm bug nào.
func testShowResultFunction() {
viewController.showResult("120.0")
XCTAssert(viewController.resultLabel.text == "120.0", "resultLabel show the wrong text")
}
Trong hàm test này, chúng ta sẽ test xem hàm showResult() của chúng ta có hiển thị đúng giá trị hay không. Để ý trong hàm XCTAssert(), chúng ta có thêm param thứ 2, là 1 string sẽ được log ra khi hàm test của chúng ta trả về kết quả failed.
Build test nào, và kết quả là... Oops, sao lạ vậy, build bị crash, và kết quả log ra trên console là "unexpectedly found nil while unwrapping an Optional value"
Thông thường, đối với các UIView được kéo vào storyboard, các UIView này sẽ được khởi tạo khi chúng ta load storyboard. Tuy nhiên, trong trường hợp này, chúng ta chỉ test riêng 1 hàm mà không hề load storyboard, nên các UIView đã không được khởi tạo, gây ra hiện tượng crash. Ở đây, chúng ta có thể xử lý tình huống này bằng cách đơn giản thêm đoạn code như sau:
func testShowResultFunction() {
let _ = viewController.view // additional code
viewController.showResult("120.0")
XCTAssert(viewController.resultLabel.text == "120.0", "resultLabel show the wrong text")
}
Khi chúng ta gọi đến thuộc tính view của instance viewController, các UIView của viewController trong trường hợp này sẽ được khởi tạo và hiện tượng crash do nil không sảy ra nữa. Build test nào, kết quả là:
Vậy là hàm showResult() của chúng ta cũng có vấn đề. Để ý dòng log bug màu đỏ, message chúng ta truyền vào hàm XCTAssert() xuất hiện ở đây khi chúng ta test ra bug.
Cùng mở hàm showResult() lên soi bug nào.
func showResult(result: String) {
resultLabel.text = result + "123"
}
Lại thêm 1 bug sơ đẳng nữa, chả ai đi cộng thêm mấy số đằng sau vào làm gì cả. Chúng ta xóa đi và test lại nào:
func showResult(result: String) {
resultLabel.text = result
}
Kết quả:
Good, vậy là hàm showResult() cũng đã được test xong và fix xong bug, Build project và test thôi nào.
Done, project của chúng ta đã chạy đúng ý chúng ta muốn rồi, nice job.
III. Kết luận
Trên đây, tôi đã giới thiệu đến các bạn về unit testing và cách sử dụng unit testing trong project iOS. Hi vọng bài viết này có thể giúp ích được các bạn trong quá trình tìm hiểu về unit testing trên iOS. Cuối cùng, xin cảm ơn các bạn đã theo dõi bài viết này, thank you !!!
All rights reserved