Có thể ngắt một tên hàm dài trên nhiều dòng không?


81

Nhóm phát triển của chúng tôi sử dụng linter PEP8 yêu cầu độ dài dòng tối đa là 80 ký tự .

Khi tôi viết các bài kiểm tra đơn vị trong python, tôi muốn có tên phương thức mô tả để mô tả những gì mỗi bài kiểm tra thực hiện. Tuy nhiên điều này thường dẫn đến việc tôi vượt quá giới hạn nhân vật.

Đây là một ví dụ về một hàm quá dài ...

class ClientConnectionTest(unittest.TestCase):

    def test_that_client_event_listener_receives_connection_refused_error_without_server(self):
        self.given_server_is_offline()
        self.given_client_connection()
        self.when_client_connection_starts()
        self.then_client_receives_connection_refused_error()

Tùy chọn của tôi:

  • Bạn chỉ có thể viết các tên phương thức ngắn hơn!

    Tôi biết, nhưng tôi không muốn làm mất đi tính mô tả của tên bài kiểm tra.

  • Bạn có thể viết bình luận nhiều dòng phía trên mỗi bài kiểm tra thay vì sử dụng tên dài!

    Đây là một ý tưởng hay, nhưng sau đó tôi sẽ không thể nhìn thấy tên thử nghiệm khi chạy thử nghiệm bên trong IDE của mình (PyCharm).

  • Có lẽ bạn có thể tiếp tục các dòng bằng dấu gạch chéo ngược (một ký tự tiếp tục dòng hợp lý).

    Thật không may, đây không phải là một tùy chọn trong Python, như đã đề cập trong câu trả lời của Dan.

  • Bạn có thể ngừng viết bài kiểm tra của mình.

    Điều này có ý nghĩa theo một số cách, nhưng thật tốt khi khuyến khích một bộ thử nghiệm được định dạng tốt.

  • Bạn có thể tăng giới hạn độ dài dòng.

    Nhóm của chúng tôi thích có giới hạn vì nó giúp giữ cho mã có thể đọc được trên các màn hình hẹp, vì vậy đây không phải là lựa chọn tốt nhất .

  • Bạn có thể xóa testtừ đầu các phương pháp của mình.

    Đây không phải là một lựa chọn. Người chạy thử nghiệm của Python cần tất cả các phương pháp thử nghiệm để bắt đầu testhoặc nó sẽ không chọn chúng.

    Chỉnh sửa: Một số trình chạy thử nghiệm cho phép bạn chỉ định một biểu thức chính quy khi tìm kiếm các hàm thử nghiệm, mặc dù tôi không muốn làm điều này vì nó là thiết lập bổ sung cho mọi người làm việc trong dự án.

  • Bạn có thể tách EventListener thành lớp riêng và kiểm tra riêng.

    Trình xử lý sự kiện nằm trong lớp riêng của nó (và được thử nghiệm). Nó chỉ là một giao diện được kích hoạt bởi các sự kiện xảy ra trong ClientConnection. Loại gợi ý này dường như có mục đích tốt, nhưng bị định hướng sai và không giúp trả lời câu hỏi ban đầu.

  • Bạn có thể sử dụng Khung BDD như Behave . Nó được thiết kế cho các bài kiểm tra biểu cảm.

    Điều này là đúng, và tôi hy vọng sẽ sử dụng chúng nhiều hơn trong tương lai. Mặc dù tôi vẫn muốn biết cách tách các tên hàm thành các dòng.

Cuối cùng...

Có cách nào trong Python để chia một khai báo hàm dài thành nhiều dòng không?

Ví dụ...

def test_that_client_event_listener_receives_
  connection_refused_error_without_server(self):
    self.given_server_is_offline()
    self.given_client_connection()
    self.when_client_connection_starts()
    self.then_client_receives_connection_refused_error()

Hay tôi sẽ phải tự cắn viên đạn và rút ngắn nó?


8
Tại sao không sử dụng một docstring chức năng mô tả? Sau đó, bạn có thể in nó vớifunc.__doc__
Jakub

62
Ngừng bôi đen các bài kiểm tra đơn vị của bạn.
John Kugelman

55
Sau đó, tắt quy tắc này. Thật là điên rồ khi bạn đang cố gắng rất nhiều để giải quyết quy tắc xơ vải này thay vì chỉ vô hiệu hóa nó.
John Kugelman

13
Truy cập lại PEP8 python.org/dev/peps/pep-0008 , Các lý do chính đáng để bỏ qua các nguyên tắc: When applying the guideline would make the code less readable, even for someone who is used to reading code that follows this PEP.Trong trường hợp của bạn sẽ sử dụng tên hàm ngắn hơn.
Akavall

56
Có hai vấn đề khó khăn trong khoa học máy tính, vô hiệu bộ nhớ cache, đặt tên cho mọi thứ và lỗi từng lỗi một.
Surt

Câu trả lời:


78

Không, điều này là không thể.

Trong hầu hết các trường hợp, một cái tên dài như vậy sẽ không được mong muốn từ quan điểm về tính dễ đọc và khả năng sử dụng của hàm, mặc dù trường hợp sử dụng của bạn cho các tên thử nghiệm có vẻ khá hợp lý.

Các quy tắc từ vựng của Python không cho phép một mã thông báo (trong trường hợp này là mã định danh) được chia thành nhiều dòng. Ký tự tiếp tục dòng logic ( \ở cuối dòng) có thể nối nhiều dòng vật lý thành một dòng logic duy nhất, nhưng không thể nối một mã thông báo duy nhất trên nhiều dòng.


2
Thật là xấu hổ. Dù vậy, tôi vẫn cảm thấy có thể có một giải pháp ma thuật nào đó. --- Tôi nên đề cập rằng tôi đã thử dấu gạch chéo ngược trong bài đăng của mình, đề phòng bất kỳ ai đề cập đến nó với tôi.
byxor

6
Cách tốt nhất của bạn là sử dụng tên mô tả của bạn dưới dạng msg kwarg arg trong phương thức self.assert *. Nếu bài kiểm tra vượt qua, bạn sẽ không nhìn thấy nó. Nhưng nếu thử nghiệm không thành công, chuỗi mô tả của bạn sẽ có sẵn trên đối tượng kết quả thử nghiệm.
B Rad C

11
Cần lưu ý rằng có chính xác một tình huống mà việc sử dụng ký tự tiếp tục dòng được chấp nhận: withcâu lệnh dài : with expr1 as x, \<newline> expr2 as y .... Trong tất cả các trường hợp khác, xin vui lòng, chỉ cần quấn biểu hiện trong ngoặc đơn: (a_very_long <newline> + expression)hoạt động tốt, và nhiều hơn nữa có thể đọc được và mạnh mẽ sau đó a_very_long \<newline> + expression... vỡ sau bởi chỉ cần thêm một không gian đơn lẻ sau dấu chéo ngược!
Bakuriu

3
@Bakuriu - Chà! Tôi không biết bạn không thể gói một withtuyên bố trong parens.
mattmc3

2
@ mattmc3 Lý do rất đơn giản: nó không phải là một biểu thức. AFAIK, đó trường hợp duy nhất mà việc sử dụng dấu ngoặc đơn để tiếp tục trên một dòng mới không phải là một lựa chọn.
Bakuriu

52

Bạn cũng có thể viết một trình trang trí thay đổi .__name__cho phương thức.

def test_name(name):
    def wrapper(f):
        f.__name__ = name
        return f
    return wrapper

Sau đó, bạn có thể viết:

class ClientConnectionTest(unittest.TestCase):
    @test_name("test_that_client_event_listener_"
    "receives_connection_refused_error_without_server")
    def test_client_offline_behavior(self):
        self.given_server_is_offline()
        self.given_client_connection()
        self.when_client_connection_starts()
        self.then_client_receives_connection_refused_error()

dựa trên thực tế là Python nối các ký tự chuỗi liền kề nguồn.


3
Đây là một ý tưởng rất tốt. Nó trông cũng rất dễ đọc. Tôi sẽ thử ngay bây giờ và xem IDE của tôi có hiển thị các tên hàm dài hơn không.
byxor

2
Rất tiếc, trình trang trí không được áp dụng trước khi chạy thử nghiệm trong PyCharm, có nghĩa là tôi không thể thấy tên mô tả từ trình chạy thử nghiệm của mình.
byxor

2
Tôi nghĩ bạn sẽ muốn trang trí wrappervới @functools.wraps(f).

2
Đây là giải pháp tốt nhất để có-bánh-và-ăn-nó-quá; nó kết hợp tất cả các tính năng mà @BrandonIbbotson đang tìm kiếm. Thật tệ là PyCharm vẫn chưa tìm ra nó.
Dan Lenski

3
Tốt hơn nữa, hãy sửa đổi trình trang trí để tạo tên mô tả từ chuỗi tài liệu của hàm.
Nick Sweeting

33

Theo câu trả lời cho câu hỏi này: Làm thế nào để vô hiệu hóa lỗi pep8 trong một tệp cụ thể? , sử dụng chú thích # nopep8hoặc ở # noqacuối để tắt PEP-8 trong một hàng dài. Điều quan trọng là phải biết khi nào nên phá vỡ các quy tắc. Tất nhiên, Zen of Python sẽ cho bạn biết rằng "Các trường hợp đặc biệt không đủ đặc biệt để phá vỡ các quy tắc."


5
Đó thực sự là một ý tưởng tuyệt vời vì nó cho phép tôi lấy phần còn lại của các tệp thử nghiệm. Tôi vừa thử nghiệm nó và nó hoạt động. Tôi cũng có thể giữ tất cả các lợi ích của các tên phương thức dài. --- Mối quan tâm duy nhất của tôi là nhóm sẽ không thích nhìn thấy # nopep8bình luận nằm rải rác trong các bài kiểm tra;)
byxor

8

Chúng ta có thể áp dụng decorator cho lớp thay vì phương thức vì unittestlấy tên phương thức từ dir(class).

Trình trang trí decorate_methodsẽ duyệt qua các phương thức của lớp và đổi tên phương thức dựa trên func_mappingtừ điển.

Nghĩ về điều này sau khi xem câu trả lời của người trang trí từ @Sean Vieira, +1 từ tôi

import unittest, inspect

# dictionary map short to long function names
func_mapping = {}
func_mapping['test_client'] = ("test_that_client_event_listener_receives_"
                               "connection_refused_error_without_server")     
# continue added more funtion name mapping to the dict

def decorate_method(func_map, prefix='test_'):
    def decorate_class(cls):
        for (name, m) in inspect.getmembers(cls, inspect.ismethod):
            if name in func_map and name.startswith(prefix):
                setattr(cls, func_map.get(name), m) # set func name with new name from mapping dict
                delattr(cls, name) # delete the original short name class attribute
        return cls
    return decorate_class

@decorate_method(func_mapping)
class ClientConnectionTest(unittest.TestCase):     
    def test_client(self):
        # dummy print for testing
        print('i am test_client')
        # self.given_server_is_offline()
        # self.given_client_connection()
        # self.when_client_connection_starts()
        # self.then_client_receives_connection_refused_error()

chạy thử nghiệm với unittestnhư bên dưới đã hiển thị tên hàm mô tả dài đầy đủ, nghĩ rằng nó có thể phù hợp với trường hợp của bạn mặc dù nó có vẻ không thanh lịch và dễ đọc khi triển khai

>>> unittest.main(verbosity=2)
test_that_client_event_listener_receives_connection_refused_error_without_server (__main__.ClientConnectionTest) ... i am client_test
ok

7

Phân loại một cách tiếp cận vấn đề theo ngữ cảnh cụ thể. Trường hợp thử nghiệm bạn đã trình bày thực sự trông rất giống với định dạng Ngôn ngữ tự nhiên mô tả các bước cần thiết để một trường hợp kiểm thử thực hiện.

Xem liệu việc sử dụng behavekhung phong cách phát triển Trình điều khiển hành vi có hợp lý hơn không ở đây. Bạn "tính năng" có thể trông giống như (xem như thế nào given, when, thenphản ánh những gì bạn có):

Feature: Connect error testing

  Scenario: Client event listener receives connection refused error without server
     Given server is offline
      when client connect starts
      then client receives connection refused error

Ngoài ra còn có pyspecsgói có liên quan , cách sử dụng mẫu từ câu trả lời gần đây về chủ đề liên quan:


Tôi đã nghĩ đến việc đề cập rằng tôi biết có các tùy chọn BDD như thế nào behave. Tuy nhiên tôi không muốn làm mọi người phân tâm quá nhiều vào câu hỏi của mình. Nó trông giống như một khuôn khổ thực sự đẹp và tôi có thể sẽ sử dụng nó trong tương lai. Tôi thực sự đã hỏi nhóm của mình nếu tôi có thể sử dụng nó trong dự án này, nhưng họ không muốn kiểm tra để trông "kỳ lạ";) --- Tôi chưa từng thấy pyspec trước đây. Cám ơn vì sự gợi ý.
byxor

1
@BrandonIbbotson gotcha, tôi hiểu tại sao bạn không muốn đề cập đến nó - rất hợp lý. pyspecsTuy nhiên, có thể dễ dàng hơn khi tích hợp vào cơ sở mã thử nghiệm của bạn - một cách "python" hơn để thực hiện BDD - không cần các tệp tính năng này. Cảm ơn!
alecxe

5

Sự cần thiết của loại tên này có thể gợi ý đến các mùi khác.

class ClientConnectionTest(unittest.TestCase):
   def test_that_client_event_listener_receives_connection_refused_error_without_server(self):
       ...

ClientConnectionTestnghe có vẻ khá rộng (và hoàn toàn không giống một đơn vị có thể kiểm tra được) và có thể là một lớp lớn với nhiều bài kiểm tra bên trong có thể được tập trung lại. Như thế này:

class ClientEventListenerTest(unittest.TestCase):
  def receives_connection_refused_without_server(self):
      ...

"Test" không hữu ích trong tên vì nó ngụ ý.

Với tất cả mã bạn đã cung cấp cho tôi, lời khuyên cuối cùng của tôi là: cấu trúc lại mã thử nghiệm của bạn, sau đó kiểm tra lại vấn đề của bạn (nếu nó vẫn còn đó).


Trình nghe sự kiện là một giao diện. Các phương thức trong đó được kích hoạt bởi những thứ xảy ra với ClientConnection. Việc kiểm tra trình lắng nghe sự kiện của riêng nó đã được thực hiện. Cá nhân tôi nghĩ ClientConnection tuân theo SRP khá tốt, nhưng tôi có thể bị thiên vị (và bạn không thể thấy điều đó). --- Tên thử nghiệm Python phải bắt đầu bằng testhoặc người chạy thử nghiệm không nhận chúng.
byxor

1
@BrandonIbbotson À, tôi hiểu rồi, bạn đang kiểm tra xem kết nối máy khách có kích hoạt thứ gì đó trong trình nghe sự kiện không. Điều đó sẽ rõ ràng hơn với một cái tên như "test_that_connection_without_server_triggers_connection_refused_event". Yêu cầu của phần "kiểm tra" là khủng khiếp vì nó buộc bạn phải đi với những cái tên khó xử hoặc những cái tên đầy keo dán vô dụng.
BM

Đó là một tên phương pháp tốt hơn. Tôi có thể đổi tên một vài phương pháp đó theo cách bạn đã đề xuất. Mặc dù tôi có thể vẫn còn rất nhiều phương thức trên 80 ký tự
byxor

Từ những gì tôi thấy, bạn có thể lồng các lớp trong Python. Người chạy thử có xử lý được điều đó không? Có thể bạn có thể chia phần bên trong của ClientConnectionTest thành các chủ đề là các lớp lồng nhau chứa các bài kiểm tra liên quan. Bằng cách đó, lớp của chủ đề sẽ mang một phần tên mà bạn không cần viết trong mỗi bài kiểm tra.
BM

1
Vâng, hình dung đó có thể là trường hợp. Không chắc chắn những gì khác để đề xuất sau đó. Dù sao thì có lẽ hãy cho phép mở rộng giới hạn ký tự, chúng tôi đã tự mình làm điều đó và cuối cùng nhận ra rằng nó không phải là vấn đề lớn và mọi người đều có chỗ để chào đón hơn 80 dòng ký tự. Chúc may mắn!
BM

4

Giải pháp tên hàm ngắn hơn có rất nhiều lợi ích. Hãy nghĩ về những gì thực sự cần thiết trong tên hàm thực của bạn và những gì đã được cung cấp.

test_that_client_event_listener_receives_connection_refused_error_without_server(self):

Chắc chắn bạn đã biết đó là một bài kiểm tra khi bạn chạy nó? Bạn có thực sự cần sử dụng dấu gạch dưới không? Những từ như 'that' có thực sự cần thiết để hiểu tên không? trường hợp lạc đà có thể đọc được không? Vậy còn ví dụ đầu tiên dưới đây là cách viết lại ở trên (số ký tự = 79): Chấp nhận quy ước sử dụng chữ viết tắt cho một tập hợp nhỏ các từ phổ biến thậm chí còn hiệu quả hơn, ví dụ: Connection = Conn, Error = Err. Khi sử dụng các từ viết tắt, bạn phải lưu ý đến ngữ cảnh và chỉ sử dụng chúng khi không có khả năng nhầm lẫn - Ví dụ thứ hai bên dưới. Nếu bạn chấp nhận rằng thực tế không cần phải đề cập đến khách hàng là đối tượng thử nghiệm trong tên phương thức vì thông tin đó nằm trong tên lớp thì ví dụ thứ ba có thể phù hợp. (54) ký tự.

ClientEventListenerReceivesConnectionRefusedErrorWithoutServer (bản thân):

ClientEventListenerReceivesConnRefusedErrWithoutServer (bản thân):

EventListenerReceiveConnRefusedErrWithoutServer (bản thân):

Tôi cũng đồng ý với gợi ý từ B Rad C "sử dụng tên mô tả như msg kwarg arg in a self.assert" Bạn chỉ nên quan tâm đến việc xem kết quả từ các bài kiểm tra thất bại khi testsuite được chạy. Việc xác minh rằng bạn đã viết tất cả các bài kiểm tra cần thiết không phụ thuộc vào việc có tên phương pháp quá chi tiết.

Tái bút, tôi có lẽ cũng muốn loại bỏ 'WithoutServer' là không cần thiết. Trình xử lý sự kiện máy khách không nên nhận sự kiện trong trường hợp máy chủ không liên lạc được vì bất kỳ lý do gì? (mặc dù tbh tôi nghĩ rằng sẽ tốt hơn nếu máy khách của họ không thể kết nối với máy chủ, nó nhận được một số loại 'kết nối không khả dụng', kết nối bị từ chối cho thấy rằng máy chủ có thể được tìm thấy nhưng từ chối chính kết nối đó.)


1
TL; DR - vui lòng so sánh độ dài câu trả lời của bạn với câu trả lời khác.
MarianD

3
MarianD: Rất tiếc, nhưng câu trả lời đã được đưa ra cho OP, những người có thể bận tâm khi dành một phút để đọc nó và giải quyết một số chiến lược để rút ngắn tên với các ví dụ và cơ sở xây dựng. Nếu bạn muốn phiên bản ngắn gọn ... "Tránh các từ và dấu câu không cần thiết và rút ngắn các từ thông dụng một cách nhất quán" - như vậy đã đủ ngắn gọn chưa?
Charemer

3
Với thư viện mới nhất của python, mỗi phương pháp thử nghiệm phải bắt đầu bằng testnếu không người chạy thử nghiệm không nhận nó.
byxor

1
@BrandonIbbotsontest_EventListenerReceiveConnRefusedErrWithoutServer(self):
Hendry

1
Tôi như CamelCase, nhưng tôi nghĩ rằng nó dường như vi phạm ba của PEP 8: "Sử dụng chức năng đặt tên quy tắc: chữ thường với các từ cách nhau bởi dấu gạch dưới khi cần thiết để cải thiện khả năng đọc."
Scooter
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.