Những tuyệt kỹ trong việc dùng RobotFramework ( *)>

Automation Checking càng ngày càng phổ biến, việc 1 tester biết và thành thạo càng nhiều tools hay test framework sẽ giúp họ có nhiều lợi thế hơn trong việc testing cũng như có giá trị cao trong mắt nhà tuyển dụng. Nên bài viết này mình sẽ chia sẻ những mẹo nhỏ có ích khi sử dụng RobotFramework (với python thì có thể coi đây là 1 test framework khá tốt ngang ngửa với testNG bên Java) trong automation checking.

RobotFramework là 1 test framework được dùng cho acceptance testing và acceptance test-driven development (ATDD) – các bạn có thể tìm kiếm thêm các thông tin về ATDD – TDD và BDD bài viết này mình sẽ không nói nhiều về sự khác nhau đó, hoặc các bạn có thể tham khảo ở link. Điểm mạnh của RobotFramework chính là được viết trên nền tảng python và được hỗ trợ bởi số lượng thư viện dành cho testing khá nhiều, ngoài ra thì nó cũng rất dễ sử dụng cũng như viết test script và có thể chạy được trên mọi nền tảng khác nhau mà không cần chỉnh sửa test script. Tuy nhiên nhiều người nói nói rằng xài RobotFramework thì không cần phải biết về coding là hoàn toàn sai lầm nên mình muốn nhấn mạnh rằng đã làm việc với automation thì đừng mong tìm kiếm 1 cái tool mà ko cần phải code ngoại trừ chỉ tay 5 ngón và sai mấy thằng lính làm =)).

Cài đặt RobotFramework:

  1. Sử dụng thư viện virtualenv(Virtual Environment) để cài đặt RobotFramework cũng như thiết lập môi trường run test. Làm việc với python thì các bạn hãy tập làm quen với việc sử dụng virtualenv.

    Virtual Environment dịch nôm na là môi trường ảo. Virtual Environment thiết lập một môi trường ảo, cho phép bạn cài đặt các packages của Python với các version khác nhau mà không làm ảnh hưởng đến những packages đã được cài đặt sẵn trên máy hoặc ở 1 môi trường ảo khác. Ví dụ bạn muốn thử nghiệm với seleniumlibrary3.0 trong khi trên hệ thống đang cài đặt selenium2.0. Cũng giống như việc bạn dùng Virtual Machine để thử nghiệm phiên bản Chrome beta mới nhất mà không muốn làm ảnh hưởng đến phiên bản đang có trên máy.

    Để cài đặt virtualenv thì các bạn làm theo các bước sau:
    Windows:

    # cài đặt thư viện virtualenv
    pip install virtualenv virtualenvwrapper-win
    # tạo một môi trường ảo tên robotframework và kích hoạt nó 
    mkvirtualenv robotframework 
    # tắt kích hoạt môi trường ảo
    deactivate 
    # kích hoạt lại môi trường ảo
    workon robotframework

    MacOS:

    # cài đặt thư viện virtualenv
    pip install virtualenv virtualenvwrapper
    # tạo một môi trường ảo tên robotframework và kích hoạt nó 
    mkvirtualenv robotframework 
    # tắt kích hoạt môi trường ảo
    deactivate 
    # kích hoạt lại môi trường ảo
    workon robotframework
  2. Thiết lập biến môi trường PYTHONPATH. Hồi mình mới làm quen với RobotFramework và Python thì thuờng hay bị lỗi như ImportError: No module named xyz. Lỗi này là do có thể thư viện hoặc file module đó chưa python interpreter scan qua. Để khắc phục lỗi này thì bạn tìm xem file python đó nằm ở đâu và add nó vô biến môi trường PYTHONPATH
    Ví dụ mình có file seleniumextend.py nằm ở đường dẫn 
    /Users/minhhoang/Workspace/NLG/Plot
    thì mở command line và gõ 
    MacOS
    export PYTHONPATH=$PYTHONPATH:/Users/minhhoang/Workspace/NLG/Plot
    Win
    set PYTHONPATH=%PYTHONPATH%;/Users/minhhoang/Workspace/NLG/Plot

    Ở trên mình đã sử dụng virtualenv thì có thể cấu hình mỗi khi kích hoạt hay tắt kích hoạt virtualenv sẽ tự động thiết lập biến môi trường PYTHONPATH, để làm việc đó thì các bạn chỉ cần vô thư mục chứa thiết lập của virtualenv:

    MacOS: sau khi kích hoạt môi trường ảo thì gõ lệnh sau trong terminal:
    # lệnh cho biết file python của Virtual Environment ở đâu 
    which python => /Users/minhhoang/.virtualenvs/robotframework/bin/python
    # lệnh di chuyển đến thư mục để cấu hình
    cd /Users/minhhoang/.virtualenvs/robotframework/bin/
    # lệnh để set biến môi trường khi kích hoạt 
    # lệnh này sẽ set biến pythonpath bao gồm đường dẫn cho
    # project robotframework hiện tại của mình 
    echo -e "export PYTHONPATH_ORG=\$PYTHONPATH \nexport PYTHONPATH=\$PYTHONPATH:/Users/minhhoang/Workspace/RobotFramework" >> postactivate
    # lệnh để set biên pythonpath về ban đầu khi tắt môi trường ảo
    echo -e "export PYTHONPATH=\$PYTHONPATH_ORG" >> postdeactivate
    # để kiểm tra mọi thứ hoạt động thì sau khi kích 
    # hoạt môi trường ảo bạn gõ lệnh
    echo $PYTHONPATH # cửa sổ sẽ hiển ra đường dẫn của project bạn đã thiết lập
    Windows: các bạn làm tương tự như MacOS chỉ thay export bằng set
  3. Hãy tập làm quen với python3, hiện tại mình thấy nhiều bạn tiếp cận với RobotFramework thường chỉ sử dụng python2 do, IDE RIDE chỉ tương thích với python2. Lời khuyên của mình là hãy từ bỏ python2 và tập làm quen với python3. Hiện tại mình sử dụng IDE PyCharm để làm việc với những project mà cần viết bằng python. Với RobotFramework thì các bạn có thể cài những plugin có sẵn ở PyCharm  như hình dưới đây – các plugin này đều hỗ trợ keyword hint, trace method từ .robot file với python test libraries:
    Screen Shot 2018-03-04 at 6.03.48 PM.png
    Screen Shot 2018-03-04 at 11.24.26 PMScreen Shot 2018-03-04 at 11.09.11 PMScreen Shot 2018-03-04 at 11.10.10 PM

    Những lý do sau mà mình khuyên các bạn tập làm quen với python3:
    - Python2 chỉ còn được hỗ trợ tới năm 2020
    - Python3 hỗ trợ kiểu dict tốt hơn (không bị xáo trộn thứ tự item)
    - Python3 hỗ trợ nhiều thư viện mới như Typing
    - Hiện tại nhiều thư viện đã hỗ trợ python3
  4.  Lưu thông tin thư viện sử dụng trong project để có thể run test ở các máy khác, lúc này khi thiết lập môi trường trong máy khác thì không cần phải nhớ tên tất cả các thư viện đang sử dụng:
    # lệnh lưu thông tin các thư viện sử dụng trong project 
    pip freeze > requirements.txt
    # lệnh để cài đặt các thư viện theo thông tin đã lưu trong file requirements.txt
    pip install -r requirements.txt

Quản lý folder và file trong RobotFramework:

  1. Nếu là project về GUI E2E test thì các bạn nên sử dụng cách tổ chức POM, dưới đây là cách mình sẽ tổ chức folder nếu mình có project sử dụng cả API và GUI:
    Screen Shot 2018-03-08 at 9.22.54 PM
  2. Kinh nghiệm mình là khi đặt tên test case hay test suite thì nên đặt ngắn gọn và dễ hiểu càng tốt. 1 điểm đặc biệt là với RobotFramework thì khi run test thì có thể thay thế tên folder có ký hiệu “_” bằng ” “. Ví dụ:
    Test:
    – Suite_Run_Fast:
    + Smoke_API.robot
    + Smoke_GUI.robot
    – All_Test.robot
# Run toàn bộ test nằm trong folder Suite_Run_Fast 
robot --name "Suite Run Fast" .
  1. Nên hạn chế số lượng tests trong 1 file .robot tối đa là 10 tests. Cái này giúp việc run test sẽ lẹ hơn cũng như test report cũng dễ phân tích hơn.
  2. Có thể sử dụng 1 file resource.robot để khởi tạo biến cho test suites cũng như chứa action keywords dành cho “Test Setup/Test Teardown” và “Suite Setup/Suite Teardown” cũng như import toàn bộ test libraries cần thiết cho test suites. Các bạn có thể coi ví dụ như hình sau(ngoài ra có thể tham khảo thêm link):

Tạo test libraries với RobotFramework:

  1. Sử dụng thuần thục các keyword builtin của RobotFramework nhằm tối ưu hoá việc viết các test libraries. Dưới đây là 1 số keywords mình thấy có ích mà mình từng sử dụng:
    from robot.libraries.BuiltIn import BuiltIn
    from SeleniumLibrary import SeleniumLibrary, AlertKeywords, BrowserManagementKeywords, CookieKeywords, ElementKeywords, \
        FormElementKeywords, FrameKeywords, JavaScriptKeywords, RunOnFailureKeywords, SelectElementKeywords, \
        ScreenshotKeywords, TableElementKeywords, WaitingKeywords, WindowKeywords
    from typing import Union
    
    TYPE_HINT = Union[
        SeleniumLibrary, AlertKeywords, BrowserManagementKeywords, CookieKeywords, ElementKeywords, FormElementKeywords, FrameKeywords, JavaScriptKeywords, RunOnFailureKeywords, SelectElementKeywords, ScreenshotKeywords, TableElementKeywords, WaitingKeywords, WindowKeywords]
    
    
    class BaseObject(object):
        def __init__(self):
    # Khởi tạo thư viện RobotFramework trong test libraries
    # RobotFramework sử dụng singleton pattern cho thư viện này
    # Nên các bạn không cần lo lắng việc khởi tạo có dẫn tới việc 
    # Object không link với nhau
            self.robot_built_in: BuiltIn = BuiltIn()
    # Keyworld get_library_instance sẽ trả về khởi tạo
    # của library đang run trong RobotFramework
    # Keyword này rất có ích nếu test libraries của 
    # các bạn cần tương tác với các thư viện có chứa state
    # ví dụ thư viện SeleniumLibrary nếu trong file test.robot
    # các bạn đã khởi tạo browser thì với keyword này sẽ giúp các 
    # bạn có thể tương tác với browser đó, nếu các bạn khởi tạo lại
    # thư viện SeleniumLibrary thì lúc này các bạn sẽ có thể gặp lỗi
    # Không tìm thấy browser instance khi run test với test libraries
    # của mình, còn chi tiết thì cứ pm mình sẽ giải thích kỹ hơn 
            self.selenium: TYPE_HINT = self.robot_built_in.get_library_instance("SeleniumLibrary")
    # Dưới đây là các thư viện để thiết lập biến cũng như lấy biến
    # được thiết lập trong test.robot 
    # ví dụ như mình từng viết file này để lấy biến config nằm ở 
    # resource.robot (link)
            self.robot_built_in.set_global_variable("${GLOBAL_VARIABLE_NAME}", "PUT YOUR VALUE HERE")
            self.robot_built_in.set_suite_variable("${GLOBAL_VARIABLE_NAME}", "PUT YOUR VALUE HERE")
            self.robot_built_in.set_test_variable("${GLOBAL_VARIABLE_NAME}", "PUT YOUR VALUE HERE")
            self.robot_built_in.log_to_console("Log to console for debugging")
            self.__do_some_thing_with_robot_variable(
                self.robot_built_in.get_variable_value("${VARIABLE_NAME}", "DefaultValue"))
           
        def __do_some_thing_with_robot_variable(self, value):
            pass
  2. Sử dụng thư viện test listener đễ thực hiện các pre-process(Kết nối database, khởi tạo data) hay post-process(submit bug lên Jira khi test script failed do bug) khi run test suties. Để run test với listener thì có 2 cách bao gồm:
    # Run test suites ở CLI và thêm parameter --listener
    robot --listener MyListener tests.robot
    robot --listener path/to/MyListener.py tests.robot
    # Sử dụng thư viện listener như 1 test libraries và import vô 
    # Test.Robot như library bình thuường
    *** Settings ***
    Library GUI.POM.RegisterPage.RegisterPage
    Library Core.Config.DriverFactory.DriverFactory
    Library SeleniumLibrary
    Library MyListener
    Suite Setup create driver
    Suite Teardown dispose driver
    *** Test Cases ***
    Sample Test
     open register page
     register with account Test Another anothernewemailsample@sample.com 12345678
     click register button

    Dưới đây là 1 đoạn code mình dùng để log ra console khi run với jenkins (các bạn có thể xem chi tiết API ở link):

    class LogDetailsExecution(object):
        ROBOT_LISTENER_API_VERSION = 3
    
        def __init__(self, report_folder_path):
            self.ROBOT_LIBRARY_LISTENER = self
            self.__final_suites_stats = None
            self.__date_time_helper = DateTimeHelper()
            self.__log_details_path = report_folder_path + os.path.sep + "SuitesDetails.log"
    
        def log_message(self, message):
            self.__log_details_message(message)
    
        def message(self, message):
            self.__log_details_message(message)
    
        def start_suite(self, data, result):
            self.__log_start_suite()
            self.__log_details_message(str(data.longname))
    
        def start_test(self, data, result):
            self.__log_start_test()
            self.__log_details_message(str(data.longname))
    
        def end_test(self, data, result):
            self.__log_details_message("================================== TEST RESULT ===================================")
            self.__log_details_message(str(data.longname) + ": " + result.status + " " +
                                       result.message)
            self.__log_end_test()
    
        def end_suite(self, data, result):
            self.__log_details_message("================================== SUITE RESULT ==================================")
            self.__log_details_message(str(data.longname) + ": " + result.stat_message + " " +
                                       str(result.message))
            self.__final_suites_stats = str(result.stat_message)
            self.__log_end_suite()
    
        def __log_details_message(self, message):
            with open(self.__log_details_path, 'a') as log_file:
                m = str(self.__date_time_helper.get_date_time_string_value) + ": " + str(message) + "\n"
                log_file.write(str(m))
    
        def __log_start_suite(self):
            self.__log_details_message("================================== START  SUITE ==================================")
            self.__log_details_message("==================================================================================")
            self.__log_details_message("==================================================================================")
    
        def __log_start_test(self):
            self.__log_details_message("=================================== START  TEST ==================================")
            self.__log_details_message("==================================================================================")
            self.__log_details_message("==================================================================================")
    
        def __log_end_test(self):
            self.__log_details_message("=================================== END  TEST ====================================")
            self.__log_details_message("==================================================================================")
            self.__log_details_message("==================================================================================")
    
        def __log_end_suite(self):
            self.__log_details_message("=================================== END  SUITE ===================================")
            self.__log_details_message("==================================================================================")
            self.__log_details_message("==================================================================================")
    
        def __log_end_execution(self):
            self.__log_details_message(self.__final_suites_stats)
            self.__log_details_message("================================== END EXECUTION =================================")
            self.__log_details_message("==================================================================================")
            self.__log_details_message("==================================================================================")
            self.__log_details_message("#  MMMMMMMM               MMMMMMMMHHHHHHHHH     HHHHHHHHH       ")
            self.__log_details_message("#  M:::::::M             M:::::::MH:::::::H     H:::::::H       ")
            self.__log_details_message("#  M::::::::M           M::::::::MH:::::::H     H:::::::H       ")
            self.__log_details_message("#  M:::::::::M         M:::::::::MHH::::::H     H::::::HH       ")
            self.__log_details_message("#  M::::::::::M       M::::::::::M  H:::::H     H:::::H         ")
            self.__log_details_message("#  M:::::::::::M     M:::::::::::M  H:::::H     H:::::H         ")
            self.__log_details_message("#  M:::::::M::::M   M::::M:::::::M  H::::::HHHHH::::::H         ")
            self.__log_details_message("#  M::::::M M::::M M::::M M::::::M  H:::::::::::::::::H         ")
            self.__log_details_message("#  M::::::M  M::::M::::M  M::::::M  H:::::::::::::::::H         ")
            self.__log_details_message("#  M::::::M   M:::::::M   M::::::M  H::::::HHHHH::::::H         ")
            self.__log_details_message("#  M::::::M    M:::::M    M::::::M  H:::::H     H:::::H         ")
            self.__log_details_message("#  M::::::M     MMMMM     M::::::M  H:::::H     H:::::H         ")
            self.__log_details_message("#  M::::::M               M::::::MHH::::::H     H::::::HH       ")
            self.__log_details_message("#  M::::::M               M::::::MH:::::::H     H:::::::H       ")
            self.__log_details_message("#  M::::::M               M::::::MH:::::::H     H:::::::H       ")
            self.__log_details_message("#  M::::::M               M::::::MH:::::::H     H:::::::H       ")
            self.__log_details_message("#  M::::::M               M::::::MH:::::::H     H:::::::H       ")
            self.__log_details_message("#  MMMMMMMM               MMMMMMMMHHHHHHHHH     HHHHHHHHH       ")
            self.__log_details_message("==================================================================================")
            self.__log_details_message("==================================================================================")
            self.__log_details_message("==================================================================================")

Data driven với RobotFramework

  1. Sử dụng data table của chính RobotFramework:
    + Ưu điểm: Sử dụng tính năng data driven được hỗ trợ sẵn bởi RobotFramework, ở đây RobotFramework có 1 khái niệm gọi là Test Template (có thể hiểu nôm na là 1 keyword action chứa nhiều steps bên trong và có thể nhận vô arguments), khi đó việc của người viết script chỉ cần gọi action keyword trong test case và truyền những arguments vào. Cách này thì file .robot sẽ khá trực quan, nhìn vô sẽ hiểu rõ mục đích của test case đó cần run với data gì.
    + Nhược điểm: Cách này thì bộ data driven thường sẽ không generate dynamic được, cũng như khi cần thay đổi data thì phải mở file .robot lên và edit trực tiếp.
    + Cách sử dụng:

    # Như ví dụ ở đây ta có Test Template là
    # Login with invalid credentials should fail
    # và 1 bộ set data driven gồm 6 data ở Test Cases 
    *** Settings ***
    Test Template         Login with invalid credentials should fail
    
    *** Test Cases ***    USERNAME             PASSWORD
    Invalid Username      invalid              ${VALID PASSWORD}
    Invalid Password      ${VALID USERNAME}    invalid
    Invalid Both          invalid              invalid
    Empty Username        ${EMPTY}             ${VALID PASSWORD}
    Empty Password        ${VALID USERNAME}    ${EMPTY}
    Empty Both            ${EMPTY}             ${EMPTY}
    
    *** Keywords ***
    Login with invalid credentials should fail
        [Arguments]    ${username}    ${password}
        Input Username    ${username}
        Input Password    ${password}
        Submit Credentials
        Error Page Should Be Open
  2. Sử dụng RobotFramework listener:
    + Ưu điểm: Bộ data lúc này có thể generate 1 cách dynamic. Người viết script khi cần thay đổi data sẽ không cần phải mở file .robot lên. Có thể sử dụng data source từ nhiều nguồn khác nhau (DB, excel, txt, etc.).
    + Nhược điểm: Cách này đòi hỏi phải viết thêm 1 test library sử dụng RobotFramework Listener vì thế đòi hỏi các bạn phải coding 1 chút. Ngoài ra thì cách này có 1 điểm các bạn cần chú ý là ở RobotFramework thì các parameter được truyền vào các hook methods có kiểu Mutable (hiểu ngắn gọn là là variable này có thể bị update bởi những method khác còn muốn hiểu kỹ các bạn có thể search thêm với từ khoá đó ngoài ra thì có thể thêm từ khoá là reference variable) vì thế các bạ nên cẩn thận khi dùng. Ngoài ra với cách này thì chúng ta sẽ không có 1 file .robot hoàn chỉnh và BA hoặc PO sẽ rất khó để biết test này được run với bộ data nào (cần mở file report lên để coi, mà nhiều khi cái này không cần thiết lắm).
    + Cách sử dụng:

    # Tạo 1 file .robot chứa Test Template
    # Tạo file test library với Robot Framework listener 
    # Sử dụng hook start_test 
    # trong method start_test kiểm tra nếu data.Template khác None
    # thì sử dụng method data.keyword.create
    # để tạo test case cho data driven
    # các bạn có thể tham khảo (link1, link2)
    *** Settings ***
    Test Template         Login with invalid credentials should fail
    
    *** Test Cases ***    USERNAME             PASSWORD
    
    *** Keywords ***
    Login with invalid credentials should fail
        [Arguments]    ${username}    ${password}
        Input Username    ${username}
        Input Password    ${password}
        Submit Credentials
        Error Page Should Be Open
  3. Sử dụng test templae generator:
    + Ưu điểm:
    + Nhược điểm:
    + Cách sử dụng:

 

Advertisements

2 comments

Leave a Reply to hnminhoutlookcom Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s