Tải bản đầy đủ (.pdf) (78 trang)

Các kỹ thuật kiểm thử đột biến và ứng dụng kiểm thử chương trình C (luận văn thạc sĩ)

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.76 MB, 78 trang )

ĐẠI HỌC QUỐC GIA HÀ NỘI
TRƢỜNG ĐẠI HỌC KHOA HỌC TỰ NHIÊN
----------

PHẠM THỊ MIÊN

CÁC KỸ THUẬT KIỂM THỬ ĐỘT BIẾN VÀ ỨNG DỤNG
KIỂM THỬ CHƢƠNG TRÌNH C

LUẬN VĂN THẠC SĨ KHOA HỌC

Hà Nội - 2014


MỤC LỤC
LỜI CẢM ƠN ........................................................................................................................ 1
MỤC LỤC ............................................................................................................................. 2
I) DANH MỤC CÁC TỪ VIẾT TẮT, THUẬT NGỮ ........ Error! Bookmark not defined.
II) DANH MỤC CÁC BẢNG BIỂU ................................... Error! Bookmark not defined.
III) DANH MỤC CÁC HÌNH VẼ ....................................... Error! Bookmark not defined.
LỜI MỞ ĐẦU ........................................................................................................................ 5
CHƢƠNG 1 – KHÁI QUÁT VỀ KIỂM THỬ PHẦN MỀM............................................... 8
1.1. Khái niệm .................................................................................................................... 8
1.2. Các cấp độ kiểm thử phần mềm .................................................................................. 8
1.2.1. Kiểm thử đơn vị (Unit Test) ................................................................................ 9
1.2.2. Kiểm thử tích hợp (Integration Test) ................................................................... 9
1.2.3. Kiểm thử hệ thống (System Test) ...................................................................... 10
1.2.4. Kiểm thử chấp nhận sản phẩm (Acceptance Test) ............................................. 12
1.3. Kỹ thuật kiểm thử phần mềm.................................................................................... 12
1.3.1. Kỹ thuật kiểm thử hộp đen (Black – box Testing)............................................. 12
1.3.1.1. Phân hoạch tƣơng đƣơng ............................................................................ 14


1.3.1.2. Phân tích giá trị biên ................................................................................... 16
1.3.2. Kỹ thuật kiểm thử hộp trắng (White – box Testing) .......................................... 17
1.3.2.1. Kiểm thử đƣờng dẫn cơ sở .......................................................................... 17
1.3.2.2. Kiểm thử cấu trúc điều khiển ...................................................................... 22
1.4.

Kết luận ................................................................................................................. 25

CHƢƠNG 2 – KỸ THUẬT KIỂM THỬ ĐỘT BIẾN......................................................... 26
2.1. Một số khái niệm ...................................................................................................... 26
2.1.1. Kiểm thử đột biến .............................................................................................. 26
2.1.2. Đột biến .............................................................................................................. 26
2.1.3. Toán tử đột biến ................................................................................................. 29
2.2. Cơ sở của kiểm thử đột biến ..................................................................................... 29
2.3. Toán tử đột biến ........................................................................................................ 29
2.4. Quy trình kiểm thử đột biến ...................................................................................... 31
2.5. Hạn chế của kiểm thử đột biến ................................................................................. 33
2.6. Kết luận ..................................................................................................................... 34
2


CHƢƠNG 3 - MỘT SỐ CẢI TIẾN KỸ THUẬT KIỂM THỬ ĐỘT BIẾN ....................... 35
3.1. Giảm chi phí tính toán .............................................................................................. 35
3.1.1. Phƣơng pháp làm ít hơn (A “do fewer” approach) ............................................ 35
3.1.1.1. Lấy mẫu đột biến (Mutant Sampling) ........................................................ 36
3.1.1.2. Đột biến ràng buộc (Constrained Mutation) ............................................... 38
3.1.1.3. N - đột biến lựa chọn (N - Selective Mutation) ......................................... 39
3.1.2. Phƣơng pháp làm nhanh hơn (A “do smarter” approach) .................................. 41
3.1.2.1. Phƣơng pháp tạo lƣợc đồ đột biến .............................................................. 42
3.1.2.2. Đột biến yếu (Weak Mutation) ................................................................... 44

3.2. Tăng tự động hóa ...................................................................................................... 47
3.2.1. Tạo dữ liệu thử tự động...................................................................................... 47
3.2.2. Xác định các đột biến tƣơng đƣơng tự động ...................................................... 49
3.3.

Kết luận ................................................................................................................. 52

CHƢƠNG 4 - ỨNG DỤNG KỸ THUẬT KIỂM THỬ ĐỘT BIẾN ĐỂ KIỂM THỬ CÁC
CHƢƠNG TRÌNH C (C – Sharp)........................................................................................ 53
4.1. Tìm hiểu về NUnit .................................................................................................... 54
4.1.1. Định nghĩa .......................................................................................................... 54
4.1.2. Đặc điểm của NUnit........................................................................................... 54
4.1.3. Thuộc tính hay dùng trong thƣ viện NUnit.Framework .................................... 54
4.1.4. Phƣơng thức tĩnh hay dùng trong NUnit.Framework.Assert ............................. 56
4.1.5. Cài đặt NUnit ..................................................................................................... 58
4.1.6. Cách sử dụng NUnit........................................................................................... 60
4.2. Công cụ Nester.......................................................................................................... 68
4.2.1. Điều kiện tiên quyết ........................................................................................... 68
4.2.2. Giải pháp cho đột biến ....................................................................................... 68
4.2.3. Chạy Nester ........................................................................................................ 69
4.2.4. Lựa chọn Nester.exe.config .............................................................................. 71
4.3. Quy trình ứng dụng kiểm thử đột biến để kiểm thử các chƣơng trình C - Sharp ..... 71
4.3.1. Kiểm thử ............................................................................................................ 72
4.3.2. Tạo đột biến ....................................................................................................... 73
4.4. Kết luận ..................................................................................................................... 75
KẾT LUẬN.......................................................................................................................... 76
TÀI LIỆU THAM KHẢO ................................................................................................... 78
3



PHỤ LỤC 1 ......................................................................... Error! Bookmark not defined.
PHỤ LỤC 2 ......................................................................... Error! Bookmark not defined.
PHỤ LỤC 3 ......................................................................... Error! Bookmark not defined.
PHỤ LỤC 4 ......................................................................... Error! Bookmark not defined.
PHỤ LỤC 5 ......................................................................... Error! Bookmark not defined.

4


LỜI MỞ ĐẦU
Kiểm thử phần mềm là một hoạt động giữ vai trò rất quan trọng để bảo
đảm chất lƣợng phần mềm và là hoạt động mang tính sống còn trong các dự
án sản xuất hoặc gia công phần mềm. Vì vậy, kiểm thử phần mềm đã trở
thành qui trình bắt buộc trong các dự án phát triển phần mềm trên thế giới. Ở
Việt Nam, ngành công nghiệp phần mềm đang phát triển thì không thể xem
nhẹ việc kiểm thử phần mềm vì xác suất thất bại sẽ rất cao, hơn nữa, hầu hết
các công ty phần mềm có uy tín đều đặt ra yêu cầu nghiêm ngặt là nếu một
phần mềm không có tài liệu kiểm thử đi kèm thì sẽ không đƣợc chấp nhận.
Tuy nhiên, hoạt động kiểm thử thƣờng gặp nhiều khó khăn:
 Thứ nhất, kiểm thử các hệ thống phức tạp đòi hỏi rất nhiều nguồn
tài nguyên và chi phí cao.
 Thứ hai, tiến trình phát triển phần mềm luôn trải qua nhiều hoạt
động biến đổi thông tin, sự mất mát thông tin trong quá trình biến
đổi là yếu tố chính làm cho hoạt động kiểm thử khó khăn.
 Thứ ba, kiểm thử chƣa đƣợc chú trọng trong đào tạo con ngƣời.
 Cuối cùng, không tồn tại kỹ thuật kiểm thử cho phép khẳng định
một phần mềm hoàn toàn đúng đắn hay không chứa lỗi.
Với mục đích phát hiện lỗi, kiểm thử phần mềm thƣờng phải trải qua các
bƣớc: tạo dữ liệu thử, thực thi phần mềm trên dữ liệu thử và quan sát kết quả
nhận đƣợc. Trong các bƣớc này, bƣớc tạo dữ liệu đóng vai trò quan trọng nhất,

bởi vì chúng ta không thể tạo ra mọi dữ liệu từ miền vào của chƣơng trình, mà
chúng ta chỉ có thể tạo ra các dữ liệu thử có khả năng phát hiện lỗi cao nhất.
Vấn đề đặt ra là làm thế nào để đánh giá đƣợc khả năng phát hiện lỗi của một
bộ dữ liệu thử?
Một kinh nghiệm để giúp giải quyết vấn đề này, đó là sử dụng khái niệm
chất lượng bộ dữ liệu thử nhƣ là một phƣơng tiện để đánh giá bộ dữ liệu thử
nhƣ thế nào là “tốt” khi kiểm thử chƣơng trình. Ở đây, “tốt” đƣợc đánh giá
5


liên quan đến tiêu chuẩn chất lƣợng đƣợc định trƣớc, thƣờng là một số dấu
hiệu bao phủ chƣơng trình. Ví dụ, tiêu chuẩn bao phủ dòng lệnh đòi hỏi bộ dữ
liệu thử thực hiện mọi dòng lệnh trong chƣơng trình ít nhất một lần. Nếu bộ
dữ liệu thử đƣợc tìm thấy không chất lƣợng liên quan đến tiêu chuẩn (tức là
không phải tất cả các câu lệnh đều đƣợc thực hiện ít nhất một lần), thì kiểm
thử nữa là bắt buộc. Do đó, mục tiêu là tạo ra một tập các kiểm thử thực hiện
đầy đủ tiêu chuẩn chất lƣợng.
Tiêu chuẩn chất lƣợng tiêu biểu nhƣ bao phủ câu lệnh và kiểm thử quyết
định (thực hiện tất cả các đƣờng dẫn đúng và sai qua chƣơng trình) dựa vào
việc thực hiện chƣơng trình với số lƣợng kiểm thử tăng dần để nâng cao độ
tin cậy của chƣơng trình đó. Tuy nhiên, chúng không tập trung vào nguyên
nhân thất bại của chƣơng trình - đƣợc gọi là lỗi. Kiểm thử đột biến là một tiêu
chuẩn nhƣ vậy. Tiêu chuẩn này tạo ra các phiên bản của chƣơng trình có chứa
các lỗi đơn giản và sau đó tìm ra các kiểm thử để chỉ ra các dấu hiệu của lỗi.
Nếu có thể tìm thấy một bộ dữ liệu thử chất lƣợng làm lộ ra các dấu hiệu này
ở tất cả các phiên bản bị lỗi, thì sự tin tƣởng vào tính đúng đắn của chƣơng
trình sẽ tăng. Kiểm thử đột biến đã đƣợc áp dụng cho nhiều ngôn ngữ lập
trình nhƣ là một kỹ thuật kiểm thử hộp trắng.
Ý thức đƣợc đây là một lĩnh vực nghiên cứu có nhiều triển vọng ứng
dụng trong phát triển phần mềm, tôi đã chọn hƣớng nghiên cứu “ Các kỹ

thuật kiểm thử đột biến và ứng dụng kiểm thử chương trình C” cho đề tài
luận văn của mình.
Luận văn đƣợc tổ chức thành 4 chƣơng nhƣ sau:
 Chƣơng 1 – Trình bày khái quát về kiểm thử phần mềm nhƣ khái
niệm kiểm thử phần mềm, mục đích, mục tiêu và các mức kiểm thử
phần mềm. Chƣơng này cũng đề cập đến việc sử dụng các kỹ thuật
kiểm thử hộp trắng và hộp đen để thiết kế dữ liệu thử.

6


 Chƣơng 2 - Mô tả chi tiết các thành phần chính của kỹ thuật kiểm thử
đột biến, giới thiệu các giả thuyết cơ bản cần thiết để thực hiện
phƣơng pháp này. Chƣơng này còn cung cấp quy trình để phân tích
đột biến, từ đó rút ra đƣợc những vấn đề còn hạn chế đối với kỹ thuật
kiểm thử đột biến, đƣợc cải tiến ở chƣơng 3.
 Chƣơng 3 – Giới thiệu một số phƣơng pháp cải tiến kỹ thuật kiểm
thử đột biến nhằm giảm chi phí tính toán và tăng tự động hóa.
 Chƣơng 4 – Tập trung vào ứng dụng kỹ thuật kiểm thử đột biến.
Phần đầu giới thiệu hai công cụ mã nguồn mở miễn phí là NUnit dùng
để kiểm thử đơn vị của chƣơng trình C#, và Nester với chức năng
phân tích và tạo đột biến. Tiếp đó là ứng dụng kỹ thuật kiểm thử đột
biến để kiểm thử các chƣơng trình C# sử dụng hai công cụ trên.

7


CHƢƠNG 1 – KHÁI QUÁT VỀ
KIỂM THỬ PHẦN MỀM
1.1. Khái niệm

Kiểm thử phần mềm là quá trình thực thi một hệ thống phần mềm để xác
định xem phần mềm có đúng với đặc tả không và thực hiện trong môi trƣờng
nhƣ mong đợi hay không.
Mục đích của kiểm thử phần mềm là tìm ra lỗi chƣa đƣợc phát hiện, tìm
một cách sớm nhất và bảo đảm rằng lỗi sẽ đƣợc sửa.
Mục tiêu của kiểm thử phần mềm là thiết kế tài liệu kiểm thử một cách
có hệ thống và thực hiện nó sao cho có hiệu quả, nhƣng tiết kiệm đƣợc thời
gian, công sức và chi phí.
1.2. Các cấp độ kiểm thử phần mềm
Cấp độ kiểm thử phần mềm đƣợc thể hiện ở hình 1.1 [25]:

Kiểm thử mức
đơn vị lập trình
(Unit test)

Các bộ phận
đơn lẻ

Kiểm thử mức
tích hợp các đơn vị
(Integration test)

Các nhóm
bộ phận

Kiểm thử mức hệ
thống, sau khi tích hợp
(System test)
Kiểm thử để chấp
nhận sản phẩm

(Acceptance test)

Toàn bộ
hệ thống

Toàn bộ hệ thống
nhìn từ khách hàng

Hình 1.1- Bốn cấp độ cơ bản của kiểm thử phần mềm
8


1.2.1. Kiểm thử đơn vị (Unit Test)

Một đơn vị (Unit) là một thành phần phần mềm nhỏ nhất mà ta có thể
kiểm thử đƣợc, ví dụ: các hàm (Function), thủ tục (Procedure), lớp (Class),
hoặc các phƣơng thức (Method).
Kiểm thử đơn vị thƣờng do lập trình viên thực hiện. Công đoạn này cần
đƣợc thực hiện 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.
Mục đích của kiểm thử đơn vị là bảo đảm thông tin đƣợc xử lý và kết
xuất (khỏi Unit) là chính xác, trong mối tƣơng quan với dữ liệu nhập và chức
năng xử lý của Unit. Điều này thƣờng đòi hỏi tất cả các nhánh bên trong Unit
đều phải đƣợc kiểm tra để phát hiện nhánh phát sinh lỗi.
Cũng nhƣ các mức kiểm thử khác, kiểm thử đơn vị cũng đòi hỏi phải
chuẩn bị trƣớc các ca kiểm thử (hay trƣờng hợp kiểm thử) (test case) hoặc
kịch bản (test script), trong đó chỉ định rõ dữ liệu vào, các bƣớc thực hiện và
dữ liệu mong muốn sẽ xuất ra. Các test case và test script đƣợc giữ lại để sử
dụng sau này.
1.2.2. Kiểm thử tích hợp (Integration Test)


Kiểm thử tích hợp kết hợp các thành phần của một ứng dụng và kiểm thử
nhƣ một ứng dụng đã hoàn thành. Trong khi kiểm thử đơn vị kiểm tra các
thành phần và Unit riêng lẻ thì kiểm thử tích hợp kết hợp chúng lại với nhau
và kiểm tra sự giao tiếp giữa chúng.
Kiểm thử tích hợp có hai mục tiêu chính là:
 Phát hiện lỗi giao tiếp xảy ra giữa các Unit
 Tích hợp các Unit đơn lẻ thành các hệ thống con (gọi là subsystem)
và cuối cùng là nguyên hệ thống hoàn chỉnh chuẩn bị cho kiểm thử ở
mức hệ thống (system test).
Có 4 loại kiểm thử trong kiểm thử tích hợp nhƣ sau:
9


 Kiểm thử cấu trúc (Structure test): Kiểm thử nhằm bảo đảm các thành
phần bên trong của một chƣơng trình chạy đúng, chú trọng đến hoạt
động của các thành phần cấu trúc nội tại của chƣơng trình, chẳng hạn
các lệnh và nhánh bên trong.
 Kiểm thử chức năng (Functional test): Kiểm thử chỉ chú trọng đến
chức năng của chƣơng trình, không quan tâm đến cấu trúc bên trong,
chỉ khảo sát chức năng của chƣơng trình theo yêu cầu kỹ thuật.
 Kiểm thử hiệu năng (Performance test): Kiểm thử việc vận hành của
hệ thống.
 Kiểm thử khả năng chịu tải (Stress test): Kiểm thử các giới hạn của hệ
thống.
1.2.3. Kiểm thử hệ thống (System Test)

Mục đích của kiểm thử hệ thống là kiểm thử xem thiết kế và toàn bộ hệ
thống (sau khi tích hợp) có thỏa mãn yêu cầu đặt ra hay không.
Kiểm thử hệ thống kiểm tra cả các hành vi chức năng của phần mềm lẫn

các yêu cầu về chất lƣợng nhƣ độ tin cậy, tính tiện lợi khi sử dụng, hiệu năng
và bảo mật.
Kiểm thử hệ thống bắt đầu khi tất cả các bộ phận của phần mềm đã đƣợc
tích hợp thành công. Thông thƣờng loại kiểm thử này tốn rất nhiều công sức
và thời gian. Trong nhiều trƣờng hợp, việc kiểm thử đòi hỏi một số thiết bị
phụ trợ, phần mềm hoặc phần cứng đặc thù, đặc biệt là các ứng dụng thời gian
thực, hệ thống phân bố, hoặc hệ thống nhúng. Ở mức độ hệ thống, ngƣời kiểm
thử cũng tìm kiếm các lỗi, nhƣng trọng tâm là đánh giá về hoạt động, thao tác,
sự tin cậy và các yêu cầu khác liên quan đến chất lƣợng của toàn hệ thống.
Điểm khác nhau then chốt giữa kiểm thử tích hợp và kiểm thử hệ thống
là kiểm thử hệ thống chú trọng các hành vi và lỗi trên toàn hệ thống, còn kiểm
thử tích hợp chú trọng sự giao tiếp giữa các đơn thể hoặc đối tƣợng khi chúng
làm việc cùng nhau. Thông thƣờng ta phải thực hiện kiểm thử đơn vị và kiểm
10


thử tích hợp để bảo đảm mọi Unit và sự tƣơng tác giữa chúng hoạt động chính
xác trƣớc khi thực hiện kiểm thử hệ thống.
Sau khi hoàn thành kiểm thử tích hợp, một hệ thống phần mềm đã đƣợc
hình thành cùng với các thành phần đã đƣợc kiểm tra đầy đủ. Tại thời điểm
này, lập trình viên hoặc kiểm thử viên (Tester) bắt đầu kiểm thử phần mềm
nhƣ một hệ thống hoàn chỉnh. Việc lập kế hoạch cho kiểm thử hệ thống nên
bắt đầu từ giai đoạn hình thành và phân tích các yêu cầu.
Đòi hỏi nhiều công sức, thời gian và tính chính xác, khách quan, kiểm
thử hệ thống đƣợc thực hiện bởi một nhóm kiểm tra viên hoàn toàn độc lập
với nhóm phát triển dự án để đảm bảo tính chính xác và khách quan.
Kiểm thử hệ thống thƣờng có các loại kiểm thử sau:
 Kiểm thử chức năng (Functional test): Bảo đảm các hành vi của hệ
thống thỏa mãn đúng yêu cầu thiết kế.
 Kiểm thử khả năng vận hành (Performance test): Bảo đảm tối ƣu việc

phân bổ tài nguyên hệ thống (ví dụ bộ nhớ) nhằm đạt các chỉ tiêu nhƣ
thời gian xử lý hay đáp ứng câu truy vấn,....
 Kiểm thử khả năng chịu tải (Stress test hay Load test): Bảo đảm hệ
thống vận hành đúng dƣới áp lực cao (ví dụ nhiều ngƣời truy xuất
cùng lúc). Stress test tập trung vào các trạng thái tới hạn, các "điểm
chết", các tình huống bất thƣờng nhƣ đang giao dịch thì ngắt kết nối
(xuất hiện nhiều trong test thiết bị nhƣ POS, ATM),....
 Kiểm thử cấu hình (Configuration test): Đảm bảo hệ thống hoạt động
tƣơng thích với các loại phần cứng khác nhau.
 Kiểm thử khả năng bảo mật (Security test): Bảo đảm tính toàn vẹn,
bảo mật của dữ liệu và của hệ thống.
 Kiểm thử khả năng phục hồi (Recovery test): Bảo đảm hệ thống có
khả năng khôi phục trạng thái ổn định trƣớc đó trong tình huống mất

11


tài nguyên hoặc dữ liệu; đặc biệt quan trọng đối với các hệ thống giao
dịch nhƣ ngân hàng trực tuyến.
1.2.4. Kiểm thử chấp nhận sản phẩm (Acceptance Test)

Mục đích của kiểm thử chấp nhận là kiểm thử khả năng chấp nhận cuối
cùng để chắc chắn rằng sản phẩm là phù hợp và thỏa mãn các yêu cầu của
khách hàng và khách hàng chấp nhận sản phẩm.
Trong giai đoạn kiểm thử chấp nhận thì ngƣời kiểm tra là khách hàng.
Khách hàng sẽ đánh giá phần mềm với mong đợi theo những thao tác sử
dụng quen thuộc của họ. Việc kiểm tra ở giai đoạn này có ý nghĩa hết sức
quan trọng tránh cho việc hiểu sai yêu cầu cũng nhƣ sự mong đợi của khách
hàng.
Gắn liền với giai đoạn kiểm thử chấp nhận thƣờng là một nhóm những

dịch vụ và tài liệu đi kèm, phổ biến nhƣ hƣớng dẫn cài đặt, sử dụng, v.v…Tất
cả tài liệu đi kèm phải đƣợc cập nhật và kiểm tra chặt chẽ.
1.3. Kỹ thuật kiểm thử phần mềm
Mục tiêu của kiểm thử là phải thiết kế các trƣờng hợp kiểm thử có khả
năng cao nhất trong việc phát hiện nhiều lỗi với thời gian và công sức tối
thiểu. Do đó có thể chia các kỹ thuật kiểm thử thành hai loại:
 Kỹ thuật kiểm thử hộp đen (Black – box Testing) hay còn gọi là kỹ
thuật kiểm thử chức năng (Functional Testing).
 Kỹ thuật kiểm thử hộp trắng (White – box Testing) hay còn gọi là kỹ
thuật kiểm thử cấu trúc (Structural Testing).
1.3.1. Kỹ thuật kiểm thử hộp đen (Black – box Testing)

Kiểm thử hộp đen còn đƣợc gọi là kiểm thử hƣớng dữ liệu (data driven) hay là kiểm thử hƣớng vào/ra (input/output driven).
Trong kỹ thuật này, ngƣời kiểm thử xem phần mềm nhƣ là một hộp đen.
Ngƣời kiểm thử hoàn toàn không quan tâm đến cấu trúc và hành vi bên trong
12


của chƣơng trình. Ngƣời kiểm thử chỉ cần quan tâm đến việc tìm các hiện
tƣợng mà phần mềm không hành xử theo đúng đặc tả của nó. Do đó, dữ liệu
kiểm thử sẽ xuất phát từ đặc tả.
Nhƣ vậy, cách tiếp cận kiểm thử hộp đen tập trung vào các yêu cầu chức
năng của phần mềm. Kiểm thử hộp đen cho phép ngƣời kiểm thử xây dựng
các nhóm giá trị đầu vào sẽ thực thi đầy đủ tất cả các yêu cầu chức năng của
chƣơng trình. Kiểm thử hộp đen không thay thế kỹ thuật kiểm thử hộp trắng,
nhƣng nó bổ sung khả năng phát hiện các lớp lỗi khác với các phƣơng pháp
hộp trắng.
Kiểm thử hộp đen cố gắng tìm các loại lỗi sau:
 Các chức năng thiếu hoặc không đúng.
 Các lỗi giao diện.

 Các lỗi cấu trúc dữ liệu trong truy cập cơ sở dữ liệu bên ngoài.
 Các lỗi thực hiện.
 Các lỗi khởi tạo hoặc kết thúc.
 Và các lỗi khác ...
Không giống với kiểm thử hộp trắng đƣợc thực hiện sớm trong quá trình
kiểm thử, kiểm thử hộp đen đƣợc áp dụng trong các giai đoạn sau của kiểm
thử. Vì kiểm thử hộp đen không để ý có chủ đích cấu trúc điều khiển, sự quan
tâm tập trung trên miền thông tin. Nếu ngƣời kiểm thử muốn sử dụng phƣơng
pháp này để tìm tất cả các lỗi trong chƣơng trình thì điều kiện bắt buộc là phải
kiểm thử tất cả các đầu vào, tức là mỗi một điều kiện đầu vào có thể có là một
trƣờng hợp kiểm thử. Bởi vì nếu chỉ kiểm thử một số điều kiện đầu vào thì
không đảm bảo đƣợc chƣơng trình đã hết lỗi. Vì thế, để đạt đƣợc mục tiêu
kiểm thử, ngƣời ta đã áp dụng một số phƣơng pháp kiểm thử hộp đen nhƣ:
phân hoạch tương đương, phân tích giá trị biên.

13


1.3.1.1. Phân hoạch tương đương

Do việc kiểm thử tất cả các đầu vào của chƣơng trình là không thể. Vì
thế, khi kiểm thử chƣơng trình nên giới hạn một tập con tất cả các trƣờng hợp
đầu vào có thể có, sao cho có xác suất tìm ra đƣợc nhiều lỗi nhất.
Một tập con nhƣ vậy cần có hai tính chất sau:
 Mỗi trƣờng hợp kiểm thử nên gồm nhiều điều kiện đầu vào khác nhau
có thể để giảm thiểu tổng số các trƣờng hợp cần thiết.
 Cố gắng phân hoạch các miền đầu vào của một chƣơng trình thành
một số xác định các lớp tƣơng đƣơng, sao cho có thể giả định hợp lý
rằng việc kiểm thử một giá trị đại diện của mỗi lớp là tƣơng đƣơng
với việc kiểm thử với một giá trị bất kỳ trong cùng lớp.

Thiết kế các trƣờng hợp kiểm thử bằng phân hoạch tƣơng đƣơng đƣợc xử
lý theo hai bƣớc: Phân hoạch các miền đầu vào/ra thành các lớp tƣơng đƣơng,
và thiết kế các trƣờng hợp kiểm thử đại diện cho mỗi lớp.
a) Xác định các lớp tương đương
Các lớp tƣơng đƣơng đƣợc nhận dạng bằng cách lấy mỗi điều kiện đầu
vào (thông thƣờng là một câu lệnh hoặc một cụm từ trong đặc tả) và phân
hoạch nó thành hai hay nhiều nhóm. Các lớp tƣơng đƣơng bao gồm một tập
các trạng thái hợp lệ hoặc không hợp lệ cho điều kiện đầu vào. Điều kiện đầu
vào là giá trị số xác định, hoặc là miền giá trị, tập giá trị có liên quan, hoặc
điều kiện logic. Để làm điều này, chúng ta sử dụng bảng liệt kê các lớp tƣơng
đƣơng.
Các lớp tƣơng đƣơng có thể đƣợc định nghĩa theo nguyên tắc sau:
1. Nếu điều kiện đầu vào xác định một khoảng giá trị [a,b], thì phân
hoạch thành một lớp tƣơng đƣơng hợp lệ và hai lớp tƣơng đƣơng
không hợp lệ. Chẳng hạn, nếu đầu vào x nằm trong khoảng [1,999],
lớp hợp lệ là: 1 <= x < = 999, các lớp không hợp lệ là x < 1 và x >
999.
14


2. Nếu điều kiện đầu vào yêu cầu một giá trị xác định, phân hoạch thành
một lớp tƣơng đƣơng hợp lệ và hai lớp tƣơng đƣơng không hợp lệ.
Chẳng hạn, nếu đầu vào x = 3, thì lớp hợp lệ là x = 3, các lớp không
hợp lệ là x < 3 và x > 3.
3. Nếu điều kiện đầu vào xác định một phần tử của tập hợp, thì phân
hoạch thành một lớp tƣơng đƣơng hợp lệ và một lớp tƣơng đƣơng
không hợp lệ.
4. Nếu điều kiện đầu vào là Boolean, thì phân hoạch thành một lớp
tƣơng đƣơng hợp lệ và một lớp tƣơng đƣơng không hợp lệ tƣơng ứng
với hai trạng thái true và false.

Ngoài ra, một nguyên tắc thứ năm đƣợc bổ sung là sử dụng khả năng
phán đoán, kinh nghiệm và trực giác của ngƣời kiểm thử.
Các lớp tƣơng đƣơng
hợp lệ
Số ID của sinh viên Các thuộc tính khóa
Điều kiện vào/ra

Tên sinh viên

Giới tính sinh viên

Điểm của sinh viên

Các lớp tƣơng đƣơng
không hợp lệ
Không phải thuộc tính khóa

Ký tự chữ cái

Không phải chữ cái

Không rỗng

Rỗng

Ký tự chữ cái, “M’ hoặc “F”

Không phải chữ cái
Không phải “M” hoặc “F”
Không phải số


Số

Số nhỏ hơn 0

Từ 0 đến 100

Số lớn hơn 100

Bảng 1.1 – Ví dụ các lớp tương đương

b) Xác định các trường hợp kiểm thử
Kế tiếp trong phân hoạch tƣơng đƣơng là thiết kế các trƣờng hợp kiểm
thử dựa trên sự ƣớc lƣợng của các lớp tƣơng đƣơng cho miền đầu vào. Tiến
trình này đƣợc thực hiện nhƣ sau:
1. Gán một giá trị duy nhất cho mỗi lớp tƣơng đƣơng
15


2. Đến khi tất cả các lớp tƣơng đƣơng hợp lệ đƣợc phủ bởi các trƣờng
hợp kiểm thử thì viết một trƣờng hợp kiểm thử mới phủ nhiều nhất có
thể các lớp tƣơng đƣơng hợp lệ chƣa đƣợc phủ.

3. Đến khi tất cả các lớp tƣơng đƣơng không hợp lệ đƣợc phủ bởi các
trƣờng hợp kiểm thử thì hãy viết các trƣờng hợp kiểm thử mới sao
cho mỗi trƣờng hợp kiểm thử mới chỉ phủ duy nhất một lớp tƣơng
đƣơng không hợp lệ chƣa đƣợc phủ.
1.3.1.2. Phân tích giá trị biên

Khi thực hiện kiểm thử phần mềm theo dữ liệu, chúng ta kiểm tra xem

đầu vào của ngƣời dùng, kết quả nhận đƣợc và kết quả tạm thời bên trong có
đƣợc xử lý chính xác hay không.
Các điều kiện biên là tình trạng trực tiếp ở phía trên và dƣới của lớp
tƣơng đƣơng đầu vào và lớp đƣơng đƣơng đầu ra. Việc phân tích các giá trị
biên khác với phân hoạch tƣơng đƣơng theo hai điểm:
 Từ mỗi lớp tƣơng đƣơng, phân hoạch tƣơng đƣơng sẽ chọn phần tử
bất kỳ làm phần tử đại diện, trong khi việc phân tích giá trị biên sử
dụng một hoặc một số phần tử. Nhƣ vậy, mỗi biên của lớp tƣơng
đƣơng chính là đích kiểm thử.
 Không chỉ chú ý tập trung những điều kiện đầu vào, các trƣờng hợp
kiểm thử cũng đƣợc suy ra từ việc xem xét các kết quả ra (tức là các
lớp tƣơng đƣơng đầu ra).
Có một số nguyên tắc phân tích giá trị biên nhƣ sau:
1. Nếu điều kiện đầu vào xác định một khoảng giá trị giữa a và b, các
trƣờng hợp kiểm thử sẽ đƣợc thiết kế với giá trị a và b, các giá trị sát
trên và sát dƣới a và b.
2. Nếu một điều kiện đầu vào xác định một số các giá trị, các trƣờng hợp
kiểm thử sẽ đƣợc phát triển để thực hiện tại các giá trị cực đại, cực

16


tiểu. Các giá trị sát trên và dƣới giá trị cực đại, cực tiểu cũng đƣợc
kiểm thử.
3. Nguyên tắc 1 và 2 đƣợc áp dụng cho các điều kiện đầu ra.
4. Nếu cấu trúc dữ liệu chƣơng trình bên trong đƣợc qui định các biên
(chẳng hạn, mảng đƣợc định nghĩa giới hạn 100 mục), tập trung thiết
kế trƣờng hợp kiểm thử để thực thi cấu trúc dữ liệu tại biên của nó.
Ngoài ra, ngƣời kiểm thử có thể sử dụng sự suy đoán và sáng tạo của
mình để tìm các điều kiện biên.

Tóm lại, chúng ta phải kiểm thử mỗi biên của một lớp tƣơng đƣơng về
tất cả các phía. Một chƣơng trình nếu vƣợt qua những trƣờng hợp kiểm thử đó
có thể vƣợt qua các kiểm thử khác từ lớp đó.
1.3.2. Kỹ thuật kiểm thử hộp trắng (White – box Testing)

Kiểm thử hộp trắng hay còn gọi là kiểm thử hƣớng logic, cho phép kiểm
tra cấu trúc bên trong của phần mềm với mục đích bảo đảm rằng tất cả các
câu lệnh và điều kiện sẽ đƣợc thực hiện ít nhất một lần. Ngƣời kiểm thử truy
nhập vào mã nguồn chƣơng trình và có thể kiểm tra nó, lấy đó làm cơ sở để
hỗ trợ việc kiểm thử.
1.3.2.1. Kiểm thử đường dẫn cơ sở

Kiểm thử đƣờng dẫn cơ sở là một kỹ thuật kiểm thử hộp trắng do Tom
McCabe đề xuất. Phƣơng pháp đƣờng dẫn cơ sở cho phép ngƣời thiết kế
trƣờng hợp kiểm thử thực hiện phép đo độ phức tạp logic của thiết kế thủ tục
và sử dụng phép đo này nhƣ một chỉ dẫn cho việc thiết kế một tập cơ sở các
đƣờng dẫn thực hiện. Những trƣờng hợp kiểm thử đƣợc suy diễn để thực hiện
tập cơ sở. Các trƣờng hợp kiểm thử đó đƣợc đảm bảo để thực hiện mỗi lệnh
trong chƣơng trình ít nhất một lần trong quá trình kiểm thử.
a) Đồ thị luồng điều khiển

17


Trong thực tế, phƣơng pháp đƣờng dẫn cơ sở có thể đƣợc dùng không
cần sử dụng đồ thị luồng điều khiển. Tuy nhiên, đồ thị luồng điều khiển (minh
họa ở hình 1.2) là một công cụ hữu ích để hiểu các luồng điều khiển và minh
họa cho phƣơng pháp này. Cấu trúc của đồ thị luồng điều khiển bao gồm:
 Mỗi đỉnh (hình tròn) biểu thị một đoạn các câu lệnh thực hiện một
cách tuần tự, có thể kết thúc bằng một lệnh rẽ nhánh.

 Mỗi cạnh (cung) biểu diễn dòng điều khiển nối hai nút với nhau.
 Phần đƣợc bao bởi các cung và các đỉnh gọi là miền.

Đỉnh điều kiện
Miền

Cung

Hình 1.2 - Đồ thị luồng điều khiển

b) Độ phức tạp chu trình
Độ phức tạp chu trình là một thƣớc đo phần mềm, cung cấp các phép đo
định lƣợng độ phức tạp của chƣơng trình. Khi đƣợc sử dụng trong ngữ cảnh
của phƣơng pháp đƣờng dẫn cơ sở, giá trị đƣợc xác định cho độ phức tạp chu
trình cho biết số lƣợng đƣờng dẫn độc lập trong một tập cơ sở của chƣơng
trình và cung cấp cho chúng ta một giới hạn trên số lƣợng kiểm thử bắt buộc
để đảm bảo rằng tất cả các câu lệnh đƣợc thực hiện ít nhất một lần.

18


Việc tính toán độ phức tạp chu trình sẽ cho chúng ta biết có bao nhiêu
đƣờng dẫn cần tìm. Cho đồ thị luồng điều khiển G, độ phức tạp chu trình
V(G) đƣợc tính theo một trong 3 công thức sau:
1. V(G) = R, trong đó R là số miền của đồ thị G.
2. V(G) = P + 1, trong đó P là số đỉnh điều kiện có trong đồ thị G.
3. V(G) = E – N + 2, trong đó E là số cung và N là số đỉnh của đồ thị G.
Đối chiếu với đồ thị luồng điều khiển trong hình 1.2, độ phức tạp chu
trình V(G) đƣợc tính nhƣ sau:
1. Công thức 1: V(G) = R = 6

2. Công thức 2: V(G) = P + 1 = 5 + 1 = 6
3. Công thức 3: V(G) = E – N + 2 = 15 – 11 + 2 = 6
Nhƣ vậy, độ phức tạp chu trình của đồ thị luồng điều khiển ở hình 1.2 là
6.
c) Phát sinh các trường hợp kiểm thử theo đường dẫn cơ sở
Phƣơng pháp kiểm thử đƣờng dẫn cơ sở có thể áp dụng để kiểm thử thủ
tục chi tiết hoặc cho mã nguồn bao gồm các bƣớc sau:
 Bƣớc 1: Sử dụng mã nguồn hoặc thiết kế để xây dựng đồ thị luồng
điều khiển tƣơng ứng.
 Bƣớc 2: Tính toán độ phức tạp chu trình V(G) .
 Bƣớc 3: Xác định tập cơ sở của các đƣờng dẫn độc lập (một đường
dẫn được gọi là độc lập với các đường dẫn khác nếu nó có ít nhất một
cạnh không xuất hiện trong các đường dẫn khác).
 Bƣớc 4: Chuẩn bị các trƣờng hợp kiểm thử có khả năng thực hiện
mỗi đƣờng dẫn trong tập cơ sở.
Chúng ta dùng hàm tính giá trị trung bình cộng của các số, average trong
C nhƣ hình 1.3 để làm ví dụ minh họa cho mỗi bƣớc thiết kế các trƣờng hợp
kiểm thử. Hàm average là một thuật toán đơn giản có chứa các tổ hợp và
vòng lặp, trong đó chƣơng trình tính giá trị trung bình của 100 hoặc một vài
19


số trong mảng values nằm trong khoảng của biên trên (max) và biên dƣới
(min). Đầu vào đƣợc kết thúc bằng giá trị -999.
1

2

3


4

8

10

9

5
7

11

6

Hình 1.3 – Ví dụ minh họa phát sinh các trường hợp kiểm thử theo
đường dẫn cơ sở

Bƣớc 1: Vẽ đồ thị luồng điều khiển (nhƣ hình 1.3)
Bƣớc 2: Tính độ phức tạp chu trình V(G)
V(G) = P (số đỉnh điều kiện) + 1 = 5 + 1 = 6 (trong hình 1.3 có 5
đỉnh điều kiện: 2, 3, 4, 5, 8)
Bƣớc 3: Tìm tập cơ sở của các đƣờng dẫn độc lập
+ Đƣờng dẫn 1: 1  2  8  9  11
+ Đƣờng dẫn 2: 1  2  8  10  11
+ Đƣờng dẫn 3: 1  2  3  8  9  11
+ Đƣờng dẫn 4: 1  2  3  4  7  2 …
+ Đƣờng dẫn 5: 1  2  3  4  5  7  2 …
+ Đƣờng dẫn 6: 1  2  3  4  5  6  7  2 …
Bƣớc 4: Thiết kế các trƣờng hợp kiểm thử cho mỗi đƣờng dẫn độc lập

trong tập cơ sở đã chọn.
20


 Trƣờng hợp kiểm thử đƣờng dẫn 1
Đầu vào: Values = {3, 5, 11, -999}, min = 0, max = 100
Đầu ra mong muốn: Average = (3 + 5 + 11) / 3
Mục đích: Để kiểm thử việc tính giá trị trung bình chính xác
Lưu ý: Đƣờng dẫn 1 không thể kiểm thử một mình, mà phải đƣợc kiểm
thử nhƣ là một phần của các kiểm thử đƣờng dẫn 4, 5 và 6.
 Trƣờng hợp kiểm thử đƣờng dẫn 2
Đầu vào: Values = {-999}, min = 0, max = 0
Đầu ra mong muốn: Average = -999
Mục đích: Để tạo ra Average = -999
 Trƣờng hợp kiểm thử đƣờng dẫn 3
Đầu vào: Values = {2, 6, 7, …, 120} (101 số), min = 0, max = 100
Đầu ra mong muốn: Trung bình của 100 số đầu tiên
Mục đích: Chỉ tính trung bình cho 100 số hợp lệ đầu tiên
 Trƣờng hợp kiểm thử đƣờng dẫn 4
Đầu vào: Values = {67, -2, 12, 23, -999}, min = 0, max = 100
Đầu ra mong muốn: Average = (67 + 12 + 23) / 3
Mục đích: Kiểm thử biên dƣới (Values[i] > min, i < 100)
 Trƣờng hợp kiểm thử đƣờng dẫn 5
Đầu vào: Values = {7, 32, 102, 23, 68, 2, -999}, min = 0, max = 100
Đầu ra mong muốn: Average = (7 + 32 + 23 + 68 + 2) / 5
Mục đích: Kiểm thử biên trên (Values[i] < max, i < 100)
 Trƣờng hợp kiểm thử đƣờng dẫn 6
Đầu vào: Values = {3, 4, 12, 15, 16, 2, -999}, min = 0, max = 100
Đầu ra mong muốn: Average = (3 + 4 + 12 + 15 + 16 + 2) / 6
Mục đích: Việc tính giá trị trung bình là đúng

Phƣơng pháp đƣờng dẫn cơ sở tập trung trên “giá trị đại diện” của mỗi
đƣờng dẫn độc lập. Cần có các trƣờng hợp kiểm thử bổ sung (ngoài các
21


trƣờng hợp kiểm thử đƣờng dẫn cơ sở), nhất là để thực hiện các điều kiện
biên.
1.3.2.2. Kiểm thử cấu trúc điều khiển

Phƣơng pháp kiểm thử đƣờng dẫn cơ sở là phƣơng pháp kiểm thử đơn
giản và hiệu quả nhƣng chƣa đủ. Chúng ta sẽ xem xét các biến thể trên kiểm
thử cấu trúc điều khiển mà phủ kiểm thử mở rộng và hoàn thiện chất lƣợng
của phƣơng pháp kiểm thử hộp trắng.
a) Kiểm thử điều kiện
Kiểm thử điều kiện là phƣơng pháp thiết kế trƣờng hợp kiểm thử thực thi
các điều kiện logic trong module chƣơng trình. Mục đích là để xác định các
lỗi điều kiện và cả các lỗi khác trong chƣơng trình. Một số phƣơng pháp kiểm
thử điều kiện nhƣ sau:
 Kiểm thử nhánh (Branch Testing): Là phƣơng pháp kiểm thử điều
kiện đơn giản nhất. Thiết kế các trƣờng hợp kiểm thử sao cho với
mỗi điều kiện rẽ nhánh phức hợp C, các nhánh true và false của C
và mỗi điều kiện đơn giản trong C cần phải đƣợc thực thi ít nhất một
lần.
 Kiểm thử miền (Domain Testing): Cần 3 hoặc 4 trƣờng hợp kiểm
thử cho biểu thức quan hệ. Với một biểu thức quan hệ có dạng E1
<op> E2, cần có 3 trƣờng hợp kiểm thử đƣợc thiết kế cho E1 == E2,
E1 > E2, E1 < E2.
b) Kiểm thử luồng dữ liệu
Phƣơng pháp kiểm thử luồng dữ liệu lựa chọn các đƣờng dẫn kiểm thử
của chƣơng trình dựa vào vị trí khai báo và sử dụng các biến trong chƣơng

trình. Với kiểm thử luồng dữ liệu, mỗi câu lệnh trong chƣơng trình đƣợc gán
số hiệu lệnh duy nhất và mỗi hàm không thay đổi tham số của nó và biến toàn
cục. Cho một lệnh với S là số hiệu câu lệnh. Ta định nghĩa:
DEF(S) = là tập các biến đƣợc khai báo trong S
22


USE(S) = là tập các biến đƣợc sử dụng trong S
Một chiến lƣợc kiểm thử luồng dữ liệu cơ bản là chiến lƣợc mà mỗi
chuỗi DU đƣợc phủ ít nhất một lần. Chiến lƣợc này đƣợc gọi là chiến lƣợc
kiểm thử DU. Kiểm thử DU không đảm bảo phủ hết tất cả các nhánh của một
chƣơng trình; tuy nhiên, một nhánh không đảm bảo đƣợc phủ bởi kiểm thử
DU chỉ trong rất ít tình huống nhƣ cấu trúc if – then – else mà trong đó phần
then không có một khai báo biến nào và có dạng khuyết (không tồn tại phần
else). Trong tình huống đó, nhánh else của lệnh if là không cần thiết phải phủ
bằng kiểm thử DU.
c) Kiểm thử vòng lặp
Là phƣơng pháp tập trung vào tính hợp lệ của các cấu trúc vòng lặp. Có
4 kiểu vòng lặp khác nhau đƣợc mô tả bằng sơ đồ khối nhƣ sau:

Vòng lặp đơn

Vòng lặp nối tiếp

Vòng lồng nhau

Hình 1.4 –Các kiểu vòng lặp

23


Vòng lặp phi cấu trúc


 Các bƣớc cần kiểm tra cho vòng lặp đơn giản
+ Bỏ qua toàn bộ vòng lặp;
+ Chỉ qua một vòng lặp;
+ Qua hai vòng lặp;
+ Qua vòng lặp m với (m < n);
+ Qua vòng lặp (n - 1), n, (n + 1).
Trong đó n là số lần lặp tối đa của vòng lặp.
 Các bƣớc cần kiểm tra cho vòng lặp dạng lồng nhau
+ Khởi đầu với vòng lặp nằm bên trong nhất. Thiết lập các tham số
lặp cho các vòng lặp bên ngoài về giá trị nhỏ nhất.
+ Kiểm tra với tham số min + 1, 1 giá trị tiêu biểu, max - 1 và max
cho vòng lặp bên trong nhất trong khi các tham số lặp của các
vòng lặp bên ngoài là nhỏ nhất.
+ Tiếp tục tƣơng tự với các vòng lặp liền ngoài tiếp theo cho đến khi
tất cả vòng lặp bên ngoài đƣợc kiểm tra.
 Các bƣớc cần kiểm tra cho vòng lặp nối tiếp
Nếu các vòng lặp là độc lập với nhau thì kiểm tra nhƣ trƣờng các vòng
lặp dạng đơn giản, nếu không thì kiểm tra nhƣ trƣờng hợp các vòng lặp lồng
nhau.

 Các bƣớc cần kiểm tra cho vòng lặp phi cấu trúc
Nếu gặp các lớp vòng lặp này chúng ta sẽ không kiểm thử, mà sẽ thiết
kế lại tƣơng ứng với sử dụng việc xây dựng chƣơng trình có cấu trúc.

24



1.4.

Kết luận
Trong chƣơng 1 đã nêu tổng quan về các cấp độ và loại kiểm thử phần

mềm cơ bản. Kiểm thử hộp trắng xem xét chƣơng trình ở mức độ chi tiết và
phù hợp khi kiểm tra các môđun nhỏ. Tuy nhiên, kiểm thử hộp trắng có thể
không đầy đủ vì kiểm thử hết các lệnh không chứng tỏ là chúng ta đã kiểm
thử hết các trƣờng hợp có thể. Ngoài ra chúng ta không thể kiểm thử hết các
đƣờng đi đối với các vòng lặp lớn.
Kiểm thử hộp đen chú trọng vào việc kiểm tra các quan hệ vào ra và
những chức năng giao diện bên ngoài, nó thích hợp hơn cho các hệ thống
phần mềm lớn hay các thành phần quan trọng của chúng. Nhƣng chỉ sử dụng
kiểm thử hộp đen là chƣa đủ. Bởi vì, kiểm thử chức năng chỉ dựa trên đặc tả
của môđun nên không thể kiểm thử đƣợc các trƣờng hợp không đƣợc khai
báo trong đặc tả. Ngoài ra, do không phân tích mã nguồn nên không thể biết
đƣợc môđun nào của chƣơng trình đã hay chƣa đƣợc kiểm thử, khi đó phải
kiểm thử lại hay bỏ qua những lỗi tiềm ẩn trong gói phần mềm.
Phƣơng pháp kiểm thử hộp trắng và kiểm thử hộp đen là hai phƣơng
pháp cơ bản có vai trò rất quan trọng trong quá trình phát triển phần mềm, nếu
chúng ta biết kết hợp chúng để bổ sung khiếm khuyết lẫn nhau.

25


×