Author: LamNT59

  • Tạo Telegram bot để phục vụ làm việc với GoogleSheet

    Tạo Telegram bot để phục vụ làm việc với GoogleSheet

    1. Bài toán

    • Việc truy xuất các dữ liệu trên một file Excel hay GoogleSheet là rất dễ dàng với những người có kĩ năng Office cơ bản.
    • Tuy nhiên với một người không có kiến thức đủ để làm việc với Excel, GoogleSheet hoặc muốn truy xuất thông tin từ file ngay lập tức mà lại đang không có các công cụ hỗ trợ làm việc, viết các câu lệnh trên Excel, GoogleSheet, thì việc truy xuất dữ liệu này sẽ trở nên khó khăn.
    • Khi đó việc tạo một trợ lý ảo, một con bot giúp người dùng có thể truy cập đến dữ liệu trên một file Excel, GoogleSheet là một việc rất hữu ích.
    • Ở đây tôi sẽ dùng bot trên Telegram vì đây là một ứng dụng nhắn tin khá phổ biến và cho phép developer phát triển nhiều thứ trên đó.
    • Về ngôn ngữ lập trình tôi sẽ sử dụng Python vì ngôn ngữ này cho phép developer làm việc dễ dàng với Excel, GoogleSheet.

    2. Tạo bot trên Telegram

    • Đầu tiên người dùng cần phải truy cập vào ứng dụng Telegram, vào thanh tìm kiếm và tìm kiếm từ khoá BotFather
    • Gõ lệnh /newbot để tạo bắt đầu tạo bot mới
    • Tiếp theo chúng ta sẽ đặt tên cho bot của mình.
    • Sau khi đặt tên BotFather sẽ yêu cầu chúng ta chọn username cho con bot, đây cũng chính là tên giúp chúng ta có thể tìm kiếm con bot ở trên Telegram như các tìm kiếm một người dùng.
    Sau khi hoàn thành xong bước trên BotFather sẽ gửi lại cho chúng ta thông tin về bot
    • Link để truy cập vào bot: t.me/csv_xlsx_bot
    • Access token: developer sẽ sử dụng token để làm việc với các API từ đó phát triển các tính năng khác cho con bot của mình.

    3. Sử dụng Python để truy xuất data từ GoogleSheet

    • Đầu tiên chúng ta cần tạo một project Python
    • Thêm các thư viện cần thiết: pip, pandas, numpy
    • Chạy các câu lệnh sau ở terminal để thêm các thư viện cần dùng
    • Lưu ý là link GoogleSheet cần được public access cho tất cả người dùng có thể truy cập
    # Install pip
    python3 get-pip.py
    # Install pandas
    pip install pandas
    # Install numpy
    pip install numpy

    Tạo file .env: file này được dùng để lưu token của Telegram API developer

    export BOT_TOKEN=6354775494:*************************************

    Tạo một file đặt tên là utils.py: file này sẽ chứa các thuật toán làm việc với file GoogleSheet

    • Chúng ta sẽ sử dụng thư viện pandas đọc file GoogleSheet
    • Các trường thông tin truy vấn được sử dụng ở đây là
    Các trường thông tin truy vấn được sử dụng:
    • download_link: đường dẫn file GoogleSheet cần truy vấn
    • sheet_name: tab trong file GoogleSheet cần truy vấn
    • usecols: các cột giá trị truy vấn
    • df = df[(df[‘City’]==’Los Angeles’)]: dùng để filter các bản ghi có trường ‘City’ là ‘Los Angeles’
    Kết quả trả về sẽ được parse sang string bằng việc sử dụng thư viện numpy
    import requests
    import pandas as pd
    import numpy as np
    
    download_link_prefix = "https://drive.google.com/uc?export=download&id="
    def read_data_from_file(file_path):
        sheet_name = 'FoodSales'
        query_cols = ['ID','Date','City']
        download_link = get_google_sheets_download_link(file_path)
        if (download_link == None): return None
        else: 
            df = pd.read_excel(download_link, sheet_name=sheet_name, usecols=query_cols)
            df = df[(df['City']=='Los Angeles')]
            mat = np.array(df)
            return np.array2string(mat)
    
    def get_google_sheets_download_link(file_path):
        file_split= file_path.split('/')
        if len(file_split) > 5:
            return download_link_prefix + file_split[5]
        else:
            return None

    Tạo file main.py: file này sẽ bao gồm các thao tác làm việc với bot Telegram

    • bot = telebot.TeleBot(BOT_TOKEN): tạo một instance của bot Telegram
    • @bot.message_handler(commands=[]): đoạn code sẽ tạo phương thức lắng nghe lệnh điều khiển từ người dùng
    • bot.reply_to(): gửi lại tin nhắn tới người dùng
    • bot.infinity_polling(): câu lệnh cho phép bot Telegram sẽ lắng nghe liên tục các thông tin, lệnh từ phía người dùng
    • bot.send_message(): gửi lại câu trả lời cho tin nhắn nhận được từ người dùng
    • bot.register_next_step_handler(): đăng ký function sẽ được thực thi ngay sau khi nhận được phản hồi từ phía người dùng
    • file_path = message.text: đọc tin nhắn, câu lệnh được gửi từ phía người dùng
    import os
    import telebot
    from dotenv import load_dotenv
    load_dotenv()
    from utils import read_data_from_file, write_data_to_file
    
    BOT_TOKEN = os.environ.get('BOT_TOKEN')
    
    bot = telebot.TeleBot(BOT_TOKEN)
    
    @bot.message_handler(commands=['start', 'hello'])
    def send_welcome(message):
        bot.reply_to(message, "Howdy, how are you doing?")
    
    @bot.message_handler(commands=['read_record'])
    def send_welcome(message):
        sent_msg = bot.send_message(message.chat.id, "So, what link would you like to read?")
        bot.register_next_step_handler(sent_msg, read_csv_xlsx_handler)
    
    def read_csv_xlsx_handler(message):
        file_path = message.text
        result = read_data_from_file(file_path)
        if (result == None): bot.send_message(message.chat.id, "Could not read")
        else: bot.send_message(message.chat.id, result)
    
    bot.infinity_polling()
    • Để chạy con bot của mình ta cần gõ lệnh sau trên terminal:
    python3 main.py

    4. Kết quả

    • File được dùng để đọc dữ liệu: Sample Link
    • Video Demo

    5. Tham khảo

    • Telegram Bot Developer API: https://core.telegram.org/bots/features
    • Pandas: https://pandas.pydata.org/docs/
    • IDE: Visual Studio Code
    • Language: Python
  • Accessibility in Android and Usage

    1. Accessibility là gì

    • Điện thoại di động đã và đang trở thành một vật bất ly thân với mỗi người chúng ta. Tuy nhiên trong nhiều trường hợp, mọi người sẽ cảm thấy khó khăn trong việc sử dụng điện thoại di động. Điều này bao gồm một người bị mù bẩm sinh hoặc mất kỹ năng vận động trong một tai nạn. Điều này cũng bao gồm cả những người không thể sử dụng tay vì họ đang bế một đứa trẻ. Bạn có thể gặp khó khăn khi sử dụng điện thoại khi đeo găng tay khi trời lạnh. Có thể bạn gặp khó khăn trong việc phân biệt các mục trên màn hình khi trời sáng.
    • Trong những trường hợp này, thứ họ cần chính là những hỗ trợ từ những chiếc điện thoại thông minh. Accessibility từ đó được sinh ra để hỗ trợ chúng ta.
    • Dịch vụ trợ năng (Accessibility) là một tính năng của Android Framework được thiết kế để cung cấp phản hồi điều hướng thay thế cho người dùng thay mặt cho các ứng dụng được cài đặt trên thiết bị Android. Dịch vụ trợ năng có thể thay mặt ứng dụng giao tiếp với người dùng, chẳng hạn như bằng cách chuyển đổi văn bản thành giọng nói hoặc cung cấp phản hồi xúc giác khi người dùng di chuột trên một khu vực quan trọng của màn hình. Phòng học mã này chỉ cho bạn cách tạo một dịch vụ trợ năng rất đơn giản.

    2. Ứng dụng của Accessibility trong Android

    • Switch Access: cho phép các người dùng android bị hạn chế vận động tương tác với điện thoại qua một hoặc nhiều nút.
    • Voice Access (beta): cho phép các người dùng Android bị hạn chế vận động điều khiển thiết bị bằng cử chỉ giọng nói.
    • Talkback: một trình đọc màn hình thường được người khiếm thị hoặc người mù sử dụng.

    3. Hướng dẫn cài đặt Accessibility Service

    Cách cài đặt Accessibility Service trong project Android

    • Accessibility yêu cầu các điện thoại chạy chúng phải có phiên bản từ Android 7 trở lên
    • Cùng xem chúng ta cần những gì trong file AndroidManifest của service này
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
        <application>
            <service
                android:name=".HelperService"
                android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" android:exported="true">
                <intent-filter>
                    <action android:name="android.accessibilityservice.AccessibilityService"/>
                </intent-filter>
                <meta-data
                    android:name="android.accessibilityservice"
                    android:resource="@xml/helper_service"/>
            </service>
        </application>
    </manifest>
    • Để có thể chạy service này ta cần thêm quyền android:permission=”android.permission.BIND_ACCESSIBILITY_SERVICE”
    • Sau đó ta thêm file metadata vào service: @xml/helper_service
    <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
        android:accessibilityFeedbackType="feedbackGeneric"
        android:accessibilityFlags="flagDefault"
        android:canPerformGestures="true"
        android:canRetrieveWindowContent="true" />
    • Để thực hiện thao tác vuốt, android:canPerformGesture được đặt thành true
    • Để truy cập nội dung cửa sổ, android:canRetrieveWindowContent được đặt thành true.

    Sau đó, để triển khai các chức năng của AccessibilityService, chúng ta phải tạo một Service kế thừa AccessibilityService

    public class HelperService extends AccessibilityService {
        @Override
        public void onAccessibilityEvent(AccessibilityEvent event) {
        }
        @Override
        public void onInterrupt() {
        }
    }
    • Chúng ta sẽ tạo một view để hiển thị các nút bấm chức năng hỗ trợ trong service này
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:gravity="start"
            android:id="@+id/power"
            android:text="@string/power"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <Button
            android:id="@+id/volume_up"
            android:text="@string/volume_up"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <Button
            android:id="@+id/volume_down"
            android:text="@string/volume_down"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <Button
            android:id="@+id/swipe_down"
            android:text="@string/swipe_down"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <Button
            android:id="@+id/swipe_right"
            android:text="@string/swipe_right"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>
    • Kết quả chúng ta sẽ có được một view như sau:
    • Trong hàm onServiceConnected() của HelperService, chúng ta có thể khởi tạo một giao diện sử dụng quyền WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY – thứ sẽ giúp chúng ta vẽ lên trên màn hình.
        @Override
        protected void onServiceConnected() {
            WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
            mLayout = new FrameLayout(this);
            WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
            lp.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
            lp.format = PixelFormat.TRANSLUCENT;
            lp.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
            lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
            lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
            lp.gravity = Gravity.TOP;
            LayoutInflater inflater = LayoutInflater.from(this);
            inflater.inflate(R.layout.action_bar, mLayout);
        }

    Thiết lập các chức năng cho AccessibilityService

    • Từ các quá trình cài đặt trên chúng ta đã có giao diện cho service của mình, giờ là lúc chúng ta thêm các chức năng cho các nút bấm này
    • Ví dụ về chức năng tắt nguồn
        private void configurePowerButton() {
            Button powerButton = mLayout.findViewById(R.id.power);
            powerButton.setOnClickListener(view -> performGlobalAction(GLOBAL_ACTION_POWER_DIALOG));
        }
    • Ví dụ về chức năng tăng âm lượng:
        private void configureVolumeButtonUp() {
            Button volumeUpButton = mLayout.findViewById(R.id.volume_up);
            volumeUpButton.setOnClickListener(view -> {
                AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
                audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
                        AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI);
            });
        }
    • Ví dụ về chức năng vuốt màn hình:
        private void configureSwipeButtonDown() {
            Button swipeButton = (Button) mLayout.findViewById(R.id.swipe_down);
            swipeButton.setOnClickListener(view -> {
                Path swipePath = new Path();
                swipePath.moveTo(100, 1000);
                swipePath.lineTo(100, 100);
                GestureDescription.Builder gestureBuilder = new GestureDescription.Builder();
                gestureBuilder.addStroke(new GestureDescription.StrokeDescription(swipePath, 0, 500));
                dispatchGesture(gestureBuilder.build(), null, null);
            });
        }

    4. Chạy thử trên thiết bị thật

    • Để chạy service này trên thiết bị thật, chúng ta phải chỉnh sửa phần configurations của mục run, chọn Launch OptionsNothing
    • Khi service đã được chạy thành công từ Android Studio, chúng ta phải vào phần Settings -> Additional settings -> Accessibility, chọn đến phần Downloaded Apps chúng ta sẽ thấy tên service của chúng ta (HelperService), ấn chọn vào service và bật service lên. Giao diện các chức năng hỗ trợ của chúng ta sẽ hiển thị lên.
    • Demo trên thiết bị thật

    5. Những hiểm nguy từ Accessibility Service

    • Accessibility Service được sinh ra với một mục đích rất tốt, đó là hộ trỡ những người khuyết tật hoặc những người hạn chế, khó trong các vận động. Tuy nhiên với các khả năng có thể đọc được các dữ liệu trên màn hình, điều khiển điện thoại,… nếu như chúng được sử dụng vào mục đích xấu thì sẽ rất dễ gây nguy hiểm, rủi ro bảo mật thông tin đến cho chủ sở hữu điện thoại.

    Để phòng tránh những rủi ro tiềm ẩn này thì chúng ta nên có những biện pháp phòng tránh như là:

    • Chỉ cài đặt các ứng dụng trên các chợ chính chủ của các hệ điều hành. Accessibility Service được đánh giá là một quyền nguy hiểm. Vậy nên việc kiểm duyệt các ứng dụng này diễn ra rất nghiêm ngặt.
    • Không tải/ cài app từ các nguồn không chính thống, trên các đường link lạ
    • Với các ứng dụng có yêu cầu quyền này, hãy đọc kỹ các điều khoản dịch vụ.

    Author: LamNT59
    References: https://codelabs.developers.google.com/codelabs/developing-android-a11y-service#0
    Github Demo: https://github.com/lamdev99/HelperService

  • Null safety trong Flutter

    Null safety trong Flutter

    1. Null safety là gì

    a. Tổng quan về null safety

    • Trong các ngôn ngữ lập trình hướng đối tượng có một lỗi mà chúng ta rất hay gặp đó là NullPointerException, lỗi này có thể dẫn đến việc ứng dụng bị crash, ảnh hưởng đến trải nghiệm người dùng.
    • Để có thể giúp cho lập trình viên quản lý được việc nullable của một đối tượng từ đó từ khóa null safety xuất hiện.
    • Null safety cho phép bạn chỉ định việc một đối tượng có thể null hay không ngay từ khi khởi tạo. Và khi bạn muốn dùng nó compiler sẽ nhắc cho bạn để bạn có thể xử lý các exception liên quan đến việc biến có thể bị null

    b. Null safety trong Flutter và Dart

    • Null safety bắt đầu có từ phiên bản Dart 2.12 và Flutter 2
    • Ở trạng thái mặc định những biến của bạn sẽ ở trạng thái non-nullable, điều đó có nghĩa là nó không thể null trừ khi bạn cho phép nó có thể. Với null safety các lỗi liên quan đến null sẽ chuyển từ các lỗi trong quá trình chạy thành các lỗi trong quá trình viết code. Điều này tránh những sai sót không đáng có trong quá trình chạy ứng dụng.
    • Trình biên dịch Dart có thể tối ưu hóa code, điều sẽ giúp cho project nhỏ hơn và chạy nhanh hơn.

    Từ đó chúng ta có bài toán migrate những source code đã được viết trước phiên bản có null safety và biến nó thành source code có sử dụng null safety

    2. Cách migrate từ source code không có null safety

    a. Migrate code theo một thứ tự

    • Nếu chúng ta có sử dụng nhiều package, chúng ta nên lần lượt migrate chúng theo một thứ tự từ package được sử dụng nhiều nhất trong các package khác trước
    Illustration of C/B/A sentence

    b. Các bước migrate code sử dụng tool

    • Để demo cho phần này chúng ta sử dụng một class User
    class User {
      String name;
      int age;
      int yearOfBirth;
      String des;
      User({this.name, this.age});
    
      set setCalculate(int cal){
        yearOfBirth = cal;
      }
    }
    

    Phiên bản của Dart

    sdk: ‘>=2.10.0 <3.0.0'

    • Để bắt đầu chúng ta kiểm tra phiên bản của dart, hãy đảm bảo rằng bạn đang sử dụng phiên bản mới nhất của Dart SDK
    dart --version
    • Tiếp theo là kiểm tra trạng thái của các dependency
    dart pub outdated --mode=null-safety

    Nếu output hiển thị ra như hình dưới đây thì bạn sẽ phải update các dependency

    Output of dart pub outdated
    • Sử dụng tool để migrate code

    Trước khi bắt đầu migrate chúng ta cần biết về một vài hint marker – những thứ sẽ giúp chúng ta đánh dấu null safety cho các biến.

    expression /*!*/Thêm “!” vào trong code của bạn, biến biểu thức đó thành non-nullable
    type /*!*/Đánh dấu đối tượng đó là non-nullable
    /*?*/Đánh dấu đối tượng đó là nullable
    /*late*/Đánh dấu đối tượng đó được khởi tạo sau
    /*late final*/Đánh dấu đối tượng được khởi tạo sau nhưng chỉ được khởi tạo một lần
    /*required*/Đánh dấu tham số bị yêu cầu

    Chúng ta sử dụng lệnh sau ở terminal để bắt đầu quá trình migrate:

    dart migrate

    Sau một thời gian sẽ có một đường link dẫn đến tool migration, giao diện như sau:

    Đến đây chúng ta sẽ bắt đầu dùng những hint marker vào những chỗ được gợi ý dùng

    Ở đây chúng ta mong muốn 3 biến name, age, yearOfBirth đều là non-nullable, vì vậy chúng ta thêm /*!*/ vào các biến đó, biến des có thể null vậy chúng ta thêm /*?*/.

    Để tiến hành xem các thay đổi chúng ta ấn nút Rerun with changes, những thay đổi sẽ có trong code sau khi được migrate sẽ hiện ra

    Giải thích:

    • Hai biến name và age được đánh dấu non-nullable, và được đặt trong cặp dấu ‘{}’ trong constructor, vì vậy từ khóa ‘required’ sẽ được thêm vào để yêu cầu giá trị init cho chúng khi đối tượng User mới được khởi tạo
    • Biến yearOfBirth được đánh dấu là non-nullable, tuy nhiên không xuất hiện trong constructor, vì vậy sẽ có thêm từ khóa ‘late’ từ khóa này
    • Biến des được đánh dấu là nullable do biến đó có thể null.

    Sau khi kết thúc quá trình migration, chúng ta ấn nút ‘Apply migration’ để những thay đổi đó xuất hiện trên code của chúng ta

    class User {
      String name;
      int age;
      late int yearOfBirth;
      String? des;
      User({required this.name, required this.age});
    
      set setCalculate(int cal){
        yearOfBirth = cal;
      }
    }
    

    Trên đây là code của lớp User sau khi được migrate

    Cùng với sự thay đổi ở code, phiên bản của Dart cũng đã được update

    sdk: ‘>=2.12.0 <3.0.0'

    Đến đây là chúng ta đã hoàn thành quá trình migrate một đoạn code Dart sang null safety

    Việc apply null safety đem lại rất nhiều lợi ích cho code của bạn, ngoài việc bắt kịp về mặt công nghệ nó còn giúp code của bạn an toàn hơn, chạy nhanh hơn.

    Tham khảo

  • Quy trình phát triển phần mềm Scrum

    Quy trình phát triển phần mềm Scrum

    Scrum là một quy trình thực hiện hóa những giá trị và nguyên tắc của Agile

    1. Tổng quan về Scrum

    • Có một quy trình phát triển phần mềm truyền thống có tên là waterfall (mô hình thác nước)
    Waterfall Methodology. Waterfall Methodology | by Chathmini Jayathilaka |  Medium

    Quy trình này kéo dài từ đầu đến cuối trong quá trình phát triển phần mềm, và khách hàng sẽ phải chờ đợi đến phần cuối cùng của quy trình này để có thể nhìn thấy được sản phẩm của cả quá trình phát triển. Điều này là khá thụ động cho cả bên phía khách hàng và cho cả bên phát triển phần mềm.

    Minh chứng là khi tiếp nhận những sự thay đổi tại thời điểm cuối của quy trình sẽ có thể phải đập đi xây lại rất nhiều phần gây mất thêm thời gian maintain.

    Từ đó chúng ta mong muốn tạo ra một dự án “Deliverable Product” hay chính là sử dụng quy trình Scrum.

    • Chúng ta chia nhỏ dự án thành các Sprint (mỗi Sprint kéo dài trong vòng 1 đến 3 tuần)
    • Và cuối mỗi Sprint này chúng ta sẽ có một bản release, một bản chạy được để đưa cho khách hàng (dù nó chỉ là một chức năng nhỏ đã được hoàn thiện)
    • VD: Giả sự một phần mềm được phát triển trong vòng 6 tháng thì tương ứng sẽ có khoảng 24 Sprint và cùng với đó là 24 bản release để đưa cho khách hàng. Con số này lớn hơn rất nhiều so với mô hình waterfall
    • Việc đưa ra các bản release liên tục này sẽ giúp cho khách hàng có được cái nhìn tổng quan, khách quan hơn về sản phẩm đang được phát triển, đưa ra các feedback cho đội ngũ phát triển

    2. Ba Roles trong Scrum

    • Product Owner: Tập hợp tất cả yêu cầu khách hàng yêu cầu dự án phân chia các task theo thứ tự ưu tiên rồi giao cho đội ngũ phát triển.
    • Scrum Master: Giúp cho team làm việc tốt nhất, đảm bảo team đi đúng theo mô hình, quy trình đã đề ra.
    • Team: Gồm Dev, Tester, Content, …

    3. Ba Artifact trong Scrum

    • Product Backlog: Tất cả các task cần làm, các thông tin Product Owner lấy được từ khách hàng.
    • Sprint Backlog: Chứa các user story trong một Sprint, đã được chọn để làm trong Sprint
    • Burndown Chart: Chứa các task bạn làm trong một Sprint, đánh giá hiệu quả của Sprint
    Scrum Burndown Chart - Cách phân tích tiến độ & điều chỉnh sprint thông qua Burndown  Chart

    User Story: là một đặc tả một task dựa trên vị trí người dùng, nó mô tả những việc người dùng cần thao tác để đạt được điều mong muốn

    4. Ba Meeting trong Scrum

    • Sprint Planing: Bàn bạc về các task sẽ bỏ vào trong Sprint Backlog, dự đoán số điểm cho các User Story. Được tổ chức trước khi làm một Sprint.
    • Daily Scrum: Đồng bộ các tin tức, thông tin giữa mọi người trong dự án với nhau, đưa ra các vấn đề, trạng thái của task.
    • Sprint Review/Sprint Retrospective: Demo các task đã hoàn thành trong Sprint. Tìm ra những điểm đã làm tốt để phát huy và hạn chế những điểm chưa tốt.

    Quy trình chuẩn của một Sprint

    What is Scrum?

    5. Điểm lợi của Scrum

    • Luôn mang lại giá trị cao nhất cho người đầu tư dự án.
    • Năng suất lao động rất cao.

    Tài liệu tham khảo: Scrum cơ bản | Quy trình phát triển phần mềm | Ong Dev – YouTube

    Author: LamNT59

  • Mô hình Agile

    Mô hình Agile

    Có rất nhiều mô hình có thể áp dụng trong quá trình phát triển phần mềm, tuy nhiên Agile là một trong những mô hình đang được ứng dụng rất nhiều và phổ biến trong các công ty.

    1. Mô hình Agile là gì?

    Agile là một phương pháp phát triển phần mềm linh hoạt để làm sao đưa sản phẩm đến tay người dùng càng nhanh càng tốt.

    Tuyên ngôn Agile:

    Tuyên ngôn Agile hay còn gọi là Tuyên ngôn phát triển phần mềm linh hoạt, đánh giá cao:

    • Cá nhân và sự tương tác hơn là quy trình và công cụ.
    • Phần mềm chạy tốt hơn là tài liệu đầy đủ.
    • Cộng tác với khách hàng hơn là đàm phán hợp đồng.
    • Phản hồi với các thay đổi hơn là bám sát kế hoạch.

    Bên cạnh đó, các nhà phát triển còn nhấn mạnh hơn mười hai nguyên lý phía sau Tuyên ngôn Agile, để giúp các nhà phát triển vận dụng trong thực tiễn:

    • Ưu tiên cao nhất của chúng tôi là thỏa mãn khách hàng thông qua việc chuyển giao sớm và liên tục các phần mềm có giá trị.
    • Chào đón việc thay đổi yêu cầu, thậm chí rất muộn trong quá trình phát triển. Các quy trình linh hoạt tận dụng sự thay đổi cho các lợi thế cạnh tranh của khách hàng.
    • Thường xuyên chuyển giao phần mềm chạy tốt tới khách hàng. Từ vài tuần đến vài tháng, ưu tiên cho các khoảng thời gian ngắn hơn.
    • Nhà kinh doanh và nhà phát triển phải làm việc cùng nhau hàng ngày trong suốt dự án.
    • Xây dựng các dự án xung quanh những cá nhân có động lực. Cung cấp cho họ môi trường và sự hỗ trợ cần thiết, và tin tưởng họ để hoàn thành công việc.
    • Phương pháp hiệu quả nhất để truyền đạt thông tin tới nhóm phát triển và trong nội bộ nhóm phát triển là hội thoại trực tiếp.
    • Phần mềm chạy tốt là thước đo chính của tiến độ.
    • Các quy trình linh hoạt thúc đẩy phát triển bền vững. Các nhà tài trợ, nhà phát triển, và người dùng có thể duy trì một nhịp độ liên tục không giới hạn.
    • Liên tục quan tâm đến các kĩ thuật và thiết kế tốt để gia tăng sự linh hoạt.
    • Sự đơn giản – nghệ thuật tối đa hóa lượng công việc chưa xong – là căn bản.
    • Các kiến trúc tốt nhất, yêu cầu tốt nhất, và thiết kế tốt nhất sẽ được làm ra bởi các nhóm tự tổ chức.
    • Đội sản xuất sẽ thường xuyên suy nghĩ về việc làm sao để trở nên hiệu quả hơn. Sau đó họ sẽ điều chỉnh và thay đổi các hành vi của mình cho phù hợp.

    Có thể thấy mô hình Agile đặt khách hàng làm chủ đạo và tích cực trao đổi để tìm kiếm được sự hoàn thiện cho phần mềm.

    2. Đặc trưng của mô hình Agile

    Tính lặp

    Dự án sẽ được thực hiện trong các phân đoạn lặp đi lặp lại. Các phân đoạn (được gọi là Iteration hoặc Sprint) này thường có khung thời gian ngắn (từ 1 – 4 tuần).

    Trong mỗi phân đoạn này, nhóm phát triển thực hiện đầy đủ các công việc cần thiết như lập kế hoạch, phân tích yêu cầu, thiết kế, triển khai, kiểm thử (với các mức độ khác nhau) để cho ra các phần nhỏ của sản phẩm.

    Scrum Framework] Sprint là gì? - Scrum Viet

    Phân rã mục tiêu thành các phần nhỏ với quá trình lập kế hoạch đơn giản và gọn nhẹ nhất có thể

    Tính tăng trưởng và tiến hóa

    Cuối các phân đoạn (Sprint) nhóm phát triển thường cho ra các sản phẩm đầy đủ, có khả năng chạy tốt và được kiểm thử cẩn thận có thể sử dụng ngay.

    Đưa đến cho khách hàng góc nhìn chi tiết về tiến độ sản phẩm, từ đó có được sự tin tưởng từ khách hàng và có những thay đổi ngay lập tức với những yêu cầu của khách hàng.

    Tính thích nghi

    Các thay đổi trong quá trình phát triển có thể được đáp ứng một cách phù hợp, bởi vì các quy trình đã được phân chia rất rõ ràng và chi tiết.

    Nhóm tự tổ chức và liên chức năng

    Các nhóm này tự phân công công việc mà không dựa trên mô tả cứng về chức danh hay làm việc trên một phân cấp rõ ràng của tổ chức.

    Quản lý tiến trình thực nghiệm

    Các nhóm Agile đưa ra các quyết định dựa trên các dữ liệu thực tiễn thay vì lý thuyết hoặc các giả định. Từ đó giúp giảm quá trình phát triển và nâng cao năng suất lao động

    Giao tiếp trực diện

    Agile khuyến khích việc giao tiếp trực tiếp giữa nhóm phát triển với khách hàng, giữa các thành viên trong nhóm phát triển. Từ đó cùng nhau phát triển các chức năng một cách hiệu quả nhất

    Phát triển dựa trên giá trị

    Ưu tiên việc chạy tốt của sản phẩm, giao tiếp với khách hàng để biết yêu cầu nào được ưu tiên cao hơn, mang lại giá trị sớm nhất cho dự án.

    3. Ưu điểm của mô hình Agile

    • Thực hiện thay đổi dễ dàng: Bởi vì dự án được chia thành các phần nhỏ, riêng biệt, không phụ thuộc lẫn nhau, nên những thay đổi được thực hiện rất dễ dàng, ở bất kỳ giai đoạn nào của dự án.
    • Không cần phải nắm mọi thông tin ngay từ đầu: Phù hợp với những dự án chưa xác định được mục tiêu cuối cùng rõ ràng, vì việc này không quá cần thiết trong giai đoạn đầu.
    • Bàn giao nhanh hơn: Việc chia nhỏ dự án cho phép đội ngũ có thể tiến hành kiểm tra theo từng phần, xác định và sửa chữa vấn đề nhanh hơn, nhờ đó việc bàn giao công việc sẽ nhất quán và thành công hơn.
    • Chú ý đến phản hồi của khách hàng và người dùng: Cả khách hàng và người dùng cuối đều có cơ hội để đóng góp các ý kiến và phản hồi, từ đó họ sẽ có ảnh hưởng một cách mạnh mẽ và tích cực tới sản phẩm cuối cùng.
    • Cải tiến liên tục: Agile khuyến khích thành viên trong đội ngũ làm việc và khách hàng cung cấp phản hồi của mình, khi đó các giai đoạn khác nhau của sản phẩm cuối có thể được kiểm tra và cải thiện lại nhiều lần nếu cần.

    4. Nhược điểm của mô hình Agile

    • Khó lên kế hoạch dự án: Khá là khó để xác định rõ ràng thời gian bàn giao sản phẩm cuối cùng, vì dự án được chia nhỏ thành các phần khác nhau và mỗi phần lại có thời gian bàn giao riêng biệt.
    • Bắt buộc phải hướng dẫn và đào tạo chi tiết: Phương pháp Agile phức tạp hơn nhiều so với phương pháp truyền thống. Họ sẽ cần phải trải qua đào tạo, hướng dẫn thì mới có thể nắm được phương pháp một cách rõ ràng, đặc biệt là thời gian đầu.
    • Ít tài liệu hướng dẫn: Vì Agile thay đổi rất nhiều nên các tài liệu thích hợp cũng thường bị bỏ qua, vì không xác định rõ được kỳ vọng và thành phẩm ngay từ đầu. Mặc dù tài liệu không phải là yếu tố quan trọng nhất, nhưng chúng vẫn rất cần thiết.
    • Bắt buộc phải hợp tác để dự án thành công: Điều này đòi hỏi một sự cam kết về thời gian từ cả hai bên trong suốt thời gian của dự án mà các cấu trúc quản lý dự án khác không luôn yêu cầu. Phải có sự tham gia tích cực của người dùng và tiếp tục cộng tác để nó hoạt động.
    • Chi phí cao: Chi phí thực hiện theo phương pháp Agile thường hơn một chút so với các phương pháp phát triển khác.

    5. Áp dụng

    Để áp dụng thành công mô hình Agile trong tổ chức, chúng ta cần:

    • Thứ nhất, các thành viên phối hợp, giao tiếp hiệu quả trong nội bộ. Kỹ năng giao tiếp tốt giúp nhóm làm việc thấu hiểu khách hàng, hợp tác tốt với nhau đảm bảo chất lượng và tốc độ. 
    • Thứ hai, tính tự chủ của mỗi thành viên phải được đảm bảo để các nhóm tự quản lý có thể vận hành một cách chủ động, trơn tru thay vì chỉ tuân thủ theo chỉ dẫn cấp trên như trong các mô hình truyền thống. 
    • Thứ ba, các hoạt động được module hóa thông qua những nhóm liên chức năng. Những nhóm này có khả năng làm việc với tốc độ và chất lượng cao, với khách hàng là trung tâm

    Tham khảo:

    Author: LamNT59

  • Shared Preferences trong Flutter

    [FLUTTER] Shared Preferences trong Flutter

    Để lưu trữ các dữ liệu ở local trong ứng dụng Flutter, ngoài cách lưu bằng sqlite, chúng ta còn thể lưu dữ liệu vào Shared Preferences

    1. Sơ lược về Shared Preferences trong Flutter

    • Dùng để lưu những tập dữ liệu nhỏ dưới dạng key-value
    • Các loại dữ liệu có thể lưu như là int, double, bool, String and List<String>
    • Các dữ liệu được lưu lại trong một file .xml và được lưu vào trong bộ nhớ đệm của máy
    • Các dữ liệu chúng ta có thể dùng để lưu như là các thông số về Settings, token,, …

    2. Cách sử dụng

    • Thêm thư viện vào trong file pubspect.yaml:
    shared_preferences: ^2.0.13

    Vì các hàm xử lý lưu dữ liệu trong shared_preferences đều là các hàm Future, nên chúng ta cần dùng await để gọi:

    • Hàm lưu dữ liệu
    // Obtain shared preferences.
    final prefs = await SharedPreferences.getInstance();
    
    // Save an integer value to 'counter' key. 
    await prefs.setInt('counter', 10);
    // Save an boolean value to 'repeat' key. 
    await prefs.setBool('repeat', true);
    // Save an double value to 'decimal' key. 
    await prefs.setDouble('decimal', 1.5);
    // Save an String value to 'action' key. 
    await prefs.setString('action', 'Start');
    // Save an list of strings to 'items' key. 
    await prefs.setStringList('items', <String>['Earth', 'Moon', 'Sun']);
    • Hàm đọc dữ liệu
    // Try reading data from the 'counter' key. If it doesn't exist, returns null.
    final int? counter = prefs.getInt('counter');
    // Try reading data from the 'repeat' key. If it doesn't exist, returns null.
    final bool? repeat = prefs.getBool('repeat');
    // Try reading data from the 'decimal' key. If it doesn't exist, returns null.
    final double? decimal = prefs.getDouble('decimal');
    // Try reading data from the 'action' key. If it doesn't exist, returns null.
    final String? action = prefs.getString('action');
    // Try reading data from the 'items' key. If it doesn't exist, returns null.
    final List<String>? items = prefs.getStringList('items');
    • Nếu chúng ta muốn xóa bỏ dữ liệu đã được lưu
    // Remove data for the 'counter' key. 
    final success = await prefs.remove('counter');

    Tài liệu tham khảo: shared_preferences | Flutter Package (pub.dev)

    Author: LamNT59

  • Khác biệt giữa Future và Stream

    [FLUTTER] Sự khác biệt giữa Future và Stream trong Flutter

    Lập trình không đồng bộ trong Flutter được đặc trưng bởi hai lớp Future và Stream

    1. Future

    Khi một hàm bất đồng bộ được thực hiện xong nó sẽ trả về một Future.

    • Một hàm Future có thể trả về một giá trị.
    • Một hàm Future cũng có thể trả về một lỗi nếu có bất kì ngoại lệ nào xảy ra.
    Future<void> fetchUserOrder() {
      // Imagine that this function is fetching user info from another service or database.
      return Future.delayed(const Duration(seconds: 2), () => print('Large Latte'));
    }
    
    void main() {
      fetchUserOrder();
      print('Fetching user order...');
    }

    2. Stream

    Định nghĩa: Stream là một chuỗi các sự kiện không đồng bộ. Nó giống như một Lặp lại không đồng bộ – trong đó, thay vì nhận được sự kiện tiếp theo khi bạn yêu cầu, luồng cho bạn biết rằng có một sự kiện khi nó sẵn sàng.

    Future<int> sumStream(Stream<int> stream) async {
      var sum = 0;
      await for (final value in stream) {
        sum += value;
      }
      return sum;
    }
    
    Stream<int> countStream(int to) async* {
      for (int i = 1; i <= to; i++) {
        yield i;
      }
    }
    
    void main() async {
      var stream = countStream(10);
      var sum = await sumStream(stream);
      print(sum); // 55
    }

    3. Khác biệt giữa Future và Stream

    Như chúng ta có thể thấy ở trên thì điểm khác biệt rõ rệt nhất của Future và Stream là:

    • Trong quá trình xử lý Future sẽ chờ đợi đến khi hoàn thành và chỉ trả lại kết quả tại thời điểm đó.
    • Stream thì sẽ trả về dữ liệu liên tục nếu nó vẫn tiếp tục được chuyển về, tạo thành một luồng.

    Từ những sự khác biệt này chúng ta có thể suy luận ra các trường hợp nào nên sử dụng Future, trường hợp nào nên sử dụng Stream.

    Future:

    • Chụp một bức ảnh từ camera, hoặc lấy từ trong bộ nhớ.
    • Lẩy các thông tin về file.
    • Tạo các https request.

    Stream:

    • Lắng nghe sự thay đổi của vị trí.
    • Chơi nhạc.
    • Đồng hồ bấm giờ.
    • Làm việc với web-socket.

    Hãy chọn cho mình những cách phù hợp nhất với các vấn đề chúng ta cần giải quyết.

    Tài liệu tham khảo:

    Author: LamNT59

  • SQLite trong Flutter

    [FLUTTER] Tìm hiểu về cách sử dụng SQLite trong Flutter

    Để lưu những dữ liệu theo dạng bảng dễ dàng cho việc truy vấn và thay đổi, chúng ta cần sử dụng SQLite

    I. Khái quát về Sqlite trong Flutter

    Trong Flutter chúng ta sử dụng thư viện sqflite, thư viện này sẽ hỗ trợ chúng ta:

    • Sử dụng trên đa nền tảng Android, iOS, MacOS.
    • Hỗ trợ transactions và batches.
    • Tự động quản lý phiên bản (version) trong khi mở.
    • Hỗ trợ các câu lệnh truy vấn CRUD (Create – Read – Update – Delete) đầy đủ.
    • Thực hiện các xử lý trên background của iOS và Android.

    II. Cách sử dụng Sqlite trong Flutter

    1. Thêm thư viện

    1.1. Thêm thư viện sqflite path (dùng để xác định vị trí lưu database trong bộ nhớ) vào phần dependencies trong file pubspec.yaml

    2. Cài đặt thư viện

    2.1. Đầu tiên chúng ta cần tạo một class model Student, đây sẽ là dữ liệu chúng ta dùng trong quá trình lưu trữ.

    class Student {
      int id;
      String name;
      int grade;
    
      Student({required this.id, required this.name, required this.grade});
    }

    Nếu bạn chưa biết rõ về Sql cũng như các câu lệnh của chúng, hãy tìm hiểu về chúng trước khi tiếp tục: SQL Introduction (w3schools.com)

    2.2. Tiếp theo chúng ta khởi tạo database

    class StudentDatabase {
      static Database? _database;
    
      static Future<Database> getInstance() async {
        _database ??= await openDatabase(
    
            /// use join to create path for db, then the path will be path/student.db
            join(await getDatabasesPath(), "student.db"),
    
            /// This function will be called in the first time database is created
            onCreate: (db, version) {
          return db.execute(
              "CREATE TABLE student(id INTEGER PRIMARY KEY, name TEXT, grade INTEGER)");
        },
    
            /// This version will use when you want to upgrade or downgrade the database
            version: 1,
            singleInstance: true);
        return _database!;
      }
    }
    • Hàm onCreate() được gọi tại lần đầu mà database được khởi tạo
    • Version chính là phiên bản của database, nếu bạn muốn thay đổi cấu trúc của database thì chúng ta phải thay đổi version.

    Tiếp theo ta thêm các hàm phục vụ cho việc chuyển đổi dữ liệu khi muốn thêm vào database cũng như lấy dữ liệu ra từ database

    class Student{
    ...
    
    Map<String, dynamic> toMap() => {'id': id, 'name': name, 'grade': grade};
    
    factory Student.fromMap(Map<String, dynamic> map) {
      return Student(id: map['id'], name: map['name'], grade: map['grade']);
    }
    ...
    }

    Ở đây mình có viết thành một project CRUD, với các phân tần giữa data và UI rõ ràng, để có thể hiểu hơn về project này hãy đọc đến cuối bài viết.

    2.3. Thêm một trường dữ liệu vào bảng

    Future<DataResult> insertStudent(Student student, String tableName) async {
        try{
          Database db = await StudentDatabase.getInstance();
          int lastInsertedRow = await db.insert(tableName, student.toMap(),
              conflictAlgorithm: ConflictAlgorithm.replace);
          return DataResult.success(lastInsertedRow);
        }catch(ex){
          return DataResult.failure(DatabaseFailure(ex.toString()));
        }
      }
    • Hàm insert() có hai tham số truyền vào là tên của bảng và dữ liệu chúng ta muốn thêm vào bảng, dữ liệu này đã được chuyển thành một map để có thể thực hiện thêm vào database.
    • Tham số conflictAlgorithm: được sử dụng để xác định các xử lý khi có sự trùng lặp dữ liệu xảy ra, ở đây tham số này có giá trị là ConflictAlgorithm.replace có nghĩa là nếu trùng primary key thì giá trị cũ sẽ được thay thế bằng giá trị mới.
    • Nếu hàm insert thực hiện thành công sẽ trả về id của hàng vừa được thêm.
    • Nếu không thành công sẽ trả về giá trị 0.

    2.4. Lấy thông tin của tất cả các trường

    Trong Android Studio có một công cụ hỗ trợ chúng ta xem được các cơ sở dữ liệu đang có trong app, đó chính là App Inspection

    Tại đây chúng ta có thể thấy được bảng “student” đã được thêm 33 trường dữ liệu, vậy bây giờ chúng ta muốn lấy tất cả những dữ liệu này ra thì phải làm thế nào?

    Future<DataResult> getAllStudent() async{
        try{
          Database db = await StudentDatabase.getInstance();
          final List<Map<String,dynamic>> maps = await db.query("student");
          List<Student> students = maps.map((e) => Student.fromMap(e)).toList();
          return DataResult.success(students);
        }catch(ex){
          return DataResult.failure(DatabaseFailure(ex.toString()));
        }
      }

    Chúng ta chỉ cần sử dụng hàm query, truyền vào tham số là bảng mà chúng ta cần lấy dữ liệu.

    • Dữ liệu trả về sẽ ở dạng List<Map<String,dynamic>>, vì vậy chúng ta cần chuyển đổi chúng sang dạng của lớp Student sử dụng hàm fromMap() đã được thêm trong class Student

    2.5. Thay đổi thông tin của một trường.

    Future<DataResult> updateStudent(Student student) async{
        try{
          Database db = await StudentDatabase.getInstance();
          int numberOfChanges = await db.update("student", student.toMap(),where: "id = ?",whereArgs: [student.id]);
          return DataResult.success(numberOfChanges);
        }catch(ex){
          return DataResult.failure(DatabaseFailure(ex.toString()));
        }
      }

    Để thực hiện được việc thay đổi dữ liệu của một trường chúng ta cần sử dụng hàm update()

    • Các tham số truyền vào gồm có tên bảng, dữ liệu thay đổi (được chuyển sang map), điều kiện thay đổi và tham số cho điều kiện đó.
    • Có thể thấy phần thay đổi dữ liệu này khá giống với phần thêm dữ liệu, chỉ khác chúng ta có thêm hai tham số khá quan trọng là “where” và “whereArgs”.
    • Giá trị trả về của hàm update() là số lượng thay đổi đã diễn ra.

    2.6. Xóa thông tin của một trường

    Future<DataResult> deleteStudent(int id) async{
        try{
          Database db = await StudentDatabase.getInstance();
          int numberOfRowEffected = await db.delete("student",where: "id = ?",whereArgs: [id]);
          return DataResult.success(numberOfRowEffected);
        }catch(ex){
          return DataResult.failure(DatabaseFailure(ex.toString()));
        }
      }

    Để thực hiện việc xóa dữ liệu chúng ta sẽ cần dùng đến hàm delete()

    • Gần giống với hàm update chúng ta cũng cần có hai tham số “where” và “whereArgs”
    • Giá trị trả về sẽ là hàng bị thay đổi giá trị.

    III. Tổng kết

    Phần Sql này khá là khó và khô khan đối với những người mới học, mới tiếp cận, vì vậy mọi người cần luyện tập thêm để trở nên master phần này nhé.

    Link github của project mình tự làm cho mọi người tham khảo: lamdev99/sqflite_crud (github.com)

    Tài liệu tham khảo:

    Author: LamNT59

  • Cách viết Unit Test trong Flutter

    [FLUTTER] Cách viết Unit Test trong Flutter (Phần 2)

    Lời ngỏ

    Mỗi ngôn ngữ, hay framework đều có các cách để triển khai Unit Test khác nhau. Tuy nhiên trong bài viết này mình sẽ chú trọng vào Unit Test trong Flutter.

    B. Cách triển khai Unit Test trong Flutter

    1. Cài đặt thư viện

    Để có thể viết Unit Test trong Flutter, chúng ta cần thêm thư viện test, nhớ thêm vào phần dev_dependencies nhé.

    2. Cấu hình và giải thích

    • Đầu tiên chúng ta tạo một hàm xử lý để giả định cho một chức năng trong app
    class Calculation{
      int add(int a, int b){
        return a+b;
      }
      int subtract(int a, int b){
        return a-b;
      }
    }
    
    
    • Trong class Calculation, chúng ta có hàm xử lý là hàm “add”,”subtract”, đây chính là hàm chúng ta cần viết unit test. Bước tiếp theo là tạo một class để viết unit test.
    • Ở trong phần package tree của project chúng ta sẽ thấy một package tên là “test”.

    Đây sẽ là nơi chúng ta viết các unit test cho ứng dụng.

    Có thể thấy ở bên trong folder này đã có một class được viết sẵn có tên là “widget_test.dart”, đây sẽ là class dùng trong việc test các widget của ứng dụng.

    • Chúng ta tạo thêm một file đặt tên là “calculation_test” (Hãy đặt tên của file theo những chức năng, lớp mà chúng ta muốn test để dễ trong việc phân biệt và tìm kiếm.
    • Chúng ta tạo một class “main”, bên trong sẽ viết các hàm unit test
    import 'package:flutter_test/flutter_test.dart';
    import 'package:unit_test_sample/calculation.dart';
    
    void main(){
      /// Init class which needs tested
      Calculation calculation = Calculation();
      /// Test function add
      test("Sum of two integer ", () {
        int result = calculation.add(5, 4);
        expect(result, 9);
      });
    }
    • Ở đây ta sử dụng hàm “test” để viết các unit test cho từng chức năng.

    Hàm “test” này có 2 tham số cần truyền vào đó là:

    • Mô tả của test, nơi chúng ta có thể mô tả xem hàm test này đang test chức năng nào, hoặc có thể bổ sung thêm một vài mô ta cho input chúng ta truyền vào (ví dụ như input đó bị lỗi, sai, hay đúng).
    • Tham số còn lại chính là một hàm, ở đây chúng ta sẽ viết các bước để có thể gọi được đến hàm cần test (ở đây là hàm “add”), cuối cùng chúng ta sử dụng hàm “expect” để kiểm tra kết quả của hàm test với kết quả mà chúng ta mong đợi. Tham số đầu tiên sẽ là kết quả của hàm được test, tham số còn lại là kết quả mà chúng ta mong muốn.

    Như vậy phần chuẩn bị hàm test đã xong, giờ việc chúng ta cần làm là chạy thôi:

    • Ấn chuột phải rồi chọn “run test in calculation” hoặc ấn tổ hợp phím Ctrl + Shift + F10:
    • Sau một lúc chạy thì ở phía dưới sẽ hiển thị một màn hình kết quả test:

    Nếu có tick xanh ở trước mô tả “Sum of two integer”, có nghĩa là test của chúng ta đã chạy đúng như mong đợi.

    Thử thay đổi kết quả mong đợi để xem chuyện gì xảy ra:

    test("Sum of two integer ", () {
        int result = calculation.add(5, 4);
        expect(result, 8);
      });

    Ngay lập tức test của chúng ta đã chạy sai, trong phần log lỗi cũng đã chỉ ra cho chúng ta biết chúng ta sai ở đâu.

    • Expected (kết quả mong đợi) là 8 trong khi Actual (kết quả được tính toán) là 9, từ đó chúng ta sẽ rất dễ dàng trong việc fix lỗi.

    Khi có nhiều hàm test cùng liên quan đến một vấn đề, chúng ta có thể cho chúng vào trong một “group” để dễ quản lý hơn.

    void main() {
      /// Init class which needs tested
      Calculation calculation = Calculation();
      group(
        "Calculate with integer",
        () {
          /// Test function add
          test("Sum of two integer ", () {
            int result = calculation.add(5, 4);
            expect(result, 9);
          });
    
          /// Test function subtract
          test("Subtraction of two integer ", () {
            int result = calculation.subtract(5, 4);
            expect(result, 1);
          });
        },
      );
    }

    Và khi chạy lên chúng ta sẽ được kết quả như sau:

    3. Tài liệu tham khảo

    Chúc các bạn luôn nhìn thấy một màu xanh lá khi chạy Unit Test

    Tác giả: LamNT59

  • Cách viết Unit Test trong Flutter

    [FLUTTER] Cách viết Unit Test trong Flutter (Phần 1)

    Lời ngỏ

    Unit Test là một phần rất quan trọng trong quá trình phát triển phần mềm, tuy nhiên nó thường xuyên bị lãng quên với một lập trình viên mới vào nghề hoặc chưa có nhiều kinh nghiệm. Mong rằng bài viết sẽ giúp bạn có cái nhìn trực quan hơn về Unit Test tron phát triển phầm mềm, đặc biệt là trong Flutter.

    A. Đôi điều về Unit Test

    1. Unit Test là gì?

    Unit Test là gì? Tìm hiểu chi tiết về Unit Test
    1.1 Ảnh unit test

    Unit Test là một loại kiểm thử phần mềm trong đó các đơn vị hay thành phần riêng lẻ của phần mềm được kiểm thử. Kiểm thử đơn vị được thực hiện trong quá trình phát triển ứng dụng. Mục tiêu của Kiểm thử đơn vị là cô lập một phần code và xác minh tính chính xác của đơn vị đó.

    Nếu khái niệm trên vẫn còn khá khó hiểu thì hãy thử tách nghĩa từng từ ra một nhé:

    • Unit là một thành phần Phần mềm nhỏ nhất mà ta có thể kiểm tra được như các hàm (Function), thủ tục (Procedure), lớp (Class), hoặc các phương thức (Method).
    • Test thì là kiểm thử, kiểm tra tính chính xác của một cái gì đó.

    Đến đây thì chắc các bạn cũng đã có cho mình một chút khái niệm về Unit Test rồi đúng không nhỉ.

    Thường các lập trình viên khi nghe về một khái niệm mà trong đó có từ “Test” thì điều mọi người sẽ nghĩ đến ngay đó là “Test là công việc của Tester đâu phải việc của mình nên mình không cần quan tâm :v”.

    Nhưng KHÔNG, các bạn đã nhầm to. Unit Test sẽ phải được viết bởi các lập trình viên, bởi chính những người viết ra những dòng code đó.

    2. Vòng đời của Unit Test

    Vòng đời của Unit Test gồm 3 giai đoạn:

    • Fail (trạng thái lỗi).
    • Ignore (tạm ngừng thực hiện).
    • Pass (trạng thái làm việc).

    Ba giai đoạn này sẽ thay phiên nhau làm việc khi một Unit Test được chạy tự động.

    3. Unit Test quan trọng không và khi nào thì cần viết Unit Test?

    Spring Break

    Unit Test là một phần không thể thiếu trong quá trình phát triển phần mềm. Unit Test đem lại cho chúng ta rất nhiều lợi ích:

    • Tạo ra một môi trường để kiểm tra bất kỳ đoạn code nào, duy trì sự ổn định của phần mềm. Unit Test giúp chúng ra kiểm tra những kết quả trả về mong muốn cũng như những ngoại lệ mong muốn.
    • Phát hiện các lỗi, các xử lý không hiệu quả trong code, các vấn đề về thiết kế.
    • Việc viết Unit Test có thể coi như việc tạo một người dùng đầu tiên cho ứng dụng, từ đó chúng ta có thể biết được những vấn đề mà trong quá trình sử dụng ứng dụng người dùng có thể gặp phải.
    • Giúp cho quá trình phát triển phần mềm trở nên nhanh hơn, số lượng test case khi được test cũng sẽ pass nhiều hơn. Điều này giúp cho các bộ phận khác như QA, Tester làm việc sẽ nhàn hơn. Và trên hết đối với những coder chúng ta, việc ít phải đối mặt với Tester cũng làm cho buổi làm việc “bớt sóng gió” hơn đúng không nào?

    Note

    Viết Unit Test càng sớm càng tốt trong giai đoạn viết code và xuyên suốt chu kỳ Phát triển phần mềm.

    4. Như nào là một Unit Test có giá trị?

    Muốn viết một Unit Test hiệu quả, đem lại nhiều lợi ích nhất cho bản thân cũng như dự án thì cần chú ý những điểm sau:

    • Unit Test chạy nhanh, sử dụng dữ liệu dễ hiểu, dễ đọc.
    • Hãy làm cho mỗi test độc lập với những phần khác. Mỗi test chỉ nên liên quan đến một hàm, thủ tục, … Điều này sẽ giúp chúng ta dễ dàng hơn trong quá trình quản lý unit test, cũng như đáp ứng được các thay đổi trong code.
    • Giả lập tất cả các dịch vụ và trạng thái bên ngoài. Ví dụ: Nếu bạn có làm việc với Database, thì KHÔNG nên sử dụng database thật của ứng dụng để viết Unit Test, bởi vì giá trị trong đó sẽ có thể thay đổi và ảnh hưởng đến các kết quả mong đợi của bạn. Thay vào đó hay tự vào cho mình một fake database, với dự liệu có sẵn và chỉ sử dụng các hàm, thủ tục để làm việc với nó.
    • Nên đặt tên các đơn vị kiểm thử rõ ràng và nhất quán với nhau để đảm bảo rằng test case dễ đọc. Để bất kỳ ai cũng có thể khởi chạy test case mà không gặp phải trở ngại.
    • Triển khai Unit Test bao quát hết tất cả các ngoại lệ, các test case.

    B. Cách triển khai Unit Test trong Flutter (Còn tiếp)

    Nguồn tham khảo:

    Tác giả: LamNT59