Hướng dẫn python bdd - python bdd
Thử nghiệm production grade code là một công việc khó khăn. Đôi khi bạn có thể mất gần như toàn bộ thời gian trong quá trình phát triển tính năng này. Hơn nữa, ngay cả khi bạn có phạm vi phủ sóng 100% và vượt qua các bài thử nghiệm, bạn vẫn có thể không cảm thấy tự tin về tính năng mới sẽ hoạt động bình thường trong quá trình sản xuất. Show Hướng dẫn này sẽ cho bạn biết quá trình phát triển ứng dụng bằng Test-Driven Development (TDD). Bài viết đề cập đến cách thức và những thứ bạn nên kiểm tra. Chúng tôi sẽ sử dụng pytest để thử nghiệm, pydantic để xác thực dữ liệu và giảm số lượng thử nghiệm cần thiết và Flask để cung cấp giao diện cho khách hàng thông qua API RESTful. Cuối cùng, bạn sẽ có một mẫu mô hình vững chắc có thể sử dụng cho bất kỳ dự án Python nào để bạn có thể tự tin vượt qua các bài thử nghiệm đúng nghĩa là một phần mềm hoạt động được. Test-Driven Development (TDD). Bài viết đề cập đến cách thức và những thứ bạn nên kiểm tra. Chúng tôi sẽ sử dụng pytest để thử nghiệm, pydantic để xác thực dữ liệu và giảm số lượng thử nghiệm cần thiết và Flask để cung cấp giao diện cho khách hàng thông qua API RESTful. Cuối cùng, bạn sẽ có một mẫu mô hình vững chắc có thể sử dụng cho bất kỳ dự án
Python nào để bạn có thể tự tin vượt qua các bài thử nghiệm đúng nghĩa là một phần mềm hoạt động được. >>> Tổng hợp 5 Phần mềm Lập trình Python phổ biến nhất năm 2022Tổng hợp 5 Phần mềm Lập trình Python phổ biến nhất năm 2022 Mục tiêu Sau bài viết này, bạn sẽ có thể: 1. Giải thích cách bạn nên thử nghiệm phần mềm của mình 2. Định cấu hình pytest và thiết lập cấu trúc dự án để thử nghiệm 3. Xác định các mô hình cơ sở dữ liệu với pydantic 4. Sử dụng đồ đạc pytest để quản lý trạng thái thử nghiệm và thực hiện các tác dụng phụ 5. Xác minh các phản hồi JSON dựa trên các định nghĩa của Lược đồ JSON 6. Tổ chức các hoạt động cơ sở dữ liệu bằng các lệnh (sửa đổi trạng thái, thêm hiệu ứng phụ tác động) và truy vấn (chỉ đọc, không có hiệu ứng phụ tác động) 7. Viết Unit test, kiểm thử tích hợp và kiểm thử end-to-end với pytest 8. Giải thích tại sao cần tập trung nỗ lực thử nghiệm vào hành vi thử nghiệm hơn là chi tiết triển khai Tôi nên kiểm thử phần mềm của mình như thế nào? Các lập trình viên phần mềm có xu hướng rất độc đoán về kiểm thử. Do đó, họ có những ý kiến khác nhau về tầm quan trọng của kiểm thử và ý tưởng về cách thực hiện nó. Vậy nên, chúng ta hãy xem xét ba nguyên tắc mà (hy vọng) hầu hết các lập trình viên sẽ đồng ý giúp bạn viết các bài kiểm thử có giá trị: 1. Kiểm thử sẽ cho biết unit test có đáp ứng yêu cầu mong đợi. Do đó, bạn nên code ngắn gọn và tập trung vào trọng tâm. Cấu trúc GIVEN, WHEN, THEN có thể giúp thực hiện điều này: - GIVEN - điều kiện ban đầu cho bài kiểm thử là gì? - KHI NÀO - điều gì đang xảy ra cần được kiểm thử? - SAU ĐÓ – kết quả mong đợi là gì? Vì vậy, bạn nên chuẩn bị môi trường kiểm thử, kiểm thử behavior, cuối cùng, hãy kiểm tra xem đầu ra có đáp ứng mong đợi hay không. 2. Mỗi behavior nên được kiểm thử một lần - và chỉ một lần. Kiểm thử cùng một behavior nhiều lần không có nghĩa là phần mềm của bạn có khả năng hoạt động cao hơn. Các bài kiểm thử cũng cần được duy trì. Nếu bạn thực hiện một thay đổi nhỏ đối với cơ sở mã của mình và sau đó hai mươi kiểm thử bị hỏng, làm thế nào để bạn biết chức năng nào bị hỏng? Nếu chỉ một lần kiểm thử không thành công thì việc tìm ra lỗi sẽ dễ dàng hơn nhiều. 3. Mỗi bài kiểm thử phải độc lập với các bài kiểm thử khác. Nếu không, bạn sẽ gặp khó khăn trong việc duy trì và chạy test suite. Hướng dẫn này cũng mang tính chủ quan. Đừng coi bất cứ điều gì là giải pháp duy nhất. Vui lòng liên hệ trên Twitter (@jangiacomelli) để thảo luận bất kỳ điều gì liên quan đến hướng dẫn này. > Nếu bạn đã cài đặt thành công phần mềm lập trình Python thì bắt đầu TỰ HỌC PYTHON ngay. Hoặc tham gia KHÓA HỌC PYTHON tại NIIT - ICT Hà Nội để được hướng dẫn với lộ trình bài bản hơn.TỰ HỌC PYTHON ngay. Hoặc tham gia KHÓA HỌC PYTHON tại NIIT - ICT Hà Nội để được hướng dẫn với lộ trình bài bản hơn. Thiết lập cơ bản Hãy chăm chỉ và sẵn sàng để xem tất cả những điều này có ý nghĩa gì trong thế giới thực. Bài kiểm thử đơn giản nhất với pytest như hình dưới đây:
Đó là ví dụ mà bạn có thể đã thấy ít nhất một lần. Trước hết, bạn sẽ không bao giờ viết các bài kiểm thử bên trong cơ sở mã, vì vậy hãy chia nó thành hai file và package. Tạo một thư mục mới cho dự án này và chuyển vào đó:
Tiếp theo, tạo (và kích hoạt) một môi trường ảo. Để biết thêm về cách quản lý dependencies và môi trường ảo, hãy xem Môi trường Python hiện đại.Môi trường Python hiện đại. Thứ ba, cài đặt pytest:
Sau đó, tạo một thư mục mới tên là "sum". Thêm an __init__.py vào thư mục mới, để biến nó thành package, cùng với tệp a another_sum.py :__init__.py vào thư mục mới, để biến nó thành package, cùng với tệp a another_sum.py :
Thêm một thư mục khác đặt tên là "tests" and them các file và thư mục dưới đây:
Bây giờ, bạn sẽ có:
Trong test_another_sum.py thêm:test_another_sum.py thêm:
Cuối cùng, thêm pytest.ini – một tệp cấu hình pytest – vào thư mục "tests", cũng có thể đang là một thư mục trống. pytest.ini – một tệp cấu hình pytest – vào thư mục "tests", cũng có thể đang là một thư mục trống. Cấu trúc dự án đầy đủ bây giờ sẽ như sau:
Để các test trong một package duy nhất cho phép bạn: 1. Sử dụng lại cấu hình pytest trong tất cả các test 2. Sử dụng lại fixtures trong tất cả các test 3. Đơn giản hóa việc chạy test Bạn có thể chạy tất cả các test bằng lệnh này:
Bạn sẽ thấy kết quả của các test, trong trường hợp này là test_another_sum:test_another_sum:
Ứng dụng thực tế Test-Driven DevelopmentTest-Driven DevelopmentBây giờ bạn đã có ý tưởng cơ bản về cách thiết lập và cấu trúc các test, hãy xây dựng một ứng dụng blog đơn giản. Chúng tôi sẽ thiết lập nó bằng TDD để xem test đang hoạt động. Chúng tôi sẽ sử dụng Flask cho frawework web của mình và để tập trung vào kiểm thử thì SQLite được sử dụng cho cơ sở dữ liệu của chúng tôi. Ứng dụng của chúng tôi sẽ có các yêu cầu sau:
Đầu tiên, hãy tạo một dự án mới: 0Thứ hai, tạo (và kích hoạt) một môi trường ảo. Thứ ba, cài đặt pytest và pydantic, một thư viện phân tích cú pháp và xác thực dữ liệu:pydantic, một thư viện phân tích cú pháp và xác thực dữ liệu: 1 pip install "pydantic[email]" cài đặt pydantic cùng với email-validator, sẽ được sử dụng để xác thực địa chỉ email. cài đặt pydantic cùng với email-validator, sẽ được sử dụng để xác thực địa chỉ email. Tiếp đó, tạo các file và thư mục dưới đây: 2Thêm code dưới đây vào models.py to để xác định mô hình mới Article với pydantic:models.py to để xác định mô hình mới Article với pydantic: 3Đây là một mô hình kiểu Active Record, cung cấp các phương pháp lưu trữ, tìm nạp một article và liệt kê tất cả các article. Bạn có thể tự hỏi tại sao chúng tôi không viết test bao gồm mô hình. Chúng tôi sẽ tìm hiểu lý do tại sao trong thời gian ngắn.Active Record, cung cấp các phương pháp lưu trữ, tìm nạp một article và liệt kê tất cả các article. Bạn có thể tự hỏi tại sao chúng tôi không viết test bao gồm mô hình. Chúng tôi sẽ tìm hiểu lý do tại sao trong thời gian ngắn. Tạo một article mới với Python Tiếp theo, hãy trình bày logic kinh doanh của chúng ta. Chúng tôi sẽ viết một số lệnh và truy vấn của trình trợ giúp để tách logic của chúng tôi khỏi mô hình và API. Vì chúng tôi đang sử dụng pydantic, chúng tôi có thể dễ dàng xác thực dữ liệu dựa trên mô hình của chúng tôi. Tạo package "test_article" trong thư mục "tests". Sau đó, thêm một tệp gọi là test_commands.py vào đó.test_commands.py vào đó. 4Thêm test dưới đây vào test_commands.py:test_commands.py: 5Các test này bao gồm các trường hợp sử dụng kinh doanh sau: • các article nên được tạo ra để có dữ liệu hợp lệ • tiêu đề article phải là duy nhất Chạy các test từ thư mục dự án của bạn để thấy rằng chúng không thành công: 6Bây giờ chúng ta có thể thực hiện lệnh. Thêm một tệp a commands.py vào thư mục "blog":commands.py vào thư mục "blog": 7Test FixturesChúng ta có thể sử dụng pytest fixtures để xóa cơ sở dữ liệu sau mỗi lần test và tạo một cơ sở dữ liệu mới trước mỗi lần test. Fixtures là các chức năng được trang trí bằng trình trang trí @ pytest.fixture. Chúng thường nằm bên trong conftest.py nhưng chúng cũng có thể được thêm vào các tệp test thực tế. Các chức năng này được thực thi mặc định trước mỗi lần test.pytest fixtures để xóa cơ sở dữ liệu sau mỗi lần test và tạo một cơ sở dữ liệu mới trước mỗi lần test. Fixtures là các chức năng được trang trí bằng trình trang trí @ pytest.fixture. Chúng thường nằm bên trong conftest.py nhưng chúng cũng có thể được thêm vào các tệp test thực tế. Các chức năng này được thực thi mặc định trước mỗi lần test. Có một tùy chọn là sử dụng các giá trị trả về của chúng bên trong các test. Ví dụ: 8Vì vậy, để sử dụng giá trị trả về từ fixture bên trong test, bạn chỉ cần thêm tên của chức năng fixture làm tham số cho chức năng test. Một tùy chọn khác là thực hiện một hiệu ứng phụ, như tạo cơ sở dữ liệu hoặc mocking một mô hình. Bạn cũng có thể chạy một phần của fixture trước và một phần sau test bằng cách sử dụng yield thay vì return. Ví dụ:yield thay vì return. Ví dụ: 9Bây giờ, hãy thêm fixture sau vào conftest.py, tạo cơ sở dữ liệu mới trước mỗi lần test và sau đó xóa: 0Cờ autouse được đặt thành True để nó tự động được sử dụng mặc định trước (và sau) mỗi test trong test suite. Vì chúng tôi đang sử dụng cơ sở dữ liệu cho tất cả các test nên sử dụng cờ này là hợp lý. Bằng cách đó, bạn không phải thêm chính xác tên cố định vào tất cả các test dưới dạng tham số.autouse được đặt thành True để nó tự động được sử dụng mặc định trước (và sau) mỗi test trong test suite. Vì chúng tôi đang sử dụng cơ sở dữ liệu cho tất cả các test nên sử dụng cờ này là hợp lý. Bằng cách đó, bạn không phải thêm chính xác tên cố định vào tất cả các test dưới dạng tham số. Nếu bạn thực sự không cần quyền truy cập vào cơ sở dữ liệu để test ở mọi mơi, bạn có thể tắt tính năng autouse với điểm test. Bạn có thể xem một ví dụ dưới đây. autouse với điểm test. Bạn có thể xem một ví dụ dưới đây.
Test thành công. Như bạn có thể thấy, test của chúng tôi chỉ kiểm tra lệnh CreateArticleCommand. Chúng tôi không kiểm tra mô hình Article thực tế vì nó không chịu trách nhiệm về logic kinh doanh. Chúng tôi biết rằng lệnh đã hoạt động như mong đợi. Do đó, không cần phải viết thêm bất kỳ test nào khác.CreateArticleCommand. Chúng tôi không kiểm tra mô hình Article thực tế vì nó không chịu trách nhiệm về logic kinh doanh. Chúng tôi biết rằng lệnh đã hoạt động như mong đợi. Do đó, không cần phải viết thêm bất kỳ test nào khác. Danh sách tất cả ArticlesYêu cầu tiếp theo là liệt kê tất cả các article. Tại đây, chúng tôi sẽ sử dụng một truy vấn thay vì lệnh, vì vậy hãy thêm một tệp mới có tên là test_queries.py vào thư mục "test_article": 2Chạy các test:
Test thất bại. Thêm một tệp queries.py vào thư mục "blog":queries.py vào thư mục "blog": 4Bây giờ chúng ta có thể triển khai truy vấn: 5Mặc dù không có tham số nào ở đây, nhưng để đảm bảo tính nhất quán, chúng tôi đã dùng lại từ BaseModel.BaseModel. Chạy lại các test:
Bây giờ các test chạy thành công. Lấy Article theo IDLấy article đơn lẻ theo ID của nó theo cách tương tự như liệt kê tất cả các article. Thêm một test mới cho GetArticleByIDQuery vào test_queries.py.GetArticleByIDQuery vào test_queries.py. 7Chạy test để đảm bảo chúng thất bại: 6Tiếp theo, thêm GetArticleByIDQuery vào queries.py:GetArticleByIDQuery vào queries.py: 9Bây giờ các test thành công: 6Tuyệt. Chúng tôi đáp ứng tất cả các yêu cầu được đề cập ở trên:
Tất cả chúng đều được test. Vì chúng tôi đang sử dụng pydantic để xác thực dữ liệu trong thời gian chạy, chúng tôi không cần nhiều test để bao quát logic kinh doanh. Nếu author không phải là một email hợp lệ, pydantic sẽ phát sinh lỗi. Tất cả những gì cần thiết là đặt thuộc tính author thành loại EmailStr. Chúng tôi cũng không cần phải test nó vì nó đã được các nhà bảo trì pydantic kiểm thử.author không phải là một email hợp lệ, pydantic sẽ phát sinh lỗi. Tất cả những gì cần thiết là đặt thuộc tính author thành loại EmailStr. Chúng tôi cũng không cần phải test nó vì nó đã được các nhà bảo trì pydantic kiểm thử. Vậy nên, chúng tôi đã sẵn sàng giới thiệu chức năng này với thế giới thông qua API RESTful của Flask. Tạo API với FlaskChúng tôi sẽ giới thiệu ba endpoint đáp ứng yêu cầu:
Đầu tiên, tạo một thư mục có tên "schemas" bên trong "test_article", and thêm hai JSON schemas vào đó, Article.json và ArticleList.json.Article.json và ArticleList.json. Article.json:: 1} ArticleList.json : 2}JSON Schemas được sử dụng để xác định phản hồi từ API endpoints. Trước khi tiếp tục, cài đặt thư viện Python jsonschema, được sử dụng để xác thực JSON payloads against the defined schemas, and Flask:JSON Schemas được sử dụng để xác định phản hồi từ API endpoints. Trước khi tiếp tục, cài đặt thư viện Python jsonschema, được sử dụng để xác thực JSON payloads against the defined schemas, and Flask: 3Tiếp theo, hãy viết các test tích hợp cho API. Thêm một tệp mới gọi là test_app.py cho "test_article":test_app.py cho "test_article": 4validate_payload(response.json,"ArticleList.json")Vậy thì điều gì đang xảy ra ở đây? (response.json,"ArticleList.json")Vậy thì điều gì đang xảy ra ở đây? Đầu tiên, chúng tôi đã xác định Flask test client như một fixture để nó có thể được sử dụng trong các test. 1. Sau đó, chúng tôi thêm một chức năng để xác thực payloads. Nó có hai tham số:
2.Cuối cùng, mỗi endpoint có 3 test. Bên trong mỗi test có một lệnh gọi tới API và xác thực payload được trả về. Chạy test để đảm bảo chúng thất bại tại thời điểm này: 6Bây giờ chúng ta có thể viết API. Cập nhật app.py như dưới đây:app.py như dưới đây: 6app.run()Các trình xử lý định tuyến khá đơn giản vì tất cả logic được bao phủ bởi các lệnh và truy vấn. Các action có sẵn với các hiệu ứng phụ (như mutations) được biểu thị bằng các lệnh - ví dụ: tạo một article mới. Mặt khác, các action không có hiệu ứng phụ, những action chỉ đọc trạng thái hiện tại, được bao phủ bởi các truy vấn..run()Các trình xử lý định tuyến khá đơn giản vì tất cả logic được bao phủ bởi các lệnh và truy vấn. Các action có sẵn với các hiệu ứng phụ (như mutations) được biểu thị bằng các lệnh - ví dụ: tạo một article mới. Mặt khác, các action không có hiệu ứng phụ, những action chỉ đọc trạng thái hiện tại, được bao phủ bởi các truy vấn. Mẫu lệnh và truy vấn được sử dụng trong bài đăng này là phiên bản đơn giản hóa của mô hình CQRS. Chúng tôi đang kết hợp CQRS và CRUD. Phương thức .dict() ở trên được cung cấp bởi BaseModel từ pydantic, thứ mà tất cả các mô hình của chúng tôi đều kế thừa từ đó..dict() ở trên được cung cấp bởi BaseModel từ pydantic, thứ mà tất cả các mô hình của chúng tôi đều kế thừa từ đó. Các bài test sẽ thành công: 6Chúng tôi đã đề cập đến các kịch bản mặc định không có lỗi. Trong thế giới thực, khách hàng không phải lúc nào cũng sử dụng API như chúng ta dự kiến. Ví dụ, khi thực hiện yêu cầu tạo một article mà không có titlethì ValidationError sẽ xuất hiện bởi lệnh CreateArticleCommand , điều này sẽ dẫn đến lỗi máy chủ nội bộ và trạng thái HTTP 500. Đó là điều mà chúng tôi muốn tránh. Do vậy, chúng tôi cần xử lý các lỗi như vậy để thông báo cho người dùng về bad request một cách hợp lý.titlethì ValidationError sẽ xuất hiện bởi lệnh CreateArticleCommand , điều này sẽ dẫn đến lỗi máy chủ nội bộ và trạng thái HTTP 500. Đó là điều mà chúng tôi muốn tránh. Do vậy, chúng tôi cần xử lý các lỗi như vậy để thông báo cho người dùng về bad request một cách hợp lý. Hãy viết các test bao gồm các trường hợp đó. Thêm như dưới đây vào test_app.py: 8Chúng tôi đã sử dụng tùy chọn tham số của pytest, giúp đơn giản hóa đầu vào khác nhau cho một test duy nhất. Test sẽ không thành công tại thời điểm này vì chúng tôi chưa xử lý ValidationError :ValidationError : 6Vậy nên hãy thêm trình xử lý lỗi vào ứng dụng Flask bên trong app.py:app.py: 0# Other code ... ValidationError có một phương thức errors trả về danh sách tất cả các lỗi cho từng trường bị thiếu hoặc do chuyển một value không qua xác thực. Chúng ta có thể đơn giản trả lại lỗi này trong phần thân và đặt trạng thái của phản hồi thành 400.errors trả về danh sách tất cả các lỗi cho từng trường bị thiếu hoặc do chuyển một value không qua xác thực. Chúng ta có thể đơn giản trả lại lỗi này trong phần thân và đặt trạng thái của phản hồi thành 400.Bây giờ lỗi đã được xử lý thích hợp, tất cả các test sẽ qua: 6 Độ bao phủ mã (Code Coverage)Bây giờ, với ứng dụng đã được test, đã đến lúc kiểm tra độ phủ của mã. Vì vậy, hãy cài đặt một plugin pytest cho phạm vi bao phủ được gọi là pytest-cov:pytest-cov: 2
After the plugin is installed, we can check code coverage of our blog application like this: Sau khi plugin được cài đặt, chúng tôi có thể kiểm tra mức độ phủ mã của ứng dụng blog như sau: 3Bạn sẽ thấy điều tương tự như sau: 4Độ che phủ 98% có đủ tốt không? Có lẽ là được. Tuy nhiên, hãy nhớ một điều: Tỷ lệ bao phủ cao là rất tốt nhưng chất lượng các bài test của bạn quan trọng hơn nhiều. Nếu chỉ có 70% mã hoặc ít hơn như thế được bao phủ, bạn nên nghĩ đến việc tăng tỷ lệ bao phủ. Nhưng nhìn chung không có ý nghĩa khi viết các bài kiểm tra từ 98% đến 100%. (Một lần nữa, các test cần được duy trì giống như logic kinh doanh của bạn!) có đủ tốt không? Có lẽ là được. Tuy nhiên, hãy nhớ một điều: Tỷ lệ bao phủ cao là rất tốt nhưng chất lượng các bài test của bạn quan trọng hơn nhiều. Nếu chỉ có 70% mã hoặc ít hơn như thế được bao phủ, bạn nên nghĩ đến việc tăng tỷ lệ bao phủ. Nhưng nhìn chung không có ý nghĩa khi viết các bài kiểm tra từ 98% đến 100%. (Một lần nữa, các test cần được duy trì giống như logic kinh doanh của bạn!) End-to-end TestsWe have a working API at this point that's fully tested. We can now look at how to write some end-to-end (e2e) tests. Since we have a simple API we can write a single e2e test to cover the following scenario: Tại thời điểm này, chúng tôi có một API đang hoạt động đã được test đầy đủ. Bây giờ chúng ta có thể xem cách viết một số end-to-end test (e2e). Vì chúng tôi có một API đơn giản, chúng tôi có thể viết một test e2e duy nhất để giải quyết tình huống sau:
Thứ nhất, hãy cài đăt requests library (thư viện yêu cầu):requests library (thư viện yêu cầu): 5 Thứ hai, thêm một test mới vào test_app.py: test_app.py: 6Có hai thứ bạn cần làm trước khi chạy test này: Đầu tiên, đăng ký một marker gọi là e2e với pytest bằng cách thêm code dưới đây vào pytest.ini:marker gọi là e2e với pytest bằng cách thêm code dưới đây vào pytest.ini: 7Pytest markers được sử dụng để loại trừ một số test chạy hoặc bao gồm các test độc lập với vị trí của chúng. được sử dụng để loại trừ một số test chạy hoặc bao gồm các test độc lập với vị trí của chúng. Để chạy mỗi test e2e, hãy chạy: 8Để chạy tất cả các test ngoại trừ e2e:e2e: 9Chạy các test e2e tốn kém hơn và yêu cầu ứng dụng phải được thiết lập và chạy, vì vậy bạn có thể luôn luôn không muốn chạy chúng. Vì test e2e gặp một máy chủ trực tiếp, chúng tôi sẽ cần mở rộng ứng dụng. Điều hướng đến dự án trong terminal window, kích hoạt môi trường ảo và chạy ứng dụng: 0Bây giờ chúng ta có thể chạy test e2e: 1 Thêm một tệp init_db.py vào thư mục "blog":init_db.py vào thư mục "blog": 2Chạy tập lệnh mới và khởi động lại máy chủ: 3Nếu bạn gặp bất kỳ sự cố nào khi chạy init_db.py , bạn có thể cần đặt đường dẫn Python: export PYTHONPATH=$PYTHONPATH:$PWD.init_db.py , bạn có thể cần đặt đường dẫn Python: export PYTHONPATH=$PYTHONPATH:$PWD. Bây giờ test sẽ qua: 1Kim tự tháp kiểm thử (Testing Pyramid)Chúng tôi bắt đầu với các unit test (để kiểm tra các lệnh và truy vấn), sau đó là các test tích hợp (để kiểm tra các API endpoint) và kết thúc bằng các test e2e. Trong các ứng dụng đơn giản, như trong ví dụ này, bạn có thể kết thúc với một số test tích hợp và unit test tương tự. Nói chung, độ phức tạp càng lớn, bạn càng có thể thấy một hình dạng giống như kim tự tháp về mối quan hệ giữa các unit test, tích hợp và e2e. Đó là nơi bắt nguồn của thuật ngữ "test pyramid". Test Pyramid là framework có thể giúp các lập trình viên tạo ra phần mềm chất lượng cao.là framework có thể giúp các lập trình viên tạo ra phần mềm chất lượng cao.
Định nghĩa:
Càng lên cao trong kim tự tháp, các test của bạn càng dễ bị tác động và ít dự đoán hơn. Hơn nữa, cho đến nay, chạy các test e2e vẫn là chậm nhất nên mặc dù chúng có thể mang lại niềm tin rằng ứng dụng của bạn đang làm những gì được mong đợi nhưng bạn không có nhiều test e2e như các unit test hoặc test tích hợp. Unit là gì? Test tích hợp và e2e trông khá đơn giản. Mọi người thảo luận nhiều hơn về các unit test vì trước tiên phải xác định "unit" thực sự là gì. Hầu hết các hướng dẫn test hiển thị một ví dụ unit test kiểm tra một chức năng hoặc phương pháp. Production code không bao giờ đơn giản. Điều đầu tiên, trước khi xác định unit là gì, chúng ta hãy xem xét test nói chung là gì và những thứ gì nên được test. Tại sao phải Test? phải Test? Chúng ta viết test để - Đảm bảo code hoạt động như mong đợi - Bảo vệ phần mềm tránh bị hồi quy Tuy nhiên, khi chu kỳ phản hồi quá dài, các lập trình viên có xu hướng bắt đầu suy nghĩ nhiều hơn về các test cần viết vì thời gian là một hạn chế lớn trong phát triển phần mềm. Đó là lý do tại sao chúng tôi muốn có nhiều unit test hơn các loại bài kiểm tra khác. Chúng tôi muốn tìm và sửa lỗi càng nhanh càng tốt. Test những gì?Vậy là bạn đã biết tại sao chúng ta nên test, bây giờ chúng ta phải xem xét nên test những gì. We should test the behavior of our software. (And, yes: This still applies to TDD, not just BDD.) This is because you shouldn't have to change your tests every time there's a change to the code base.BDD.) This is because you shouldn't have to change your tests every time there's a change to the code base. Think back to the example of the real world application. From a testing perspective, we don't care where the articles are stored. It could be a text file, some other relational database, or a key/value store -- it doesn't matter. Again, our app had the following requirements: Chúng ta nên test behavior của phần mềm. (Và Điều này vẫn áp dụng cho TDD, không chỉ BDD.) Vì khi test behavior, bạn không cần phải thay đổi các test của mình mỗi khi có thay đổi đối với cơ sở mã. Hãy nghĩ lại ví dụ về ứng dụng trong thế giới thực. Từ góc độ thử nghiệm, chúng tôi không quan tâm nơi articles được lưu trữ. Nó có thể là một tệp văn bản, một số cơ sở dữ liệu quan hệ khác hoặc một kho lưu trữ khóa/ giá trị - điều đó không quan trọng. Ứng dụng của chúng tôi vẫn có các yêu cầu sau:
Miễn là các yêu cầu đó không thay đổi, thay đổi đối với phương tiện lưu trữ sẽ không phá vỡ các test của chúng tôi. Tương tự như vậy, chúng tôi biết rằng miễn là các test vượt qua thì phần mềm của mình đáp ứng các yêu cầu đó - vì vậy nó đang hoạt động. Vậy cuối cùng Unit là gì? Về mặt kỹ thuật, mỗi chức năng / phương pháp là một unit, nhưng chúng ta vẫn không nên test từng chức năng / phương thức trong số chúng. Thay vào đó, hãy tập trung sức lực của bạn vào việc test các chức năng và phương pháp được hiển thị công khai từ một mô hình / package. Trong trường hợp của chúng tôi, đây là các phương thức execute. Chúng tôi không mong đợi gọi mô hình Article trực tiếp từ API Flask, vì vậy đừng tập trung nhiều (nếu có) năng lượng vào việc test nó. Nói chính xác hơn, trong trường hợp của chúng ta, các "unit", cần được kiểm tra, là các phương thức execute từ các lệnh và truy vấn. Nếu một số phương pháp không nhằm mục đích được gọi trực tiếp từ các phần khác của phần mềm hoặc từ người dùng cuối, thì đó có thể là chi tiết triển khai. Do đó, các test của chúng tôi có khả năng chống cấu trúc lại các chi tiết triển khai, đây là một trong những ưu điểm tuyệt vời của các test.execute. Chúng tôi không mong đợi gọi mô hình Article trực tiếp từ API Flask, vì vậy đừng tập trung nhiều (nếu có) năng lượng vào việc test nó. Nói chính xác hơn, trong trường hợp của chúng ta, các "unit", cần được kiểm tra, là các phương thức execute từ các lệnh và truy vấn. Nếu một số phương pháp không nhằm mục đích được gọi trực tiếp từ các phần khác của phần mềm hoặc từ người dùng cuối, thì đó có thể là chi tiết triển khai. Do đó, các test của chúng tôi có khả năng chống cấu trúc lại các chi tiết triển khai, đây là một trong những ưu điểm tuyệt vời của các test. Ví dụ: các bài kiểm tra của chúng tôi vẫn sẽ vượt qua nếu chúng tôi bọc logic cho get_by_id và get_by_title trong một phương thức "được bảo vệ" được gọi là _get_by_attribute:get_by_id và get_by_title trong một phương thức "được bảo vệ" được gọi là _get_by_attribute: 5Mặt khác, nếu bạn thực hiện một thay đổi đột ngột bên trong Article, các test sẽ không thành công. Và đây chính xác là những gì chúng tôi muốn. Trong tình huống đó, chúng ta có thể hoàn nguyên breaking change hoặc thích ứng với nó trong lệnh hoặc truy vấn. Bởi vì có một điều mà chúng tôi đang cố gắng làm được: Vượt qua các test có nghĩa là phần mềm hoạt động. Khi nào bạn nên sử dụng Mocks? Chúng tôi đã không sử dụng bất kỳ mock nào trong các test của mình, bởi vì chúng tôi không cần chúng. Các phương pháp mocking hoặc lớp mocking bên trong các mô hình hoặc package tạo ra các test không chống lại việc tái cấu trúc bởi vì chúng được kết hợp với các chi tiết triển khai. Những test như vậy thường xuyên bị hỏng và rất tốn kém để bảo trì. Mặt khác, khi tốc độ là vấn đề thì mocking các tài nguyên bên ngoài rất có lợi. (lệnh gọi tới các API bên ngoài, gửi email, quy trình không đồng bộ kéo dài, v.v.). Ví dụ: chúng tôi có thể kiểm tra mô hình Article riêng biệt và mock nó bên trong các test của chúng tôi cho CreateArticleCommand như sau:Article riêng biệt và mock nó bên trong các test của chúng tôi cho CreateArticleCommand như sau: 6 Tóm lại những cần nắm được
Kết luậnCó rất nhiều thứ bạn có thể học được ở đây. Hãy nhớ rằng đây chỉ là những ví dụ được sử dụng để thể hiện các ý tưởng. Bạn có thể sử dụng các ý tưởng tương tự với Domain-driven design (DDD), Behavior-driven design (BDD) và nhiều cách tiếp cận khác. Hãy nhớ rằng các test phải được xử lý giống như bất kỳ mã nào khác: Chúng là một khoản nợ phải trả chứ không phải tài sản. Viết các test để bảo vệ phần mềm của bạn tránh khỏi các lỗi nhưng đừng để nó đốt thời gian của bạn.Domain-driven design (DDD), Behavior-driven design (BDD) và nhiều cách tiếp cận khác. Hãy nhớ rằng các test phải được xử lý giống như bất kỳ mã nào khác: Chúng là một khoản nợ phải trả chứ không phải tài sản. Viết các test để bảo vệ phần mềm của bạn tránh khỏi các lỗi nhưng đừng để nó đốt thời gian của bạn. |