Bài 5-6: Phân tích, Kiểm thử Phần mềm & Fuzzing Cơ bản


1. Kiểm thử Phần mềm (Software Testing)

1.1 Định nghĩa

Kiểm thử phần mềm là quá trình đánh giá và xác minh xem phần mềm hoặc ứng dụng có hoạt động đúng như mong đợi hay không. Mục tiêu chính:

  • Phát hiện và sửa chữa các lỗi (bug)
  • Đảm bảo chất lượng sản phẩm
  • Thẩm định và xác minh phần mềm đáp ứng đúng yêu cầu đặt ra
  • Cải thiện độ tin cậy của phần mềm

1.2 Phân loại kiểm thử

Theo cách thức kiểm thử

LoạiMô tả
Black Box TestingChỉ quan tâm đầu vào/đầu ra, không biết mã nguồn bên trong
White Box TestingDựa vào mã nguồn để kiểm tra logic, cấu trúc bên trong
Gray Box TestingKết hợp cả hai: có một phần kiến thức về cấu trúc bên trong

Theo giai đoạn kiểm thử

Unit Testing → Integration Testing → System Testing → Acceptance Testing
  • Unit Testing: Kiểm tra từng hàm/lớp nhỏ nhất một cách độc lập
  • Integration Testing: Kiểm tra sự tương tác giữa các module sau khi ghép lại
  • System Testing: Kiểm tra toàn bộ hệ thống hoàn chỉnh
  • Acceptance Testing: Xác nhận phần mềm đáp ứng yêu cầu khách hàng, sẵn sàng triển khai

Theo mục tiêu kiểm thử

  • Functional Testing: Kiểm tra các chức năng có hoạt động đúng yêu cầu không
  • Non-functional Testing: Kiểm tra hiệu suất, bảo mật, độ tin cậy, khả năng sử dụng
  • Regression Testing: Sau khi thay đổi code, đảm bảo các tính năng cũ không bị ảnh hưởng
  • Security Testing: Kiểm tra khả năng chống tấn công xâm nhập, lỗ hổng bảo mật
  • Performance Testing: Load test, stress test, scalability test
  • Usability Testing: Đánh giá trải nghiệm người dùng, UI/UX

Theo mức độ tự động hóa

  • Manual Testing: Tester thực hiện thủ công, phù hợp kiểm tra UI/UX
  • Automated Testing: Dùng công cụ và script, phù hợp regression test, performance test

1.3 Quy trình kiểm thử (STLC)

Software Testing Life Cycle gồm 6 giai đoạn:

flowchart LR A[1. Requirement Analysis\nPhân tích yêu cầu] --> B[2. Test Planning\nLập kế hoạch] B --> C[3. Test Case Development\nThiết kế kịch bản] C --> D[4. Environment Setup\nThiết lập môi trường] D --> E[5. Test Execution\nThực hiện kiểm thử] E --> F[6. Test Cycle Closure\nĐóng chu trình]

2. Kiểm thử Hộp Trắng (White Box Testing)

2.1 Chiến lược

Thiết kế test case dựa vào cấu trúc nội tại của đối tượng kiểm thử, đảm bảo tất cả câu lệnh, biểu thức điều kiện trong chương trình được thực hiện ít nhất một lần.

Gồm 3 kỹ thuật chính:

  • Basis Path Testing
  • Control-flow / Coverage Testing
  • Data-flow Testing

2.2 Kiểm thử Đường dẫn Cơ sở (Basis Path Testing)

Được McCabe đề xuất năm 1976. Phương pháp này:

  • Đảm bảo tất cả independent path (đường dẫn độc lập) đều được thực thi ít nhất một lần
  • Cho biết số lượng test case tối thiểu cần thiết kế

Independent path: Bất kỳ path nào bổ sung ít nhất một tập lệnh hoặc một biểu thức điều kiện mới so với các path đã có.

Xây dựng Control Flow Graph (CFG)

- Node: đại diện cho một hoặc nhiều câu lệnh xử lý
- Edge: đại diện cho một luồng điều khiển giữa các node
- Predicate node: node đại diện cho biểu thức điều kiện (if/while/for)
flowchart TD Start([Start]) --> N1[1. do while records remain\nread record] N1 --> P2{2. record.field1 == 0?} P2 -- true --> N3[3. process record\nstore in buffer\nincrement counter] P2 -- false --> P4{4. record.field2 == 0?} P4 -- true --> N5[5. reset record] P4 -- false --> N6[6. process record\nstore in file] N3 --> N7b[7b. enddo] N5 --> N7a[7a. endif] N6 --> N7a N7a --> N7b N7b --> P2 N7b --> N8[8. end]

Tập path cơ sở cần test (ví dụ trên):

  • Path 1: 1 → 2 → 5 (không còn record)
  • Path 2: 1 → 2 → 3 → 4 → 2 → 5 (field1 == 0)
  • Path 3: 1 → 2 → 3 → 6 → 7 → 4 → 2 → 5 (field2 == 0)
  • Path 4: 1 → 2 → 3 → 6 → 8 → 7 → 4 → 2 → 5 (else)

Mỗi path tương ứng một test case → 4 test case tối thiểu.


2.3 Kiểm thử Coverage (Phủ)

Coverage = tỉ lệ các thành phần thực sự được kiểm thử so với tổng thể.

CấpTênMô tả
0Không có chiến lượcKiểm thử tùy tiện, không có trách nhiệm
1Statement CoverageMỗi câu lệnh được thực thi ít nhất 1 lần
2Branch CoverageMỗi nhánh (true/false) của điều kiện đều được đi qua
3Condition CoverageMỗi điều kiện con trong biểu thức phức được test cả true lẫn false
4Branch & Condition CoverageKết hợp cấp 2 và cấp 3

Ví dụ minh họa

float foo(int a, int b, int c, int d, float e) {
    float e;
    if (a == 0)          // Predicate 1
        return 0;
    int x = 0;
    if ((a == b) OR ((c == d) AND bug(a)))  // Predicate 2
        x = 1;
    e = 1 / x;
    return e;
}
  • Statement Coverage 100%: foo(1,1,1,1,1) → đi qua tất cả câu lệnh
  • Branch Coverage 100%: cần thêm foo(1,2,1,2,1) để nhánh false của Predicate 2 được đi
  • Condition Coverage 100%: cần thêm foo(1,2,1,1,1)foo(3,2,1,1,1) để phủ từng điều kiện con

2.4 Kiểm thử Luồng Dữ liệu (Data-flow Testing)

Tập trung vào chu kỳ sống của biến: định nghĩa (define) → sử dụng (use) → hủy (kill).

Hệ thống ký hiệu

Ký hiệuÝ nghĩa
ddefined – biến được khai báo/gán giá trị
kkilled – biến bị hủy/ra khỏi scope
uused – biến được sử dụng
cc-use – dùng trong biểu thức tính toán
pp-use – dùng trong biểu thức điều kiện

Các pattern anomaly

PatternÝ nghĩa
ddĐịnh nghĩa 2 lần không dùng ở giữa → potential bug
duĐịnh nghĩa rồi dùng → bình thường
udDùng rồi định nghĩa lại → bình thường
ukDùng rồi hủy → bình thường
dkĐịnh nghĩa rồi hủy không dùng → potential bug
~dDùng biến chưa định nghĩa → bug
k~Biến đã hủy được dùng → bug

DU Path và DU Pair

  • DEF(S): tập biến được định nghĩa tại câu lệnh S
  • USE(S): tập biến được sử dụng tại câu lệnh S
  • DU Path của biến X từ S đến S’: đường đi trên CFG không có định nghĩa lại của X ở giữa
  • DU Pair (S, S’): tồn tại ít nhất một DU path nối S và S'

Các chiến lược kiểm thử luồng dữ liệu

flowchart TD AP[ALL PATHS] --> ADUP[ALL DU PATHS] ADUP --> AU[ALL USES] AU --> ACP[ALL-C / SOME-P] AU --> APC[ALL-P / SOME-C] ACP --> ACU[ALL C-USES] APC --> APU[ALL P-USES] ACU --> AD[ALL DEFS] APU --> AD AD --> BR[BRANCH] BR --> ST[STATEMENT]

Mối quan hệ: chiến lược trên mạnh hơn chiến lược dưới (bao phủ nhiều hơn).



3. Kiểm thử Hộp Đen (Black Box Testing)

3.1 Đặc điểm

  • Còn gọi là specification-based testing
  • Tester chỉ quan tâm WHAT (phần mềm làm gì), không quan tâm HOW (làm như thế nào)
  • Không cần truy cập mã nguồn

Quy trình:

Phân tích chức năng → Thiết kế test case → Thực thi → So sánh kết quả → Báo cáo

3.2 Phân vùng Tương đương (Equivalence Partitioning – EP)

Ý tưởng: Nếu một giá trị đại diện trong nhóm đúng thì các giá trị còn lại trong nhóm cũng đúng và ngược lại → giảm thiểu số lượng test case không cần thiết.

Hai giá trị tương đương khi:

  • Tương tự nhau về mặt trực giác
  • Đặc tả mô tả chương trình xử lý chúng như nhau
  • Chúng dẫn chương trình đi cùng một nhánh
  • Cho cùng kết quả với các giả thiết đã đưa ra

3.3 Phân tích Giá trị Biên (Boundary Value Analysis – BVA)

Tập trung kiểm thử tại các giá trị biên giữa các phân vùng — nơi lỗi thường xảy ra nhất.

Standard BVA

Với biến x có miền [min, max], chọn 5 giá trị:

min | min+1 | nom (trung bình) | max-1 | max

Số test case = 4n + 1 (n = số biến).

Robust Testing (mở rộng Standard BVA)

Thêm 2 giá trị ngoài biên: min-1max+1 (invalid).

Số test case = 6n + 1.

Worst-case Testing

Kiểm tra đồng thời tất cả các biến tại biên. Số test case = 5^n.

Robust Worst-case Testing

Tương tự Worst-case nhưng thêm các giá trị invalid. Số test case = 7^n.


4. Fuzzing

4.1 Định nghĩa

Fuzzing là kỹ thuật phát hiện lỗi phần mềm bằng cách tự động hoặc bán tự động gửi dữ liệu đầu vào không hợp lệ, ngẫu nhiên, hoặc bất ngờ vào chương trình, sau đó theo dõi hành vi bất thường.

Dữ liệu đầu vào fuzzing thường là:

  • Giá trị vượt quá biên
  • Giá trị đặc biệt (null, 0, MAX_INT…)
  • Chuỗi ký tự sai định dạng
  • Dữ liệu không mong đợi theo protocol
flowchart LR A[Seed Files /\nSpecification] --> B[Test Case Generator] B --> C[Test Cases] C --> D[Target Program] D --> E[Monitor] E --> F[Bug Detector] F --> G{Violation?} G -- yes --> H[Bug Filter] H --> I[Real Bugs /\nVulnerabilities] G -- no --> B

4.2 Phân loại Fuzzing

Theo mức độ hiểu biết về chương trình

LoạiMô tả
Blackbox FuzzingKhông biết cấu trúc bên trong, gửi input ngẫu nhiên
Whitebox FuzzingCó mã nguồn, kết hợp phân tích tĩnh/động
Greybox FuzzingBiết một phần cấu trúc (ví dụ: AFL dùng coverage feedback)

Theo chiến lược tạo dữ liệu

Chiến lượcMô tả
Mutation-basedLấy input mẫu hợp lệ rồi biến đổi ngẫu nhiên một phần
Generation-basedSinh input từ đầu dựa trên đặc tả cú pháp/ngữ pháp (grammar)

Theo phản hồi

  • Feedback-based: Sử dụng thông tin thu được (coverage, trace) để hướng dẫn các lần thử tiếp theo
  • No-feedback: Không quan tâm kết quả các lần trước, hoàn toàn ngẫu nhiên

4.3 Các kỹ thuật Fuzzing

Kỹ thuật Sinh mẫu (Sample Generation)

1. Biến đổi ngẫu nhiên (Random Mutation / Blackbox Fuzzing)

def RandomFuzzing(input_seed):
    numWrites = random(len(seed) / 1000) + 1
    newInput = seed
    for i in range(numWrites):
        loc = random(len(seed))
        byte_value = random(255)
        newInput[loc] = byte_value
    result = ExecuteAppWith(newInput)
    if result == crash:
        print("bug found!")

2. Biểu diễn cấu trúc ngữ pháp (Grammar Representation)

Sinh input theo cú pháp được định nghĩa trước, ví dụ HTTP request:

# SPIKE fuzzing code (grammar-based)
string("POST /api/blog/ HTTP/1.2\r\n")
string("Content-Length: ")
blocksize_string("blockA", 2)
block_start("blockA")
string('{"body": "')
string_variable("XXX")
string('"')
block_end("blockA")

3. Thuật toán lập lịch (Scheduling Algorithms)

Chọn seed nào để fuzz tiếp theo dựa trên tiềm năng khám phá node chưa được thăm. Ví dụ: seed gần nhiều node chưa thăm → ưu tiên hơn.

Kỹ thuật Phân tích Động (Dynamic Analysis)

  • Dynamic Symbolic Execution: Kết hợp thực thi ký hiệu để hướng dẫn fuzzing
  • Coverage Feedback: Dùng thông tin coverage để chọn và biến đổi seed hiệu quả
  • Dynamic Taint Analysis: Theo dõi luồng dữ liệu taint để xác định vùng nhạy cảm

Coverage-based Fuzzing:

flowchart LR IS[Initial Seeds] --> SS[Seed Selection] SS --> PUT[Program Under Test] PUT --> AN[Analysis] AN --> CD{Coverage\nIncreased?} CD -- yes --> DB[Seed Database] CD -- no --> MU[Mutation] DB --> MU MU --> SS

Kỹ thuật Phân tích Tĩnh (Static Analysis)

Yêu cầu truy cập mã nguồn:

  • Control-flow Analysis: Phân tích đồ thị luồng điều khiển
  • Data-flow Slices: Cắt lát luồng dữ liệu để xác định vùng nguy hiểm

4.4 Ưu điểm và Nhược điểm


4.5 Một số công cụ Fuzzing phổ biến

Công cụMô tả
AFL (American Fuzzy Lop)Coverage-guided greybox fuzzer, rất phổ biến
LibFuzzerIn-process fuzzer của LLVM, tích hợp sanitizer
HonggfuzzFeedback-driven fuzzer hỗ trợ hardware coverage
OSS-FuzzGoogle’s continuous fuzzing service cho open source
ClusterFuzzDistributed fuzzing infrastructure của Google
Address SanitizerPhát hiện memory errors, thường kết hợp với fuzzer
ValgrindDynamic analysis tool, phát hiện memory leak

AFL đã tìm thấy lỗi trong hàng trăm dự án: OpenSSL, PHP, Mozilla Firefox, bash, nginx, tcpdump, ImageMagick, BIND, QEMU…


5. Symbolic Execution

5.1 Định nghĩa

Symbolic Execution (Thực thi Ký hiệu) là kỹ thuật phân tích chương trình trong đó các giá trị đầu vào không phải giá trị cụ thể mà là ký hiệu đại diện cho một tập hợp giá trị có thể có.

  • Thay vì chạy f(5), ta chạy f(sym_x) — sym_x đại diện cho mọi giá trị có thể
  • Tại mỗi nhánh rẽ (if/while), tạo ra path condition (điều kiện đường đi)
  • Sử dụng SMT solver (Z3, CVC4, STP) để tìm giá trị cụ thể thỏa mãn điều kiện đó

5.2 Ví dụ minh họa

def check_value(x):
    if x > 0:
        y = x + 1
    else:
        y = x - 1
    return y

Symbolic execution với x = sym_x:

Đường dẫnPath ConditionKết quả
Đường 1sym_x > 0y = sym_x + 1
Đường 2sym_x <= 0y = sym_x - 1
void check_input(int x, int y) {
    if (x > 10) {
        if (y < 5) {
            // Condition 1 satisfied!
        } else {
            // Condition 2 satisfied!
        }
    } else {
        // Condition 3 satisfied!
    }
}
flowchart TD Start([sym_x, sym_y]) --> P1{sym_x > 10?} P1 -- true --> P2{sym_y < 5?} P1 -- false --> C3[Condition 3\nsym_x <= 10\nVD: x=10, y=bất kỳ] P2 -- true --> C1[Condition 1\nsym_x > 10 AND sym_y < 5\nVD: x=11, y=4] P2 -- false --> C2[Condition 2\nsym_x > 10 AND sym_y >= 5\nVD: x=11, y=6]

5.3 Giải constraint bằng Z3

from z3 import *

# Khởi tạo biến tượng trưng
x = Int('x')
y = Int('y')

# Điều kiện cho đường dẫn 1.1 (Condition 1 satisfied)
solver = Solver()
solver.add(x > 10, y < 5)

if solver.check() == sat:
    model = solver.model()
    print(f"Solution: x={model[x]}, y={model[y]}")
    # Output: x=11, y=4


5.4 Symbolic Execution-guided Fuzzing

Tại sao kết hợp?

  • Symbolic Execution đơn thuần: Path explosion — số đường đi tăng theo hàm mũ
  • Fuzzing đơn thuần: Không thể chạm đến các nhánh deep với điều kiện phức tạp
  • Kết hợp: Symbolic Execution tạo path condition → Solver tìm input → Fuzzing dùng input đó để kiểm thử
flowchart LR A[Program] --> B[Symbolic Execution] B --> C[Path Conditions] C --> D[SMT Solver\nZ3/CVC4/STP] D --> E[Concrete Input Values] E --> F[Fuzzer\nAFL/LibFuzzer] F --> G[Bug Detection]

Lợi ích:

  • Tăng coverage: khám phá nhiều đường đi hơn, kể cả đường hiếm
  • Tìm lỗi sâu hơn: buffer overflow, integer overflow, logic bug
  • Tối ưu hóa: giảm số lượng test case không hiệu quả

Hạn chế:

  • Path explosion: số path tăng theo hàm mũ với số nhánh
  • Tốn tài nguyên tính toán (solver, memory)
  • Khó áp dụng cho hệ thống có nhiều side-effect, I/O phức tạp

Công cụ hỗ trợ:

LoạiCông cụ
Symbolic ExecutionKLEE (C/C++), Manticore (EVM/x86/LLVM)
FuzzingAFL, LibFuzzer
Kết hợpS2E (Selective Symbolic Execution)


6. Câu hỏi Trắc nghiệm

Câu 1. Mục tiêu chính của kiểm thử phần mềm là gì?

  • A. Chứng minh phần mềm không có lỗi
  • B. Phát hiện lỗi, đảm bảo chất lượng và xác minh phần mềm đáp ứng yêu cầu
  • C. Tối ưu hóa hiệu năng phần mềm
  • D. Viết lại phần mềm từ đầu nếu có lỗi

Câu 2. Black Box Testing khác White Box Testing ở điểm nào cơ bản nhất?

  • A. Black Box nhanh hơn
  • B. Black Box không cần biết mã nguồn, White Box cần
  • C. White Box chỉ kiểm tra giao diện
  • D. Black Box chỉ dành cho security testing

Câu 3. Thứ tự đúng của các giai đoạn kiểm thử theo giai đoạn phát triển là:

  • A. System → Unit → Integration → Acceptance
  • B. Unit → Integration → System → Acceptance
  • C. Acceptance → System → Integration → Unit
  • D. Integration → Unit → System → Acceptance

Câu 4. Regression Testing được thực hiện khi nào?

  • A. Trước khi bắt đầu dự án
  • B. Sau khi có thay đổi trong mã nguồn, để đảm bảo tính năng cũ không bị ảnh hưởng
  • C. Chỉ khi phát hiện lỗi nghiêm trọng
  • D. Sau khi triển khai sản phẩm lên production

Câu 5. Basis Path Testing do ai đề xuất và vào năm nào?

  • A. Dijkstra, 1968
  • B. McCabe, 1976
  • C. Boehm, 1981
  • D. Myers, 1979

Câu 6. Trong CFG (Control Flow Graph), “Predicate Node” đại diện cho gì?

  • A. Một hàm được gọi
  • B. Một biểu thức điều kiện (if/while/for)
  • C. Điểm bắt đầu chương trình
  • D. Một vòng lặp vô hạn

Câu 7. Statement Coverage (phủ câu lệnh) yêu cầu gì?

  • A. Mỗi nhánh true/false đều được thực thi
  • B. Mỗi câu lệnh được thực thi ít nhất một lần
  • C. Mỗi path từ đầu đến cuối đều được thực thi
  • D. Mỗi biến được định nghĩa ít nhất một lần

Câu 8. Với hàm foo(a, b, c, d, e), test case foo(0,0,0,0,0) đạt được bao nhiêu % Statement Coverage nếu câu lệnh if(a==0) return 0 ở đầu hàm?

  • A. 100%
  • B. 75%
  • C. 42%
  • D. 50%

Câu 9. Branch Coverage yêu cầu gì thêm so với Statement Coverage?

  • A. Mỗi hàm phải được gọi ít nhất 2 lần
  • B. Mỗi biểu thức điều kiện phải được đánh giá cả true lẫn false
  • C. Tất cả paths phải được kiểm thử
  • D. Mỗi vòng lặp phải chạy ít nhất 10 lần

Câu 10. Condition Coverage khác Branch Coverage ở điểm nào?

  • A. Condition Coverage kiểm tra điều kiện đơn trong biểu thức phức
  • B. Condition Coverage chỉ áp dụng cho vòng lặp
  • C. Branch Coverage mạnh hơn Condition Coverage
  • D. Không có sự khác biệt

Câu 11. Trong Data-flow Testing, ký hiệu p-use của biến nghĩa là gì?

  • A. Biến được dùng trong phép tính (computation)
  • B. Biến được dùng trong biểu thức điều kiện (predicate)
  • C. Biến vừa được định nghĩa (pre-use)
  • D. Biến được print ra màn hình

Câu 12. Pattern anomaly nào trong Data-flow Testing là potential bug?

  • A. du (define → use)
  • B. uk (use → kill)
  • C. dd (define → define lại không dùng ở giữa)
  • D. ud (use → define lại)

Câu 13. DU Path của biến X từ câu lệnh S đến S’ là gì?

  • A. Bất kỳ đường đi nào từ S đến S'
  • B. Đường đi từ S đến S’ không có định nghĩa lại của X ở giữa
  • C. Đường đi ngắn nhất từ S đến S'
  • D. Đường đi từ S đến S’ qua tất cả các node

Câu 14. Chiến lược kiểm thử luồng dữ liệu nào mạnh nhất (bao phủ nhiều nhất)?

  • A. All-Defs
  • B. All-Uses
  • C. All-DU-Paths
  • D. All-P-Uses

Câu 15. Equivalence Partitioning (EP) giúp giải quyết vấn đề gì trong kiểm thử?

  • A. Tăng tốc độ thực thi chương trình
  • B. Giảm số lượng test case không cần thiết trong khi vẫn đảm bảo coverage
  • C. Tự động sinh test case từ mã nguồn
  • D. Phát hiện lỗi bảo mật trong mã nguồn

Câu 16. Khi xếp loại học lực theo điểm [0,10], phân vùng tương đương cho bài toán này có bao nhiêu phân vùng (gồm cả valid và invalid)?

  • A. 5
  • B. 6
  • C. 7
  • D. 8

Câu 17. Standard BVA với hàm có 3 biến độc lập tạo ra bao nhiêu test case?

  • A. 9
  • B. 13
  • C. 15
  • D. 25

Câu 18. Robust Testing mở rộng Standard BVA bằng cách nào?

  • A. Thêm test case ngẫu nhiên
  • B. Thêm giá trị min-1max+1 (ngoài miền hợp lệ) cho mỗi biến
  • C. Test tất cả tổ hợp biên cùng lúc
  • D. Tăng gấp đôi số test case

Câu 19. Worst-case Testing với n=2 biến tạo bao nhiêu test case?

  • A. 9
  • B. 25
  • C. 49
  • D. 13

Câu 20. BVA hiệu quả nhất trong trường hợp nào?

  • A. Các biến đầu vào có quan hệ ràng buộc nhau phức tạp
  • B. Các biến đầu vào độc lập nhau và mỗi biến có miền giá trị hữu hạn
  • C. Chương trình không có điều kiện rẽ nhánh
  • D. Chương trình xử lý chuỗi ký tự phức tạp

Câu 21. Fuzzing là gì?

  • A. Kiểm thử phần mềm bằng cách đọc mã nguồn
  • B. Tự động gửi dữ liệu không hợp lệ/ngẫu nhiên vào chương trình để phát hiện lỗi
  • C. Phân tích tĩnh mã nguồn để tìm lỗ hổng
  • D. Kiểm thử hiệu năng dưới tải cao

Câu 22. Fuzzing thuộc loại kiểm thử nào theo mặc định?

  • A. White Box Testing
  • B. Gray Box Testing
  • C. Black Box Testing
  • D. Unit Testing

Câu 23. Mutation-based Fuzzing khác Generation-based Fuzzing như thế nào?

  • A. Mutation dùng AI, Generation dùng random
  • B. Mutation biến đổi input mẫu có sẵn, Generation tạo input từ đặc tả/ngữ pháp
  • C. Mutation chỉ dùng cho network protocol, Generation cho file format
  • D. Không có sự khác biệt đáng kể

Câu 24. AFL (American Fuzzy Lop) sử dụng kỹ thuật nào để hướng dẫn fuzzing?

  • A. Symbolic Execution
  • B. Coverage-guided greybox fuzzing (feedback từ code coverage)
  • C. Pure random mutation không feedback
  • D. Static analysis

Câu 25. Loại lỗi nào Fuzzing kém hiệu quả nhất trong phát hiện?

  • A. Buffer overflow
  • B. Memory leak
  • C. Logic error trong virus/worm không gây crash
  • D. Null pointer dereference

Câu 26. Trong quy trình Fuzzing, thành phần nào có nhiệm vụ phân biệt lỗi thật và false positive?

  • A. Test Case Generator
  • B. Monitor
  • C. Bug Filter
  • D. Seed Corpus

Câu 27. Grammar-based Fuzzing (Generation-based) có lợi thế gì so với random mutation?

  • A. Nhanh hơn nhiều lần
  • B. Tạo ra input có cấu trúc đúng định dạng, dễ pass qua parser để test logic sâu hơn
  • C. Không cần seed corpus
  • D. Tự động phát hiện ngữ pháp của target

Câu 28. Address Sanitizer (ASan) thường được kết hợp với fuzzer để làm gì?

  • A. Tăng tốc độ fuzzing
  • B. Phát hiện memory errors (buffer overflow, use-after-free) trong quá trình fuzzing
  • C. Tạo corpus seed tự động
  • D. Phân tích mã nguồn trước khi fuzzing

Câu 29. Symbolic Execution khác với kiểm thử thông thường ở điểm cơ bản nào?

  • A. Chạy chương trình nhanh hơn
  • B. Biến đầu vào là ký hiệu (symbol) thay vì giá trị cụ thể, phân tích toàn bộ không gian đầu vào
  • C. Không cần mã nguồn
  • D. Chỉ áp dụng cho chương trình Python

Câu 30. “Path Condition” trong Symbolic Execution là gì?

  • A. Số lượng đường dẫn trong chương trình
  • B. Tập hợp các ràng buộc (constraints) phải thỏa mãn để thực thi một đường đi cụ thể
  • C. Điều kiện để chương trình biên dịch thành công
  • D. Đường dẫn file của chương trình đang kiểm thử

Câu 31. SMT Solver được sử dụng trong Symbolic Execution để làm gì?

  • A. Biên dịch chương trình
  • B. Tìm giá trị đầu vào cụ thể thỏa mãn path condition
  • C. Tối ưu hóa mã nguồn
  • D. Quản lý bộ nhớ chương trình

Câu 32. Với đoạn code sau, Symbolic Execution tạo ra bao nhiêu đường dẫn?

def f(x, y):
    if x > 10:
        if y < 5:
            return 1
        else:
            return 2
    else:
        return 3
  • A. 2
  • B. 3
  • C. 4
  • D. 5

Câu 33. Vấn đề “Path Explosion” trong Symbolic Execution nghĩa là gì?

  • A. Chương trình bị crash trong quá trình phân tích
  • B. Số lượng đường dẫn tăng theo hàm mũ với số điều kiện rẽ nhánh → không thể duyệt hết
  • C. Bộ nhớ bị tràn khi chạy solver
  • D. Các path condition mâu thuẫn nhau

Câu 34. Tại sao kết hợp Symbolic Execution với Fuzzing lại hiệu quả hơn từng phương pháp riêng lẻ?

  • A. Vì hai phương pháp cùng ngôn ngữ lập trình
  • B. Symbolic Execution tạo input cho các nhánh khó trigger, Fuzzing khám phá rộng nhanh → bù đắp nhược điểm nhau
  • C. Vì chi phí thực hiện giảm đi
  • D. Vì không cần SMT solver khi kết hợp

Câu 35. Công cụ KLEE được sử dụng cho mục đích gì?

  • A. Fuzzing network protocols
  • B. Symbolic Execution cho chương trình C/C++
  • C. Static analysis cho Java
  • D. Coverage measurement

Câu 36. Coverage-based Fuzzing (như AFL) hoạt động theo nguyên tắc nào?

  • A. Luôn chọn seed ngắn nhất để fuzz
  • B. Giữ lại và ưu tiên seed tạo ra code coverage mới, loại bỏ seed không tạo coverage mới
  • C. Chọn seed ngẫu nhiên hoàn toàn
  • D. Chỉ fuzz các hàm có annotation @fuzz

Câu 37. Concolic Execution là gì?

  • A. Symbolic Execution chỉ cho các vòng lặp
  • B. Kết hợp Concrete execution và Symbolic execution: chạy cụ thể nhưng theo dõi ký hiệu song song
  • C. Symbolic Execution phân tán trên nhiều máy
  • D. Symbolic Execution chỉ cho điều kiện đơn giản

Câu 38. Trong Data-flow Testing, “All-Uses” strategy yêu cầu gì?

  • A. Tất cả các vòng lặp phải được thực thi
  • B. Mỗi cặp DU (definition-use) của mỗi biến phải có ít nhất một test path
  • C. Mỗi biến phải được sử dụng ít nhất một lần
  • D. Tất cả các hàm phải được gọi

Câu 39. Tester có kinh nghiệm khác tester ít kinh nghiệm trong ví dụ kiểm thử ktNguyenTo ở chỗ nào?

  • A. Tester kinh nghiệm test nhiều giá trị hơn
  • B. Tester kinh nghiệm dùng phân vùng tương đương để thiết kế test case có hệ thống, bao gồm cả invalid input
  • C. Tester kinh nghiệm chỉ test số nguyên tố
  • D. Tester kinh nghiệm test bằng cách đọc mã nguồn

Câu 40. Phần mềm nào sau đây KHÔNG phải là một fuzzer?

  • A. AFL (American Fuzzy Lop)
  • B. Honggfuzz
  • C. Valgrind
  • D. LibFuzzer

Câu 41. Scheduling Algorithm trong Fuzzing giải quyết vấn đề gì?

  • A. Lên lịch chạy test tự động hàng ngày
  • B. Quyết định seed nào trong corpus nên được fuzz tiếp theo để tối đa hóa khả năng khám phá code mới
  • C. Phân phối công việc fuzzing trên nhiều máy
  • D. Tối ưu thứ tự các biến thể mutation

Câu 42. Dynamic Taint Analysis trong Fuzzing giúp gì?

  • A. Phát hiện SQL injection tự động
  • B. Theo dõi luồng dữ liệu từ input đến các điểm nhạy cảm, xác định phần nào của input ảnh hưởng đến điều kiện rẽ nhánh
  • C. Mã hóa dữ liệu trong quá trình fuzzing
  • D. Tạo báo cáo lỗi tự động

Câu 43. Số test case Robust Worst-case Testing với n=2 biến là bao nhiêu?

  • A. 25
  • B. 36
  • C. 49
  • D. 64

Câu 44. Câu nào đúng về mối quan hệ giữa Statement Coverage và Branch Coverage?

  • A. 100% branch coverage đảm bảo 100% statement coverage
  • B. 100% statement coverage đảm bảo 100% branch coverage
  • C. Hai loại coverage không liên quan nhau
  • D. Branch coverage luôn bằng statement coverage

Câu 45. Data-flow Testing phát hiện được những loại lỗi nào mà Control-flow Testing có thể bỏ qua?

  • A. Lỗi chia cho 0
  • B. Biến khai báo nhưng không dùng, dùng biến chưa khởi tạo, gán đè giá trị chưa dùng
  • C. Buffer overflow
  • D. SQL injection

Câu 46. Trong tương lai của Fuzzing, AI/ML được tích hợp chủ yếu để làm gì?

  • A. Thay thế hoàn toàn lập trình viên
  • B. Làm fuzzer thông minh hơn trong việc chọn seed, mutation strategy và hướng exploration
  • C. Tự động vá lỗi phát hiện được
  • D. Tạo báo cáo kiểm thử tự động

Câu 47. Với chương trình có biến unsigned 32-bit, nếu chỉ 1 trong 2^32 giá trị kích hoạt một nhánh đặc biệt, fuzzing ngẫu nhiên cần kỳ vọng bao nhiêu lần thử?

  • A. ~1,000
  • B. ~1,000,000
  • C. ~2,147,483,648 (2^31)
  • D. ~4,294,967,296 (2^32)

Câu 48. S2E (Selective Symbolic Execution) giải quyết vấn đề path explosion như thế nào?

  • A. Giới hạn thời gian chạy solver
  • B. Chỉ thực thi ký hiệu (symbolic) cho phần code quan tâm, phần còn lại chạy concrete
  • C. Bỏ qua các vòng lặp
  • D. Chạy song song nhiều instance

Câu 49. Điểm yếu lớn nhất của Black Box Testing là gì?

  • A. Quá tốn thời gian
  • B. Không thể phủ hết mọi trường hợp và khó thiết kế test case hiệu quả khi không biết cấu trúc bên trong
  • C. Yêu cầu mã nguồn
  • D. Chỉ dùng được cho ứng dụng web

Câu 50. Đâu là lý do chính khiến Fuzzing tìm được nhiều lỗi bảo mật quan trọng mà kiểm thử thông thường bỏ qua?

  • A. Fuzzing chạy nhanh hơn kiểm thử thông thường
  • B. Fuzzing tự động tạo ra hàng triệu input ngẫu nhiên, kể cả các trường hợp biên cực kỳ bất ngờ mà tester thủ công không nghĩ tới
  • C. Fuzzing đọc được mã nguồn và hiểu logic
  • D. Fuzzing dùng AI nên thông minh hơn

Câu 51. Trong Symbolic Execution, khi gặp điều kiện while(x > 0), vấn đề gì xảy ra?

  • A. Chương trình dừng vô điều kiện
  • B. Vòng lặp được unroll vô tận tạo ra vô số path → loop không thể handle tốt bởi pure symbolic execution
  • C. Symbolic execution bỏ qua vòng lặp
  • D. Symbolic execution convert vòng lặp thành đệ quy

Câu 52. Manticore được dùng để Symbolic Execution cho loại chương trình nào?

  • A. Chỉ cho Python scripts
  • B. EVM (Ethereum Virtual Machine), x86, LLVM IR
  • C. Chỉ cho Java bytecode
  • D. Chỉ cho Windows PE binaries

Câu 53. Phương pháp kiểm thử nào phù hợp nhất để kiểm tra xem các module đã hoạt động đúng với nhau chưa sau khi tích hợp?

  • A. Unit Testing
  • B. Acceptance Testing
  • C. Integration Testing
  • D. Usability Testing

Câu 54. Công thức Worst-case Testing tạo ra 125 test case ứng với bao nhiêu biến?

  • A. 2
  • B. 3
  • C. 4
  • D. 5

Câu 55. Tại sao performance testing (kiểm thử hiệu suất) được phân loại vào “non-functional testing”?

  • A. Vì nó không cần mã nguồn
  • B. Vì nó không kiểm tra chức năng logic mà kiểm tra thuộc tính hành vi như tốc độ, khả năng chịu tải
  • C. Vì nó được thực hiện tự động
  • D. Vì nó không cần test case