1. Trang chủ
  2. » Luận Văn - Báo Cáo

Kiểm thử tích hợp với độ phức tạp tương đương kiểm thử thành phần

47 437 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 47
Dung lượng 832,88 KB

Nội dung

Bước cuối cùng – Kiểm thử: sau khi xác định giao diện đơn vị - môi trường, tất cả các phương thức và các thuộc tính liên quan trực tiếp đến các hành vi mà đơn vị và môi trường tác động q

Trang 1

MỤC LỤC

CHƯƠNG 1 – GIỚI THIỆU ··· 1

1.1 Mục tiêu nghiên cứu ··· 1

1.2 Ý tưởng giải quyết bài toán··· 2

1.3 Cấu trúc luận văn··· 3

CHƯƠNG 2 – KIẾN THỨC NỀN TẢNG··· 5

2.1 Kiểm thử đơn vị··· 5

2.2 Phân tích tĩnh··· 5

2.3 Kiểm thử luồng điều khiển ··· 6

2.4 Kiểm thử luồng dữ liệu··· 9

2.4.1 Kiểm thử định nghĩa – sử dụng (Define/Use Testing) ··· 10

2.4.2 Kiểm thử dựa trên lát cắt (Slice-based testing)··· 11

CHƯƠNG 3 – KỸ THUẬT KIỂM THỬ SINH MÔI TRƯỜNG ··· 14

3.1 Chọn chức năng cần kiểm thử và xác định đơn vị··· 14

3.2 Xác định giao diện đơn vị - môi trường ··· 14

3.3 Kiểm thử··· 19

CHƯƠNG 4 - ỨNG DỤNG ··· 20

4.1 Giới thiệu phần mềm TestSystem ··· 20

4.2 Kiểm thử chức năng đăng nhập của phần mềm TestSystem··· 21

4.2.1 Xác định đơn vị ··· 21

4.2.2 Xác định giao diện đơn vị - môi trường ··· 21

4.2.3 Kiểm thử chức năng đăng nhập··· 22

4.2.4 Báo cáo kết quả kiểm thử··· 36

CHƯƠNG 5 – KẾT LUẬN ··· 38

TÀI LIỆU THAM KHẢO ··· 40

PHỤ LỤC ··· 41

Phụ lục 1 – Mã lớp TestLogin ··· 41

Trang 2

DANH SÁCH CÁC HÌNH VẼ

Hình 1.1 Tạo môi trường cho đơn vị ··· 2

Hình 2.1 Các thành phần cơ bản của đồ thị luồng điều khiển ··· 7

Hình 2.2 Các cấu trúc điều khiển phổ dụng ··· 7

Hình 3.1 Minh họa thuật toán giao diện đơn vị··· 16

Hình 3.2 Minh họa thuật toán xác định giao diện môi trường ··· 18

Hình 4.1 Đồ thị luồng điều khiển lớp Login ··· 27

Hình 4.2 Mã phương thức GetStudent··· 31

Hình 4.3 Đồ thị luồng điều khiển phương thức GetStudent ··· 31

Hình 4.4 Mã phương thức GetDataTeacher ··· 33

Hình 4.5 Mã phương thức SetPresenceStudent··· 34

Hình 4.6 Đồ thị luồng điều khiển phương thức SetPresenceStudent ··· 34

Hình 4.7 Kiểm thử chức năng đăng nhập của TestSystem ··· 37

Hình 4.8 Giao diện trang chủ của phần mềm TestSystem ··· 37

Trang 3

DANH SÁCH CÁC BẢNG

Bảng 4.1 Các nút định nghĩa, sử dụng của các biến··· 25

Bảng 4.2 Các trường hợp kiểm thử của biến nTypeUser ··· 26

Bảng 4.3 Các trường hợp kiểm thử của biến nIDStudent··· 27

Bảng 4.4 Các trường hợp kiểm thử của biến nIDTeacher ··· 28

Bảng 4.5 Các trường hợp kiểm thử của biến countTeacher··· 28

Bảng 4.6 Các trường hợp kiểm thử của biến countStudent ··· 29

Bảng 4.7 Các trường hợp kiểm thử của biến countAdmin ··· 29

Bảng 4.8 Các trường hợp kiểm thử của biến countAnonymous ··· 30

Bảng 4.9 Các trường hợp kiểm thử của phương thức GetStudent ··· 32

Bảng 4.10 Các trường hợp kiểm thử của phương thức GetDataTeacher ··· 33

Bảng 4.11 Các trường hợp kiểm thử của phương thức SetPresenceStudent ··· 35

Bảng 4.12 Các trường hợp kiểm thử của phương thức SetPresenceTeacher ··· 35

Trang 4

CHƯƠNG 1 - GIỚI THIỆU 1.1 Mục tiêu nghiên cứu

Với những chương trình có kích thước nhỏ và chức năng đơn giản, ta dễ dàng tiến hành kiểm thử bằng kỹ thuật kiểm thử đơn vị Kiểm thử đơn vị có thể được lập trình viên thực hiện, có độ phức tạp nhỏ và tốn ít chi phí Với sự phát triển của ứng dụng công nghệ thông tin, hiện nay, những phần mềm thường lớn và chứa nhiều lớp Các lớp có thể được thực thi độc lập, sau đó được tích hợp lại thành một hệ thống như mong muốn Để kiểm thử một chức năng trên hệ thống, người ta thường sử dụng kỹ thuật kiểm thử tích hợp

Việc kiểm thử tích hợp một chức năng trên toàn hệ thống thường có độ phức tạp cao, đòi hỏi kiểm thử trên một không gian kiểm thử lớn Để làm giảm không gian cần kiểm thử, có thể tiếp cận theo hướng mô-đun hóa bằng cách chia phần mềm thành hai phần: đơn vị (unit) và môi trường Đơn vị bao gồm tập hợp một số lớp liên quan đến chức năng cần kiểm thử, môi trường là tập hợp các lớp

mà đơn vị tương tác với Các tương tác này xẩy ra tại giao diện đơn vị – môi trường Đơn vị có thể tác động một số hành vi lên môi trường và ngược lại, môi trường cũng có thể tác động một số hành vi lên đơn vị Sự tương tác giữa đơn vị

và môi trường có thể là những hành vi về điều khiển, như gọi đến một thuộc tính hay một phương thức; hoặc có thể là những hành vi về dữ liệu, như thay đổi giá trị của một thuộc tính Tất cả những thuộc tính và những phương thức thể hiện hành vi của đơn vị và môi trường tạo nên giao diện đơn vị – môi trường

Với mỗi đơn vị cần kiểm thử, việc tạo ra môi trường là một công việc quan trọng Môi trường được chia thành hai phần là driver và stub Driver bao gồm các lớp gọi đến các thuộc tính hay các phương thức của đơn vị, còn stub là các lớp mà đơn vị gọi đến Như vậy, driver thể hiện những hành vi mà môi trường tác động lên đơn vị, còn stub thể hiện những hành vi mà đơn vị tác động đến môi trường

Với việc định nghĩa các khái niệm như trên, có thể tổng quát hóa quá trình tạo ra môi trường cho đơn vị như hình 1.1 Toàn bộ phần mềm được thể hiện ở bên trái mũi tên Phần mềm gồm nhiều lớp Mỗi lớp được minh họa bởi hình chữ nhật có các dòng kẻ ngang bên trong Đơn vị cần kiểm thử được bao quanh bởi đường viền chấm Các đường mũi tên nối giữa các lớp, thể hiện quan hệ giữa các lớp với nhau Những đường mũi tên nối trực tiếp với đường bao quanh đơn vị thể hiện tương tác giữa đơn vị và môi trường Từ những thông tin trên, ta

Trang 5

cần xác định driver và stub cho đơn vị Phần bên phải mũi tên thể hiện mối quan hệ giữa đơn vị, driver và stub

Hình 1.1 Tạo môi trường cho đơn vị [1]

Sau khi tạo ra môi trường cho đơn vị, để đảm bảo đơn vị được kiểm thử trên toàn bộ hệ thống, cần đặt đơn vị vào mối quan hệ với môi trường và kiểm thử đơn vị trong tổng thể mối quan hệ đơn vị – môi trường đó Với một phạm vi không lớn, để tiến hành kiểm thử các tính chất của đơn vị, hoàn toàn có thể sử dụng các kỹ thuật kiểm thử đơn vị như kiểm thử luồng điều khiển, kiểm thử luồng dữ liệu

Kết quả, chỉ cần sử dụng các kỹ thuật kiểm thử đơn vị, có thể kiểm thử một chức năng trên toàn bộ hệ thống mà không cần sử dụng kỹ thuật kiểm thử tích hợp Nghiên cứu này có ý nghĩa rất lớn trong việc kiểm thử những hệ thống phức tạp, làm đơn giản và tăng hiệu quả kiểm thử

Trong luận văn, kiểm thử thành phần hay kiểm thử đơn vị được tiến hành trên từng lớp, kiểm thử tích hợp là kiểm thử tích hợp giữa đơn vị và môi trường

1.2 Ý tưởng giải quyết bài toán

Để làm được công việc nêu trong mục 1.1, với mỗi chức năng cần kiểm thử của phần mềm, lần lượt làm các bước sau:

Tạo môi trường

Mã chương trình

Driver

Stub

Trang 6

Bước thứ nhất - Xác định đơn vị: Sử dụng kỹ thuật phân tích tĩnh, cùng các tài liệu đặc tả, thiết kế, mã chương trình, xác định những lớp quyết định trực tiếp đến chức năng cần kiểm thử Tập hợp các lớp đó gọi là đơn vị

Bước thứ hai - Xác định giao diện đơn vị – môi trường: giao diện đơn vị – môi trường bao gồm hai phần: giao diện đơn vị và giao diện môi trường Giao diện đơn vị bao gồm các thuộc tính, phương thức của đơn vị mà môi trường tham chiếu đến Giao diện đơn vị thể hiện các hành vi của môi trường tác động lên đơn vị Ngược lại, giao diện môi trường bao gồm các thuộc tính, phương thức của các lớp trong môi trường mà đơn vị tham chiếu đến Giao diện môi trường thể hiện các hành vi của đơn vị tác động lên môi trường

Bước cuối cùng – Kiểm thử: sau khi xác định giao diện đơn vị - môi trường, tất cả các phương thức và các thuộc tính liên quan trực tiếp đến các hành vi mà đơn vị và môi trường tác động qua lại lên nhau đã được chỉ rõ Sử dụng các kỹ thuật kiểm thử đơn vị như kiểm thử luồng điều khiển, kiểm thử luồng dữ liệu, tiến hành kiểm thử các thuộc tính và các phương thức trên Kết quả chính xác của các thuộc tính và các phương thức đó đảm bảo chức năng ban đầu cần kiểm thử sẽ hoạt động đúng trên toàn hệ thống

Như vậy, với việc sử dụng kỹ thuật kiểm thử đơn vị, ta đã kiểm thử được chức năng cần kiểm thử trên toàn bộ hệ thống mà không cần sử dụng kỹ thuật kiểm thử tích hợp Trong luận văn này, ta gọi kỹ thuật kiểm thử được trình bày ở trên là kỹ thuật kiểm thử sinh môi trường

1.3 Cấu trúc luận văn

Để trình bầy cụ thể các kiến thức, giúp người đọc tiếp cận phương pháp kiểm thử sinh môi trường nêu trên, nội dung của luận văn được tập trung vào ba chương

Những kiến thức nền tảng về kiểm thử được trình bày trong chương 2 -

Kiến thức nền tảng Đó là những kiến thức về kiểm thử đơn vị, phân tích tĩnh,

các kỹ thuật kiểm thử đơn vị bao gồm kiểm thử luồng điều khiển và kiểm thử luồng dữ liệu Những kiến thức này hỗ trợ người đọc trong việc tiếp cận và hiểu vấn đề mà luận văn nghiên cứu

Lý thuyết chính của luận văn được đề cập ở chương 3 – Kiểm thử sinh môi trường Chương 3 trình bày cơ sở lý thuyết và các thuật toán được sử dụng trong

kỹ thuật kiểm thử sinh môi trường Kỹ thuật bao gồm các bước: xác định đơn vị, xác định giao diện đơn vị - môi trường, áp dụng kỹ thuật kiểm thử đơn vị để kiểm thử chức năng cần kiểm thử

Trang 7

Việc áp dụng kỹ thuật kiểm thử sinh môi trường với một ví dụ cụ thể được

trình bầy trong chương 4 - Ứng dụng Chương 4 giới thiệu khái quát về phần

mềm kiểm tra trực tuyến - TestSystem Sau đó trình bày từng bước áp dụng kỹ thuật kiểm thử sinh môi trường để kiểm thử chức năng đăng nhập (login) trong

hệ thống TestSystem

Trang 8

CHƯƠNG 2 - KIẾN THỨC NỀN TẢNG 2.1 Kiểm thử đơn vị

Kiểm thử đơn vị là kỹ thuật kiểm thử trên một đơn vị của chương trình Đơn vị

đó được kiểm thử trong sự cô lập với toàn bộ chương trình Người ta thường hiểu với nghĩa rằng, một đơn vị là một hàm, một thủ tục, một phương thức hay một lớp [1]

Với định nghĩa như trên, ta thấy rằng đơn vị có kích thước nhỏ, lập trình viên tạo ra đơn vị để đảm bảo một chức năng nhỏ nào đó cho hệ thống Vì vậy việc kiểm thử đơn vị thường dễ dàng tiến hành Với phạm vi cần kiểm thử không lớn, nếu phát hiện ra lỗi trong quá trình kiểm thử thì cũng dễ dàng tìm ra nguyên nhân và khắc phục lỗi Việc tiến hành kiểm thử đơn vị đòi hỏi người kiểm thử phải hiểu cặn kẽ về mã chương trình nên quá trình kiểm thử đơn vị thường được thực hiện bởi chính lập trình viên viết chương trình đó

Kiểm thử đơn vị là mức kiểm thử thấp nhất nhưng nó có ý nghĩa rất quan trọng Việc đảm bảo các đơn vị chạy chính xác sẽ làm giảm thời gian và chi phí rất nhiều cho các mức kiểm thử cao hơn về sau Quá trình kiểm thử đơn vị nên được tiến hành sớm nhất có thể ngay trong giai đoạn viết mã và cần được thực hiện thường xuyên và xuyên suốt cả chu kỳ phát triển phần mềm

Mục đích của kiểm thử đơn vị là đảm bảo với một dữ liệu đầu vào, đơn vị phải cho ra kết quả chính xác Việc kiểm thử đòi hỏi chúng ta phải kiểm tra tất

cả các đường thi hành trong đơn vị để phát hiện đường phát sinh lỗi Để tiến hành kiểm thử, đòi hỏi phải có một bộ các ca kiểm thử hoặc kịch bản kiểm thử chỉ rõ dữ liệu đầu vào và đầu ra mong muốn Trong quá trình kiểm thử, với mỗi

dữ liệu đầu vào, ta so sánh dữ liệu đầu ra thực, so sánh nó với đầu ra mong muốn để viết báo cáo kiểm thử [6]

Kiểm thử đơn vị bao gồm hai hướng: Kiểm thử tĩnh và kiểm thử động Trong đó, kiểm thử tĩnh là kiểm thử dựa trên phân tích tất cả các hành vi có thể xẩy ra khi chạy chương trình mà không chạy chương trình thực sự Còn kiểm thử động là kiểm thử dựa trên việc chạy chương trình và phân tích kết quả đầu ra

2.2 Phân tích tĩnh

Phân tích tĩnh là một kỹ thuật đòi hỏi duyệt lại các tài liệu về yêu cầu, đặc tả, thiết kế và mã chương trình để kiểm tra tính logic của chương trình, mà không chạy chương trình [1]

Trang 9

Người ta thường ứng dụng phân tích tĩnh trong việc tối ưu mã hoặc kiểm thử chương trình Ví dụ như ứng dụng để loại bỏ các đoạn mã dư thừa, sử dụng tài nguyên tối ưu, phát hiện các lỗi tiềm năng,… Phân tích tĩnh được ứng dụng trong kiểm thử, đảm bảo thông tin nó thu thập được cần phải đúng với tất cả các đường thi hành chương trình và với các đầu vào có thể [3]

Trong kiểm thử, phân tích tĩnh thường được dùng như kỹ thuật bổ xung cho các kỹ thuật kiểm thử truyền thống và kiểm tra mô hình Do sự không chính xác của phân tích, phân tích tĩnh ít chính xác hơn, nhưng nói chung có khả năng mở rộng tốt hơn kiểm tra mô hình Có một số phương pháp tiếp cận phân tích tĩnh, trong đó có Phân tích lưu lượng dữ liệu và Diễn giải tóm tắt Những phương pháp tiếp cận như vậy đòi hỏi người thiết kế phân tích phải quyết định trên một trạng thái cân bằng chi phí và độ chính xác: các phân tích càng chính xác, chi phí càng tốn kém [4]

2.3 Kiểm thử luồng điều khiển

Kiểm thử luồng điều khiển là một kỹ thuật kiểm thử đơn vị do Tom McCabe đề xuất Để tiếp cận với kiểm thử luồng điều khiển, ta cần làm quen với các khái niệm được trích dẫn trong tài liệu [2] được đề cập sau đây

Định nghĩa 1

Đường thi hành (execution path) được định nghĩa là một danh sách có thứ

tự các lệnh trong chương trình được thực thi trong 1 lần chạy, bắt đầu từ điểm nhập đến điểm kết thúc của đơn vị

Định nghĩa 2

Đồ thị luồng điều khiển là một công cụ hữu ích để hiển thị luồng điều khiển trong chương trình Trong đó các nút hình tròn biểu thị các đoạn câu lệnh trong chương trình, các cung biểu thị luồng điều khiển

Các thành phần cơ bản của đồ thị luồng điều khiển được biểu diễn trong hình 2.1 Các nút tròn màu trắng biểu thị các đoạn câu lệnh, các nút hình bầu dục màu đen biểu thị điểm bắt đầu hoặc điểm kết thúc chương trình Các mũi tên

đi đến và đi ra khỏi nút biểu thị luồng điều khiển của chương trình tại nút đó Điểm xuất phát không có luồng đi đến và chỉ có một luồng đi ra Điểm kết thúc không có luồng đi ra Khối xử lý bình thường có một luồng đi đến và một luồng

đi ra Tại điểm quyết định sẽ có nhiều nhánh rẽ ra từ nó Ngược lại, điểm nối có nhiều luồng đi đến và một luồng đi ra

Trang 10

Hình 2.1 Các thành phần cơ bản của đồ thị luồng điều khiển [2]

Các cấu trúc điều khiển phổ dụng của đồ thị luồng điều khiển được minh họa trong hình 2.2 Với cấu trúc tuần tự, tại mỗi điểm của đồ thị sẽ có một luồng

đi đến và một luồng đi ra Khác với cấu trúc if, tại điểm quyết định chỉ có hai luồng đi ra, tại điểm quyết định của cấu trúc switch có thể có ba hay nhiều hơn

ba luồng đi ra Trong cấu trúc lặp, điểm quyết định được biểu diễn là điểm có hai luồng rẽ nhánh ra từ nó Với cấu trúc lặp while…do, điểm quyết định được đặt trước thân vòng lặp, nếu điều kiện không thỏa mãn, sẽ thoát ra khỏi vòng lặp Ngược lại, trong cấu trúc do…while, điểm quyết định được đặt sau thân vòng lặp, thực hiện thân vòng lặp, sau đó kiểm tra điều kiện, điều kiện đúng thì quay lại thực hiện thân vòng lặp Lặp lại cho đến khi điều kiện sai thì thoát khỏi vòng lặp

Hình 2.2 Các cấu trúc điều khiển phổ dụng [2]

Định nghĩa 3

Độ phức tạp Cyclomatic C được định nghĩa là một thước đo phần mềm, đo

độ phức tạp của chương trình Trong ngữ cảnh kiểm thử luồng điều khiển, độ phức tạp cyclomatic cho biết số đường thi hành tuyến tính độc lập cơ bản của chương trình cần kiểm thử

Độ phức tạp Cyclomatic C = V(G) của đồ thị dòng điều khiển được tính bởi một trong các công thức sau :

o V(G) = E - N + 2, trong đó E là số cung, N là số nút của đồ thị

o V(G) = P + 1, nếu đồ thị chỉ chứa các nút quyết định luận lý (chỉ có 2 cung xuất đúng/sai) và P là số nút quyết định

Để xác định C đường tuyến tính độc lập, Tom McCabe đưa ra qui trình gồm các bước sau:

switch while c do do while c điểm xuất phát khối xử lý điểm quyết định điểm nối điểm kết thúc

Trang 11

o Xác định đường cơ bản, đường này nên là đường thi hành phố biến nhất

o Để chọn đường thứ 2, thay đổi cung xuất của nút quyết định đầu tiên và

cố gắng giữ lại tối đa phần còn lại

o Để chọn đường thứ 3, dùng đường cơ bản nhưng thay đổi cung xuất của nút quyết định thứ 2 và cố gắng giữ lại tối đa phần còn lại

o Tiếp tục thay đổi cung xuất cho từng nút quyết định trên đường cơ bản

để xác định đường thứ 4, 5, cho đến khi không còn nút quyết định nào trong đường cơ bản nữa

o Lặp dùng tuần tự các đường tìm được làm đường cơ bản để xác định các đường mới xung quanh nó y như các bước 2, 3, 4 cho đến khi không tìm được đường tuyến tính độc lập nào nữa

Mục tiêu của phương pháp kiểm thử luồng điều khiển là đảm bảo mọi đường thi hành của đơn vị phần mềm cần kiểm thử đều chạy đúng Nhưng trên thực tế, công sức và thời gian để đạt mục tiêu trên đây là rất lớn, ngay cả trên những đơn vị phần mềm nhỏ bởi có những chương trình có một số lượng quá lớn các đường thi hành hoặc khi chương trình được lập trình không tốt, bỏ sót một số đường thi hành Vì vậy, mục tiêu của kiểm thử là sử dụng tối thiểu các ca kiểm thử để kiểm thử mà có thể đem lại kết quả độ tin cậy tối đa [2] Để xác định độ tin cậy của kết quả, sử dụng khái niệm phủ kiểm thử (Coverage)

Định nghĩa 4

Phủ kiểm thử là tỉ lệ các thành phần thực sự được kiểm thử so với tổng thể sau khi đã kiểm thử các ca kiểm thử được chọn Phủ càng lớn thì độ tin cậy càng cao

o Phủ cấp 0: kiểm thử những gì có thể kiểm thử được, phần còn lại để người dùng phát hiện và báo lại sau Đây là mức độ kiểm thử không thực sự có trách nhiệm

o Phủ cấp 1: kiểm thử sao cho mỗi lệnh được thực thi ít nhất 1 lần, gọi là mức kiểm thử phủ các lệnh

o Phủ cấp 2: kiểm thử sao cho mỗi điểm quyết định đều được thực hiện ít nhất 1 lần cho trường hợp đúng lẫn sai Ta gọi mức kiểm thử này là phủ các nhánh (branch coverage) Phủ các nhánh đảm bảo phủ các lệnh

o Phủ cấp 3: kiểm thử sao cho mỗi điều kiện luận lý con (subcondition) của từng điểm quyết định đều được thực hiện ít nhất 1 lần cho trường hợp đúng lẫn sai Ta gọi mức kiểm thử này là phủ các điều kiện con

Trang 12

(subcondition coverage) Phủ các điều kiện con chưa chắc đảm bảo phủ các nhánh

o Phủ cấp 4: kiểm thử sao cho mỗi điều kiện luận lý con (subcondition) của từng điểm quyết định đều được thực hiện ít nhất 1 lần cho trường hợp đúng lẫn sai và điểm quyết định cũng được kiểm thử cho cả 2 nhánh Ta gọi mức kiểm thử này là phủ các nhánh và điều kiện con (branch & subcondition coverage)

Vậy để kiểm thử luồng điều khiển một đơn vị, lần lượt tiến hành các bước sau:

o Xác định phủ kiểm thử

o Xây dựng đồ thị luồng điều khiển dựa trên phủ kiểm thử

o Tính độ phức tạp Cyclomatic của đồ thị (=C)

o Xác định C đường thi hành tuyến tính cơ bản cần kiểm thử

o Tạo từng ca kiểm thử cho từng đường thi hành tuyến tính cơ bản

o Thực hiện kiểm thử trên từng ca kiểm thử

o So sánh kết quả có được với kết quả được kỳ vọng

o Lập báo cáo kết quả kiểm thử

2.4 Kiểm thử luồng dữ liệu

Kiểm thử luồng dữ liệu là một thuật ngữ mà hầu hết các nhà phát triển phần mềm khi đọc nó, ngay lập tức nghĩ sẽ liên quan đến sơ đồ dòng dữ liệu Kiểm thử luồng dữ liệu đề cập đến các hình thức của kiểm thử cấu trúc, nó tập trung vào các điểm mà tại đó các biến nhận giá trị hoặc giá trị của biến được sử dụng Chúng ta sẽ xem sét 2 hình thức chính của kiểm thử luồng dữ liệu, một là kiểm thử theo định nghĩa – sử dụng và dạng hai được dựa trên khái niệm được gọi là

“lát cắt chương trình” (program slide), gọi là kiểm thử dựa trên lát cắt [5] Hầu hết các chương trình phân phối các chức năng theo dữ liệu Biến đại diện cho một loại dữ liệu nào đấy và được nhận các giá trị (value), các giá trị này được sử dụng để tính toán ra giá trị cho biến khác Từ những năm đầu thập niên 1960, nhiều lập trình viên đã phân tích mã nguồn tại các điểm mà tại đó các biến được nhận giá trị hay giá trị của biến được sử dụng Ngày nay, những ý tưởng trên cũng đã được vận dụng trong kiểm thử định nghĩa – sử dụng

Trang 13

2.4.1 Kiểm thử định nghĩa - sử dụng (Define/Use Testing)

Để tiếp cận với lý thuyết kiểm thử định nghĩa – sử dụng, cần làm quen với một

số định nghĩa về nút định nghĩa, nút sử dụng, đường định nghĩa – sử dụng,… Nội dung các định nghĩa này được trích dẫn từ tài liệu [5]

Những định nghĩa này áp dụng cho một chương trình P P có đồ thị chương trình G(P) và có tập hợp các biến chương trình V G(P) được thiết lập với các nút là các đoạn chương trình, các cạnh biểu diễn thứ tự các nút G(P) có một nút đơn đầu vào và một nút đơn đầu ra Trong G(P) không cho phép một cạnh bắt đầu từ một nút và kết thúc tại chính nút đó

Với chương trình P được xác định như trên, ta có các định nghĩa sau đây:

Định nghĩa 1

Nút n G(P) là nút định nghĩa (defining node) của biến v V, ký hiệu là

DEF(v,n), nếu giá trị của biến v được định nghĩa tại đoạn câu lệnh tương ứng với nút n

Những câu lệnh nhập, câu lệnh gán, câu lệnh điều khiển vòng lặp, lời gọi thủ tục là tất cả các ví dụ của các lệnh tương ứng với nút định nghĩa Khi các câu lệnh như trên được thực thi, giá trị lưu trữ trong bộ nhớ tương ứng với biến sẽ được thay đổi

Định nghĩa 2

Nút n G(P) là nút sử dụng (usage node) của biến v V, ký hiệu là

USE(v,n), nếu giá trị của biến v được sử dụng tại đoạn câu lệnh tương ứng với nút n

Những câu lệnh xuất, lệnh gán, lệnh điều kiện, lệnh điều khiển vòng lặp, lời gọi thủ tục là tất cả các ví dụ của các lệnh tương ứng với nút sử dụng Khi các câu lệnh như trên được thực thi, giá trị lưu trữ trong bộ nhớ tương ứng với biến

sẽ không bị thay đổi

Định nghĩa 3

Một nút sử dụng USE(v,n) là một sử dụng mệnh đề (predicate use) (kí hiệu

là P-use) nếu câu lệnh n là một câu lệnh mệnh đề, trong trường hợp ngược lại

USE(v,n) là một sử dụng tính toán (kí hiệu là C-use)

Những nút tương ứng với sử dụng mệnh đề luôn luôn có bậc xuất (outdegree) ≥ 2, còn những nút tương ứng với sử dụng tính toán luôn luôn có bậc xuất ≤ 1

Trang 14

Định nghĩa 4

Đường định nghĩa – sử dụng (definition-use path) với một biến v là một

đường thuộc PATHS(P) với m và n lần lượt là các nút bắt đầu và nút kết thúc, trong đó DEF(v, m) và USE(v,n) lần lượt là các nút định nghĩa và nút sử dụng của biến v

Định nghĩa 5

Đường định nghĩa - rõ ràng (definition-clear path) với một biến v là

đường định nghĩa - sử dụng trong PATHS(P) với nút bắt đầu và kết thúc là DEF(v,m) và USE(v,n), đồng thời không tồn tại một nút định nghĩa khác của biến v trong đường

Người kiểm thử cần chú ý đến những định nghĩa trên để nắm bắt được bản chất quá trình tính toán với các giá trị dữ liệu lưu trữ Định nghĩa - sử dụng và định nghĩa - rõ ràng mô tả dòng dữ liệu của đoạn mã nguồn từ điểm giá trị được định nghĩa đến điểm giá trị được sử dụng Những đường định nghĩa - sử dụng

mà không phải là định nghĩa - rõ ràng sẽ chứa đựng nhiều điểm rắc rối

Vậy để kiểm thử định nghĩa – sử dụng một chương trình, cần tiến hành kiểm thử theo quy trình sau :

o Xác định phủ kiểm thử

o Xây dựng đồ thị chương trình dựa trên phủ kiểm thử

o Tìm các đường định nghĩa - sử dụng với mỗi biến

o Với mỗi đường định nghĩa - sử dụng, có thể xây dựng một ca kiểm thử

đi qua các câu lệnh trong đường đó để kiểm thử dòng dữ liệu của biến

2.4.2 Kiểm thử dựa trên lát cắt (Slice-based testing)

Khái niệm lát cắt chương trình được đề cập đến trong tài liệu kỹ sư phần mềm từ những năm đầu 1980 Khái niệm về lát cắt chương trình được trình bày rất tự nhiên, rõ ràng trực quan Một lát cắt chương trình là một tập hợp các câu lệnh, những câu lệnh mà góp phần tạo ra hoặc ảnh hưởng đến giá trị của một biến tại một số điểm trong chương trình [5]

Bắt đầu bằng việc phát triển định nghĩa về lát cắt chương trình Tiếp tục sử dụng những ký hiệu dùng cho các đường định nghĩa - sử dụng ở mục 2.4.1: Một chương trình P có đồ thị chương trình G(P), và có tập hợp các biến chương trình

V Đầu tiên cần xem xét các định nghĩa để cho phép chuyển đổi các nút trong G(P) thành các đoạn câu lệnh

Trang 15

Định nghĩa 1

Cho một chương trình P, và một tập V các biến trong P Một lát cắt trên tập các biến V tại câu lệnh n, ký hiệu là S(V,n), là tập hợp tất cả các câu lệnh trong

P mà có đóng góp tạo ra các giá trị của các biến trong V

Liệt kê các phần tử của lát cắt S(V,n) sẽ rất rườm rà, bởi vì các phần tử đó chính là các đoạn mã chương trình Sẽ đơn giản hơn nhiều nếu liệt kê số các đoạn trong G(P) [5]

Định nghĩa 2

Cho một chương trình P, một đồ thị chương trình G(P), trong đó các câu lệnh và các đoạn câu lệnh được đánh số, và tập hợp V các biến của P Một lát cắt trên tập các biến V tại đoạn câu lệnh n, ký hiệu là S(V,n), là tập hợp số của các nút của tất cả các đoạn câu lệnh trong P đứng trước n mà có đóng góp tạo ra các giá trị của các biến trong V tại đoạn câu lệnh n [5]

Ý tưởng của các lát cắt khi chia chương trình thành các phần nhỏ có một số

ý nghĩa sau Trước tiên, cần thảo luận về hai phần của định nghĩa Cụm từ “đứng trước” mang ý nghĩa động, như vậy một lát cắt nắm bắt được thời gian thực thi các hành động của chương trình tương ứng với các biến trong lát cắt Cuối cùng

sẽ phát triển một mạch (đồ thị hở, có hướng) của các lát cắt, trong đó, các nút là các lát cắt, các cạnh tương ứng với các mối quan hệ tập hợp con

Về việc sử dụng các biến, chúng ta có thể chia mối quan hệ sử dụng (use) thành năm hình thức sau:

o P-use: sử dụng trong một quyết định (predicate)

o C-use: sử dụng trong tính toán

o O-use: sử dụng cho xuất dữ liệu

o L-use: sử dụng cho việc đặt vị trí (con trỏ, subscript)

o I-use: việc lặp (biến đếm, chỉ số vòng lặp)

Theo đó, chúng ta xác định hai hình thức của các nút định nghĩa:

o I-def: định nghĩa bằng việc nhập dữ liệu

o A-def: định nghĩa bằng việc gán

Giả sử lát cắt S(V,n) là một lát cắt trên một biến, nghĩa là, tập V chỉ chứa một biến v Nếu đoạn lệnh n là một nút định nghĩa của biến v, thì n được bao gồm trong lát cắt Nếu đoạn lệnh n là một nút sử dụng của biến v, thì n không được bao gồm trong lát cắt P-use và C-use của các biến khác (không phải là v)

Trang 16

được bao gồm nếu việc thực thi chúng ảnh hưởng đến giá trị của biến v Theo như hướng dẫn, nếu giá trị của biến v là như nhau thì đoạn câu lệnh được loại bỏ khỏi lát cắt

Như vậy để cung cấp kiến thức nền tảng chuẩn bị cho nghiên cứu chuyên sâu vào luận văn, luận văn đã lần lượt tìm hiểu về kiểm thử đơn vị, phân tích tĩnh, kiểm thử luồng điều khiển và hai dạng của kiểm thử luồng dữ liệu là kiểm thử định nghĩa – sử dụng và kiểm thử dựa trên lát cắt

Trang 17

CHƯƠNG 3 - KỸ THUẬT KIỂM THỬ SINH MÔI TRƯỜNG

Chương 3 trình bầy từng bước để tạo ra môi trường cho đơn vị và tiến hành kiểm thử đơn vị trong sự tương tác với môi trường Quá trình bao gồm các bước: chọn chức năng cần kiểm thử; xác định đơn vị gồm những lớp nào; xác định giao diện đơn vị - môi trường; liệt kê các hành vi của môi trường lên đơn vị và các hành vi của đơn vị lên môi trường; cuối cùng, tiến hành kiểm thử trên tổng thể mối quan hệ đơn vị và môi trường để đảm bảo tính đúng đắn của chức năng

3.1 Chọn chức năng cần kiểm thử và xác định đơn vị

Những phần mềm lớn có rất nhiều lớp và nhiều chức năng Cần xác định chức năng cần kiểm thử Sử dụng kỹ thuật phân tích tĩnh, cùng các tài liệu về đặc tả, thiết kế, mã chương trình, xác định những lớp nào quyết định trực tiếp đến chức năng này Tập hợp những lớp đó gọi là đơn vị, những lớp này được gọi là các lớp của đơn vị

3.2 Xác định giao diện đơn vị - môi trường

Nội dung bước hai là tìm hiểu cấu trúc của giao diện đơn vị – môi trường Giao diện của đơn vị - môi trường gồm hai phần: giao diện đơn vị và giao diện môi trường Giao diện đơn vị thể hiện các hành vi mà môi trường có thể thực thi trên đơn vị Ngược lại, giao diện môi trường thể hiện các hành vi mà đơn vị tác động lên môi trường

Giao diện đơn vị: Bằng việc sử dụng kỹ thuật phân tích tĩnh, lần lượt phân

tích các lớp của đơn vị Giao diện đơn vị bao gồm tất cả các phương thức và các thuộc tính công khai (public) của các lớp trong đơn vị, cái mà có thể bị tham chiếu bởi môi trường Nghĩa là, lần lượt xét các thuộc tính và các phương thức của các lớp trong đơn vị, xem những thuộc tính, phương thức nào được gọi đến bởi môi trường, hay bị thay đổi giá trị bởi môi trường

Để xác định giao diện đơn vị ta sử dụng thuật toán 3.1 Thuật toán với đầu vào là tập hợp các lớp của đơn vị, kí hiệu là U Đầu ra thu được sau khi thực hiện thuật toán là tập hợp một số phương thức và một số thuộc tính của đơn vị tạo nên giao diện đơn vị, kí hiệu là S Ban đầu, giao diện đơn vị S được khởi tạo bằng rỗng, nghĩa là không chứa thuộc tính hay phương thức nào Lần lượt xét tất

cả các lớp trong đơn vị, lớp được xét kí hiệu là u (dòng 1) Với mỗi lớp u, xét tất

cả các phương thức của lớp, phương thức được xét kí hiệu là m (dòng 2) Kiểm tra xem m có được thi hành bởi driver của đơn vị hay không (dòng 3) Nghĩa là, tìm xem phương thức nào của u được gọi bởi các lớp ngoài đơn vị Nếu phương thức m đó được gọi bởi các lớp ngoài đơn vị, thêm phương thức m vào giao diện

Trang 18

đơn vị S (dòng 4) Tương tự, xét tất cả các thuộc tính của lớp u, thuộc tính đang xét được kí hiệu là f (dòng 7) Kiểm tra xem thuộc tính f có được sử dụng bởi các lớp ngoài đơn vị hay không (dòng 8) Nếu thuộc tính f được sử dụng bởi các lớp ngoài đơn vị, thêm thuộc tính đó vào giao diện đơn vị S (dòng 9) Trong thuật toán, việc kiểm tra phương thức và thuộc tính có được sử dụng bởi các lớp ngoài đơn vị hay không được minh họa bằng hàm isRevelant (dòng 3, 8)

Thuật toán 3.1 – Thuật toán xác định giao diện đơn vị [5]

Đầu vào: U: tập hợp các lớp của đơn vị

Đầu ra: S: tập hợp các phương thức và các thuộc tính của đơn vị

Khởi tạo: S =

1: for each class u U do

2: for each method m u.getMethods() do

Ví dụ 3.1 Minh họa thuật toán xác định giao diện đơn vị

Giả sử đơn vị chỉ bao gồm một lớp Login thuộc không gian tên TestSystem.Models Xét tất cả các thuộc tính và phương thức của lớp Login, nhận thấy chỉ có thuộc tính TypeUser bị gọi bởi phương thức Page_Load của lớp Default thuộc không gian tên TestSystem, thuộc tính IDTeacher, IDStudent

bị gọi bởi phương thức Logout_bt_Click của lớp Logout thuộc không gian tên TestSystem.Models, thuộc tính countAdmin, countTeacher, countStudent, countAnonymous bị gọi bởi phương thức Page_Load của lớp Status thuộc không gian tên TestSystem.Models (xem hình 3.1) Như vậy giao diện đơn vị bao gồm các thuộc tính TypeUser, IDTeacher, IDStudent, countAdmin,

countTeacher, countStudent, countAnonymous của lớp Login

Trang 19

Hình 3.1 Minh họa thuật toán giao diện đơn vị

Giao diện môi trường: Để xác định giao diện môi trường, phân tích lần

lượt các phương thức và các thuộc tính trong từng lớp của đơn vị, tìm kiếm các liên kết ngoài mà các lớp của đơn vị tham chiếu đến Sử dụng thuật toán 3.2 để xác định giao diện môi trường Đầu vào của thuật toán là tập hợp các lớp của đơn vị, kí hiệu là U, và đồ thị biểu diễn việc các phương thức của chương trình gọi lẫn nhau, kí hiệu là CG Đầu ra thu được sau khi thực hiện thuật toán là tập hợp một số các thuộc tính, các phương thức của môi trường tạo nên giao diện môi trường, kí hiệu là E Ban đầu, giao diện môi trường E được khởi tạo bằng rỗng, nghĩa là không chứa thuộc tính hay phương thức nào Lần lượt xét tất cả các lớp trong đơn vị, lớp được xét kí hiệu là u (dòng 1) Với mỗi lớp u, xét lớp cha của nó có nằm ngoài đơn vị hay không, nếu nó nằm ngoài và chưa có trong môi trường thì thêm nó vào môi trường (dòng 2) Xét tất cả các giao diện (interface) của u (dòng 3), kiểm tra xem giao diện đó có nằm ngoài đơn vị hay không, nếu nó nằm ngoài và chưa có trong môi trường thì thêm nó vào môi

trường (dòng 4) Công việc kiểm tra trên được minh họa bởi hàm envCheck

trong thuật toán Với mỗi lớp u, xét tất cả các thuộc tính trong lớp, thuộc tính được xét kí hiệu là f (dòng 6) Kiểm tra xem kiểu của f có nằm ngoài đơn vị hay không, nếu nó nằm ngoài và chưa có trong môi trường thì thêm nó vào môi trường (dòng 7) Với mỗi lớp u, xét tất cả các phương thức trong lớp, phương

thức được xét kí hiệu là m (dòng 9) Dùng hàm envCheckSignature kiểm tra

xem kiểu của giá trị trả về của m có nằm ngoài đơn vị hay không, nếu nó nằm ngoài và chưa có trong môi trường thì thêm nó vào môi trường (dòng 10) Lần lượt xét tất cả các biến cục bộ của m, biến đang xét kí hiệu là l (dòng 11), kiểm tra xem kiểu của l có nằm ngoài đơn vị hay không, nếu nó nằm ngoài và chưa có

Trang 20

trong môi trường thì thêm nó vào môi trường (dòng 12) Với mỗi câu lệnh s trong phương thức m (dòng 14), kiểm tra xem nó có gọi đến phương thức nào ngoài đơn vị hay không Nếu trong câu lệnh có lời gọi hàm (dòng 15), tìm xem hàm đó là hàm nào, kí hiệu là hàm m’ (dòng 16), kiểm tra xem kiểu của giá trị trả về của m’ có nằm ngoài đơn vị hay không, nếu nó nằm ngoài và chưa có trong môi trường thì thêm nó vào môi trường (dòng 17), tìm xem m’ thuộc lớp nào, kí hiệu là lớp D (dòng 18) Kiểm tra xem lớp D có nằm ngoài đơn vị không

và nếu lớp D nằm ngoài và chưa được thêm vào môi trường thì thêm lớp D vào môi trường (dòng 19), đồng thời thêm hàm m’ vào giao diện môi trường E (dòng 20) Tương tự, nếu trong câu lệnh s có gọi đến một trường nào đó (dòng 23), tìm xem thuộc tính đó là thuộc tính nào, kí hiệu là thuộc tính f (dòng 24), tìm xem thuộc tính f thuộc lớp nào, kí hiệu là lớp D (dòng 25) Kiểm tra xem lớp D có nằm ngoài đơn vị không và nếu lớp D nằm ngoài và chưa được thêm vào môi trường thì thêm lớp D vào môi trường (dòng 26), đồng thời thêm thuộc tính f vào giao diện môi trường E (dòng 27)

Thuật toán 3.2: Thuật toán xác định giao diện môi trường [5]

Đầu vào: U: tập hợp các lớp của đơn vị, CG: đồ thị gọi các phương thức Đầu ra: E: tập hợp các lớp, các phương thức, các thuộc tính của môi

Trang 21

Ví dụ 3.2 Minh họa thuật toán xác định giao diện môi trường

Sử dụng ví dụ 3.1, xác định giao diện môi trường Đơn vị chỉ bao gồm một lớp Login thuộc không gian tên TestSystem.Models Xét tất cả các phương thức của lớp Login, xét từng câu lệnh trong từng phương thức, nhận thấy phương thức Login_bt_Click của lớp Login gọi đến các phương thức GetStudent của lớp Student và phương thức GetDataTeacher của lớp Teacher; phương thứ PrLogin của lớp Login gọi đến các phương thức SetPresenceStudent của lớp Student và phương thức SetPresenceTeacher của lớp Teacher (xem hình 3.2) Như vậy giao diện môi trường trong ví dụ bao gồm các phương thức GetStudent, SetPresenceStudent của lớp Student và các phương thức GetDataTeacher, SetPresenceTeacher của lớp Teacher

Hình 3.2 Minh họa thuật toán xác định giao diện môi trường

Trang 22

Kết quả giao diện đơn vị - môi trường tổng hợp được từ hai ví dụ 3.1 và 3.2 bao gồm các thuộc tính TypeUser, IDTeacher, IDStudent, countAdmin, countTeacher, countStudent, countAnonymous của lớp Login và các phương thức GetStudent, SetPresenceStudent của lớp Student và các phương thức GetDataTeacher, SetPresenceTeacher của lớp Teacher

Đơn vị và môi trường có thể tác động qua lại với nhau về mặt điều khiển hay về mặt dữ liệu Môi trường có thể làm thay đổi giá trị của thuộc tính hay gọi đến một thuộc tính, một phương thức nào đó của đơn vị Tập hợp những hành vi

đó gọi là những hành vi mà môi trường tác động lên đơn vị Sử dụng giao diện đơn vị được xác định ở trên, có thể liệt kê ra những hành vi của môi trường tác động lên đơn vị, từ đó đòi hỏi cần tiến hành kiểm thử những thuộc tính, phương thức nào của đơn vị để đảm bảo những hành vi mà môi trường tác động lên đơn

vị có kết quả chính xác

Tương tự, đơn vị cũng có thể có những tác động ngược lại lên môi trường, chẳng hạn như đơn vị có thể sử dụng một phương thức hay thuộc tính nào đó nằm ngoài đơn vị Những hành vi đó gọi là hành vi của đơn vị tác động lên môi trường Với kết quả tìm được giao diện môi trường ở trên, có thể xác định được cần tiến hành kiểm thử những phương thức, thuộc tính nào ở những lớp nào để đảm bảo tính đúng đắn của những hành vi mà đơn vị tác động lên môi trường Sau khi xác định được những hành vi của đơn vị và môi trường tác động lên nhau, sử dụng những kỹ thuật kiểm thử đơn vị để kiểm thử, đảm bảo chức năng được thỏa mãn

3.3 Kiểm thử

Để kiểm tra tính đúng đắn của chức năng, có thể sử dụng các kỹ thuật kiểm thử luồng điều khiển và kiểm thử luồng dữ liệu đã trình bày ở mục 2.3 và 2.4 để kiểm thử các thuộc tính và phương thức liên quan đến các hành vi mà đơn vị và môi trường tác động qua lại lên nhau

Áp dụng các kỹ thuật kiểm thử đơn vị để kiểm thử các thuộc tính và phương thức của giao diện đơn vị - môi trường Sau khi phân tích, ta có một hệ thống các ca kiểm thử (testcase) Chạy chương trình với các ca kiểm thử, theo dõi kết quả, so sánh với kết quả kỳ vọng của từng ca kiểm thử, lập báo cáo quá trình kiểm thử Quá trình kiểm thử cho kết quả chính xác sẽ đảm bảo chức năng hoạt động đúng trên toàn hệ thống

Trang 23

CHƯƠNG 4 - ỨNG DỤNG

Nội dung chương 4 bao gồm giới thiệu khái quát về hệ thống TestSystem – một

hệ thống kiểm tra trực tuyến đơn giản do tác giả xây dựng, đồng thời trình bày việc áp dụng các bước của kỹ thuật kiểm thử sinh môi trường để kiểm thử chức năng đăng nhập của hệ thống TestSystem

4.1 Giới thiệu hệ thống TestSystem

Hệ thống TestSystem cho phép người dùng thực hiện và quản lý các bài trắc nghiệm trực tuyến qua mạng của một trường đại học Hệ thống được viết trên C# và ASP.NET Cơ sở dữ liệu được thiết kế trên Microsoft Access Hệ thống bao gồm 3 loại người dùng: sinh viên, giáo viên và người quản trị Khi vừa truy nhập hệ thống, người dùng được coi như là khách Khách chỉ có quyền xem giới thiệu chung về hệ thống mà không có chức năng, thao tác nào với hệ thống và

cơ sở dữ liệu Để có thể thực hiện các chức năng cho phép của mình, người dùng khi truy cập vào hệ thống cần phải đăng nhập với tên (user) và mật khẩu (password) đã được đăng ký trước Hệ thống sẽ thường xuyên thống kê số khách

và các loại người dùng hiện đang truy cập hệ thống Các chức năng của từng loại người dùng đối với hệ thống bao gồm như sau:

Chức năng của sinh viên

Sinh viên có thể thực hiện bài trắc nghiệm bằng cách thực hiện quy trình như sau:

o Đăng nhập với tên và mật khẩu của sinh viên

o Lựa chọn cấp độ bài trắc nghiệm

o Lựa chọn môn học

o Hệ thống chọn ngẫu nhiên một số câu hỏi trong ngân hàng câu hỏi

o Sinh viên thực hiện bài trắc nghiệm, hoàn thành trong thời gian quy định

o Khi kết thúc bài làm, hệ thống thông báo kết quả

Ngoài ra, sinh viên còn có chức năng xem lại điểm của tất cả các bài trắc nghiệm mình đã làm

Chức năng của giáo viên

Giáo viên sau khi đăng nhập hệ thống, có quyền quản lý đề thi, quản lý câu hỏi và xem điểm của sinh viên Giáo viên có quyền tạo, sửa, xóa đề của những môn mình có tham gia làm câu hỏi Có quyền thêm, xóa, sửa các câu hỏi của

một môn nào đó

Ngày đăng: 25/03/2015, 09:47

TỪ KHÓA LIÊN QUAN

TRÍCH ĐOẠN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

w