Teste de unidade para tubulações de dados munging compostas de funções de uma linha

10

Lendo a Introdução Prática à Programação Funcional de Mary Rose Cook , ela oferece um exemplo de anti-padrão

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

Desde a

  • a função faz mais de uma coisa
  • o nome não é descritivo
  • tem efeitos colaterais

Como solução proposta, ela sugere pipelining de funções anônimas

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

No entanto, isso me parece ter a desvantagem de ser ainda menos testável; pelo menos format_bands poderia ter um teste de unidade para verificar se faz o que deveria, mas como testar o pipeline? Ou é a ideia de que as funções anônimas são tão auto-explicativas que não precisam ser testadas?

Meu aplicativo do mundo real para isso é tentar tornar meu pandascódigo mais funcional. Muitas vezes terei algum tipo de pipeline dentro de uma função "munging"

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

Ou reescrevendo no estilo de pipeline:

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

Alguma sugestão para as melhores práticas nesse tipo de situação?

Max Flander
fonte
4
Essas funções lambda individuais são muito pequenas para o teste de unidade. Teste o resultado final. Em outras palavras, as funções anônimas não podem ser testadas por unidade, portanto, não escreva a função como uma função anônima se você planeja testá-la individualmente.
Robert Harvey

Respostas:

1

Acho que você perdeu a parte mais importante do exemplo corrigido do livro. A mudança mais fundamental no código é do método que opera em todos os valores em uma lista para operar em um elemento.

Já existem funções como iter(neste caso, nomeadas pipeline_foreach) que executam uma determinada operação em todos os elementos em uma lista. Não havia necessidade de duplicar isso com um forloop. O uso de uma operação de lista conhecida também torna clara sua intenção. Com mapvocê está transformando os valores. Com itervocê está realizando um efeito colateral com cada elemento. Com forloop você é ... bem, você realmente não sabe até olhar através dele.

O código corrigido de exemplo ainda não é muito funcional, porque (até onde eu sei) modifica os valores na lista sem retorná-los, impedindo a composição adicional de tubulação ou função. O método funcionalmente preferido mapcriaria uma nova lista de bandas com o atualizado countrye name. Em seguida, você pode canalizar essa saída para a próxima função ou compor mapcom outra função que tenha uma lista de bandas. Com iter, é como um beco sem saída.

Eu acho que o código do resultado final tem pequenas funções que são muito triviais para incomodar os testes aqui. Afinal, você não precisa escrever testes de unidade contra replaceou title. Agora, talvez você queira compor esses itens em sua própria função e teste de unidade, para que a combinação desejada seja alcançada em um único item. Eu mesmo, provavelmente teria mudado format_bandspara format_bandsingular, eliminado o loop for e chamado pipeline_each(bands, format_band). Em seguida, você pode testar format_band para garantir que não se esqueceu de algo.

De qualquer forma, no seu código. Seu segundo exemplo de código parece mais pipeline-y. Mas isso por si só não fornece os benefícios da programação funcional. Na prática, programação funcional significa garantir a compatibilidade de funções com outras funções, definindo sua compatibilidade apenas em termos de entradas e saídas. Se houver efeitos colaterais ocultos dentro da função, apesar da entrada / saída alinhada com outra função, não será possível saber se eles são compatíveis até o tempo de execução. Se, no entanto, duas funções não tiverem efeitos colaterais e corresponderem de saída para entrada, você poderá fazer o pipeline ou compor com pouca preocupação com resultados inesperados.

Kasey Speakman
fonte