Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Tools & Tips
Code

Các mã kịch bản Shell dựa trên kiểm thử

by
Difficulty:AdvancedLength:LongLanguages:

Vietnamese (Tiếng Việt) translation by Andrea Ho (you can also view the original English article)

Viết shell script rất giống việc lập trình. Một số script yêu cầu thời gian đầu tư ít; trong khi đó, các script phức tạp khác có thể yêu cầu các ý tưởng, lập kế hoạch và cam kết nhiều hơn. Từ góc độ này, sẽ có ý nghĩa khi thực hiện cách tiếp cận dựa trên nền tảng test và unit test các shell script của chúng tôi.

Để tận dụng tối đa hướng dẫn này, bạn cần làm quen với giao diện dòng lệnh (CLI); bạn có thể muốn xem hướng dẫn The Command Line is Your Best Friend nếu bạn cần một phần bổ sung. Bạn cũng có hiểu biết cơ bản về shell script giống Bash. Sau cùng, bạn có thể muốn làm quen với các khái niệm phát triển dựa trên test (TDD) và unit test nói chung; hãy bảo đảm xem qua các hướng dẫn Test-Driven PHP để nằm được khái niệm cơ bản.


Chuẩn bị cho môi trường lập trình

Đầu tiên, bạn cần một trình soạn thảo văn bản để viết các shell script và unit test. Hãy cái mà bạn ưa thích!

Chúng tôi sẽ sử dụng framework unit test tên gọi shUnit2 để chạy unit test của chúng tôi. Nó được thiết kế và hoạt động với shell giống với Bash. shUnit2 là một framework mã nguồn mở được phát hành theo giấy phép GPL và bản sao của framework cũng được bao gồm trong mã nguồn mẫu của hướng dẫn này.

Cài đặt shUnit2 rất dễ dàng; chỉ cần tải xuống và giải nén đến bất kỳ vị trí nào trên ổ cứng của bạn. Framework được viết bằng Bash và vì thế, framework chỉ bao gồm các file script. Nếu bạn có kế hoạch thường xuyên sử dụng shUnit2, tôi khuyên bạn nên đưa nó vào trong PATH của bạn.


Viết test đầu tiên của chúng tôi

Với hướng dẫn này, extract shUnit vào một thư mục có cùng tên trong thư mục Sources của bạn (xem code được gửi kèm với hướng dẫn này). Tạo thư mục Tests tra bên trong Sources và thêm file mới gọi FirstTest.sh.

Sau đó làm sao file test của bạn có thể thực thi.

Bây giờ bạn có thể chỉ cần chạy và quan sát kết quả:

Kết quả cho biết chúng tôi đã chạy test thành công. Bây giờ, hãy làm cho test thất bại; thay đổi câu lệnh assertEquals để hai chuỗi không giống nhau và chạy lại test:


Trò chơi tennis

Bạn viết các acceptance test khi ban đầu của project/feature/story khi bạn có thể xác định rõ ràng một yêu cầu cụ thể.

Bây giờ chúng ta có một môi trường test làm việc, hãy viết một script đọc file, đưa ra quyết định dựa trên nội dung của file và xuất thông tin ra màn hình.

Mục tiêu chính của kịch bản là hiển thị điểm số của một trận đấu tennis giữa hai người chơi. Chúng tôi sẽ chỉ tập trung vào việc ghi nhận điểm của một game duy nhất; thứ khác là tùy thuộc vào bạn. Các quy tắc tính điểm là:

  • Lúc đầu, mỗi người chơi có số điểm bằng 0, được gọi là "love"
  • Quả bóng thứ nhất, thứ hai và thứ ba giành được được đánh dấu là "15", "30" và "40".
  • Nếu ở "40" điểm số bằng nhau, nó được gọi là "deuce".
  • Sau này, điểm số được gọi là "Advantage" (lợi điểm) cho tay vợt ghi được nhiều điểm hơn tay vợt khác.
  • Tay vợt là người chiến thắng nếu anh ta có lợi thế ít nhất hai điểm và giành được ít nhất ba điểm (nghĩa là, nếu ít nhất anh ta đạt được "40").

Định nghĩa input và output

Ứng dụng của chúng tôi sẽ đọc điểm từ một file. Một hệ thống khác sẽ đưa thông tin vào file này. Dòng đầu tiên của file dữ liệu này sẽ chứa tên của các tay vợt. Khi tay vợt ghi được một điểm, tên của họ được ghi ở cuối file. Một file điểm điển hình giống thế này:

Bạn có thể tìm thấy nội dung này trong file input.txt trong thư mục Source.

Kết quả của chương trình của chúng tôi ghi điểm số ra màn hình một dòng tại một thời điểm. Kết quả phải là:

Kết quả này cũng có thể được tìm thấy trong file output.txt. Chúng tôi sẽ sử dụng thông tin này để kiểm tra xem chương trình có đúng không.


Acceptance test

Bạn viết các acceptance test lúc ban đầu của project/feature/story khi bạn có thể xác định rõ ràng một yêu cầu cụ thể. Trong trường hợp của chúng tôi, test này chỉ đơn giản gọi script sắp được tạo của chúng tôi với tên của file nhập vào là tham số và hy vọng kết quả giống hệt với file viết tay từ phần trước:

Chúng tôi sẽ chạy test trong thư mục Source/Tests; do đó, cd .. dẫn chúng ta vào thư mục Source. Sau đó, nó cố gắng chạy tennisGamse.sh chưa tồn tại. Sau đó, lệnh diff sẽ so sánh hai file: ./output.txt là kết quả viết tay của chúng tôi và ./results.txt sẽ chứa kết quả của script của chúng tôi. Cuối cùng, assertTrue kiểm tra giá trị thoát của diff.

Nhưng hiện tại, test của chúng tôi trả về lỗi sau:

Chúng ta hãy biến những lỗi đó thành một thất bại thú vị bằng cách tạo một file trống có tên tennisGame.sh và khiến nó có thể thực thi được. Bây giờ khi chạy test, chúng tôi không gặp lỗi:


Triển khai với TDD

Tạo một file khác gọi là unitTests.sh cho các unit test của chúng tôi. Chúng tôi không muốn chạy script của chúng tôi cho mỗi test; chúng tôi chỉ muốn chạy các chức năng mà chúng tôi kiểm tra. Vì vậy, chúng tôi sẽ cho tennisGame.sh chỉ chạy các chức năng sẽ nằm trong functions.sh:

Test đầu tiên của chúng tôi rất đơn giản. Chúng tôi cố gắng truy xuất tên của tay vợt đầu tiên khi một dòng chứa hai tên được phân tách bằng dấu gạch nối. Test này sẽ thất bại vì chúng tôi chưa có hàm getFirstPlayerFrom:

Triển khai cho getFirstPlayerFromis rất đơn giản. Đó là regular expression được đưa đến thông qua lệnh sed:

Bây giờ test đã hoàn thành:

Hãy viết một test khác cho tên của tay vợt thứ hai:

Thất bại:

Và bây giờ thực triển khai hàm để cho nó vượt qua:

Bây giờ chúng tôi đã hoàn thành các test:


Hãy tăng tốc

Bắt đầu từ thời điểm này, chúng tôi sẽ viết một test và phần triển khai, và tôi sẽ chỉ giải thích những điều đáng được đề cập.

Hãy kiểm tra xem có phải chúng tôi có tay vợi chỉ có một điểm. Đã bổ sung test sau đây:

Và giải pháp:

Chúng tôi sử dụng một số trích dẫn để đưa vào chuỗi dòng mới (\n) bên trong một tham số string. Sau đó, chúng tôi sử dụng grep để tìm các dòng có chứa tên của tay vợt và đếm chúng bằng wc. Cuối cùng, chúng tôi trừ 1 đơn vị khỏi kết quả để vô hiệu sự tồn tại của dòng đầu tiên (nó chứa dữ liệu không liên quan đến điểm số).

Bây giờ chúng tôi đang ở giai đoạn refactor của TDD.

Tôi vừa nhận ra rằng code thực sự hiệu quả cho nhiều điểm số của mỗi người chơi và chúng tôi có thể refactor các test của mình để phản ánh điều này. Thay đổi hàm test trên thành như sau:

Các test vẫn thành công. Giờ là lúc tiếp tục với logic của chúng tôi:

Và triển khai:

Tôi chỉ kiểm tra tham số thứ hai. Có vẻ như tôi gian lận, nhưng đó là code đơn giản nhất để hoàn thành test. Viết một test khác yêu cầu chúng ta phải bổ sung logic, nhưng chúng ta nên viết test nào tiếp theo đây?

Có hai path chúng ta có thể chọn. Kiểm tra nếu tay vợt thứ hai nhận được một điểm buộc chúng ta phải viết một câu lệnh if khác, nhưng chúng ta chỉ phải thêm một câu lệnh else nếu chúng ta chọn test điểm thứ hai của tay vợt thứ nhất. Bài test ngụ ý việc thực hiện dễ dàng hơn, vì vậy hãy thử nó:

Và phần triển khai:

Điều này vẫn có vẻ gian lận, nhưng nó đạt hiệu quả hoàn hảo. Tiếp tục cho điểm số thứ ba:

Triển khai:

Điều này if-elif-else đang bắt đầu làm tôi khó chịu. Tôi muốn thay đổi nó, nhưng trước tiên hãy refactor các test của chúng tôi. Chúng tôi có ba test rất giống nhau; vì vậy hãy gộp chúng thành một test duy nhất đưa ra ba khẳng định:

Điều đó tốt hơn, và nó vẫn vượt qua. Bây giờ, hãy tạo ra một test tương tự cho người chơi thứ hai:

Chạy test này cho kết quả thú vị:

Vâng đó là điều không mong đợi. Chúng tôi biết rằng Michael có điểm số không chính xác. Điều ngạc nhiên là John; anh ta nên có 0 chứ không phải 40. Hãy sửa lỗi đó bằng cách sửa đổi biểu thức if-elif-else:

Bây giờ if-elif-else phức tạp hơn, nhưng ít nhất chúng tôi đã sửa điểm của John:

Bây giờ hãy sửa Michael:

Điều đó đã hiệu quả! Bây giờ đã đến lúc cuối cùng refactor biểu thức if-elif-else xấu xí đó:

Các xếp đặt giá trị thật tuyệt vời! Hãy chuyển sang trường hợp "Deuce":

Chúng tôi kiểm tra "Deuce" khi tất cả tay vợt có ít nhất 40 điểm.

Bây giờ chúng tôi kiểm tra lợi thế của tay vợt đầu tiên:

Và giúp nó hoàn thành:

Có cái if-elif-else xấu xí đó nữa, và chúng ta cũng có rất nhiều trùng lặp. Tất cả các test của chúng tôi đều vượt qua, vì vậy hãy refactor:

Điều này sẽ có tác dụng ngay bây giờ. Hãy test lợi thế cho người chơi thứ hai:

Và code như sau:

Điều này hiệu quả, nhưng chúng tôi có một số trùng lặp trong hàm checkAdvantage. Hãy đơn giản hóa nó và gọi nó hai lần:

Điều này thực sự tốt hơn giải pháp trước đây của chúng tôi và nó trở lại với việc triển khai ban đầu của phương pháp này. Nhưng bây giờ chúng tôi có một vấn đề khác: tôi cảm thấy không thoải mái với các biến $1, $2, $3$4. Họ cần những cái tên có ý nghĩa:

Điều này làm cho code dài hơn, nhưng nó có ý nghĩa hơn. Tôi thích nó.

Đã đến lúc tìm ra người chiến thắng:

Chúng tôi chỉ phải sửa đổi hàm checkAdvantageFor:

Chúng ta đang gần hoàn tất! Bước cuối cùng của chúng tôi, chúng tôi sẽ viết code trong tennisGame.sh để vượt qua acceptance test. Đây sẽ là code khá đơn giản:

Chúng tôi đọc dòng đầu tiên để lấy tên của hai tay vợt và sau đó chúng tôi từng bước đọc file để tính điểm.


Tổng kết

Các Shell script có thể dễ dàng phát triển từ một vài dòng đến vài trăm dòng code. Khi điều này xảy ra, việc bảo trì ngày càng khó khăn. Sử dụng TDD và unit test có thể giúp cải thiện script phức tạp của bạn dễ dàng hơn, không đề cập đến việc nó yêu cầu bạn phải xây dựng các script phức tạp theo cách chuyên nghiệp hơn.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.