Возможно ли разбить длинное имя функции на несколько строк?

В нашей команде разработчиков используется PEP8 linter, для которого требуется максимальная длина строки 80 символов.

Когда я пишу модульные тесты на питоне, мне нравится иметь описательные имена методов, чтобы описать, что делает каждый тест. Однако это часто приводит меня к превышению предела характера.

Вот пример слишком длинной функции...

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()

Мои параметры:

  • Вы можете просто написать более короткие имена методов!

    Я знаю, но я не хочу потерять описательность имен тестов.

  • Вы можете писать многострочные комментарии над каждым тестом вместо использования длинных имен!

    Это достойная идея, но тогда я не смогу увидеть имена тестов при запуске тестов внутри своей IDE (PyCharm).

  • Возможно, вы можете продолжить строки с обратной косой чертой (символ продолжения логической строки).

    К сожалению, это не вариант в Python, как упоминалось в ответе Дэна.

  • Вы можете прекратить эксперименты.

    Это имеет смысл в некоторых отношениях, но приятно поощрять хорошо отформатированный набор тестов.

  • Вы можете увеличить лимит длины строки.

    Наша команда любит иметь лимит, потому что помогает сохранить читаемый код на узких дисплеях, поэтому это не лучший вариант.

  • Вы можете удалить test с начала ваших методов.

    Это не вариант. Тест-тестировщик Python нуждается во всех методах тестирования, чтобы начать с test, или он не подберет их.

    Изменить: некоторые тестовые бегуны позволяют указать регулярное выражение при поиске тестовых функций, хотя я бы предпочел не делать этого, потому что это дополнительная настройка для всех, кто работает над проектом. Кроме того, он не отвечает на исходный вопрос.

  • Вы можете разделить EventListener на свой собственный класс и протестировать его отдельно.

    Слушатель событий есть в своем классе (и тестируется). Это просто интерфейс, который запускается событиями, происходящими в ClientConnection. Такое предложение, похоже, имеет хорошее намерение, но неверно направлено и не отвечает на исходный вопрос.

  • Вы можете использовать BDD Framework, например Behave. Он предназначен для экспресс-тестов.

    Это верно, и я надеюсь использовать их больше в будущем. Хотя мне все же хотелось бы знать, как разделить имена функций по строкам.

В конечном счете...

Есть ли способ в Python разделить объявление длинной функции на несколько строк?

Например...

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()

Или мне придется укусить пулю и сократить ее сам?

Ответ 1

Нет, это невозможно.

В большинстве случаев такое длинное имя было бы нежелательным с точки зрения удобочитаемости и удобства использования функции, хотя ваш пример использования для тестовых имен кажется довольно разумным.

лексические правила Python не позволяют разделить один токен (в данном случае идентификатор) на несколько строк. Символ продолжения логической строки (\ в конце строки) может присоединяться к нескольким физическим линиям в одну логическую строку, но не может присоединиться к одному токену по нескольким строкам.

Ответ 2

Вы также можете написать декоратор, который мутирует .__name__ для метода.

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

Тогда вы могли бы написать:

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()

полагаясь на то, что Python объединяет исходные смежные строковые литералы.

Ответ 3

В ответ на этот вопрос: Как отключить ошибку pep8 в определенном файле?, используйте комментарий t20 > или # noqa для отключения PEP -8 для длинной линии. Важно знать, когда нарушать правила. Конечно, Дзен Питона сказал бы вам, что "Особые случаи не являются достаточно сложными, чтобы нарушать правила".

Ответ 4

Мы можем применить decorator к классу вместо метода, так как unittest получить имя метода из dir(class).

Декоратор decorate_method будет проходить через методы класса и переименовать имя метода на основе словаря func_mapping.

Мысль об этом, увидев ответ декоратора от @Sean Vieira, +1 от меня

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()

тестовый запуск с unittest, как показано ниже, показывает полное длинное описательное имя функции, думает, что это может сработать для вашего случая, хотя оно может показаться не таким элегантным и читаемый из реализации

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

Ответ 5

Вид контекстно-зависимого подхода к проблеме. Выбранный тестовый пример действительно очень похож на формат Natural Language, описывающий необходимые шаги для тестового примера.

Посмотрите, будет ли здесь более понятным использование behave Стиль стиля разработки драйверов поведения. Ваша "функция" может выглядеть (см., Как given, when, then отражает то, что у вас было):

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

Существует также релевантный pyspecs package, пример использования из недавнего ответа по теме:

Ответ 6

У более короткого имени функции есть много преимуществ. Подумайте о том, что действительно необходимо в вашем фактическом имени функции и том, что уже поставлено.

test_that_client_event_listener_receives_connection_refused_error_without_server(self):

Неужели вы уже знаете это тест, когда вы его запускаете? Вам действительно нужно использовать символы подчеркивания? слова, подобные "тому", действительно необходимы для понимания имени? будет ли случай верблюда быть таким же удобочитаемым? как насчет первого примера ниже как переписывание выше (число символов = 79): Принятие соглашения об использовании сокращений для небольшой коллекции общих слов еще более эффективно, например. Connection = Conn, Error = Err. При использовании сокращений вы должны помнить о контексте и использовать их только тогда, когда нет возможности смущения - второй пример ниже. Если вы согласны с тем, что нет фактической необходимости упоминать клиента в качестве объекта теста в имени метода, поскольку эта информация находится в имени класса, тогда может быть уместен третий пример. (54).

ClientEventListenerReceivesConnectionRefusedErrorWithoutServer (сам):

ClientEventListenerReceivesConnRefusedErrWithoutServer (сам):

EventListenerReceiveConnRefusedErrWithoutServer (сам):

Я также согласен с предложением от B Rad C "использовать описательное имя как msg kwarg arg in in self.assert". Вы должны быть заинтересованы только в том, чтобы увидеть результат неудачных тестов при запуске testuite. Проверка того, что у вас есть все необходимые тесты, не должна зависеть от того, чтобы имена методов были настолько подробными.

P.S. Возможно, я также удалю "Безсервер" как лишний. Не должен ли обработчик события клиента получать событие в случае, если сервер по какой-либо причине не подключен? (хотя tbh я бы подумал, что было бы лучше, если бы клиент не смог подключиться к серверу, он получил какое-то "соединение недоступно", отказ в подключении предполагает, что сервер можно найти, но он отказывается от самого соединения.)

Ответ 7

Необходимость такого рода имен может намекать на другие запахи.

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

ClientConnectionTest звучит довольно широко (и совсем не похоже на тестируемое устройство), и, вероятно, это большой класс с большим количеством тестов внутри, которые могут быть переориентированы. Вот так:

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

"Тест" не полезен для имени, потому что он подразумевается.

Со всем кодом, который вы мне дали, мой последний совет: реорганизовать свой тестовый код, а затем пересмотреть свою проблему (если он все еще там).