Pregunta ¿Es posible romper un nombre de función larga en múltiples líneas?


Nuestro equipo de desarrollo utiliza un linter PEP8 que requiere un longitud máxima de línea de 80 caracteres.

Cuando estoy escribiendo pruebas unitarias en python, me gusta tener nombres de métodos descriptivos para describir lo que hace cada prueba Sin embargo, esto a menudo me lleva a exceder el límite de caracteres.

Aquí hay un ejemplo de una función que es demasiado larga ...

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

Mis opciones:

  • ¡Podrías escribir nombres de métodos más cortos!

    Lo sé, pero no quiero perder la descriptividad de los nombres de las pruebas.

  • ¡Podría escribir comentarios de múltiples líneas sobre cada prueba en lugar de usar nombres largos!

    Esta es una buena idea, pero no podré ver los nombres de las pruebas cuando ejecute las pruebas dentro de mi IDE (PyCharm).

  • Quizás puedas continuar las líneas con una barra diagonal inversa (un personaje de continuación de línea lógica).

    Desafortunadamente, esta no es una opción en Python, como se menciona en la respuesta de Dan.

  • Podrías dejar de tirar tus pruebas.

    Esto tiene sentido en algunos aspectos, pero es bueno alentar un conjunto de pruebas bien formateado.

  • Puede aumentar el límite de longitud de línea.

    A nuestro equipo le gusta tener el límite porque ayuda a mantener el código legible en pantallas angostas, por lo que este no es el mejor opción.

  • Puedes eliminar test desde el comienzo de tus métodos

    Esto no es una opción. El corredor de prueba de Python necesita todos los métodos de prueba para comenzar con test o no los recogerá.

    Editar: Algunos corredores de prueba te permiten especificar una expresión regular cuando buscas funciones de prueba, aunque prefiero no hacerlo porque es una configuración extra para todos los que trabajan en el proyecto. Además, realmente no responde la pregunta original.

  • Puede separar el EventListener en su propia clase y probarlo por separado.

    El oyente del evento es en su propia clase (y está probado). Es solo una interfaz que se desencadena por eventos que suceden dentro de ClientConnection. Este tipo de sugerencia parece tener buenas intenciones, pero está mal dirigida y no ayuda a responder la pregunta original.

  • Podría usar un Marco BDD como Comportarse. Está diseñado para pruebas expresivas.

    Esto es cierto, y espero usar más de ellos en el futuro. Aunque me gustaría saber cómo dividir los nombres de las funciones entre líneas.

Por último...

¿Hay alguna manera en Python de dividir una larga declaración de función en múltiples líneas?

Por ejemplo...

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

¿O tendré que morder la bala y acortarla?


73
2017-12-04 04:12


origen


Respuestas:


No, esto no es posible

En la mayoría de los casos, un nombre tan largo sería indeseable desde el punto de vista de la legibilidad y la facilidad de uso de la función, aunque su caso de uso para los nombres de prueba parece bastante razonable.

los reglas léxicas de Python no permita que un solo token (en este caso un identificador) se divida en múltiples líneas. El carácter de continuación de línea lógica (\al final de una línea) puede unir múltiples líneas físicas en una sola línea lógica, pero no puede unirse a un solo simbólico a través de múltiples líneas.


71
2017-12-04 04:33



podría también escribe un decorador que muta .__name__ para el método.

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

Entonces podrías escribir:

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

confiando en el hecho de que Python concatena literales de cadenas fuente-adyacentes.


49
2017-12-04 13:46



Por la respuesta a esta pregunta:¿Cómo deshabilitar un error pep8 en un archivo específico?, utilizar el # nopep8 o # noqa comentario final para deshabilitar PEP-8 para una línea larga. Es importante saber cuándo romper las reglas. Por supuesto, el Zen de Python le dirá que "los casos especiales no son lo suficientemente especiales como para romper las reglas".


33
2017-12-04 05:01



Podemos aplicar decorador a la clase en lugar de método desde unittest obtener el nombre de métodos de dir(class).

El decorador decorate_method irá a través de los métodos de clase y cambiará el nombre del nombre del método en función de func_mapping diccionario.

Pensé en esto después de ver la respuesta del decorador de @Sean Vieira, +1 de mi parte

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

prueba ejecutar con unittest como a continuación se muestra el nombre completo de la función descriptiva, cree que podría funcionar para su caso, aunque puede que no suene tan elegante y legible desde la implementación

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

8
2017-12-04 08:05



Una especie de enfoque de contexto al problema. El caso de prueba que has presentado se parece mucho a un Formato de lenguaje natural de describir los pasos necesarios para un caso de prueba.

Vea si usa el behave Comportamiento del estilo de desarrollo del controlador de comportamiento tendría más sentido aquí. Su "característica" podría ser similar (vea cómo given, when, then refleja lo que tenías):

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

También es relevante pyspecs paquete, muestra el uso de una respuesta reciente sobre un tema relacionado:


7
2017-12-04 05:33



La solución de nombre de función más corta tiene muchos méritos. Piensa en lo que es De Verdad necesario en su nombre de función real y lo que ya se proporciona.

test_that_client_event_listener_receives_connection_refused_error_without_server(self):

Seguramente ya sabes que es una prueba cuando lo ejecutas? ¿De verdad necesitas usar guiones bajos? ¿Son palabras como 'eso' realmente necesarias para que se entienda el nombre? ¿Sería el caso de camello tan legible? ¿Qué tal el primer ejemplo a continuación como una reescritura de lo anterior (número de caracteres = 79):   Aceptar una convención para usar abreviaturas para una pequeña colección de palabras comunes es aún más efectivo, p. Conexión = Conn, Error = Err. Cuando use abreviaturas, debe tener en cuenta el contexto y solo utilizarlas cuando no haya posibilidad de confusión: segundo ejemplo a continuación.   Si acepta que no hay una necesidad real de mencionar al cliente como sujeto de prueba en el nombre del método, ya que esa información está en el nombre de la clase, entonces el tercer ejemplo puede ser apropiado. (54) caracteres.

ClientEventListenerReceivesConnectionRefusedErrorWithoutServer (self):

ClientEventListenerReceivesConnRefusedErrWithoutServer (self):

EventListenerReceiveConnRefusedErrWithoutServer (self):

También estoy de acuerdo con la sugerencia de B Rad C "usar el nombre descriptivo como el msg kwarg arg en un self.assert" Solo debería estar interesado en ver el resultado de las pruebas fallidas cuando se ejecuta el conjunto de pruebas. La verificación de que tiene todas las pruebas necesarias escritas no debe depender de tener los nombres de los métodos tan detallados.

PD Probablemente también eliminaría 'WithoutServer' como superfluo también. ¿No debería el controlador de eventos del cliente recibir el evento en el caso de que el servidor no pueda contactarse por algún motivo? (aunque creo que sería mejor que si el cliente no se puede conectar a un servidor recibe algún tipo de "conexión no disponible", la conexión rechazada sugiera que se puede encontrar el servidor pero se niega a hacerlo).


5
2017-12-04 13:22



La necesidad de este tipo de nombres puede insinuar otros olores.

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

ClientConnectionTest suena bastante amplio (y para nada como una unidad comprobable), y es probable que sea una clase grande con muchas pruebas dentro que podrían reenfocarse. Me gusta esto:

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

"Prueba" no es útil en el nombre porque está implícito.

Con todo el código que me ha dado, mi consejo final es: refactorizar su código de prueba, luego revisar su problema (si todavía está allí).


5
2017-12-08 21:10