Thứ tư, 30/09/2020 | 00:00 GMT+7

Cách sử dụng đơn nhất để viết trường hợp thử nghiệm cho một hàm trong Python

Các thư viện chuẩn của Python bao gồm các unittest module để giúp bạn viết và chạy thử nghiệm cho mã Python của bạn.

Kiểm tra viết bằng unittest module có thể giúp bạn tìm thấy lỗi trong các chương trình của bạn, và ngăn chặn hồi quy từ xảy ra khi bạn thay đổi mã của bạn theo thời gian. Đội tôn trọng phát triển thử nghiệm điều khiển có thể tìm thấy unittest hữu ích đảm bảo tất cả các mã có tác giả có một bộ tương ứng của bài kiểm tra.

Trong hướng dẫn này, bạn sẽ sử dụng Python unittest module để viết một bài kiểm tra cho một hàm.

Yêu cầu

Để tận dụng tối đa hướng dẫn này, bạn cần :

Định nghĩa một lớp con TestCase

Một trong những lớp học quan trọng nhất được cung cấp bởi các unittest module được đặt tên TestCase . TestCase cung cấp giàn giáo chung để kiểm tra các chức năng của ta . Hãy xem xét một ví dụ:

test_add_fish_to_aquarium.py
import unittest  def add_fish_to_aquarium(fish_list):     if len(fish_list) > 10:         raise ValueError("A maximum of 10 fish can be added to the aquarium")     return {"tank_a": fish_list}   class TestAddFishToAquarium(unittest.TestCase):     def test_add_fish_to_aquarium_success(self):         actual = add_fish_to_aquarium(fish_list=["shark", "tuna"])         expected = {"tank_a": ["shark", "tuna"]}         self.assertEqual(actual, expected) 

Đầu tiên ta nhập khẩu unittest để làm cho các module có sẵn để mã của ta . Sau đó, ta xác định chức năng mà ta muốn kiểm tra — đây là add_fish_to_aquarium .

Trong trường hợp này, hàm add_fish_to_aquarium của ta chấp nhận một danh sách các loài cá có tên là fish_list và gây ra lỗi nếu fish_list có nhiều hơn 10 phần tử. Các chức năng sau đó trả về một ánh xạ từ điển tên của một bể cá "tank_a" đến cho fish_list .

Một lớp có tên TestAddFishToAquarium được định nghĩa là một lớp con của unittest.TestCase . Phương thức có tên test_add_fish_to_aquarium_success được xác định trên TestAddFishToAquarium . test_add_fish_to_aquarium_success gọi hàm add_fish_to_aquarium với một đầu vào cụ thể và xác minh giá trị trả về thực tế trùng với giá trị mà ta mong đợi được trả về.

Bây giờ ta đã xác định một lớp con TestCase với một bài kiểm tra, hãy xem lại cách ta có thể thực hiện bài kiểm tra đó.

Thực thi TestCase

Trong phần trước, ta đã tạo một lớp con TestCase có tên là TestAddFishToAquarium . Từ cùng một folder với file test_add_fish_to_aquarium.py , hãy chạy kiểm tra đó bằng lệnh sau:

  • python -m unittest test_add_fish_to_aquarium.py

Ta đã gọi module thư viện Python có tên là unittest với python -m unittest . Sau đó, ta cung cấp đường dẫn đến file chứa TestAddFishToAquarium TestCase của ta làm đối số.

Sau khi ta chạy lệnh này, ta nhận được kết quả như sau:

Output
. ---------------------------------------------------------------------- Ran 1 test in 0.000s OK

Các unittest module chạy thử nghiệm của ta và nói với ta rằng ta thử nghiệm chạy OK . Độc thân . trên dòng đầu tiên của kết quả đại diện cho bài kiểm tra đã vượt qua của ta .

Lưu ý: TestCase công nhận các phương pháp thử nghiệm là bất kỳ phương pháp nào bắt đầu với test . Ví dụ: def test_add_fish_to_aquarium_success(self) được công nhận là một bài kiểm tra và sẽ được chạy như vậy. Ngược lại, def example_test(self) sẽ không được công nhận là một bài kiểm tra vì nó không bắt đầu bằng test . Chỉ các phương thức bắt đầu bằng test sẽ được chạy và được báo cáo khi bạn chạy python -m unittest ...

Bây giờ ta hãy thử một bài kiểm tra với một thất bại.

Ta sửa đổi dòng được đánh dấu sau trong phương pháp thử nghiệm của bạn để đưa ra lỗi:

test_add_fish_to_aquarium.py
import unittest  def add_fish_to_aquarium(fish_list):     if len(fish_list) > 10:         raise ValueError("A maximum of 10 fish can be added to the aquarium")     return {"tank_a": fish_list}   class TestAddFishToAquarium(unittest.TestCase):     def test_add_fish_to_aquarium_success(self):         actual = add_fish_to_aquarium(fish_list=["shark", "tuna"])         expected = {"tank_a": ["rabbit"]}         self.assertEqual(actual, expected) 

Kiểm tra đã sửa đổi sẽ không thành công vì add_fish_to_aquarium sẽ không trả về "rabbit" trong danh sách cá thuộc "tank_a" . Hãy chạy thử nghiệm.

, từ cùng một folder với test_add_fish_to_aquarium.py ta chạy:

  • python -m unittest test_add_fish_to_aquarium.py

Khi ta chạy lệnh này, ta nhận được kết quả như sau:

Output
F ====================================================================== FAIL: test_add_fish_to_aquarium_success (test_add_fish_to_aquarium.TestAddFishToAquarium) ---------------------------------------------------------------------- Traceback (most recent call last): File "test_add_fish_to_aquarium.py", line 13, in test_add_fish_to_aquarium_success self.assertEqual(actual, expected) AssertionError: {'tank_a': ['shark', 'tuna']} != {'tank_a': ['rabbit']} - {'tank_a': ['shark', 'tuna']} + {'tank_a': ['rabbit']} ---------------------------------------------------------------------- Ran 1 test in 0.001s FAILED (failures=1)

Đầu ra lỗi cho biết thử nghiệm của ta không thành công. Sản lượng thực tế của {'tank_a': ['shark', 'tuna']} không phù hợp với kỳ vọng (không chính xác) mà ta đã thêm vào test_add_fish_to_aquarium.py của: {'tank_a': ['rabbit']} . Cũng lưu ý thay vì a . , dòng đầu tiên của kết quả bây giờ có F Trong khi . nhân vật được outputted khi kiểm tra vượt qua, F là sản phẩm khi unittest điều hành một thử nghiệm thất bại.

Bây giờ ta đã viết và chạy một bài kiểm tra, hãy thử viết một bài kiểm tra khác cho một hành vi khác của hàm add_fish_to_aquarium .

Kiểm tra một chức năng phát sinh một ngoại lệ

unittest cũng có thể giúp ta xác minh hàm add_fish_to_aquarium tạo ra một Ngoại lệ ValueError nếu đưa quá nhiều cá làm đầu vào. Hãy mở rộng ví dụ trước đó của ta và thêm một phương pháp thử nghiệm mới có tên test_add_fish_to_aquarium_exception :

test_add_fish_to_aquarium.py
import unittest  def add_fish_to_aquarium(fish_list):     if len(fish_list) > 10:         raise ValueError("A maximum of 10 fish can be added to the aquarium")     return {"tank_a": fish_list}   class TestAddFishToAquarium(unittest.TestCase):     def test_add_fish_to_aquarium_success(self):         actual = add_fish_to_aquarium(fish_list=["shark", "tuna"])         expected = {"tank_a": ["shark", "tuna"]}         self.assertEqual(actual, expected)      def test_add_fish_to_aquarium_exception(self):         too_many_fish = ["shark"] * 25         with self.assertRaises(ValueError) as exception_context:             add_fish_to_aquarium(fish_list=too_many_fish)         self.assertEqual(             str(exception_context.exception),             "A maximum of 10 fish can be added to the aquarium"         ) 

Phương thức thử nghiệm mới test_add_fish_to_aquarium_exception cũng gọi hàm add_fish_to_aquarium , nhưng nó thực hiện như vậy với một danh sách dài 25 phần tử có chứa chuỗi "shark" lặp lại 25 lần.

test_add_fish_to_aquarium_exception sử dụng trình quản lý ngữ cảnh with self.assertRaises(...) do TestCase cung cấp để kiểm tra xem add_fish_to_aquarium từ chối danh sách đã nhập quá lâu hay không. Đối số đầu tiên của self.assertRaises là lớp Exception mà ta mong đợi sẽ được nâng lên — trong trường hợp này là ValueError . Trình quản lý ngữ cảnh self.assertRaises được liên kết với một biến có tên là exception_context . Thuộc tính exception trên exception_context chứa ValueError cơ bản mà add_fish_to_aquarium đã nêu ra. Khi ta gọi str() trên ValueError đó để truy xuất thông báo của nó, nó sẽ trả về thông báo ngoại lệ chính xác mà ta mong đợi.

Từ cùng một folder với test_add_fish_to_aquarium.py , hãy chạy thử nghiệm của ta :

  • python -m unittest test_add_fish_to_aquarium.py

Khi ta chạy lệnh này, ta nhận được kết quả như sau:

Output
.. ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK

Đáng chú ý, thử nghiệm của ta sẽ không thành công nếu add_fish_to_aquarium không tăng một Ngoại lệ hoặc tăng một Ngoại lệ khác (ví dụ: TypeError thay vì ValueError ).

Lưu ý: unittest.TestCase cho thấy một số phương pháp khác ngoài assertEqualassertRaises mà bạn có thể sử dụng. Danh sách đầy đủ các phương pháp xác nhận có thể được tìm thấy trong tài liệu , nhưng một lựa chọn được bao gồm ở đây:

phương pháp Quả quyết
assertEqual(a, b) a == b
assertNotEqual(a, b) a != b
assertTrue(a) bool(a) is True
assertFalse(a) bool(a) is False
assertIsNone(a) a is None
assertIsNotNone(a) a is not None
assertIn(a, b) a in b
assertNotIn(a, b) a not in b

Bây giờ ta đã viết một số thử nghiệm cơ bản, hãy xem cách ta có thể sử dụng các công cụ khác do TestCase cung cấp để khai thác bất kỳ mã nào ta đang thử nghiệm.

Sử dụng phương pháp setUp để tạo tài nguyên

TestCase cũng hỗ trợ phương thức setUp để giúp bạn tạo tài nguyên trên cơ sở mỗi lần kiểm tra. Phương thức setUp có thể hữu ích khi bạn có một bộ mã chuẩn bị chung mà bạn muốn chạy trước mỗi và mọi thử nghiệm của bạn . setUp cho phép bạn đặt tất cả mã chuẩn bị này vào một nơi duy nhất, thay vì lặp đi lặp lại nó cho mỗi bài kiểm tra riêng lẻ.

Hãy xem một ví dụ:

test_fish_tank.py
import unittest  class FishTank:     def __init__(self):         self.has_water = False      def fill_with_water(self):         self.has_water = True  class TestFishTank(unittest.TestCase):     def setUp(self):         self.fish_tank = FishTank()      def test_fish_tank_empty_by_default(self):         self.assertFalse(self.fish_tank.has_water)      def test_fish_tank_can_be_filled(self):         self.fish_tank.fill_with_water()         self.assertTrue(self.fish_tank.has_water) 

test_fish_tank.py định nghĩa một lớp có tên FishTank . Ban đầu FishTank.has_water được đặt thành False , nhưng có thể được đặt thành True bằng cách gọi FishTank.fill_with_water() . Các TestCase lớp con TestFishTank định nghĩa một phương thức có tên setUp mà instantiates một mới FishTank dụ và chuyển nhượng mà dụ để self.fish_tank .

setUp được chạy trước mọi phương pháp thử nghiệm riêng lẻ, một version FishTank mới được khởi tạo cho cả test_fish_tank_empty_by_defaulttest_fish_tank_can_be_filled . test_fish_tank_empty_by_default xác minh has_water bắt đầu là False . test_fish_tank_can_be_filled xác minh has_water được đặt thành True sau khi gọi fill_with_water() .

Từ cùng một folder với test_fish_tank.py , ta có thể chạy:

  • python -m unittest test_fish_tank.py

Nếu ta chạy lệnh trước đó, ta sẽ nhận được kết quả sau:

Output
.. ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK

Kết quả cuối cùng cho thấy cả hai bài kiểm tra đều vượt qua.

setUp cho phép ta viết mã chuẩn bị chạy cho tất cả các bài kiểm tra của ta trong một lớp con TestCase .

Lưu ý: Nếu bạn có nhiều file thử nghiệm với các lớp con TestCase mà bạn muốn chạy, hãy xem xét sử dụng python -m unittest discover để chạy nhiều hơn một file thử nghiệm. Chạy python -m unittest discover --help trợ giúp để biết thêm thông tin.

Sử dụng phương pháp tearDown để làm sạch tài nguyên

TestCase hỗ trợ một bản sao cho phương thức setUp có tên là tearDown . tearDown hữu ích nếu, chẳng hạn, ta cần xóa các kết nối đến database hoặc các sửa đổi được thực hiện đối với hệ thống file sau mỗi lần kiểm tra hoàn tất. Ta sẽ xem xét một ví dụ sử dụng tearDown với hệ thống file :

test_advanced_fish_tank.py
import os import unittest  class AdvancedFishTank:     def __init__(self):         self.fish_tank_file_name = "fish_tank.txt"         default_contents = "shark, tuna"         with open(self.fish_tank_file_name, "w") as f:             f.write(default_contents)      def empty_tank(self):         os.remove(self.fish_tank_file_name)   class TestAdvancedFishTank(unittest.TestCase):     def setUp(self):         self.fish_tank = AdvancedFishTank()      def tearDown(self):         self.fish_tank.empty_tank()      def test_fish_tank_writes_file(self):         with open(self.fish_tank.fish_tank_file_name) as f:             contents = f.read()         self.assertEqual(contents, "shark, tuna") 

test_advanced_fish_tank.py định nghĩa một lớp có tên AdvancedFishTank . AdvancedFishTank tạo một file có tên fish_tank.txt và ghi chuỗi "shark, tuna" vào file đó. AdvancedFishTank cũng cho thấy một phương thức empty_tank loại bỏ file fish_tank.txt . Các TestAdvancedFishTank TestCase lớp con định nghĩa cả một setUptearDown phương pháp.

Các setUp phương pháp tạo ra một AdvancedFishTank dụ và gán nó vào self.fish_tank . Các tearDown phương pháp gọi các empty_tank phương pháp trên self.fish_tank : Đảm bảo này mà fish_tank.txt file được lấy ra sau mỗi phương pháp thử nghiệm chạy. Bằng cách này, mỗi bài kiểm tra bắt đầu với một phương tiện chặn sạch. Phương thức test_fish_tank_writes_file xác minh nội dung mặc định của "shark, tuna" được ghi vào file fish_tank.txt .

Từ cùng một folder với test_advanced_fish_tank.py hãy chạy:

  • python -m unittest test_advanced_fish_tank.py

Ta sẽ nhận được kết quả sau:

Output
. ---------------------------------------------------------------------- Ran 1 test in 0.000s OK

tearDown cho phép bạn viết mã dọn dẹp được chạy cho tất cả các bài kiểm tra của bạn trong một lớp con TestCase .

Kết luận

Trong hướng dẫn này, bạn đã viết TestCase lớp học với khẳng định khác nhau, sử dụng các setUptearDown phương pháp, và chạy thử nghiệm của bạn từ dòng lệnh.

Các unittest module cho thấy các lớp học bổ sung và tiện ích mà bạn không bao gồm trong hướng dẫn này. Đến đây bạn có một đường cơ sở, bạn có thể sử dụng các unittest tài liệu module của để tìm hiểu thêm về các lớp học khác có sẵn và các tiện ích. Bạn cũng có thể quan tâm đến Cách thêm kiểm thử đơn vị vào dự án Django của bạn .


Tags:

Các tin liên quan

Cách chuyển đổi int thành float trong Python 3
2020-09-18
Bắt đầu với Python - Get Request
2020-09-15
Cách sử dụng f-string để tạo chuỗi trong Python 3
2020-09-10
Cách chuyển đổi số nguyên thành chuỗi trong Python 3
2020-09-03
Cách chuyển đổi chuỗi thành số nguyên trong Python 3
2020-09-03
Cách sử dụng module bộ sưu tập trong Python 3
2020-08-19
Cách chuyển đổi kiểu dữ liệu trong Python 3
2020-08-07
Cách sử dụng hàm bản đồ Python
2020-08-03
Cách sử dụng quy trình con để chạy các chương trình bên ngoài trong Python 3
2020-07-30
Làm thế nào để đánh lừa một mạng neural trong Python 3
2020-07-30