Có một lộ trình chuyển từ Manual Tester sang Automation Tester khá phổ biến:
- Cài Selenium hoặc Playwright.
- Học cách tìm element.
- Click được nút Đăng nhập.
- Đăng ảnh report xanh lên mạng.
- Ba tuần sau nút Đăng nhập đổi một cái
id, nguyên bộ test đỏ như cây thông Noel.
Tới đây nhiều người kết luận automation khó, tool này dở, tool kia mới là chân ái. Xoá project, cài tool mới, lại click nút Đăng nhập từ đầu. Một vòng luân hồi khá tốn RAM.
Vấn đề thường không nằm ở cái nút. Nó nằm ở việc chúng ta đem nguyên cách làm manual sang code: thấy gì làm nấy, case nào viết case đó, chạy được là mừng, còn sáu tháng sau ai bảo trì thì... sáu tháng sau tính.
Vì thế bài đầu tiên trong series Từ Manual Tới Fullstack chưa vội dạy bạn viết một dòng code nào. Mình muốn nói về thứ phải đổi trước khi chọn ngôn ngữ, tool hay design pattern: tư duy.
Kiểm thử tự động là gì?
Nói ngắn gọn, kiểm thử tự động là dùng phần mềm để thực hiện thao tác, thu thập kết quả và so sánh kết quả thực tế với kết quả mong đợi theo một kịch bản đã được định nghĩa.
Chỗ cần để ý là đã được định nghĩa. Tool không tự biết đăng nhập thành công nghĩa là gì. URL đổi sang /dashboard, tên người dùng xuất hiện, API trả 200, token được lưu hay database có session mới? Người viết test phải chọn tín hiệu nào đủ để kết luận. Nếu test oracle sai thì máy có chạy nhanh đến mấy cũng chỉ giúp mình sai với tốc độ cao hơn.
Chú thích khái niệm: Test oracle là căn cứ dùng để quyết định kết quả đúng hay sai. Nó có thể đến từ requirement, business rule, một phép tính đã biết hoặc trạng thái hệ thống đã được xác nhận.
Nhìn từ phía hệ thống: cũng là từng ấy tác động
Giả sử bạn test chức năng đăng nhập bằng tay:
- Mở trình duyệt.
- Nhập email và password.
- Bấm nút Đăng nhập.
- Trình duyệt gửi request HTTP tới server.
- Server kiểm tra dữ liệu rồi trả response.
Chú thích khái niệm: Nôm na, HTTP là cách browser và server nói chuyện với nhau. Browser gửi một request để yêu cầu làm gì đó, server trả một response cho biết kết quả. Địa chỉ cụ thể nhận request, ví dụ
POST /api/login, thường được gọi là endpoint. Chỉ cần hiểu phía sau một cú click thường là một cuộc trao đổi mà mắt người dùng không nhìn thấy.
UI automation cũng đi con đường ấy. Selenium hay Playwright điều khiển trình duyệt nhập dữ liệu và bấm nút, sau đó trình duyệt vẫn gửi request HTTP. Server không ngồi bên kia đoán xem nút vừa được ngón tay bấm hay bị WebDriver chọc vào. Tác động mà hệ thống nhận được về cơ bản vẫn là các request, event và thay đổi dữ liệu.
API automation đi tắt một đoạn: thay vì mở UI rồi chờ trình duyệt tạo request, test gọi thẳng endpoint. Nó không kiểm tra được nút có lệch sang bên hàng xóm hay không, nhưng kiểm tra business rule và response nhanh hơn, ít phụ thuộc giao diện hơn.
Vậy nhìn từ hệ thống, manual và automation không phải hai loại phép thuật đối nghịch. Chúng cùng tác động qua những interface mà hệ thống cung cấp. Khác biệt nằm ở chỗ ai hoặc cái gì tạo ra tác động đó, quan sát ở lớp nào và thu evidence ra sao.
Nhìn từ phía người làm: khác rất nhiều
Manual testing dựa nhiều vào quan sát và khả năng thích nghi tại thời điểm test. Popup hiện bất ngờ thì tester đọc, đóng nó rồi đi tiếp. Label đổi từ “Đăng nhập” thành “Tiếp tục” thì mắt nhìn ra ngay. Thấy tổng tiền hơi đáng nghi, tester có thể dừng lại, mở DevTools và đào thêm.
Automation không linh hoạt kiểu đó nếu mình chưa dạy nó. Script cần locator, dữ liệu, điều kiện chờ, assertion và cách xử lý state đủ rõ. Đổi lại, khi đã viết ổn, script có thể chạy lại cùng một flow hàng chục, hàng trăm lần mà không tới lần thứ 37 tự nhiên quên kiểm tra toast vì đang nghĩ trưa nay ăn gì.
Chú thích khái niệm: Locator là cách script xác định đúng element cần thao tác. Assertion là câu kiểm tra kết quả thực tế có khớp điều mình mong đợi hay không. State là trạng thái hiện tại của hệ thống, ví dụ user đang đăng nhập, account đang bị khoá hay order đã được thanh toán.
Chuyển sang automation vì thế không phải đổi từ click bằng tay sang click bằng code. Chúng ta phải biến suy nghĩ đang nằm trong đầu tester thành thứ máy có thể thực hiện lặp lại, đồng thời biến project test thành một sản phẩm code cần được thiết kế, review và bảo trì.
UI, API và mô hình lai
Có thể tạm chia automation theo interface mà test tác động vào:
- UI automation điều khiển giao diện web hoặc mobile gần với hành vi người dùng. Nó phù hợp để kiểm tra các critical flow xuyên qua nhiều lớp, nhưng thường chậm và nhạy với thay đổi giao diện.
- API automation gọi trực tiếp HTTP API, kiểm tra status, header, body, schema và business rule. Nó chạy nhanh hơn, bóc lỗi dễ hơn, nhưng không nói được UI đang hiển thị đúng hay nút có bấm được hay không.
- Mô hình lai dùng đúng lớp cho đúng việc. Ví dụ tạo dữ liệu và đăng nhập qua API, sau đó mở UI để kiểm tra checkout; hoặc test case đi qua một framework nội bộ kiểu MDF, bên dưới có thể dùng Appium cho thao tác mobile và service/API cho setup, cleanup.
Chú thích khái niệm: Trong một API response, status cho biết request thành công hay lỗi, header chứa thông tin đi kèm, body là dữ liệu chính server trả về, còn schema mô tả dữ liệu đó phải có cấu trúc thế nào.
MDF ở đây nên được hiểu theo implementation cụ thể của team, không phải một chuẩn chung mà cứ cài package là có. Cái đáng quan tâm là lớp abstraction đó giải quyết vấn đề gì. MDF có thể giúp dùng chung flow cho Android và iOS, che bớt chi tiết của Appium, quản lý device hay kết hợp UI với API. Tên viết tắt kêu mấy mà không trả lời được câu này thì vẫn hơi ảo ma Canada.
Chú thích khái niệm: Nói nôm na, abstraction là dựng một lớp dễ dùng ở phía trên để người viết test không phải chạm vào mọi chi tiết lằng nhằng phía dưới.
Công cụ kiểm thử tự động
Chữ framework trong automation bị dùng khá thoải mái. JUnit là framework, Selenium cũng bị gọi là framework, bộ code do team tự dựng cũng tên framework. Gọi thế không hẳn sai trong mọi ngữ cảnh, nhưng nếu không tách vai trò thì lúc chọn giải pháp rất dễ ông nói gà, bà cài thêm dependency.
Test framework lo chuyện tổ chức và chạy test
JUnit, TestNG và pytest thuộc nhóm test framework. Chúng cho chúng ta những thứ như:
- Cách khai báo một test.
- Assertion và trạng thái pass, fail, skip.
- Setup, teardown, fixture hoặc hook.
- Gom nhóm, lọc và parametrise test.
- Chạy song song, retry hoặc tích hợp report tuỳ framework và plugin.
Chú thích khái niệm: Setup là việc chuẩn bị trước khi test chạy, teardown là dọn dẹp sau khi chạy. Fixture đóng gói và cấp những thứ test cần, ví dụ browser, user hoặc dữ liệu. Hook là điểm móc để chạy một đoạn xử lý trước hoặc sau test, suite hay một sự kiện nhất định. Tên gọi giữa các framework có thể khác nhau, nhưng về cơ bản chúng giúp mình đỡ chép lại cảnh mở cửa, kê bàn rồi quét nhà trong từng test case.
Chúng biết @Test, assert hay fixture có nghĩa gì, nhưng tự thân không biết bấm nút trên Chrome hoặc vuốt màn hình Android. Đưa cho JUnit một locator XPath rồi nhìn nó chằm chằm cũng không làm nút tự được click.
Automation tool hoặc library lo chuyện chạm vào hệ thống
Selenium và Playwright điều khiển browser. Appium tự động hoá UI trên mobile và nhiều nền tảng ứng dụng khác. REST Assured cung cấp API Java để gửi request và kiểm tra REST API. Karate có DSL cho API testing, assertion, mock và thêm các khả năng khác trong cùng hệ sinh thái.
Một vài combo thường gặp:
| Nhu cầu | Test framework | Tool/library tương tác |
|---|---|---|
| Web UI với Java | JUnit hoặc TestNG | Selenium |
| API với Java | JUnit hoặc TestNG | REST Assured |
| Web/mobile với Python | pytest | Selenium hoặc Appium client |
| Mobile với Java | JUnit hoặc TestNG | Appium client |
Rồi có những lựa chọn trông gần kiểu all-in-one hơn. Với TypeScript hoặc JavaScript, Playwright Test có sẵn runner, fixture, assertion, report, browser automation và cả APIRequestContext để gọi API. Karate cung cấp DSL, assertion, khả năng gọi HTTP, mock và chạy qua JUnit hoặc CLI; một project API có thể không cần ghép thủ công nhiều mảnh như combo REST Assured + JUnit.
“All-in-one” không có nghĩa một tool cân được mọi mặt trận. Playwright rất mạnh cho web và API, nhưng không vì thế mà biến thành lựa chọn mặc định cho native mobile. Karate có UI automation, nhưng việc team đã giỏi Karate API không tự động chứng minh nó là đáp án tốt nhất cho mọi web UI phức tạp. Appium chạm được mobile, nhưng vẫn cần test runner, kiến trúc project, report và cách quản lý device quanh nó.
Phân biệt các vai trò này giúp mình hỏi đúng câu:
- Team cần chạy và tổ chức test bằng gì?
- Cần tương tác với web, API hay native mobile?
- Tool nào đã bao gồm runner và assertion, tool nào chỉ cung cấp client API?
- Khi ghép thêm UI, API hoặc mobile, phần nào dùng chung được và phần nào phải tách riêng?
Nếu không hỏi, chúng ta rất dễ chọn một tool vì tutorial đăng nhập dài tám phút chạy ngon, rồi tới lúc cần test OTP trên hai hệ điều hành mới phát hiện đoạn hay nhất của tutorial là nhạc nền.
Automation có đáng tiền không?
Automation không miễn phí. Nó chỉ chuyển chi phí từ “người ngồi làm lại” sang “người xây, chạy và bảo trì thứ sẽ làm lại”.
Một test manual có thể mất mười phút. Viết automation cho nó có thể mất vài giờ hoặc vài ngày, cộng thêm thời gian dựng môi trường, tạo data, nối CI, đọc report và sửa khi sản phẩm đổi. Nếu flow chỉ chạy đúng một lần rồi bỏ, automation có khi là mua máy rửa bát để rửa một cái thìa.
Phần hoàn vốn nằm ở sự lặp lại. Regression suite chạy mỗi release, smoke test chạy trên nhiều môi trường, cùng một bộ rule cần thử với nhiều data hoặc nhiều browser: số lần chạy càng nhiều, chi phí đầu tư ban đầu càng có cơ hội được chia nhỏ. Máy còn chạy được vào ban đêm, miễn là môi trường test không ngủ trước nó.
Đó là lúc chúng ta phải nhìn vào ROI. Automation có ROI tốt không phải vì report nhìn xịn, mà vì phần giá trị thu về qua nhiều lần chạy vượt được chi phí xây và nuôi nó.
Chú thích khái niệm: ROI là viết tắt của Return on Investment, nôm na là bỏ vào bao nhiêu công và tiền, sau đó thu lại được bao nhiêu giá trị.
Muốn nói ROI cho tử tế, ít nhất phải tính:
Giá trị tiết kiệm được qua các lần chạy - chi phí xây dựng - chi phí bảo trì - chi phí vận hành
Đừng chỉ so thời gian một lần chạy automation với một lần chạy manual rồi tuyên bố tiết kiệm 90%. Hai phút chạy xanh không kể cho bạn nghe ba ngày dựng framework và hai giờ sáng thứ Hai sửa test data bị người khác dùng mất.
“Có thể tự động hoá” khác “nên tự động hoá”
Về kỹ thuật, khá nhiều thứ có thể automation nếu thời gian và ngân sách đủ rộng. Câu hỏi có ích hơn là có nên hay không.
Một candidate thường đáng tự động hoá khi:
- Được chạy lặp lại nhiều lần.
- Kết quả mong đợi rõ và máy kiểm tra được.
- Flow tương đối ổn định.
- Nếu sai sẽ tạo risk đáng kể.
- Chi phí chuẩn bị data và môi trường không nuốt hết lợi ích.
Ngược lại, flow đang thay đổi mỗi tuần, chỉ chạy một lần, cần đánh giá thẩm mỹ hoặc cần con người khám phá điều chưa biết thì manual có thể kinh tế hơn. Không tự động hoá một case không phải thất bại đạo đức. Đôi lúc đó chỉ là biết dùng tiền đúng chỗ.
CAPTCHA là ví dụ rất đẹp. Nó được tạo ra để phân biệt người với bot. Dùng bot giải CAPTCHA trong mọi regression run nghĩa là mình đang bỏ tiền xây một cỗ máy đánh nhau với đúng cơ chế chống cỗ máy.
Thay vì ngồi nghiên cứu cách cho Selenium nhận diện vạch qua đường, QA có thể trao đổi với dev để tạo một cơ chế riêng cho môi trường test. Ví dụ, dev mock dịch vụ CAPTCHA để nó trả về kết quả pass hoặc fail theo dữ liệu mình chọn. Cách đơn giản hơn nữa là tắt CAPTCHA thật ở môi trường test và thay nó bằng một checkbox CAPTCHA passed. Automation chỉ cần tích vào ô đó để đi tiếp; muốn test nhánh CAPTCHA thất bại thì để trống hoặc chọn một giá trị fail.
Như vậy regression suite vẫn kiểm tra được hệ thống xử lý thế nào khi CAPTCHA pass hoặc fail, nhưng không phải gọi dịch vụ thật và cũng không phải dạy con bot giả vờ làm người. Phần tích hợp với CAPTCHA thật có thể được giữ trong một nhóm test riêng nếu risk yêu cầu, thay vì bắt toàn bộ suite đi giải đố mỗi lần chạy.
Đương nhiên, checkbox hoặc đường bypass này chỉ được bật bằng cấu hình dành cho môi trường test và không được tồn tại trên production. Nếu production cũng có ô CAPTCHA passed thì automation chạy rất mượt, hacker chắc cũng đánh giá trải nghiệm người dùng năm sao.
Chú thích khái niệm: Mock hoặc stub nghĩa là thay dependency thật bằng một phiên bản test trả về kết quả mình kiểm soát được. Bypass cho phép flow test đi vòng qua một chốt kiểm tra theo cách đã được giới hạn ở môi trường test. Cả hai không có nghĩa xoá security cho tiện rồi cầu mong file config không lên production. Chúng phải được thiết kế, giới hạn quyền và tách khỏi môi trường thật.
OTP cũng tương tự. Thay vì mỗi lần regression lại ngồi chờ SMS rồi đọc code từ một chiếc điện thoại đang cắm ở góc phòng, team có thể stub nhà cung cấp, dùng test endpoint hoặc cơ chế lấy OTP dành riêng cho môi trường test. Mục tiêu của phần lớn test là kiểm tra flow xác thực của hệ thống, không phải kiểm tra nhà mạng hôm nay có tâm trạng gửi tin nhắn hay không.
Với OCR hoặc face recognition, business flow thông thường có thể mock kết quả nhận diện để kiểm tra các nhánh pass, fail và retry một cách ổn định. Bản thân model OCR hoặc face recognition vẫn phải được test bằng bộ dữ liệu kiểm soát, threshold và metric riêng. Mock dependency không có nghĩa bỏ test dependency; nó chỉ ngăn một bài test checkout phải kiêm luôn nghề giám khảo thị lực.
Thực hiện kiểm thử tự động như thế nào?
Sau khi chọn được thứ đáng automation, lúc này mới tới code.
Có runner, có tool, rồi dựng project cho đàng hoàng
Một project cơ bản cần test framework để tổ chức việc chạy test và automation tool/library để tương tác với hệ thống. Sau đó team dựng cấu trúc quanh chúng sao cho test case không ôm hết mọi trách nhiệm.
Ví dụ một project có thể tách như sau:
tests/ # Kịch bản và assertion ở mức business
pages/ # Tương tác với từng page hoặc component UI
services/ # API client và nghiệp vụ theo service
fixtures/ # Khởi tạo state, user, browser, device
data/ # Test data có kiểm soát
config/ # Cấu hình theo môi trường
Không phải project nào cũng cần đúng sáu folder này. Project có năm test mà dựng mười bảy layer thì không phải scalable, nó là nhà ba tầng xây cho một con mèo. Ý chính là nơi nào giữ locator, nơi nào gọi API, nơi nào chứa test data và nơi nào đưa ra assertion phải đủ rõ để người sau tìm được.
Tư duy tái sử dụng cũng không phải thấy ba dòng giống nhau là tạo ngay CommonUtilsHelperManagerFactory. Hãy gom những hành vi có cùng ý nghĩa và cùng lý do thay đổi. loginAs(user) có thể đáng tái sử dụng vì nhiều test cùng cần đăng nhập. Một hàm nhận mười hai boolean để vừa login, vừa tạo order, vừa chụp ảnh và đôi khi logout thì đã tái sử dụng tới mức không ai dám sử dụng nữa.
Page Object Model cho UI, service layer cho API
Chú thích khái niệm: OOP là viết tắt của Object-Oriented Programming, hay lập trình hướng đối tượng. Nôm na, đây là cách tổ chức code quanh các object; mỗi object gom dữ liệu nó quản lý và những hành động liên quan vào cùng một chỗ. Tưởng tượng thay vì vứt chìa khoá, đăng ký xe và hướng dẫn lái ở ba phòng khác nhau, mình gom chúng quanh object
Car. OOP còn class, inheritance, encapsulation và nhiều thứ khác, nhưng bài này chưa cần bưng cả giáo trình vào.
Với UI, Page Object Model áp dụng cách tổ chức của OOP bằng việc gom locator và thao tác của một page hoặc component vào một object. Test gọi loginPage.loginAs(user) thay vì tự tìm email input, password input và nút submit ở mọi file. Khi UI đổi locator, mình có một chỗ hợp lý để sửa.
POM không nên nuốt luôn toàn bộ business flow. Nếu một CheckoutPage vừa chọn sản phẩm, tạo user qua database, gọi API lấy voucher, thanh toán rồi verify email thì Page Object đã từ “đại diện một trang” tiến hoá thành trưởng phòng tổng hợp.
Với API, cách tương tự là tổ chức client theo service hoặc resource: AuthService, OrderService, PaymentService. Mỗi service biết endpoint, auth, request/response mapping; test vẫn giữ scenario và assertion mang ý nghĩa business. Phần thiết kế POM và service layer có khá nhiều chỗ dễ làm quá tay, nên mình sẽ đào riêng trong bài viết khác.
Thiết kế từ nhu cầu, không từ tên tool
Giả sử sản phẩm chỉ có API. Dựng Selenium Grid vì “framework xịn phải có UI” là vẽ rắn thêm chân. Nếu sản phẩm là native mobile trên Android và iOS, REST Assured chạy nhanh đến mấy cũng không kiểm tra được gesture, permission hay keyboard che mất nút.
Ngay trong mobile cũng có đánh đổi. Dùng Appium trực tiếp cho team quyền truy cập đầy đủ vào driver API, dễ tra documentation và debug gần nguồn. Đổi lại, test có thể dính nhiều capability, locator và khác biệt nền tảng.
Đi qua một lớp MDF của team có thể cho test case dùng keyword ở mức business, tái sử dụng flow và che bớt Android/iOS ở phía dưới. Đổi lại, mỗi abstraction là một lớp phải học, phải debug và phải bảo trì. Khi Appium có tính năng mới, MDF chưa expose thì team hoặc chờ, hoặc chọc thủng abstraction rồi từ đó project bắt đầu có hai đường đi.
Không có đáp án “framework tốt nhất” nằm ngoài bối cảnh. Có đáp án phù hợp hơn với app, năng lực team, CI, device farm, thời gian và mức bảo trì chấp nhận được. Chọn giải pháp là chọn cả phần lợi lẫn cục nợ đi kèm. Chỉ xem demo mà không xem cục nợ thì tới kỳ trả góp hơi bất ngờ.
Chú thích khái niệm: CI là viết tắt của Continuous Integration, một hệ thống tự build và chạy các kiểm tra khi code được đưa lên kho chung. Device farm là một nhóm thiết bị thật hoặc ảo được quản lý để chạy test mobile trên nhiều cấu hình.
Quy tắc tác nghiệp
Automation project là codebase nhiều người cùng sống trong đó. Một test chạy đúng trên máy mình mới là điều kiện nhập môn, chưa phải giấy chứng nhận công dân tốt.
Code cần dễ đọc, có naming thống nhất, assertion nói rõ mình đang kiểm tra gì và tránh copy-paste vô tội vạ. Nhưng tối ưu tái sử dụng cũng phải đi cùng khả năng hiểu và sửa. Đừng nén năm bước business vào một helper thần bí chỉ để test case còn ba dòng trông rất sang.
Trách nhiệm trong project nên được tách đủ rõ. Người sửa locator chủ yếu chạm vào page/component. Người thêm endpoint làm việc trong service tương ứng. Test data và config không rải mỗi file một ít. Khi hai người làm hai feature khác nhau mà đều phải sửa BaseTest.java dài 2.000 dòng, conflict không còn là sự cố; nó là lịch sinh hoạt.
Chú thích khái niệm: Conflict trong Git xảy ra khi Git không tự quyết định được phải ghép các thay đổi như thế nào, thường vì hai người cùng sửa một vùng code theo hai hướng khác nhau. Lúc đó con người phải chọn phần nào được giữ, phần nào bỏ hoặc viết lại để hai thay đổi sống chung. Conflict không phải lúc nào cũng do ai làm sai, nhưng một file khổng lồ mà cả team đều phải sờ vào sẽ mời conflict tới uống trà thường xuyên hơn.
Git convention cũng cần được thống nhất: tên branch dễ hiểu, commit nhỏ, một commit tập trung vào một thay đổi có nghĩa, message nói được mình vừa làm gì. Commit kiểu update code final final 2 có thể chạy, nhưng ba tháng sau git blame nhìn vào cũng chỉ biết người viết từng rất tuyệt vọng.
Chú thích khái niệm: Branch là nhánh làm việc tách ra để phát triển thay đổi. Commit là một mốc ghi lại nhóm thay đổi kèm lời mô tả.
git blamelà lệnh dùng để lần xem dòng code được đổi ở commit nào và bởi ai.
Commit nhỏ giúp review dễ hơn, revert ít đau hơn và truy ra thay đổi làm test toang nhanh hơn. Đừng gom đổi locator, format toàn project, nâng dependency và thêm 40 test vào cùng một cục rồi mời đồng đội “review giúp em, có tí thôi”.
Quy tắc mình muốn giữ cho series này khá đơn giản:
Hợp tác, hỗ trợ, không ngáng chân nhau.
Nếu một abstraction giúp bạn viết nhanh hơn nhưng làm ba người khác không debug nổi, nó chưa phải tối ưu cho team. Nếu một convention đẹp trên giấy nhưng ngày nào cũng khiến mọi người đi đường vòng, convention đó cần được sửa. Automation tốt không chỉ chạy được với hệ thống; nó còn phải sống được với những người đang cùng bảo trì nó.
Kết luận
Từ manual sang automation, thứ thay đổi lớn nhất không phải con chuột được thay bằng Selenium hay Playwright. Chúng ta bắt đầu nhìn test như code, nhìn framework như một giải pháp vận hành lâu dài và nhìn mỗi candidate automation bằng câu hỏi ROI.
Tool vẫn quan trọng. Chọn nhầm tool thì mệt thật. Nhưng chạy theo tool mà chưa biết mình cần test lớp nào, lặp lại bao nhiêu lần, ai sẽ bảo trì và chấp nhận đánh đổi gì thì cũng giống mua nguyên bộ đồ nghề rồi mới nghĩ xem nhà có thứ gì cần sửa không.
Automation bắt đầu có giá trị khi tư duy của mình chuyển từ “làm sao cho máy chạy case này?” sang “làm sao để cả hệ thống test chạy đúng, chạy lại được và không trở thành cục nợ của team?”
Code là đoạn sau. Đổi cách nghĩ mới là đoạn mở màn.