Nuestro equipo de desarrollo utiliza un linter PEP8 que requiere una longitud máxima de línea de 80 caracteres .
Cuando escribo 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 el carácter descriptivo de los nombres de las pruebas.
¡Puede escribir comentarios de varias líneas encima de cada prueba en lugar de usar nombres largos!
Esta es una idea decente, pero luego no podré ver los nombres de las pruebas cuando las ejecute dentro de mi IDE (PyCharm).
Quizás pueda continuar las líneas con una barra invertida (un carácter 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ía dejar de hacer sus pruebas.
Esto tiene sentido de alguna manera, pero es bueno fomentar un conjunto de pruebas bien formateado.
Podría aumentar el límite de longitud de la línea.
A nuestro equipo le gusta tener el límite porque ayuda a mantener el código legible en pantallas estrechas, por lo que esta no es la mejor opción.
Puede eliminar
test
desde el principio de sus métodos.Esto no es una opción. El ejecutor de pruebas de Python necesita todos los métodos de prueba para empezar
test
o no los recogerá.Editar: algunos corredores de prueba le permiten especificar una expresión regular al buscar funciones de prueba, aunque prefiero no hacerlo porque es una configuración adicional para todos los que trabajan en el proyecto.
Puede separar EventListener en su propia clase y probarlo por separado.
El Event Listener está en su propia clase (y está probado). Es solo una interfaz que se activa por eventos que ocurren dentro de ClientConnection. Este tipo de sugerencia parece tener una buena intención, pero está mal dirigida y no ayuda a responder la pregunta original.
Podrías usar un BDD Framework como Behave . Está diseñado para pruebas expresivas.
Esto es cierto y espero utilizar más en el futuro. Aunque todavía me gustaría saber cómo dividir los nombres de las funciones entre líneas.
Por último...
¿Hay alguna forma en Python de dividir una declaración de función larga en varias 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 acortarlo yo mismo?
func.__doc__
When applying the guideline would make the code less readable, even for someone who is used to reading code that follows this PEP.
en su caso, usaría un nombre de función más corto.Respuestas:
No, esto no es posible.
En la mayoría de los casos, un nombre tan largo no sería deseable desde el punto de vista de la legibilidad y usabilidad de la función, aunque su caso de uso para los nombres de prueba parece bastante razonable.
Las reglas léxicas de Python no permiten que un solo token (en este caso, un identificador) se divida en varias líneas. El carácter de continuación de línea lógica (
\
al final de una línea) puede unir varias líneas físicas en una sola línea lógica, pero no puede unir un solo token en varias líneas.fuente
with
declaraciones:with expr1 as x, \<newline> expr2 as y ...
. En todos los demás casos, por favor, sólo colocar la expresión entre paréntesis:(a_very_long <newline> + expression)
funciona bien, y es mucho más fácil de leer y robusta entoncesa_very_long \<newline> + expression
... este último se rompe apenas añadiendo un solo espacio después de la barra invertida!with
declaración en parens.También podrías escribir un decorador que mute
.__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 cadena adyacentes a la fuente.
fuente
wrapper
con@functools.wraps(f)
.Según la respuesta a esta pregunta: ¿Cómo deshabilitar un error pep8 en un archivo específico? , use el comentario final
# nopep8
o# noqa
para desactivar PEP-8 para una línea larga. Es importante saber cuándo romper las reglas. Por supuesto, el Zen de Python le diría que "los casos especiales no son lo suficientemente especiales como para romper las reglas".fuente
# nopep8
comentarios esparcidos durante las pruebas;)Podemos aplicar el decorador a la clase en lugar del método, ya que
unittest
obtenemos el nombre de los métodosdir(class)
.El decorador
decorate_method
revisará los métodos de clase y cambiará el nombre del método según elfunc_mapping
diccionario.Pensé en esto después de ver la respuesta del decorador de @Sean Vieira, +1 de mí
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()
La ejecución de prueba con
unittest
como se muestra a continuación mostró 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
fuente
Una especie de enfoque específico del contexto del problema. El caso de prueba que ha presentado en realidad se parece mucho a un formato de lenguaje natural que describe los pasos necesarios para realizar un caso de prueba.
Vea si usar el
behave
marco de estilo de desarrollo del controlador de comportamiento tendría más sentido aquí. Su "función" podría parecerse (ver cómo elgiven
,when
,then
reflejar lo que tenía):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 hay un
pyspecs
paquete relevante , uso de muestra de una respuesta reciente sobre un tema relacionado:fuente
behave
. Sin embargo, no quería distraer demasiado a la gente en mi pregunta. Parece un marco realmente agradable y probablemente lo usaré en el futuro. De hecho, le pregunté a mi equipo si podía usarlo en este proyecto, pero no querían que las pruebas se vieran "raras";) --- Nunca había visto pyspecs antes. Gracias por la sugerencia.pyspecs
, por cierto, podría ser más fácil de integrar en su base de código de prueba - una forma más "python" de hacer BDD - no se necesitan estos archivos de características. ¡Gracias!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 en absoluto como una unidad comprobable), y es probable que sea una clase grande con muchas pruebas en su interior 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: refactorice su código de prueba, luego revise su problema (si todavía está ahí).
fuente
test
o el ejecutor de pruebas no los recoge.La solución de nombre de función más corto tiene mucho mérito. Piense en lo que realmente se necesita en el nombre de su función real y lo que ya se proporciona.
¿Seguro que ya sabes que es una prueba cuando la ejecutas? ¿Realmente necesitas usar guiones bajos? ¿Son realmente necesarias palabras como 'eso' para que se entienda el nombre? ¿Sería igual de legible el caso del camello? ¿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, por ejemplo, Conexión = Conexión, Error = Err. Al usar abreviaturas, debe tener en cuenta el contexto y solo usarlas 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 (auto):
EventListenerReceiveConnRefusedErrWithoutServer (auto):
También estoy de acuerdo con la sugerencia de B Rad C "use un nombre descriptivo como 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 debería depender de tener los nombres de los métodos tan detallados.
PD: Probablemente también eliminaría 'WithoutServer' como superfluo. ¿No debería el controlador de eventos del cliente recibir el evento en el caso de que no se pueda contactar con el servidor por algún motivo? (aunque tbh, creo que sería mejor que si el cliente no puede conectarse a un servidor, recibe algún tipo de 'conexión no disponible', la conexión rechazada sugiere que se puede encontrar el servidor pero rechaza la conexión en sí).
fuente
test
contrario, el ejecutor de la prueba no lo capta.test_EventListenerReceiveConnRefusedErrWithoutServer(self):