Category: Tip

  • Delegate trong Kotlin

    Delegate trong Kotlin

    Delegate là gì nhỉ? Tại sao lại là Delegate? Đúng như cái tên, Delegate là một design pattern mà bạn ủy quyền xử lý logic của Class hiện tại cho một Object/Class khác. Delegate thường được sử dụng để tách logic code theo việc của nó (separate concerns) hoặc common hóa một đoạn logic.
    Bài viết này sẽ giúp bạn tìm hiểu cơ bản về Delegate trong Kotlin và cách sử dụng khái niệm này. Trong Kotlin, có 2 cách để sử dụng Delegate
    – Interface/Class Delegation
    – Delegate Properties

    Delegate là cách bạn cho phép một object khác xử lý một logic cho object hiện tại

    Interface/Class Delegation

    Với cách thứ nhất, chúng ta sẽ sử dụng một interface làm abstract cho một object và truyền object đó vào phần khai báo implement thông qua keyword by. Bằng cách này, các abstract methods (method của interface) sẽ chạy code của delegating object!

    interface CameraOptimization {
        fun optimize()
    }
    
    object XiaomiDevicesOptimization : CameraOptimization {
        override fun optimize() {
            TODO("do something for Xiaomi devices")
        }
    
    }
    
    object DefaultDevicesOptimization : CameraOptimization {
        override fun optimize() {
            TODO("do something for others devices")
        }
    }
    
    class CameraManager(optimization: CameraOptimization) : CameraOptimization by optimization {
        fun cameraFocus() {
            //todo: focus camera
        }
    }

    Đây là cách setup cơ bản của Interface/Class Delegation. Như các bạn thấy thì implementation của method optimize() không trực tiếp xuất hiện ở trong class CameraManager mà sẽ được delegate đến object truyền vào bằng keyword by.

    class CameraActivity : BaseActivity() {
        private lateinit var _cameraManager: CameraManager
    
        private fun initView() {
            val vendor = android.os.Build.MANUFACTURER
            val config = when {
                vendor.equals("Xiaomi", ignoreCase = true) -> XiaomiDevicesOptimization
                else -> DefaultDevicesOptimization
            }
            _cameraManager = CameraManager(config)
            _cameraManager.optimize()
            _cameraManager.cameraFocus()
        }
    }


    Khi method optimize() được gọi, nó sẽ delegate đến Config của XiaomiDevicesOptimization hoặc DefaultDevicesOptimization tùy theo device đó là gì. Với cách tiếp cận này logic của CameraManager vẫn có khả năng tối ưu mà không cần phải quan tâm rằng nó sinh ra cho vendor cụ thể nào cả. Đồng thời cũng tăng khả năng mở rộng của Class này hơn. Nếu app của bạn quyết định support optimize thêm cả anh zai Samsung cũng oke luôn, code thêm 1 class và 1 dòng duy nhất.

    Delegate Properties

    Chắc bạn đã từng sử dụng rất nhiều lần lazy trong kotlin rồi đúng không? Delegate đó :v Delegate Properties là việc bạn implement operator getValue (có thể thêm cả setValue luôn nếu bạn muốn nó set được cả value) của một class. Hoặc một cách khác tường mình hơn là implement interface ReadWriteProperty/ReadOnlyProperty. Class đó sẽ trở thành Delegate. Vẫn là keyword by, chúng ta khai báo một biến với Delegate thông qua by.

    class IntNaturalSet : ReadWriteProperty<Any, Int> {
        private var _value: Int = 0
        override fun getValue(thisRef: Any, property: KProperty<*>): Int {
            return _value
        }
    
        override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) {
            _value = if(value < 0) 0 else value
        }
    }

    Trong ví dụ đơn giản này chúng ta đã ủy quyền getter setter của biến cho class IntNaturalSet can thiệp và xử lý logic. Bên cạnh việc ủy quyển xử lý logic getter (setter) thì Delegate cũng có thể access đến Class chứa biến được delegate thông qua param thisRef(Chính là generic T trong ReadWriteProperty/ReadOnlyProperty). Có thể là built-in delegate cho một Type hoặc ép kiểu thisRef trong logic của getter setter để có thể sử dụng param này.

    Ứng dụng của Delegate Properties rất rộng, chúng ta có thể custom cho logic in/out data như shared preferences, cache…
    Trong Androidx/Kotlin cơ bản cũng có vài delegate như lazy, viewModels, activityViewModels…

    Summary

    Delegate là một phương pháp khá hay trong lập trình giúp chúng ta tối ưu logic source code. Một source base có thể sẽ clean hơn nếu rút gọn các common logic hay boilerplate code. Một class có thể tăng tính mở rộng trong tương lai. Một project có thể sẽ triển khai nhanh hơn nhờ những common và khả năng scalable tốt. Lần tới nếu như bạn gặp phải một vấn đề có thể xử lý bằng Delegate, cứ thử xem sao nhé!
    Hi vọng bài viết giúp bạn có thêm chút kiến thức trong chặng đường của một Engineer :3!

  • Unit Test – How to unwrap optional value in XCTest

    Unit Test – How to unwrap optional value in XCTest

    Trước khi Xcode 11 ra mắt, để unwrap một optional value chúng ta vẫn thường phải dùng Guard/if let, điều này khá bất tiện trong khi viết Unit test. Khi viết test case chúng ta không nên đưa các câu lệnh điều kiện vào trong các func test vì nó sẽ tạo ra một logic mới trong Unit Test nó khiến test case của chúng ta rắc rối và phức tạp.

    Để các bạn dễ hình dung hơn, mình có tạo một Struct Person có thuộc tính address là optional (có thể nil) như dưới đây:

    struct Person {
        let name: String
        let address: String?
    }

    Thông thường chúng ta sẽ thực hiện unwrap như sau:

    func test_Address_caseNil() throws {
        let personModel: Person = Person(name: "John", address: nil)
        // unwrap optional value 
        guard let address = personModel.address else {
            XCTFail("Expected non-nil address")// throw fail
            return
        }
        
        XCTAssertEqual(address, "Hanoi")
    }

    Trong trường hợp này địa chỉ đang nil nên test case này sẽ bị lỗi và throw thông báo “Expected non-nil address”

    Có một cách thông dụng hơn là không unwrap mà sử dụng trực tiếp giá trị optional để verify test case như sau:

    func test_address_caseNil() throws {
        let personModel: Person = Person(name: "John", address: nil)
        
        XCTAssertEqual(personModel.address, "Hanoi")
    }

    Khi chạy test case này ta nhận được thông báo lỗi như sau:

    test_address_caseNil(): XCTAssertEqual failed: ("nil") is not equal to ("Optional("Hanoi")")

    Sử dụng cách này thì khá là tiện và nhanh, tuy nhiên nó có một nhược điểm là các thông báo lỗi thường không rõ ràng. Ngoài ra nó cũng sẽ không sử dụng được trong các trường hợp đặc biệt.

    Sử dụng XCTUnwrap

    XCTUnwrap() được giới thiệu trên Xcode 11, nó làm nhiệm vụ kiểm tra giá trị optional có nil hay không? nếu nil nó sẽ throw ra lỗi, không nil thì trả về giá trị. Từ đó ta có thể thoải mái sử dụng giá trị đó để thực hiện việc testing.

    func test_address_caseNil() throws {
        let personModel: Person = Person(name: "John", address: nil)
        // result
        let result = try XCTUnwrap(personModel.address)
        XCTAssertEqual(personModel.address, "Hanoi")
    }

    Sử dụng XCTUnwrap giúp source code của chúng ta gọn gàng sạch sẽ hơn rất nhiều so với các cách thông thường khác.

    Để sử dụng được XCTUnwrap bạn nhớ thêm throws cho func test để khi kiểm tra dữ liệu bị nil nó sẽ throw lỗi và đánh dấu test case này bị fail. Nếu bạn muốn thông báo rõ ràng hơn hay đơn giản là bạn muốn viết thông báo lỗi dễ hiểu bạn có thể thêm thuộc tính như sau

     let result = try XCTUnwrap(personModel.address, "Width is nil, please config data for test case")

    Thông báo lỗi ta nhận được sẽ như sau:

    test_address_caseNil(): XCTUnwrap failed: expected non-nil value of type "String" - Width is nil, please config data for test case

    Lúc này thông báo lỗi đã rõ ràng hơn, từ đó bạn có thể xử lí vấn đề một cách nhanh hơn.

    Hi vọng bài viết sẽ giúp cho các bạn có thêm lựa chọn để xử lí các tình huống khi viết Unit Test, từ đó sử dụng nó một cách hiệu quả và phù hợp với các tình huống.

  • Unit Test – Các cách chạy test trên Xcode

    Unit Test – Các cách chạy test trên Xcode

    Thông thường khi các bạn mới vào nghề khi viết xong các test case của mình các bạn thường sử dụng Command + U để chạy. Điều này không sai, tuy nhiên nếu bạn đang thực hiện trên một project lớn thì mình nghĩ bạn không nên dùng cách này vì nó có thể khiến bạn mất rất nhiều thời gian để có được kết quả test. Sau đây mình sẽ giới thiệu cho các bạn một số cách để bạn chạy test case một cách nhanh chóng và hiệu quả hơn.

    Chạy test case trên Test Navigator

    Các bạn có thể tuỳ chọn việc test cả một file hay test từng test case một trên Test Navigator

    1. Chạy test cho cả Target tests: Dùng khi bạn muốn chạy test và lấy báo cáo cho cả target này, khi này Xcode sẽ chạy nhiều test case cùng lúc nên có thể sẽ mất nhiều thời gian
    2. Chạy test cho chỉ một file tests: Dùng khi bạn viết xong toàn bộ test case của một file và muốn chạy test để kiểm tra báo cáo hay khi bạn muốn kiểm tra xem giữa các test case có bị conflict hay không?
    3. Chạy test một case cụ thể: Dùng khi bạn vừa viết xong test case và muốn kiểm tra xem func đã chạy đúng hay chưa, đây là trường hợp bạn nên dùng khi viết UT vì nó chạy nhanh nên tốn ít thời gian mà vẫn đảm bảo mục đích của bạn

    Bạn có thể chạy một hoặc nhiều test case mà bạn muốn bằng cách chọn các test case đó -> chuột phải -> Run x Methods

    Chạy test case trực tiếp trên file Tests

    Khi các bạn viết xong các test case của mình và bấm Command + S, trên func test case của bạn sẽ xuất hiện một button cho bạn chạy test luôn, điều này giúp bạn dễ dàng kiểm tra được test case của mình. Ngoài ra Xcode cũng cung cấp cho chúng ta một nút chạy test cho toàn bộ file ở trên đầu của file. Bạn có thể xem chi tiết ở hình dưới đây

    Chạy lại test case vừa test

    Để chạy lại test các test case bạn vừa mới test thì bạn dùng tổ hợp phím sau:

    ⌃ Control + ⌥ Option + ⌘ Command + G

    Đây là tổ hợp phím rất hữu dụng và được sử dụng thường xuyên vì nó giúp bạn chạy lại test case một cách nhanh chóng khi các bạn phải sửa lại các test case bị fail do viết sai input/output.

    Chạy test tất cả file trong Test Plan

    Khi bạn muốn xem báo cáo tất cả cho file Test Plan của bạn, bạn chỉ cần nhấn tổ hợp phím Command + U.

    Bật Code Coverage trên Sidebar

    Theo mặc định thì code coverage sẽ được bật khi bạn chạy test, tuy nhiên nếu bạn nhỡ tay tắt nó đi mà không biết bật nó ở đâu thì làm theo hướng dẫn sau: Adjust Editor Options -> Code Coverage

    Khi này Xcode sẽ cho bạn biết được dòng code đó của bạn được chạy qua bao nhiêu lần, nếu con số là 0 thì có vẻ bạn chưa có test case nào được viết để test đoạn code đó. Từ dữ liệu đó bạn có thể thực hiện viết thêm test case nếu cần.

    Hi vọng bài viết giúp ích cho các bạn. Xin cảm ơn!

  • Unit Test – Cách tạo mới Test Plan trên XCode

    Unit Test – Cách tạo mới Test Plan trên XCode

    Thông thường thì các ứng dụng phát triển càng lâu thì tính năng của nó sẽ càng nhiều, điều này cũng làm cho khối lượng source code và Unit Test cũng tăng theo, có một số dự án xây dựng theo mô hình module hoá, khi này sẽ có rất nhiều module trong dự án cần thực hiện test nếu chúng ta chạy tất cả các file của project thì nó sẽ tốn rất nhiều thời gian, làm giảm đi năng suất làm việc của bản thân. Vì vậy để thực hiện các test case một cách nhanh chóng và hiệu quả Xcode cho phép chúng ta tự tạo Test Plan theo đúng kế hoạch mà chúng ta muốn. Chúng ta có thể tạo một test plan để test một màn hình riêng biệt cho đến hàng trăm màn hình hoặc có thể là một module mà chúng ta đang làm hay tất cả test case của ứng dụng.

    Tạo mới Test Plan

    Để tạo mới Test Plan ta làm theo các bước như sau:

    Tạo mới test plan

    Đặt tên cho test plan, bạn có thể đặt tên tuỳ ý miễn sao nó đúng với ý nghĩa là được. Sau đó bấm “Create” để Xcode thực hiện.

    Sau khi tạo xong, test plan mới tạo sẽ được thêm vào danh sách, bạn có thể kiểm tra bằng cách thao tác như hình phía dưới đây

    Để thực hiện cấu hình cho test plan mới này chúng ta bấm vào Edit Test Plan ở trên hình.

    Do test plan mới được tạo nên bạn sẽ thấy nó trống, lúc này chưa có một file/class/module/target nào được thêm vào để test nên chúng ta cần thêm nó vào bằng cách bấm vào dấu + trên màn hình dưới đây:

    Tương tự nếu bạn muốn loại bỏ một target nào thì ta có thể bấm dấu – để xoá khỏi test plan.

    Chọn target mà bạn muốn thêm vào test plan và bấm “Add” để thêm target vào test plan.

    Xcode sẽ thêm tất cả các file test chúng ta đã viết vào trong mục Tests, ở đây bạn sẽ nhìn thấy tất cả các file test mà bạn viết, bạn có thể bỏ tích một func hay 1 file để Xcode không chạy phần đó khi run test plan của bạn.

    Để khi chạy test chúng ta xem được Code Coverage thì chúng ta cần đổi giá trị Code Coverage bên Configurations sang ON

    Lúc này khi chạy Test Plan này chúng ta sẽ xem được kết quả bằng cách Report Navigator -> Local -> Test -> Coverage

    Dựa vào báo cáo này bạn sẽ biết được bạn đã viết test case chạy qua được bao nhiêu phần trăm của source code từ đó bạn sẽ có thể đưa ra các action phù hợp.

    Lưu ý Code Coverage chỉ là con số chỉ ra rằng source code test case của bạn đã chạy qua bao nhiêu dòng code, chứ nó không phải là chỉ số đảm bảo độ tin cậy source code của bạn. Vì vậy nó chỉ là điều kiện đủ, code coverage càng cao thì điều đó chứng tỏ Test Case của bạn đã chạy qua càng nhiều dòng code. Để đảm bảo chất lượng source code thì các bạn cần có một chút tư duy về testing, biết xác định các cặp input và output để thực hiện test, cần xác định các điều kiện biên, ngoại lệ, trường hợp lỗi, trường hợp thành công …. thì việc viết test case mới có hiệu quả.

    Hi vọng bài viết giúp ích được cho các bạn!

  • Fake GPS location on Xcode

    Fake GPS location on Xcode

    Ở phần trước mình đã hướng dẫn các bạn các cách để fake GPS location trên Simulator của iPhone. Nếu các bạn muốn tìm hiểu thêm thông tin thì có thể tham khảo ở đường link: HƯỚNG DẪN CÁCH FAKE GPS TRÊN SIMULATOR

    Trong bài này mình sẽ hướng dẫn các bạn Fake GPS location on Xcode nhằm mục đích giúp các bạn có thêm các cách để giảm thời gian và dễ dàng test các ứng dụng liên quan tới GPS, location services. Đặc biệt hữu dụng với các ứng dụng yêu cầu người dùng phải di chuyển kiểu như app chạy bộ, đạp xe, chỉ đường, hướng dẫn … v.v.

    Giải thích về ý tưởng

    Để dễ dàng cho việc test các tính năng yêu cầu người dùng ứng dụng phải di chuyển, chúng ta sẽ sử dụng cách Fake GPS location bằng cách sử dụng GPX file trên Xcode. GPX file cho phép chúng ta thêm các vị trí và thời gian di chuyển đến vị trí đó, từ đó ta có thể tạo ra một danh sách các điểm cần di chuyển đến để thực hiện test ứng dụng mà không cần di chuyển thực tế. GPX file cho phép ta có thể tạo ra các kịch bản test di chuyển một cách dễ dàng và tiết kiệm thời gian và công sức. Ngoài ra ta có thể sử dụng lại được nhiều lần và dễ dàng chỉnh sửa theo các kịch bản test.

    Từ các tính năng như trên chúng ta có thể tạo ra một ứng dụng chuyên để Fake GPS Location từ đó giả lập vị trí của iPhone.

    NOTE

    Đây là fake GPS location của thiết bị luôn, và nó hoạt động và ảnh hưởng lên tất cả các ứng dụng đang sử dụng Map, Location Services. Vậy nên các bạn lưu ý khi sử dụng nha.

    Hướng dẫn Fake Location GPS trên XCode sử dụng GPX file

    Để tiện cho việc demo các bạn có thể tải xuống source ở link dưới đây:

    Bước 1: Tạo file GPX để fake GPS Location trên Xcode

    Sử dụng Xcode mở project DemoGPXMapKit lên -> Bấm chuột phải vào tạo new file…

    Fake GPS location on Xcode

    Khi của sổ mở lên, ở ô tìm kiếm bạn điền từ khoá “GPX” thì sẽ được kết quả như hình dưới đây

    Fake GPS location on Xcode

    Bấm Next và đặt tên cho file, ở ví dụ này mình sẽ đặt là mydinh-hanoi. Đặt tên xong Xcode sẽ tạo cho các bạn một file và có sẵn nội dung là một điểm.

    Các bạn có thể thay đổi file GPX tuỳ theo trường hợp mà bạn muốn. Ở đây mình muốn Fake GPS Location về Sân vận động Mỹ Đình nên mình sửa lại file như sau:

    <?xml version="1.0"?>
    <gpx version="1.1" creator="Xcode">
        <wpt lat="21.02025349623149" lon="105.76408736691972">
            <name>My-dinh</name>
            <time>2014-09-24T14:55:37Z</time>
        </wpt>
    </gpx>
    

    Về toạ độ lat, lon các bạn có thể sử dụng Google Map để lấy -> bấm chuột phải vào vị trí bạn muốn lấy vị trí sẽ hiển thị như hình và bạn chọn vào hàng có toạ độ là nó tự copy toạ độ cho các bạn, rồi các bạn paste vào file GPX để thay đổi vị trí một cách dễ dàng.

    Fake GPS location on Xcode

    Bước2: Chỉnh sửa scheme để file GPX fake GPS Location tự động ăn khi chạy ứng dụng

    Chọn Scheme -> Edit Scheme như hình dưới

    Fake GPS location on Xcode

    Một popup hiển thị lên, lúc này bạn chọn Run -> Options.
    – Ở mục CoreLocation tích vào Allow Location Simolation.
    – Ở mục Default Location chọn file gpx của các bạn, trường hợp này của mình là mydinh-hanoi. Nếu không chọn thì mặc định nó sẽ là None và khi đó nó sẽ ăn vào location của Simulator

    Fake GPS location on Xcode

    Thực hiện Build(Command + R) ứng dụng để ứng dụng sử dụng file GPX.

    Khi này ở thanh Debug chúng ta sẽ có thêm icon Location để cho phép chúng ta thay đổi vị trí Fake GPS location một cách dễ dàng và nhanh chóng hơn.

    Fake GPS location on Xcode

    Kết quả thu được như sau:

    Fake GPS location on Xcode

    Các bạn có thể tạo ra nhiều file GPX với nhiều vị trí khác nhau để thuận tiện cho việc test, hoặc có thể sửa trực tiếp file GPX rồi chọn lại mà không cần build lại ứng dụng.

    Fake GPS Location di chuyển theo đường đi bằng file GPX

    Chúng ta sẽ tạo mới một file như các bước mình đã hướng dẫn ở đầu bài. Nội dung thì sửa lại như sau:

    <?xml version="1.0"?>
    <gpx version="1.1" creator="Xcode">
        <wpt lat="21.02186996901372" lon="105.7645261741194">
            <name>Cupertino</name>
            <time>2014-09-24T14:05:30Z</time>
        </wpt>
        <wpt lat="21.021606653375308" lon="105.76616386527535">
            <name>Cupertino</name>
            <time>2014-09-24T14:05:40Z</time>
        </wpt>
        <wpt lat="21.019302621702572" lon="105.76618737280388">
            <name>Cupertino</name>
            <time>2014-09-24T14:05:50Z</time>
        </wpt>
    </gpx>

    Ở đây mình có thêm vào 3 waypoints để fake GPS location, nhằm giả lập việc di chuyển qua các điểm này. Các bạn để ý kỹ thì mình có thay đổi thời gian của các waypoint tăng dần và cách nhau 10s. Các bạn có thể tính toán vận tốc và quãng đường để thực hiện giả lập một cách chân thực nhất, ví dụ như tốc độ của ô tô với máy bay thì sẽ khác nhau vì vậy cùng quãng đường thời gian cũng sẽ khác nhau.

    Giờ khi các bạn chọn icon location sẽ hiển thị ra file bạn mới tạo và có thể thay đổi luôn mà không cần phải build lại nữa, như mình mới tạo file ride-mydinh-hanoi

    Kết quả chúng ta sẽ thu được như sau

    Tuy nhiên ta có thể thấy nó cứ lặp đi lặp lại vô hạn mà không dừng lại tại điểm cuối cùng. Để cho nó dừng lại ở điểm cuối cùng thì chúng ta sẽ sử dụng thêm một waypoint nữa và thay đổi vị trí không đáng kể và tăng thời gian di của điểm này cách xa so với điểm gần cuối như sau:

    <?xml version="1.0"?>
    <gpx version="1.1" creator="Xcode">
        <wpt lat="21.02186996901372" lon="105.7645261741194">
            <name>Cupertino</name>
            <time>2014-09-24T14:05:30Z</time>
        </wpt>
        <wpt lat="21.021606653375308" lon="105.76616386527535">
            <name>Cupertino</name>
            <time>2014-09-24T14:05:40Z</time>
        </wpt>
        <wpt lat="21.019302621702572" lon="105.76618737280388">
            <name>Cupertino</name>
            <time>2014-09-24T14:05:50Z</time>
        </wpt>
        <wpt lat="21.019302611702572" lon="105.76618737280388">
            <name>Cupertino</name>
            <time>2014-09-24T15:05:50Z</time>
        </wpt>
    </gpx>

    Bạn để ý kĩ chỗ mình bôi đậm sẽ hiểu rõ nguyên lý hoạt động của nó.

    Từ đây bạn có thể mở rộng ra bằng cách tạo thêm nhiều file GPX với các cung đường đa dạng để phục vụ cho việc test ứng dụng của các bạn khi cần.

    Mình hi vọng bài viết có thể giúp mọi người làm việc một cách dễ dàng và hiệu quả hơn.

  • How to enable UT Coverage on Xcode?

    How to enable UT Coverage on Xcode?

    You want to check your UT index?
    You want to display the coverage index on xcode to know if your code has reached the standard?
    How to show Coverage index on xcode?
    How to enable UT Coverage on XCode?
    Those are the questions I get asked often, but there aren’t many tutorials on the internet yet. So today I will guide you to enable Code Coverage on Xcode

    How to enable UT Coverage on Xcode 13.4.1 or below?

    For projects using Xcode 13 or below to create, we do the following:

    Step 1: Select Scheme -> edit Scheme -> a popup is displayed

    Step 2: In the left menu of the popup select Test, then in the upper menu of the popup select option -> tick Code Coverage for

    Step 3: Run test app (command + U)

    How to enable UT Coverage on Xcode 14.0 or later?

    For projects using Xcode 14 or later to create, we do the following:

    Step 1: Select Scheme -> edit Scheme -> a popup is displayed

    How to enable UT Coverage on XCode

    Step 2: In the left menu of Popup select Test, then hover on Test Plan as shown and then click the button to go to settings.

    How to enable UT Coverage on XCode

    Step 3: Change code coverage to On

    How to enable UT Coverage on XCode

    If you don’t need to test UI, you can remove the test plan for UI to make Unit tests run faster

    How to enable UT Coverage on XCode

    Step 4: Run test (command + U), ta thu được kết quả như sau

    How to enable UT Coverage on XCode

    I hope it can help!

  • Testing SCD Loại 1 trong ETL validator

    Testing SCD Loại 1 trong ETL validator

    Xin chào mọi người, ở bài viết SCD (Slowly Changing Dimension) là gì? Các loại SCD và ví dụ cụ thể (https://magz.techover.io/2023/07/03/scd-slowly-changing-dimension-la-gi-cac-loai-scd-va-vi-du-cu-the/) chúng ta đã nắm được sơ lược về SCD type 1. Hôm nay chúng ta cùng đi tìm hiểu kỹ hơn về SCD type 1 và các test cases cần thiết trong quá trình kiểm thử nhé!

    1. Tìm hiểu về SCD type 1 (Ghi đè – Overwrite)

    Ở SCD1 các dữ liệu mới sẽ ghi đè dữ liệu cũ, không theo dõi dữ liệu lịch sử.

    Ví dụ:

    Original Record – Dữ liệu gốc

    Cust_ID Name City
    1001 Nguyễn Văn A HCM
    1002 Nguyễn Văn B Nam Định

    Updated Record – Dữ liệu được thay đổi

    Cust_ID Name City
    1001 Nguyễn Văn A Hà Nội
    1002 Nguyễn Văn B Nam Định

    Ưu điểm: Dễ bảo trì.

    Nhược điểm: Không kiểm tra được dữ liệu lịch sử.

    2. SCD1 validation test cases

    image

    Kiểm tra SCD Loại 1 khá đơn giản vì chúng ta có thể đạt được kết quả bằng cách so sánh đơn giản giữa dữ liệu nguồn và dữ liệu đích. Ví dụ chúng ta có bẳng nguồn và bảng đích như sau:

    Employee table – bảng nguồn với các cột:

    Cust_ID
    Name
    City

    EMPLOYEE_DIM SCD Type 1 – bảng đích với các cột dưới đây:

    Cust_ID
    Name
    City
    Partition_date

    2.1 Test case 1: Chúng ta sẽ đi kiểm tra số lượng cột, tên cột và data type từng cột có đúng với yêu cầu document không?

    VD: Cus_ID có type là bigint, thì trên bảng đích data type cũng là bigint.

    Target Query: Describe Table_Name; or Desc Table_Name

    2.2 Test case 2: Kiểm tra số lượng records.

    Source Query: Select count (*) from db.Emlpoyee

    Target Query: Select count (*) from db.Employee_Dim

    2.3 Test case 3: Kiểm tra logic insert

    1. Insert thêm records mới ở bảng nguồn:

    Chúng ta sẽ thêm các records mới tại bảng nguồn, rồi khởi chạy job ETL. Sau khi job chạy thành công thì kiểm tra dữ liệu ở bảng đích có lên đúng và đủ so với bảng nguồn không.

    1. Delete records ở bảng nguồn

    Ngược lại với thêm mới, chúng ta sẽ xóa bớt dữ liệu ở bảng nguồn rồi chạy job ETL. Sau khi job chạy thành công thì kiểm tra dữ liệu ở bảng đích và bảng nguồn có mapping với nhau không.

    2.4 Test case 4: Kiểm tra logic update

    Tại bảng nguồn chúng ta sẽ update các bản ghi trước đó, rồi chạy job ETL. Sau khi job chạy thành công, kiểm tra dữ liệu bảng đích có lên đúng so với bảng nguồn không.

    2.5 Test case 5: Kiểm tra dữ liệu có bị duplicate không? – Xác minh tính duy nhất của dữ liệu

    Target Query:

    Select Cust_ID, Name, City, Partition_date, count(*) from db.Employee_Dim group by Cust_ID, Name, City, Partition_date having count(*) > 1 ;

    Select Cust_ID, count(Cust_ID) from db.Employee_Dim group by Cust_ID having count(Cust_ID) > 1;

    2.6 Test case 6: Kiểm tra dữ liệu null

    Target Query:

    Select * from db.Employee_Dim where Cust_ID is not null;

    Kết luận

    • SCD1: các dữ liệu mới sẽ ghi đè dữ liệu cũ, không theo dõi dữ liệu lịch sử
    • Kiểm tra số lượng bản ghi bảng đích so với bảng nguồn
    • Sau khi thay đổi dữ liệu bảng nguồn (insert, delete, update) phải thực hiện quy trình ETL để EMPLOYEE_DIM có dữ liệu mới nhất
    • Sửa đổi một vài bản ghi trong bảng nguồn bằng cách cập nhật các giá trị trong các cột chính (key columns) để kiểm tra logic update
    • Xác minh tính duy nhất của dữ liệu
    • Xác minh rằng sự khác biệt là như mong đợi

    Trên đây là những chia sẻ của mình về testing SCD type 1 trong ETL testing, mong rằng bài viết này sẽ giúp ích được cho các bạn. Nếu mọi người có thắc mắc hay câu hỏi gì đừng ngần ngại comment và cùng nhau giải đáp nhé!

    Hẹn gặp lại mọi người trong bài viết tiếp theo.

  • Hướng dẫn cách Fake GPS trên Simulator

    Hướng dẫn cách Fake GPS trên Simulator

    Hi, trong quá trình phát triển các ứng dụng iOS, đôi khi chúng ta sẽ được phát triển các tính năng liên quan tới bản đồ, một số ứng dụng còn cần phải sử dụng Location Service để hiển thị vị trí của người dùng. Vì vậy khi phát triển ứng dụng bạn sẽ cần phải kiểm tra ứng dụng của mình xem nó chạy đúng chưa, hiển thị đúng vị trí trên bản đồ chưa? Nếu chúng ta cầm thiết bị chạy đi chạy lại thì thật quá tốn công, tốn sức. Thấu hiểu nỗi khổ của các iOS developers Apple đã phát triển tính năng Fake GPS trên Simulator giúp cho việc kiểm tra ứng dụng của mình một cách dễ dàng và đỡ mất công mất sức hơn.

    Fake GPS trên Simulator – Di chuyển đến trụ sở chính của Apple

    Để bật tính năng này chúng ta mở Simulator lên, trên Menu bar của Simulator chọn Features -> Location -> Apple như hình dưới đây

    fake gps on simulator

    Kết quả chúng ta sẽ được vị trí GPS ở trụ sở của Apple.

    Fake GPS trên Simulator – Di chuyển đến vị trí bất kì trên bản đồ

    Việc fake GPS trên Simulator đến vị trí trung tâm Apple không giúp ích nhiều cho việc kiểm tra ứng dụng của bạn, vì vậy mình sẽ hướng dẫn các bạn cách để Fake GPS trên Simulator đến vị trí bất kì trên bản đồ. Để làm được điều này chúng ta cần Kinh độ và Vĩ độ của điểm chúng ta cần fake GPS. Các bạn có thể lên Google Map để lấy toạ độ nhá. Ví dụ mình sẽ lấy toạ độ của Đền Ngọc Sơn trên Hồ Hoàn Kiếm ở Hà Nội.

    Để ý hình mình có khoanh, Ta sẽ lấy toạ độ (lat, long) theo thứ tự từ trái qua phải là Latitude và Longitude ở trên URL của google map. Trong ví dụ này Latitude: 21.0298318Longitude: 105.8532851.

    Khi lấy được toạ độ cần fake GPS trên Simulator rồi thì ta sẽ làm như sau:
    Trên Menu bar của Simulator -> chọn Features -> Location -> Custom Location… -> Điền Latitude và Longitude vào và bấm OK như hình dưới.

    Vậy là chúng ta đã có thể fake GPS đên mọi nơi mà chúng ta cần để kiểm tra ứng dụng của mình.

    NOTE: Có một lưu ý là khi các bạn copy Latitude và Longitude từ Google đôi khi ngôn ngữ không giống với simulator nên simulator nó không nhận, bạn hãy để ý và thay dấu chấm “.” <-> thành dấu phẩy “,” hoặc ngược lại nhé.

    Fake GPS trên Simulator – Fake trường hợp vị trí chuyển động liên tục.

    Trong thực tế có một số ứng dụng yêu cầu ta phải hiển thị vị trí của người dùng chính xác và liên tục, tuy nhiên để kiểm tra trường hợp này một cách mượt mà thì sử dụng các cách phía trên là bất khả thi và tốn rất nhiều thời gian, vì vậy apple đã cung cấp một số cách để thuận tiện cho việc kiểm tra vị trí di chuyển đó là: City Run, City Bicycle Ride và Freeway Drive.
    Trên Menu bar của Simulator -> chọn Features -> Location -> City Run hoặc City Bicycle Ride hoặc Freeway Drive.

    Vậy là bạn đã có thể ngồi 1 chỗ và test được việc di chuyển trên ứng dụng của mình mà không tốn một giọt mồ hôi nào.

    Hiện tại Apple chỉ hỗ trợ 3 loại di chuyển như trên vì vậy nó khá hạn chế trong việc kiểm thử, trong trường hợp các bạn cần linh hoạt hơn thì chúng ta có thể sử dụng file GPX để fake GPS trên Simulator chủ động hơn, tuy nhiên cách này sẽ phức tạp hơn một chút. Vì vậy mình sẽ hướng dẫn các bạn ở bài viết tiếp theo nhé!

  • Coroutine và Bài toán nhiều Task vụ

    Coroutine và Bài toán nhiều Task vụ

    Chào mọi người. Trong xử lý lập trình bất đồng bộ, mọi người rất hay gặp phải tình huống xử lý nhiều task vụ cùng một lúc, hoặc các task vụ xử lý lần lượt, task vụ này phụ thuộc vào kết quả của các task vụ kia. Hôm nay mình xin chia sẻ một số cách để xử lý bài toán này bằng Coroutine.

    Hãy xem xét một tình huống, trong đó Task_1 đang mong đợi một id thực hiện Task_2 với id được nhận từ Response của Task_1 và dựa trên phản hồi từ Task_2, các điều kiện và tham số của Task_3 sẽ bị thay đổi.

    Task_1 → Task_2 with id -> Task_3

    Làm thế nào để nhiều task song song phụ thuộc được thực hiện.

    Thông thường, Task đầu tiên sẽ được thực hiện và sau khi nhận được phản hồi (Response), thao tác dữ liệu thì cuộc gọi tiếp theo sẽ được thực hiện.

    viewModelScope.launch {
        val data1Response:BaseResponse<Data1>?
        try{
        val call1 = repository.getAPIcall1()
        }
      catch (ex: Exception) {
            ex.printStackTrace()
        }
        processData(data1Response)
    }
    
      viewModel?.data1?.collect { dataResponse1 ->
             repository.getAPIcall2()         
    }
    
    viewModel?.data1?.collect { dataResponse2 ->
             repository.getAPIcall3()         
    }
    

    Như cách triển khai trên với các task vụ phục thuộc vào nhau. Chúng ta cần phải Sync up trước khi thao tác với dữ liệu, cũng như xử lý với task vụ tiếp theo.

    Với Couroutine có một số cách tiếp cận, xử lý tối ưu hơn để xử lý dữ liệu bất đồng bộ và đội kết quả để thực hiện Logic nghiệp vụ của bài toán.

    Sau đây là một số cách tiếp cận.

    1. Concurrent Approach with Wait Time async-await with Kotlin Coroutines

    viewModelScope.launch {
            val data1Response:BaseResponse<Data1>?
            val data2Response: BaseResponse<Data2>?
            val data3Response: BaseResponse<Data3>?
            val call1 = async { repository.getAPIcall1()}
            val call2 = async { repository.getAPIcall2()}
            val call3 = async { repository.getAPIcall3() }
            try {
                data1Response = call1.await()
                data2Response = call2.await()
                data3Response = call3.await()
            } catch (ex: Exception) {
                ex.printStackTrace()
            }
            processData(data1Response, data2Response, data3Response)
    }
    

    Async sẽ mong đợi Response của task vụ. Tại đây, Task_1 sẽ cung cấp dữ liệu sẽ được đồng bộ hóa với Task_2, v.v. Sau đó, thao tác dữ liệu có thể được thực hiện với Response đã nhận và xử lý. Nhưng nó sẽ có một số thời gian chờ đợi giữa mỗi cuộc gọi.

    Có một cách triển khai mà chúng ta có thể kết hợp gọi nhiều task vụ cùng tại một thời điểm.

    suspend fun fetchData() =       
        coroutineScope {
            val mergedResponse = listOf(   
                async { getAPIcall1() },  
                async { getAPIcall2() } 
            )
            mergedResponse.awaitAll()        
        }
    

    2. Concurrent/ parallel Approach with thread switchingwithContext() will switch to seperate thread

    Nó tương tự như async-await. Nhưng đâu đấy sẽ tiết kiệm chi phí hơn. Thay vì triển khai trên Main Thread, withcontext sẽ chuyển sang một Thread riêng và thực hiện tác vụ. Nó sẽ không có thời gian chờ như async-await.

    Nếu runBlocking được thêm overlapping lên withContext(), nó sẽ đảo ngược tính chất bất đồng bộ và có thể hủy của Coroutines và chặn luồng. Cho đến khi nhiệm vụ cụ thể được hoàn thành.

    Có 5 loại Dispatchers. IO, Main, Default, New Thread, Unconfined

    • Default Dispatcher: Đây là dispatcher mặc định trong hầu hết các hệ thống Coroutine. Nó sử dụng một pool thread cố định để thực thi các coroutine. Mặc dù nó hữu ích cho các hoạt động đơn giản, nhưng nó không phù hợp cho các hoạt động đòi hỏi nhiều tài nguyên hoặc chạy lâu.

    • IO Dispatcher: Dispatcher này được tối ưu hóa để thực thi các hoạt động I/O chậm, chẳng hạn như đọc/ghi dữ liệu từ đĩa hoặc mạng. Thay vì sử dụng pool thread cố định, IO Dispatcher thường sử dụng một số luồng I/O đặc biệt để tận dụng tối đa tài nguyên hệ thống.

    • Unconfined Dispatcher: Loại dispatcher này cho phép coroutine chạy trên bất kỳ luồng nào. Nó không liên kết với luồng nào cụ thể và cho phép coroutine chuyển đổi giữa các luồng trong quá trình thực thi. Điều này có thể hữu ích trong một số trường hợp đặc biệt, nhưng cũng có thể gây ra vấn đề về đồng bộ hóa và xử lý của task vụ.

    • New Thread Dispatcher: Dispatcher này tạo ra một luồng mới cho mỗi coroutine được thực thi. Điều này đảm bảo rằng mỗi coroutine sẽ chạy độc lập trên một luồng riêng biệt. Tuy nhiên, việc tạo và quản lý nhiều luồng có thể ảnh hưởng đến hiệu suất và tài nguyên hệ thống.

    • Main Dispathcher: Loại dispatcher đặc biệt được sử dụng để thực thi các coroutine trên luồng chính của ứng dụng. Main Dispatcher đảm bảo rằng các coroutine chạy trên luồng chính không bị chặn (block) trong quá trình thực thi, để đảm bảo khả năng phản hồi của giao diện người dùng.

    viewModelScope.launch {
        withContext(Dispatchers.Default) {
            val apiResponse1 = api.getAPICall1() 
            val apiResponse2 = api.getAPICall2() 
            if (apiResponse1.isSuccessful() && apiResponse2.isSuccessful() { .. }
        }
    }
    

    3. Parallel Approach with data merging

    Cách tiếp cận thứ ba hơi khác một chút và nếu chúng ta muốn có hai task vụ độc lập và ghép chúng lại với nhau để có phản hồi mới, thì Zip Operator sẽ giúp chúng tôi xử lý song song chúng và đưa cho chúng ta kết quả mà chúng ta cần.

    repository.getData1()
    .zip(repository.getData2()) { data1, data2 ->
        return@zip data1 + data2
    }
    .flowOn(Dispatchers.IO)
    .catch { e ->
      ..
    }
    .collect { it ->
        handleSuccessResponse(..)
    }
    

    Tổng kết

    Trên đây mình đã giới thiệu 3 cách tiếp cận và triển khai, bạn có sử dụng cho từng bài toán, requirement cụ thể:

    • Khi chúng tôi muốn gọi nhiều Task vụ song song với thời gian chờ, thì cách tiếp cận async-await sẽ phù hợp.
    • Khi chúng ta muốn cách tiếp cận hiệu quả hơn với chuyển đổi các Thread, withcontext sẽ phù hợp
    • Và để ghép hai Response lại với nhau và thực hiện một số thao tác dữ liệu, phương pháp toán tử Zip là phù hợp.

    Hy vọng bài viết này ít nhiều giúp các bạn về một số cách, một số lựa chọn xử lý với bài toàn xử lý nhiều task vụ, cụ thể ở đây là với Couroutine. Hẹn gặp mọi người ở bài viết sắp tới.

  • Context trong Android và những lưu ý

    Context trong Android và những lưu ý

    Chào mọi người, hôm nay mình xin chia sẻ về một chủ đề khá thông dụng trong Android, đó là Context. Mục đích của bài này đâu đấy các bạn mới tiếp cận với Android có thể hiểu đúng bản chất của Context, các loại Context và quan trọng hơn là cách cân nhắc sử dụng chúng trong từng trường hợp một cách hợp lý và chính xác nhất, tránh gây ra những lỗi không mong muốn.

    1. Khái niệm

    Mình xin trích dẫn một đoạn trong Official Document Android về Context

    Mình xin trích dẫn một đoạn trong Official Document Android về Context

    Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.

    Các bạn cũng có thể hiểu cơ bản thế này

    Context là trạng thái của ứng dụng tại một thời điểm nhất định.

    Context là 1 lớp cơ bản chứa hầu hết các thông tin về môi trường ứng dụng của android, tức là mọi thao tác, tương tác với hệ điều hành đều phải thông qua lớp này.

    Context là 1 abstract class, nó cung cấp cho các lớp triển khai các phương thức truy cập vào tài nguyên của ứng dụng và hệ thống. Ví dụ như nó có thể khởi tạo và chạy các activities, broadcast, các intents….

    Nào giờ chúng ta xem xét 3 functions hay được sử dụng nhiều nhất để truy xuất Context:

    • getContext() — trả về Context được liên kết với Activity được gọi.
    • getApplicationContext() — trả về Context được liên kết với Application chứa tất cả các Activity đang chạy bên trong nó.
    • getBaseContext() — có liên quan đến ContextWrapper, được tạo xung quanh Context hiện có và cho phép thay đổi hành vi của nó. Với getBaseContext() chúng ta có thể lấy Context hiện có bên trong lớp ContextWrapper.

    Trong số này nổi bật hơn cả là getContext()getApplicationContext(). Liên quan đến getBaseContext(), có một lưu ý nhỏ là tránh sử dụng loại Context này – lớp này được triển khai khi một class extends từ ContextWrapper. Mà lớp này lại có khoảng 40 lớp con trực tiếp và không trực tiếp. Vì vậy, nên gọi trực tiếp đến getContext, Activity, Fragment… để tránh gây ra memory leak.

    2. Các loại truy cập Context

    2.1. getContext()

    Trong getContext(), Context được gắn với một Activity và vòng đời của nó. Chúng ta có thể hình dung Context là layer đứng sau Activity và nó sẽ tồn tại chừng nào Activity còn tồn tại. Thời điểm Activity chết, Context cũng vậy.

    Dưới đây là danh sách các chức năng mà Activity’s Context cung cấp cho chúng ta:

    Load Resource Values,
    Layout Inflation,
    Start an Activity,
    Show a Dialog,
    Start a Service,
    Bind to a Service,
    Send a Broadcast,
    Register BroadcastReceiver.
    

    2.2. getApplicationContext()

    Trong getApplicationContext(), Context của chúng ta được gắn với ứng dụng và vòng đời của nó. Chúng ta có thể coi nó như một lớp đằng sau toàn bộ ứng dụng. Miễn là người dùng không tắt ứng dụng, thì nó vẫn tồn tại.

    Bây giờ bạn có thể tự hỏi, đâu là sự khác biệt giữa getContext() và getApplicationContext(). Sự khác biệt là Context của ứng dụng không liên quan đến giao diện người dùng. Điều đó có nghĩa là, chúng ta không nên sử dụng nó để Inflate một Layout, start một Activity cũng như Dialog. Về phần còn lại của các chức năng từ Context của Activity, chúng cũng có sẵn trong Application Context. Vì vậy, danh sách các chức năng cho Application Context bao gồm như sau:

    Load Resource Values,
    Start a Service,
    Bind to a Service,
    Send a Broadcast,
    Register BroadcastReceiver.
    

    3. Cách sử dụng hợp lý Context trong Android

    Việc sử dụng Context phù hợp trong Android là rất quan trọng để đảm bảo ứng dụng của bạn hoạt động bình thường và tránh các sự cố như memoryleak hoặc sự cố tài nguyên. Dưới đây là một số nguyên tắc giúp bạn sử dụng ngữ cảnh phù hợp:

    • Chọn Context cụ thể theo ngữ cảnh: Chọn Context cung cấp phạm vi cần thiết cho thao tác bạn đang thực hiện. Ví dụ: nếu bạn đang tạo thành phần giao diện người dùng trong một Activity, hãy sử dụng Activity Context thay vì Application Context.
    • Lưu ý đến Lifespan của Context: Xem xét vòng đời của thành phần yêu cầu Context. Đối với các hoạt động tồn tại trong thời gian ngắn, hãy sử dụng Context phù hợp với Lifespan của Component. Ví dụ: nếu bạn cần Context trong phương thức onReceive() của BroadcastReceiver, hãy sử dụng tham số Context được cung cấp thay vì Context tồn tại lâu dài như Application Context.
    • Tránh sử dụng Context hoạt động ngoài vòng đời của nó: Hãy thận trọng khi sử dụng Context hoạt động bên ngoài vòng đời của Activity, vì nó có thể dẫn đến memoryleak. Chẳng hạn, nếu bạn lưu trữ tham chiếu đến ngữ Activity Context và sử dụng nó sau khi Activity đã bị hủy, thì điều đó có thể gây ra sự cố. Trong những trường hợp như vậy, hãy chuyển sang sử dụng Application Context để thay thế.
    • Lưu ý về bộ nhớ: Tránh lưu trữ các tham chiếu lâu dài đến các Context một cách không cần thiết, vì chúng có thể giữ một tham chiếu đến toàn bộ Activity và Application và ngăn các tài nguyên bị thu gom rác. Nếu bạn cần một Context trong một Component tồn tại lâu dài, chẳng hạn như một singleton, hãy ưu tiên sử dụng Application Context.
    • Cân nhắc sử dụng Dependency Injection: Việc sử dụng các Dependency Injection như Dagger, Hilt hoặc Koin có thể đơn giản hóa việc quản lý Context bằng cách cung cấp ngữ cảnh phù hợp khi cần. Nó giúp tách rời các thành phần và đảm bảo Context chính xác được đưa vào dựa trên phạm vi và yêu cầu của hoạt động.

    4. Kết luận

    Việc lựa chọn Context phụ thuộc vào các yêu cầu cụ thể của mã của bạn và thành phần bạn đang xử lý. Điều quan trọng là chọn Context phù hợp để tránh memoryleak, sự cố tài nguyên hoặc sự không nhất quán trong ứng dụng của bạn. Theo nguyên tắc chung, hãy cố gắng sử dụng bối cảnh hạn chế nhất có thể để hoàn thành nhiệm vụ hiện tại, đảm bảo rằng nó có phạm vi và Lifespan cần thiết cho hoạt động.

    Mong bài viết này sẽ giúp ích ít nhiều cho các bạn. Hẹn gặp lại mọi người trong bài viết tiếp theo.