Pruebas unitarias para tuberías de munging de datos formadas por funciones de una línea

10

Al leer la Introducción práctica de Mary Rose Cook a la programación funcional , ella da como ejemplo un antipatrón

def format_bands(bands):
    for band in bands:
        band['country'] = 'Canada'
        band['name'] = band['name'].replace('.', '')
        band['name'] = band['name'].title()

ya que

  • la función hace más de una cosa
  • el nombre no es descriptivo
  • tiene efectos secundarios

Como solución propuesta, sugiere canalizar funciones anónimas

pipeline_each(bands, [call(lambda x: 'Canada', 'country'),
                      call(lambda x: x.replace('.', ''), 'name'),
                      call(str.title, 'name')])

Sin embargo, esto me parece tener la desventaja de ser aún menos comprobable; al menos format_bands podría tener una prueba unitaria para verificar si hace lo que debe hacer, pero ¿cómo probar la tubería? ¿O es la idea de que las funciones anónimas son tan autoexplicativas que no necesitan ser probadas?

Mi aplicación en el mundo real para esto es tratar de hacer que mi pandascódigo sea más funcional. A menudo tendré algún tipo de tubería dentro de una función "munging"

def munge_data(df)
     df['name'] = df['name'].str.lower()
     df = df.drop_duplicates()
     return df

O reescribiendo en el estilo de canalización:

def munge_data(df)
    munged = (df.assign(lambda x: x['name'].str.lower()
                .drop_duplicates())
    return munged

¿Alguna sugerencia para las mejores prácticas en este tipo de situación?

Max Flander
fuente
44
Esas funciones lambda individuales son demasiado pequeñas para la prueba unitaria. Prueba el resultado final. Para decirlo de otra manera, las funciones anónimas no son comprobables por unidad, por lo tanto, no escriba la función como una función anónima si planea probarla individualmente.
Robert Harvey

Respuestas:

1

Creo que te perdiste probablemente la parte más importante del ejemplo corregido del libro. El cambio más fundamental en el código es desde el método que opera en todos los valores de una lista hasta el que opera en un elemento.

Ya existen funciones como iter(en este caso con el nombre pipeline_foreach) que realizan una operación dada en todos los elementos de una lista. No había necesidad de duplicar eso con un forbucle. También el uso de una operación de lista bien conocida aclara su intención. Con mapustedes están transformando los valores. Con iterusted está realizando un efecto secundario con cada elemento. Con el forbucle eres ... bueno, realmente no lo sabes hasta que lo miras.

El código corregido de ejemplo todavía no es muy funcional, porque (por lo que puedo decir) muta los valores en la lista sin devolverlos, lo que evita la creación de tuberías o funciones adicionales. El método funcionalmente preferido mapcrearía una nueva lista de bandas con las actualizaciones countryy name. Luego, podría canalizar esa salida a la siguiente función o componer mapcon otra función que tomó una lista de bandas. Con iter, es como un callejón sin salida.

Creo que el código de resultado final tiene pequeñas funciones que son demasiado triviales para molestarse en probar aquí. Después de todo, no debería necesitar escribir pruebas unitarias contra replaceo title. Ahora, tal vez desee componerlos juntos en su propia función y prueba unitaria de que la combinación deseada se logra en un solo elemento. Yo mismo, probablemente hubiera cambiado format_bandsa format_bandsingular, soltado el bucle for y llamado pipeline_each(bands, format_band). Luego, puede probar format_band para asegurarse de que no olvidó algo.

De todos modos, a su código. Su segundo ejemplo de código parece más pipeline-y. Pero eso por sí solo no proporciona los beneficios de la programación funcional. En la práctica, la programación funcional significa garantizar la compatibilidad de las funciones con otras funciones definiendo su compatibilidad solo en términos de sus entradas y salidas. Si hay efectos secundarios ocultos dentro de la función, a pesar de su entrada / salida alineada con otra función, no puede saber si son compatibles hasta el tiempo de ejecución. Sin embargo, si dos funciones son libres de efectos secundarios y coinciden de salida a entrada, puede canalizarlas o componerlas sin preocuparse por los resultados inesperados.

Kasey Speakman
fuente