Tải bản đầy đủ (.docx) (23 trang)

Báo cáo: Tổng quan về TEST DRIVEN DEVELOPMENT

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 (439.65 KB, 23 trang )

NHÓM 8: TEST-DRIVEN DEVELOPMENT
BÁO CÁO BÀI TẬP LỚN
NGÀNH : CÔNG NGHỆ THÔNG TIN
ĐỀ TÀI: TEST DRIVEN DEVELOPMENT
DANH SÁCH NHÓM 8:
1.PHẠM ANH ĐẠT (Nhóm trưởng)
2.NGUYỄN THỊ LOAN
3.NGUYỄN THỊ ÁNH
Hà Nội, 15/10/2013
1
NHÓM 8: TEST-DRIVEN DEVELOPMENT
MỤC LỤC
2
NHÓM 8: TEST-DRIVEN DEVELOPMENT
NỘI DUNG CHÍNH
A. LÝ THUYẾT
I. Định nghĩa Test-driven Development
Test-driven Development (TDD) là một kỹ thuật phát triển phần mềm dựa trên sự lặp
lại của một chu kỳ phát triển: đầu tiên các nhà phát triển viết một automated test case xác
định một cải thiện mong muốn hoặc một chức năng mới, sau đó tạo ra một lượng tối
thiểu code để vượt qua test case đó, và cuối cùng cấu trúc lại mã mới với các tiêu chuẩn
chấp nhận được.
Phát triển dựa trên kiểm thử (Test-Driven Development-TDD) là một phương pháp
tiếp cận cải tiến để phát triển phần mềm trong đó kết hợp phương pháp Phát triển kiểm
thử trước (Test First Development) và phương pháp Điều chỉnh lại mã nguồn
(Refactoring). Có quan điểm cho rằng mục tiêu của TDD là đặc tả (specification) chứ
không phải là xác nhận tính đúng đắn (validation). Nói cách khác, đó là một cách để nghĩ
về thiết kế của bạn trước khi viết mã nguồn cho chức năng. Một quan điểm khác lại cho
rằng TDD là một kỹ thuật lập trình. Nhưng nhìn chung, mục tiêu của TDD là viết mã
nguồn sáng sủa, rõ ràng và có thể chạy được.
TDD hoàn toàn thay đổi cách phát triển truyền thống. Khi bạn bắt đầu thực hiện một


tính năng mới, câu hỏi đầu tiên đặt ra là liệu thiết kế hiện tại có phải là thiết kế tốt nhất
cho phép bạn thực hiện các chức năng hay không. Nếu có, bạn tiến hành thông qua một
phương pháp Phát triển kiểm thử trước TFD. Nếu không, bạn điều chỉnh lại nó một cách
cục bộ để thay đổi riêng phần thiết kế bị ảnh hưởng bởi tính năng mới, cho phép bạn dễ
dàng bổ thêm các tính năng có thể. Kết quả là chất lượng thiết kế của bạn sẽ luôn luôn
được nâng cao, do đó sẽ thuận lợi hơn khi làm việc với nó trong tương lai.
3
NHÓM 8: TEST-DRIVEN DEVELOPMENT
Một giả định cơ bản của TDD là bạn có sẵn một nền tảng (framework) cho kiểm thử
mức đơn vị (unit-test). Những lập trình viên phần mềm theo phương pháp Agile thường
sử dụng các công cụ mã nguồn mở thuộc họ xUnit, như JUnit hay PyUnit, mặc dù các
công cụ thương mại cũng là những lựa chọn khả dĩ. Nếu không có những công cụ như
vậy thì TDD hầu như không thể thực hiện được.
Hai nguyên tắc đơn giản cho TDD: Trước tiên, bạn nên viết mã xử lý nghiệp vụ mới
chỉ khi mẫu kiểm thử tự động thực hiện không thành công. Thứ hai, bạn nên loại bỏ bất
kỳ sự trùng lặp mà bạn tìm thấy. Những quy tắc đơn giản:
• Bạn thiết kế với mã nguồn mà chúng chạy được và tạo ra kết quả phản hồi giữa
các quyết định.
• Bạn viết các mẫu kiểm thử cho riêng bạn bởi vì bạn không thể chờ đợi 20 lần mỗi
ngày một người nào khác viết thay bạn.
• Môi trường phát triển của bạn phải cung cấp được kết quả nhanh với những thay
đổi nhỏ (ví dụ như bạn cần một trình biên dịch nhanh và chuỗi kiểm thử hồi quy
(regression test)
• Thiết kế của bạn phải bao gồm những thành phần gắn kết, sự phụ thuộc lẫn nhau
nhỏ (loosely coupled) để thực hiện các mẫu kiểm thử dễ dàng hơn (điều này cũng
làm cho quá trình nâng cấp và bảo trì các hệ thống của bạn dễ dàng hơn).
II. Tại sao một phần mềm nên phát triển theo hướng TDD?
1. TDD và cách kiểm thử truyền thống
TDD là một kỹ thuật thiết kế với một hiệu ứng phụ là việc đảm bảo toàn bộ mã
nguồn của bạn được thực hiện kiểm thử mức đơn vị. Tuy nhiên, có những điều còn quan

trọng hơn cả việc thực hiện kiểm thử. Bạn sẽ vẫn cần xem xét các kỹ thuật kiểm thử khác
như kiểm thử chấp nhận sản phẩm (acceptance test) hay kiểm thử dò hỏi (investigative
test) theo kiểu Agile. Bạn có thể thực hiện nhiều những kiểu kiểm thử này trong dự án
nếu như bạn chọn làm điều đó (và bạn nên làm).
4
NHÓM 8: TEST-DRIVEN DEVELOPMENT
Với kiểu kiểm thử truyền thống, một mẫu kiểm thử thành công sẽ tìm ra một hoặc
nhiều lỗi. Tương tự với TDD, khi một mẫu kiểm thử thất bại thì bạn cũng có sự tiến triển
bởi vì bây giờ bạn biết rằng bạn cần phải giải quyết một số vấn đề. Quan trọng hơn, bạn
có một cách đo rõ ràng về sự thành công khi mẫu kiểm thử không thất bại nữa. TDD tăng
niềm tin về hệ thống của bạn đáp ứng được các yêu cầu được định nghĩa cho nó, và hệ
thống của bạn đang hoạt động và do đó bạn có thể tiếp tục với một sự tự tin.
Như với thử nghiệm truyền thống, hệ thống càng có nhiều rủi ro lớn càng cần phải có
nhiều mẫu kiểm thử được thực hiện. Với cả hai kiểu kiểm thử truyền thống và TDD bạn
không phấn đấu cho sự hoàn hảo, thay vào đó bạn kiểm thử tầm quan trọng của hệ thống.
Một hiệu ứng phụ thú vị của TDD là bạn đạt được 100% khi kiểm thử độ phủ mã nguồn
(coverage test) - mọi dòng mã đều được kiểm thử - điều mà kiểm thử truyền thống không
bảo đảm dù cho nó khuyến khích điều đó. Không có gì ngạc nhiên khi nói rằng TDD là
một kỹ thuật đặc tả (specification technique), với một tác dụng phụ có giá trị là nó đem
lại kết quả trong việc kiểm thử mã nguồn tốt hơn đáng kể so với các kỹ thuật truyền
thống.
2. Tại sao dùng TDD?
Một lợi thế đáng kể của TDD là nó cho phép bạn thực hiện các bước nhỏ khi viết
phần mềm.Đây là một thực tế mà người ta đã phát huy trong nhiều năm qua bởi vì nó
mang lại hiệu quả nhiều hơn so với cố gắng viết mã trong những bước lớn. Ví dụ, giả sử
bạn thêm một số mã nguồn cho chức năng mới, biên dịch, và kiểm thử nó. Khả năng rất
lớn là các kiểm thử của bạn sẽ thất bại bởi những lỗi có trong mã nguồn mới. Sẽ dễ dàng
hơn nhiều trong việc tìm kiếm, và sau đó sửa chữa những lỗi đó nếu bạn đã viết thêm hai
dòng mã mới thay vì hai nghìn dòng.
Nhiều người cho rằng các kỹ thuật Agile hoạt động rất ổn với những dự án nhỏ, cần

một số ít người trong một vài tháng, nhưng chúng không hoạt động đối với những dự án
thực sự lớn hơn. Tuy nhiên, điều đó không hoàn toàn đúng. Người ta đã đưa ra một bản
5
NHÓM 8: TEST-DRIVEN DEVELOPMENT
báo cáo rằng làm việc với một hệ thống Smalltalk sử dụng hoàn toàn phương pháp hướng
kiểm thử (test-driven) hết 4 năm với chi phí nhân công 40 man-year, ra kết quả gồm
250,000 dòng mã nguồn chức năng và 250,000 dòng mã kiểm thử. Có 4000 mẫu kiểm
thử chạy dưới 20 phút, còn với bộ mẫu kiểm thử đầy đủ thì cần chạy vài ngày. Điều đó
chứng tỏ rằng TDD vẫn hoạt động tốt với những dự án có kích thước lớn.
III. Chu trình phát triển
Chu trình phát triển của TDD
1. Add a test
Trong phát triển dựa trên kiểm thử, mỗi tính năng mới bắt đầu bằng cách viết một
kiểm thử (a test) . Kiểm thử này chắc chắn phải thất bại vì nó được viết trước khi tính
năng đã được thực hiện (nếu nó không thất bại, thì hoặc là đề xuất tính năng " mới " đã
6
NHÓM 8: TEST-DRIVEN DEVELOPMENT
tồn tại hoặc kiểm thử là khiếm khuyết). Để viết một kiểm thử (a test), các nhà phát triển
phải hiểu rõ đặc tả yêu cầu của tính năng này. Nhà phát triển có thể thực hiện điều này
thông qua mô hình Use cases và những câu chuyện của người sử dụng bao gồm các yêu
cầu và điều kiện ngoại lệ, và có thể viết các test trong bất cứ framework kiểm thử nào
phù hợp với môi trường phần mềm. Điều này có thể dẫn đến sửa đổi một test hiện tại .
Đây là một tính năng khác biệt của phát triển dựa trên kiểm thử so với kiểm thử đơn vị
sau khi các mã được viết: nó làm cho các nhà phát triển tập trung vào các yêu cầu trước
khi viết mã.
2. Run all tests and see if the new one fails
Điều này xác nhận rằng kiểm thử (test) đang làm việc một cách chính xác và rằng các
test mới không pass nhầm mà không cần bất kỳ code mới nào. Bước này cũng kiểm thử
chính các test, trong trường hợp tiêu cực: nó bác bỏ khả năng các kiểm thử mới luôn luôn
pass, và do đó là vô giá trị. Các test mới cũng fail vì lý do kỳ vọng. Điều này làm tăng sự

tự tin (mặc dù không đảm bảo) mà nó được kiểm thử đúng, và chỉ pass trong trường hợp
dự định.
3. Write some code
Bước tiếp theo là viết một số mã tạo ra kiểm thử để pass. Mã mới được viết trong giai
đoạn này là không hoàn hảo. Điều đó là chấp nhận được vì các bước sau sẽ cải thiện nó.
Tại thời điểm này, mục đích duy nhất của các mã được viết là phải vượt qua các test,
không có thêm chức năng (và do đó chưa được kiểm tra) nên được dự đoán và "cho
phép" bất kỳ lúc nào.
4. Run tests
Nếu tất cả các test cases đều pass, lập trình viên có thể tự tin rằng code đã đáp ứng
được tất cả các yêu cầu. Đây là thời điểm tốt để bắt đầu bước cuối cùng của chu kỳ phát
triển.
7
NHÓM 8: TEST-DRIVEN DEVELOPMENT
5. Refactor code
Tái cấu trúc là quá trình làm thay đổi hiện có, code mà không cần thay đổi hành vi
bên ngoài của nó. Nói cách khác, thay đổi như thế nào mà nó phải là nó, nhưng không
phải những gì nó làm. Mục đích là để cải thiện cấu trúc nội bộ.
Các tình huống cần phải cấu trúc lại là:
- Khi có sự trùng lặp.
- Khi chúng ta thấy rằng code và/hoặc mục đích của nó không rõ ràng.
- Khi chúng ta cảm thấy code có vấn đề (theo cảm tính)
6. Repeat
Bắt đầu với một kiểm thử (a test) mới, chu kỳ sau đó được lặp đi lặp lại để hoàn thiện
các chức năng. Kích thước của các bước cần luôn luôn là nhỏ, với ít nhất là 1-10 sửa đổi
giữa mỗi lần chạy kiểm thử. Nếu mã mới không nhanh chóng đáp ứng một kiểm thử mới,
hoặc các kiểm thử khác fail không ngờ tới, các lập trình viên nên lùi lại hoặc trở lại ưu
tiên cho gỡ lỗi nhiều hơn.
IV. Thuận lợi khi sử dụng TDD
1. Chất lượng tốt hơn với TDD

a. Chất lượng trên nhiều khía cạnh
Bằng chứng là bộ phận đảm bảo chất lượng của thế giới doanh nghiệp của ngày nay , có
xu hướng gán chất lượng với số lỗi được tìm ra sau khi sử dụng phần mềm. Một số khác
lại coi việc chất lượng là những thứ khác như mức độ mà các phần mềm đáp ứng được
nhu cầu và mong đợi từ người dùng. Một só xem xét không chỉ chất lượng có thể nhìn từ
bên ngoài mà còn đặc tính bên trong của phần mềm (bên ngoài : chi phí phát triển bảo
trì). TDD góp phần cải thiện chất lượng trong tất cả khía cạnh với thiết kế hướng dẫn và
chất lượng theo định hướng bản chất của nó.
Rất có thể nhiều lí do cho các lỗi là sự sơ suất trong quá trình tạo sản phẩm, đó là không
test xác minh lại các trường hợp đặc biệt mà theo code nó vẫn hoạt động đúng(có thể là
không chạy test hoặc chạy test một cách cẩu thả). TDD giải quyết vấn đề này bằng cách
đảm bảo rằng có thực tế nếu không có mã code trong hệ thống đó là không yêu cầu và do
đó thực hiện bằng các test. TDD hiệu quả là đảm bảo rằng bất cứ điều gì bạn viết một
8
NHÓM 8: TEST-DRIVEN DEVELOPMENT
test cho, chất lượng hơn nếu có nhiều hơn các hàm chức năng chúng ta làm thế nào
chúng ta thành công , qua các testcase.
Một phần quan trọng của nhiệm vụ đó là vấn để kĩ năng test đó là khả năng lấy các
testcase cho các trường hợp bình thường , các trường hợp biên, người sử dụng có thể dự
đoán được lỗi,… Các TDD có thể hỗ trợ phần này bằng cách cho phép chúng ta tập trung
vào các giao diện cho các module, các class, những gì bạn có. Bởi những gì không biết
cài đặt như thế nào, chúng ta có thể định vị tốt hơn ngoài việc nghĩ về khuôn khổ chúng
ta chỉ tập trung làm thế nào để code nên xử lívà làm thế nào để các nhà phát triển của
khách hàng có thể , muốn sử dụng nó.
Điều quan trọng là trong suốt quá trình phát triển các vấn đề được phát hiện sớm và
chỉnh sửa khi chúng được tìm thấy. Thông thường thì các vấn đề lớn xảy ra khi có một
sự hiểu lầm, hiểu sai yêu cầu giữa khách hàng và người lập trình.những vấn đề này có thể
tránh được nếu có một cách để xác định những yêu cầu rõ rằng trước khi bắt đầu phát
triển. Nhập các test. Các test xác định yêu cầu bằng cái cách mà không yêu cầu con
người giải thích về sự thành công hay thất bại . Nếu có đủ số lượng test và chúng được

tạo ra trước khi phát triển . Chỉ đơn giản là chạy các test và các định thành công hay thất
bại sẽ giúp giải quyết các vấn đề cũ của phát triển phần mềm. “Are We Done?” Câu trả
lời không phải còn là sự giải thích dài dòng, mà là code có qua tất cả test hay không. Sau
khi nó qua các test, nghĩa là đã hoàn thành.
Giải pháp cho vấn đề có thể như thế là đủ, nhưng nó nhiêù hơn thế. Các test được viết
trong quá trình phát triển , có thể chạy và tăng cường bởi đảm bảo chất lượng phần
mềm(QA) với các test. Vì code được viết với việc test như là một động lực , cơ sở
chính, kết quả code dễ dàng hơn để test. Có một cơ sở là bộ test hiện có và code thì nó
dễ hơn để test nên cho cho phép chuyển từ việc phản ứng thụ động sang chủ động hơn
bản thân các test là hữu dụng không chỉ trong phát triển ban đầu của phần mềm , nếu
chúng được duy trì với việc viết code, chúng có thể được sử dụng để duy trì phần mềm.
Ví dụ, nếu một sản phẩn được phát hiện trong quá trình viết code, bước đầu tiên nên
được viết là một test để xác định rõ ràng vấn đề và sau đó, bạn có một test fail, nếu đúng
là có vấn đề. Sau đó, test mới này xác định kịch bản mà được xác định trong suốt quá
trình phát triển trước đó. Nếu bạn làm nhất quán được điều này. Các test sẽ phát triển
thành các chương trình được sử dụng trong đời sống thực, làm tăng giá trị của chúng theo
cấp số nhân. Khi thêm một tính năng bạn có thể chạy các bộ test để chắc chắn những mã
code mới không phá vỡ bất kì test nào đã có . Nếu sự bao quát của các test là đủ, chạy bộ
test và nhận được một kết quả thành công sẽ giảm đi sự lo ngại của bạn . Sự phá vỡ
chức năng hiện tại có thể là vị bạn quá thận trọng, làm bạn chậm chạp hơn. Viẹc dùng test
như là cách để lấy lại cân bằng và hướng đi để bạn phát triển chương trình.
b. Thời gian cho fix lỗi
9
NHÓM 8: TEST-DRIVEN DEVELOPMENT
TDD giúp chúng ta tăng tốc độ bằng cách giảm thời gian cần đẻ sửa chữa các lỗi . Một
cảm giác phổ biến là sửa chữa một lỗi sau khi được sản phẩm được sử dụng hai tháng
mất nhiều thời gian và tiền bạc hơn nhiều so với sửa chữa nó trong cùng với một gnày nó
được giới thiệu. Bất cứ điều gì chúng ta có thể làm để giảm thiểu số lõi giới thiệu ở lần
đầu tiên, và để giúp chúng ta tìm thấy những lỗi đó khi đang được làm, đó là các ràng
buộc phải trải qua. Tiến hành tesst đầu tiên trong các bước nhỏ để đảm bảo rằng bạn

không bao giờ phải chạm vào việc fix lỗi. TDD giúp chúng ta xây dựng code với chất
lượng kĩ thuật code , những gì chúng ta mong đợi là code dễ dàng để hiểu và để làm việc
với nó. Ngay cả những code tốt nhất bằng văn bản
2. Nhu cầu về việc trao đổi để xác định chức năng
Giúp định hình ý tưởng thiết kế hơn là kiểm nghiệm mã chương trình, thực hiện theo
TDD sẽ làm sáng tỏ thêm các yêu cầu bài toán, giải tỏa bế tắc trong khi đi tim giải pháp
phát hiện sớm các vấn đề về thiết kế và tránh được những công việc phải làm lại. Chúng
ta xác định đúng bài toán
1. Có thể làm tài liệu cho nhà phát triển
Các nhà phát triển thường thích đọc code và những ví dụ hơn là gặm nhấm tài liệu
giải thích các vấn đề kĩ thuật bằng ngôn ngữ phi kỹ thuật. TDD làm chính xác những
công việc đó. Việc thêm một vài comment trước mỗi test nếu cần, bạn đã có tài liệu
hướng dẫn cho các nhà phát triển khác. Tất nhiên cần phân biệt tài liệu cho nhà phát triển
và cho người sử dụng. Tài liệu cho nhà phát triển cũng cần có hướng dẫn tổng quan,
phương pháp luận , quy ước…. nữa
2. Các lập trình viên được làm chủ
Các lập trình viên có thể làm mọi thứ trên code của mình mà không sợ ảnh hưởng đến
hệ thống như thế nào miễn là code đấy đảm bảo pass qua các test. Không ai dám đảm
bảo điều gì với một hệ thống có nhiều nguy cơ, các lập trình viên không cần quan tâm
đến điều đó. Với mỗi một test hoàn thành chỉ có thể là fail thì chứng tỏ bạn đã phá vỡ
một cái gì đó còn nếu pass thì bạn không cần phải làm gì nữa cả.
V. Hướng phát triển, áp dụng TDD cho các công nghệ phát triển
1. Test_driving với các thành phần web
a. Test-driving với Java Servlets
Có 2 đối số HttpServletRequest đại diện yêu cầu phương HTTP và đóng gói các thông
số cần thiết. Đối tượng yêu cầu cũng hoạt động như một phương tiện truyền dữ liệu từ
thành phần yêu cầu xử lý dữ liệu.Đối số thứ 2 là đối tượng HttpServletResponse là cổng
để tạo ra phản ứng cho phía client. Làm thế nào để chúng ta test những cái này? Chúng
ta sẽ tạo các request và response giả mạo.
10

NHÓM 8: TEST-DRIVEN DEVELOPMENT
Nếu chúng ta muốn test cho Servlet trước, cần phải xác minh rằng nó thực sự tạo
ra các response dự kiến. Chúng ta phải cung cấp các trường hợp mà HttpServletRequest
và HttpServletResponse có thể gọi với một cặp test có sẵn của bên thứ ba. Sau đó nữa,
các test trước được thực hiện một Servlet bình thường.
Với yêu cầu phức tạp hơn, ví dụ xử lý chuyển hướng trong đăng nhập . Làm thế
nào để Servlet xử lý đăng nhập ? đăng nhập thất bại vì sai mật khẩu thông qua nhưng
chuyển tiếp yêu cầu tới một trang báo lỗi . Chúng ta muốn mô phỏng một request, và
kiểm tra xem các request đã được chuyển hướng đến trang lỗi chưa.Việc thực hiện giả
HttpServletResponse cho phép chúng ta làm điều đó .
public class TestLoginServlet {
@Test
public void wrongPasswordShouldRedirectToErrorPage()
throws Exception {
HttpServlet servlet = new LoginServlet();
MockHttpServletRequest request = create fake request object
new MockHttpServletRequest("GET", "/login");
request.addParameter("j_username", "nosuchuser");
request.addParameter("j_password", "wrongpassword");
MockHttpServletResponse response =
new MockHttpServletResponse(); fake response object
servlet.service(request, response);
assertEquals("/invalidlogin", response.getRedirectedUrl()); Assert
expected
Ngoài ra chúng ta có thể làm việc trên parameter và session tại một thời điểm
public void validLoginForwardsToFrontPageAndStoresUsername()
throws Exception {
HttpServlet servlet = new LoginServlet();
MockHttpServletRequest request =
new MockHttpServletRequest("GET", "/login");

request.addParameter("j_username", "validuser");
request.addParameter("j_password", "correctpassword");
MockHttpServletResponse response =
new MockHttpServletResponse();
servlet.service(request, response);
11
NHÓM 8: TEST-DRIVEN DEVELOPMENT
assertEquals("/frontpage", response.getRedirectedUrl());
assertEquals("validuser",
request.getSession().getAttribute("username"));
phát triển hướng tới một thiết kế tốt hơn
Từ khóa để giữ Java Servlets không lỗi và có thể kiểm chứng được là phân chia và chinh
phục. Lập trình bởi ý tưởng, nhầm có được test pass trong các bước nhỏ nhất .
b. Test-driving với Spring controllers
Framework Spring ứng với phần C trong mô hình MVC , chúng tương tự như
Servlet nhưng trừu tượng hơn. Đối với một Spring Controller không chịu trách nhiệm
response cho request. Thay vào đó, chúng chỉ ra những gì mà framework có thể trả lại
một đối tượng ModelAndView.
public class SampleController implements Controller {
protected ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response)
throws Exception {
return new ModelAndView("viewname");
}
}
Các sampleController sẽ được ánh xạ để xử lý các URL cụ thể. Đối tượng request đến
cổng để lấy tất cả dữliệu cần để làm bất cứ điều gì SampleController cần để làm, và đối
tượng ModelAndView trả lại nói với Spring MVC cái nhằm trả lại các response
Chúng ta bắt đầu viết test thứ nhất với ví dụ đăng nhập tương tự như trên với Spring
MVC

public class TestLoginController {
@Test
public void wrongPasswordShouldRedirectToErrorPage()
throws Exception {
MockHttpServletRequest request =
new MockHttpServletRequest(); populate
mock obj
request.addParameter("j_username", "nosuchusername");
request.addParameter("j_password", "nosuchpassword");
MockHttpServletResponse response =
new MockHttpServletResponse();
Controller c = new LoginController(); invock
ModelAndView v = c.handleRequest(request, response);
12
NHÓM 8: TEST-DRIVEN DEVELOPMENT
assertEquals("wrongpassword", v.getViewName()); wrongpass
}
}
Chúng ta tạo B kiểm thử kép với các giao diện request và response, sau đó C gọi
phương thức xử lý request với kiểm thử kép và cuối cùng D thực hiện assertioin để xác
minh hành vi mong đợi.
Sự khác biệt với Servlet là khái niệm ModelAndView, có thể mang tất cả dữ liệu để biểu
diễn .
2. Test-driving với truy cập dữ liệu
Sự khác biệt chính trong truy cập cơ sở dữ liệu là hành vi của nó thường kéo dài nhiều
lớp kiến trúc của hệ thống. Nó có xu hướng sử dụng API bên thứ ba . Để có thể test mà
không cần các công cụ của bên thứ ba chúng ta cần tách rời những vấn đề này.
Chúng ta bắt đầu với xác định ranh giới truy cập dữ liệu, sau đó nghiên cứu mẫu thiết kế
để test.
Biên

Làm việc trên tầng Application logic , nó dễ dàng để sơ khai persistence logic. Chúng ta
xác minh business logic, testing gọi các phương thức thích hợp trên giao diện tầng
persistence logic phù hợp với các parameters.
Phát triển phần mềm thường theo kiểu truyền thống dựa trên phương pháp thủ công,
kiểm tra trực quan cho các truy cập dữ liệu. Việc kiểm tra trực quan là chậm và dễ bị lỗi.
Chúng ta cần sử dụng dịch vụ kiên trì từ test driven. Cho đến nay chúng ta bỏ qua dữ liệu
là một yếu tố trong test của chúng ta bằng cách giả mạo nó. Chúng ta có những lợi thế
tương đối và bất lời cho việc áp dụng TDD với việc hội nhập và các chiến lược khác
nhau cho quản lý dữ liệu test của chúng ta.
3. Test-driving the unpredictable
Chúng ta sẽ đi đến cụ thể từng hàm chức năng trong java
a. Test-driving với chức năng theo thời gian
13
NHÓM 8: TEST-DRIVEN DEVELOPMENT
Nhiều developer đã ít nhất một lần đối mặt với vấn đề viết một test cho một đoạn code
có đầu ra hoặc hành vi phụ thuộc vào thời gian hiện tại. Vấn đề với thời gian là chúng ta
không thể kiểm soát nó. Các vấn đề liên quan : trừu tượng hóa thời gian hệ thống, test vói
log output với thời gian hệ thống giả.
b. Test-driving với đa luồng
Chúng ta xét về một luồng chương trình đơn giản với quy định rõ ràng và xác định
các bước thực hiện một chương trình logic. Đồng thời, mã đa luồng là khác nhau. Việc
thực hiện không phải như xác định nữa, với nhiều threads tương tác với nhau và cạnh
tranh để cùng truy cập vào một đối tượng.
Tương tự như vậy, có một sự khác biệt giữa các mã liên tục và đồng thời trong cách
chúng ta test hành vi mong muốn của đoạn mã. Khi test_driving code của hệ thống,
chúng ta không chỉ quan tâm trong các văn bản test cho các hành vi liên tục mà còn đối
với các hành vi đồng thời và sau đó mỗi không gian lỗi sẽ càng lớn hơn.
Chúng ta sẽ xét kỹ hơn ở hai khía cạnh
Mở rộng không gian lỗi
Trong concurrent programming, có nhiều cách đều có thể sai, và kịch bản có

nhiều khó khăn để mô phỏng. Nói cách khác, concurrent programming có một không
gian lỗi lớn hơn nhiều so với các đối tác liên tục của chúng. Hơn nữa test code có thể vo
tình ảnh hưởng đến đồng thời của mã dưới test, gây hiện tượng được gọi là Heisenbugs -
lỗi mà biến mất khi thêm test hoặc debug .
Sự khác biệt về số lượng các lỗi tiềm năng đáng kể, đủ cho chúng là không thực
tế. Chúng ta sẽ có một phương sách để mô phỏng một vài lựa chọn rõ ràng
Mặc dù phân tích thủ công có thể loại bỏ rõ ràng hầu hết các lỗi, nhưng chúng ta
cũng nên tăng sự tự tin của chúng ta trong hệ thống hành vi Concurrent, bằng cách sử
dụng lặp đi lặp lại , chạy các test dài để xác định các lỗi xảy ra một cách ngẫu nhiên.
Nhiều hành vi concurrent
Có nhiều loại hành vi mà chúng ta nhắm tới mục tiêu kiểm thử đơn vị : Good stuff, bad
stuff.
4. Test-driving với Swing
Những gì chúng ta cần quan tâm trong kiểm thử mộtgiao diện đồ họa ngừoi dùng?
Mặc dù không có câu trả lời đơn giản hoặc hướng đi chung cho các nhà phát triển Swing,
nhưng chúng ta cũng có thể xác định một số loại khác nhau hoặc các thuộc tính khác
nhau của code Swing và đưa ra một số câu trả lời bằng cách chỉ tập trung vào một trong
các loại tại một thời điểm: Internal plumbing và utilities, rendering and layout, interaction
14
NHÓM 8: TEST-DRIVEN DEVELOPMENT
Internal plumbing and utilities
Mặc dù giao diện người dùng chỉ là cách trình bày và bố trí các hình ảnh, điều đó
không có nghĩa là không có cách phần code thực tế là không giống với những gì chúng
ta có thể viết trong khi phát triển hệ thống.
Redering and layout
Chúng ta muuốn đặt các layout đúng để xác nhận như là một phần của việc kiểm
tra bằng mắt . Đôi khi cần test cho việc bố trí thô những gì chúng ta cần bố trí. Đó là Tôi
có thể viết một test” Button A nên ở trên button B khi bảng C đưa ra” or “Button A và B
có độ rộng tương tự khi đưa ra màn hình”
Interaction

Các thành phần trên giao diện sẽ là vô ích nếu chúng ta không thể tương tác với
ứng dụng.
Loại tương tác này không phải có thể test một cách tầm thường giống như mã tiện ích mà
chúng không có bất kỳ sự phụ thuộc vào Swing API. Nó không đòi hỏi một sự thông thái,
nhưng chúng ta có thể kiểm tra sự tương tương tác mong muốn dễ dàng miễn là chúng ta
biết Swing API. Ví dụ chúng ta cần phải làm thế nào một thành phần Swing nhận được
input ngừoi dùng đăng nhập vàođể mô phỏng hoặc mong đọi nó trong bài test đơn vịcủa
chúng ta
VI. Thách thức
1. Thời gian đầu tư lớn
Đối với các trường hợp đơn giản bạn mất khoảng 20% của thời gian cài đặt, nhưng
với các dự án phức tạp thì nó lớn hơn nhiều
2. Phức tạp hơn
Trong các bài toán phức tạp , rất khó để tính toán được các testcase, trong những trường
hợp đó chúng ta có thể test tự động song song với thiết kế chứ không thể dùng test đơn
vị đơn giản
3. Gắn liền với thiết kế
Một số trường hợp phần thiết kế không rõ ràng từ đầu , và thống nhất cho đến khi kết
thúc nên đòi hỏi việc viết test rất lâu và khó khăn.
4. Cần phải tinh chỉnh liên tục
Với cấu trúc dữ liệu và kiểm thử hộp đen việc kiểm thử đơn vị là hoàn hảo. Nhưng với
các thuật toán có hướng thay đổi, tinh chỉnh hoặc điều chỉnh tốt . Điều này cần một đầu
tư thời gian rất lớn mà việc bồi thường kinh phí là không hợp lý, vì vậy khi sử dụng cần
cân nhắc đến hệ thống đang triển khai
15
NHÓM 8: TEST-DRIVEN DEVELOPMENT
5. Liên quan chặt chẽ đến ”Agile”
Không đặt tầm quan trọng vào tài liệu của hệ thống, tất cả nằm trong nhà phát triển , nếu
nhà phát triển không tiếp tục lâu dài hoặc quên đi tại sao chỉ có một giá trị trả về mà
không phải là các giá trị khác thì tất cả đều rất mơ hồ. TDD tốt khi mà việc ghi chép tài

liệu cho các test phải rõ ràng.
VII. Đặc điểm của Unit Test (Kiểm thử đơn vị)
Unit Test (UT) là một kỹ thuật quan trọng góp phần nâng cao chất lượng phần mềm
(PM), nhưng có nhiều quan điểm trái ngược nhau về việc đưa UT vào quy trình phát triển
PM. Sau đây là đặc điểm của UT với mô hình phát triển phần mềm hiện đại TDD
1. Định nghĩa:
UT là kỹ thuật kiểm nghiệm các hoạt động của mọi chi tiết mã (code) với một quy trình
tách biệt với quy trình phát triển PM, giúp phát hiện sai sót kịp thời. UT còn có thể giúp
phát hiện các vấn đề tiềm ẩn và các lỗi thời gian thực ngay cả trước khi chuyên viên kiểm
định chất lượng (QA - Quality Assurance) tìm ra, thậm chí có thể sửa lỗi ngay từ ý tưởng
thiết kế.
2. Cấu trúc của một UT:
UT là các đoạn mã có cấu trúc giống như các đối tượng được xây dựng để kiểm tra từng
bộ phận trong hệ thống. Mỗi UT sẽ gửi đi một thông điệp và kiểm tra câu trả lời nhận
được đúng hay không, bao gồm:
• Các kết quả trả về mong muốn
• Các lỗi ngoại lệ mong muốn
Các đoạn mã UT hoạt động liên tục hoặc định kỳ để thăm dò và phát hiện các lỗi kỹ thuật
trong suốt quá trình phát triển, do đó UT còn được gọi là kỹ thuật kiểm nghiệm tự động.
3. Đặc điểm của một UT
• Đóng vai trò như những người sử dụng đầu tiên của hệ thống
16
NHÓM 8: TEST-DRIVEN DEVELOPMENT
• Chỉ có giá trị khi chúng có thể phát hiện các vấn đề tiềm ẩn hoặc lỗi kỹ thuật.
4. Vòng đời của một UT
• Fail (trạng thái lỗi)
• Ignore (tạm ngừng thực hiện)
• Pass (trạng thái làm việc)
Toàn bộ UT được vận hành trong một hệ thống tách biệt. Có rất nhiều PM hỗ trợ thực thi
UT với giao diện trực quan. Thông thường, trạng thái của UT được biểu hiện bằng các

màu khác nhau: màu xanh (pass), màu vàng (ignore) và màu đỏ (fail).
UT chỉ thực sự đem lại hiệu quả khi:
• Được vận hành lặp lại nhiều lần
• Tự động hoàn toàn
• Độc lập với các UT khác.
5. Thiết kế UT
Mỗi UT đều được tiết kế theo trình tự sau:
• Thiết lập các điều kiện cần thiết: khởi tạo các đối tượng, xác định tài nguyên cần thiết,
xây dựng các dữ liệu giả
• Triệu gọi các phương thức cần kiểm tra.
• Kiểm tra sự hoạt động đúng đắn của các phương thức.
• Dọn dẹp tài nguyên sau khi kết thúc kiểm tra.
6. Ứng dụng của UT
• Kiểm tra mọi đơn vị nhỏ nhất là các thuộc tính, sự kiện, thủ tục và hàm.
• Kiểm tra các trạng thái và ràng buộc của đối tượng ở các mức sâu hơn mà thông thường
chúng ta không thể truy cập được.
• Kiểm tra các quy trình (process) và mở rộng hơn là các khung làm việc(workflow - tập
hợp của nhiều quy trình).
7. Lợi ích của UT
17
NHÓM 8: TEST-DRIVEN DEVELOPMENT
• Thời gian đầu, người ta thường do dự khi phải viết UT thay vì tập trung vào viết mã
cho các chức năng nghiệp vụ. Công việc viết UT có thể ngốn nhiều thời gian, tuy nhiên
UT đem lại lợi ích to lớn như:
• Tạo ra môi trường lý tưởng để kiểm tra bất kỳ đoạn mã nào, có khả năng thăm dò và
phát hiện lỗi chính xác, duy trì sự ổn định của toàn bộ PM và giúp tiết kiệm thời gian so
với công việc gỡ rối truyền thống.
• Phát hiện các thuật toán thực thi không hiệu quả, các thủ tục chạy vượt quá giới hạn
thời gian.
• Phát hiện các vấn đề về thiết kế, xử lý hệ thống, thậm chí các mô hình thiết kế.

• Phát hiện các lỗi nghiêm trọng có thể xảy ra trong những tình huống rất hẹp.
• Tạo hàng rào an toàn cho các khối mã: Bất kỳ sự thay đổi nào cũng có thể tác động đến
hàng rào này và thông báo những nguy hiểm tiềm tàng.
UT tạo thành hàng rào an toàn cho mã ứng dụng
• UT là môi trường lý tưởng để tiếp cận các thư
viện API bên ngoài một cách tốt nhất. Sẽ rất nguy
hiểm nếu chúng ta ứng dụng ngay các thư viện này
mà không kiểm tra kỹ lưỡng công dụng của các thủ
tục trong thư viện. Dành ra thời gian viết UT kiểm
tra từng thủ tục là phương pháp tốt nhất để khẳng
định sự hiểu đúng đắn về cách sử dụng thư viện
đó. Ngoài ra, UT cũng được sử dụng để phát hiện
sự khác biệt giữa phiên bản mới và phiên bản cũ của cùng một thư viện.
Trong môi trường làm việc cạnh tranh, UT còn có tác dụng rất lớn đến năng suất làm
18
NHÓM 8: TEST-DRIVEN DEVELOPMENT
việc:
• Giải phóng chuyên viên QA khỏi các công việc kiểm tra phức tạp.
• Tăng sự tự tin khi hoàn thành một công việc. Chúng ta thường có cảm giác không chắc
chắn về các đoạn mã của mình như liệu các lỗi có quay lại không, hoạt động của module
hiện hành có bị tác động không, hoặc liệu công việc hiệu chỉnh mã có gây hư hỏng đâu
đó
• Là công cụ đánh giá năng lực của bạn. Số lượng các tình huống kiểm tra (test case)
chuyển trạng thái "pass" sẽ thể hiện tốc độ làm việc, năng suất của bạn.
8. Chiến lược viết mã hiệu quả với UT
• Phân tích các tình huống có thể xảy ra đối với mã. Đừng bỏ qua các tình huống tồi tệ
nhất có thể xảy ra, thí dụ dữ liệu nhập làm một kết nối cơ sở dữ liệu thất bại, ứng
dụng bị treo vì một phép toán chia cho không, các thủ tục đưa ra lỗi ngoại lệ sai có thể
phá hỏng ứng dụng một cách bí ẩn
• Mọi UT phải bắt đầu với trạng thái "fail" và chuyển trạng thái "pass" sau một số

thay đổi hợp lý đối với mã chính.
• Mỗi khi viết một đoạn mã quan trọng, hãy viết các UT tương ứng cho đến khi bạn
không thể nghĩ thêm tình huống nào nữa.
• Nhập một số lượng đủ lớn các giá trị đầu vào để phát hiện điểm yếu của mã theo
nguyên tắc:
- Nếu nhập giá trị đầu vào hợp lệ thì kết quả trả về cũng phải hợp lệ
- Nếu nhập giá trị đầu vào không hợp lệ thì kết quả trả về phải không hợp lệ
• Sớm nhận biết các đoạn mã không ổn định và có nguy cơ gây lỗi cao, viết UT tương
ứng để khống chế.
• Ứng với mỗi đối tượng nghiệp vụ (business object) hoặc đối tượng truy cập dữ liệu
(data access object), nên tạo ra một lớp kiểm tra riêng vì những lỗi nghiêm trọng có thể
phát sinh từ các đối tượng này.
• Để ngăn chặn các lỗi có thể phát sinh trở lại thực thi tự động tất cả UT mỗi khi có
một sự thay đổi quan trọng, hãy làm công việc này mỗi ngày. Các UT lỗi cho chúng ta
biết thay đổi nào là nguyên nhân gây lỗi.
• Để tăng hiệu quả và giảm rủi ro khi viết các UT, cần sử dụng nhiều phương thức
kiểm tra khác nhau. Hãy viết càng đơn giản càng tốt.
19
NHÓM 8: TEST-DRIVEN DEVELOPMENT
• Cuối cùng, viết UT cũng đòi hỏi sự nỗ lực, kinh nghiệm và sự sáng tạo như viết
PM.
VIII. KHUNG KIỂM THỬ PYUNIT
1. Khái niệm:
- PyUnit là một phiên bản ngôn ngữ Python của JUnit (trên ngôn ngữ Java) , được
viết bởi Kent Beck và Erich Gramma.
- PyUnit là một thư viện hỗ trợ việc kiểm tra tự động hóa, chia sẻ các thiết lập và
ngừng việc thay đổi mã nguồn để thử nghiệm, tổng hợp các test , chạy chúng một
cách độc lập sau đó trả về các báo cáo. Những module có sẵn của unittest cung
cấp các lớp hỗ trợ cho một tập hợp các bài kiểm tra tương ứng
2. Những API quan trọng của unittest

- Để đat được những điều trên unittest hỗ trợ những khái niệm sau:
o Test fixture : Thử nghiệm đại diện cho việc chuẩn bị cần thiết để thực hiện
một hay nhiều bài kiểm tra và mọi hành động dọn dẹp liên kết cần thiết
o Test Case : Là đơn vị nhỏ nhất của thử nghiệm, nó kiểm tra một phản ứng
cụ thể cho một tập hợp các yếu tố đầu vào. TestCase có thể được sử dụng
để tạo ra các trường hợp thử nghiệm mới. Tuy nhiên bạn có thể sử dụng
cách của bạn thay vì sử dụng phân lớp này từ Unittest
o Test Suite : là một tập hợp của các TestCase và TestSuite. Nó được sử dụng
để kiểm tra tổng hợp, vì thế cần có các bộ kiểm tra để có thể bắt đầu một
TestSuite
o Test Runner : là chạy thử nghiệm một thành phần và trả về kết quả kiểm
tra cho người sử dụng . Nó có thể sử dụng giao diện đồ họa, giao diện văn
bản hoặc giá trị đặc biệt để chỉ ra kết quả của các bài kiểm tra
3. Cấu trúc đơn giản của một testCase
- Bắt buộc cần khai báo thư viện unittest để chương trình hiểu và sử dụng thư viện
này
- Cần lấy hàm cần test làm thư viện để chương trình hiểu và thực thi
20
NHÓM 8: TEST-DRIVEN DEVELOPMENT
- Các bộ test sẽ được gọi đến trong class, khi test mỗi testCase sẽ được gọi và chạy
độc lập với nhau, sau đó thống kê xem có bao nhiêu test đã vượt qua và bao nhiêu
test vẫn còn lỗi
- Câu lệnh if __name__ == “__main__” được sử dụng để gọi tới chương trình
chính, nếu tập tin được gọi ở một nơi khác thì name sẽ được thiết lập theo tên của
module này
IX. DEMO CHƯƠNG TRÌNH PHÁT TRIỂN THEO HƯỚNG TDD SỬ DỤNG
PYUNIT
BÀI TOÁN:
Với đầu vào là giá trị lần lượt của 3 cạnh một tam giác. Yêu cầu chương trình cần
kiểm tra đầu vào này có thỏa mãn điều kiện chúng tạo thành tam giác hay không, và nếu

nó là một tam giác thì nó là loại tam giác nào?
HƯỚNG GIẢI QUYẾT:
Từ yêu cầu bài toán nêu trên, ta tập trung tạo ra các trường hợp có thể của bài toán
, đưa nó vào TestCase của PyUnit, sau đó thực thi unittest. Quay trở lại chỉnh sửa mã
nguồn hợp lý để có kết quả như mong đợi. Sau khi đã hoàn thành các TestCase ta có thể
tạm tin tưởng tại thời điểm hiện tại chương trình có thể đáp ứng được các TestCase đã
cho, giải quyết được những tình huống tương tự
Video demo tại: />21
NHÓM 8: TEST-DRIVEN DEVELOPMENT
X. TÀI LIỆU THAM KHẢO
 Beck, Kent Test-Driven Development: By Example Addison-Wesley, 2003.
 S. Amber. Introduction to Test Driven Development (TDD), www.agiledata.org

 Learn Python The Hard Way Release Zed a Shaw
 Unit Testing Framework: />22

×