Giới thiệu về Selenium và Jenkins trong quy trình QA sản phẩm

Mở đầu

Trong quy trình phát triển một phần mềm, Test nói chung và UT nói riêng luôn là những process rất quan trọng. Tuy nhiên khi Application càng ngày càng lớn thì khối lượng Test càng phình to và cost sẽ vượt quá cost bỏ ra cho coding. Để giảm thiểu số công sức bỏ ra, developer thường hay dùng các test framework có sẵn và tìm cách automation quy trình test.

PHP có PHPUnit, Java có JUnit, Python có nose v.v… Tuy nhiên bên ngoài UT vẫn còn những quy trình bắt buộc phải làm bằng tay. Bạn viết ra 1 website, bạn muốn test trên website bạn có đúng những link hiện ra như bạn muốn hay ko, màu sắc có thay đổi hay không, khi user click vào link có nhảy đến page target và mang theo hàm callback đã định nghĩa hay ko v.v…

Trong bài viết này sẽ giới thiệu 2 công cụ automation UT và test khá nổi tiếng. Sủ dụng và tận dụng, đôi khi sẽ thành những tool mang lại hiệu quả bất ngờ trong cả những công việc khác :D

Selenium

Selenium - nói 1 cách đơn giản, là công cụ automator tất cả các thao tác của con người trên browser. Bạn có thể giả lập 1 set các action, VD như: < User bật browser lên, User vào trang web của bạn, User đi theo link route linh1-link2-link3-link4, User click vào download button trong link 4 >

Selenium có 2 cách dùng, Selenium Server và Selenium WebDriver. Hiện nay trên firefox đã có plugin Selenium IDE. Bạn có thể dùng nó để record lại hành động của mình và export ra test code in Python, Ruby, C#, hoặc Java. Selenium test code chạy ở chế độ bình thường sẽ tự động bật browser của bạn lên và tự hành động y như bạn đã làm trước dó :D

Tuy nhiên để quy trình test không tốn quá nhiều tài nguyên, người ta thường hay config để selenium chạy headless trên Linux. VD như Python sẽ có package Xvfb, cho phép tạo display chạy ngầm và bật web driver trên đó.

Ở dưới là VD config cho CentOS distro

1
2
3
4
pip install selenium
yum install Xvfb 
yum install firefox
pip install pyvirtualdisplay
~/.bash_profile
1
2
3
4
...
#Create virtual screen by Xvfb
Xvfb :5 -ac -screen 0 1024x768x8 &
...
TestMySite.py
1
2
3
4
5
6
7
8
from selenium import webdriver
...
class SeleniumHeadlessTest(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Firefox()
        self.base_url = "" #Testing URL here 
        self.verificationErrors = []
....
running python test
1
2
export DISPLAY=:5.0
python TestMySite.py

Bypass the working attendance system

Ở phần này tôi sẽ đặt ra 1 ví dụ trực quan: giả sử, công ty bạn có hệ thống check time làm việc của nhân viên. Nhân viên khi đến công ty phải login vào page, ấn vào button “Đã đến”. Tương tự khi về lại phải login vào và ấn thêm button “Đã về”

Có selenium trong tay, bạn có thể giải quyết vấn đề khá đơn giản, viết 1 test script mô phỏng toàn bộ quá trình nói trên, đặt cron cho nó chạy vào giờ đến và giờ về chỉ định

selenium - selenium.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from selenium import webdriver
...
class SeleniumHeadlessTest(unittest.TestCase):
    def setUp(self):
        mock= ""
        self.driver = webdriver.Firefox()
        self.base_url = mock
        self.verificationErrors = []

    def test_in(self):
        name=""
        pwd=""
        driver = self.driver
        driver.get(self.base_url)
        driver.find_element_by_xpath("..._name_field_").send_keys(name)
        driver.find_element_by_xpath("..._password_field_").send_keys(pwd)
        driver.find_element_by_xpath("..._login_button_").click()
...

Thêm 1 ít sleep time random(VD random trong khoảng 10 phút) và đặt cron 30 10 * * 1-5. Bạn đã có 1 con bot luôn check time in cho bạn trong khoảng 10h30-10h40 mỗi ngày trong tuần :D Với package Xvfb như đã nói ở trên, con bot sẽ chạy silent bên trong máy (hay máy ảo) và ko tạo ra 1 notice nào. Redirect log ra 1 file ẩn sẽ giúp manage quá trình chạy tốt hơn.

Khi xuất hiện 1 số ngày nghỉ ko phải thứ 7, CN mà muốn đặt lịch để con bot ko chạy, vấn đề sẽ hơi phức tạp hơn 1 chút. bạn cần phải viết 1 con bot observer khác check ngày và update lại cron job

Selenium + Nose

Nếu bạn đã dùng Python 1 thời gian, chắc hẳn sẽ biết đến Nose - UT framework cho Python. Ở phần này sẽ demo thêm 1 config cho Selenium + Nose

Yêu cầu: Trang web www.mysite.com của bạn có 100 sublink: www.mysite.com/1 , www.mysite.com/2, www.mysite.com/3 ….. www.mysite.com/100

Bạn phải test xem trong từ sublink đó có 1 file image tên là “myimage.png” với div=myimage hay không.

Như vậy có 100 test case ở đây, tuy nhiên content khá giống nhau, bạn có thể nghĩ đến chuyện tạo template

template.py

template.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from selenium import webdriver
from selenium import unittest
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException

class SeleniumHeadlessTest(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Firefox()
        self.base_url = "www.mysite.com"
        self.failures = []

    def Test(self):
        driver = self.driver
        driver.get(self.base_url+ "foo")
        try: driver.find_element_by_xpath("//div[@id='myimage']")
        except NoSuchElementException:
            driver.back()
            self.failures.append("foo")
        self.assertEqual(len(self.failures),0)

    def tearDown(self):
        self.driver.quit()

if __name__ == "__main__":
    unittest.main()

Tạo file config chứa các giá trị muốn replace cho “foo” trong template.py, ở đây cụ thể là 1..100

config
1
2
3
4
5
6
7
# any comment
! another comment
$ I'm comment too, do u believe me :D
1
2
...
100

Code 1 cái test_generator, ở đây tôi dùng chính Python fabric:D

selenium - fabfile.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# pip install fabric
from __future__ import with_statement
from fabric.api import *
import sys, os, fileinput, textwrap

_DIR=os.getcwd()

def test_generator():
    with open('./config','r') as f:
        # Ignore comment line in config file
        prefs=["#","!","<","/","$","%","&","\n"]
        ffilter = filter(lambda item: not any(item.startswith(prefix) for prefix in prefs), f.readlines()
        for line in ffilter:
            bar=line.strip()
            with lcd(_DIR):
                # create test source 
                local('cp -r template.py '+bar+'.py')
            with open('./'+bar+'.py','r+') as tf:
                # replace all "foo" with variable read from config file
                tfmap = map(lambda l: l.replace("foo","bar") if "foo" in l else l, tf.readlines())
                tf.seek(0)
                tf.writelines(tfmap)
            tf.closed
    f.closed

Done! Giờ bạn có thể

1
2
3
4
#pip install multiprocessing
export DISPLAY=:5.0
fab test_generator 2>fabErr.log
nosetest --exe -v --process=4 --process-restartworker .

Jenkins

Tiếp tục về quy trình test automation, Jenkins là 1 tool xuất sắc khác. Jenkins đại diện cho khái niệm CI (continuous integration).

Bạn muốn cả bộ UT/test chạy mỗi ngày vào 10 h sáng. Mội ngày system leader đến check log để biết có bao nhiêu test đang fail, bao nhiêu error, quản lý health theo từng ngày, bạn hoàn toàn có thể dùng test framework + cronjob

Tuy nhiên Jenkins cung cấp 1 giao diện trực quan hơn, percentage, graph, coverage report, code convention checking ,v.v….. hoàn toàn tự động, có thể wake up theo svn hoặc git. Jenkins cũng có rất nhiều plugin và support hầu hết các test framework.

VD về Jenkins jobs

imagesimagesSource Code Management

imagesimagesExecute Shell

imagesimagesBuild Trigger

Đặt lịch, tương tự cron jobs

1 recommended config là tạo 1 jenkins job observer check svn và git, nếu có update tại subsystem nào thì run jenkins jobs tương ứng với subsystem đấy. 1 sub system lại có thể chứa nhiều test framework cần wakeup (VD với PHP + Oracle, bạn có PHPUnit và utPLSQL)