Por que o teste de unidade é mais difícil na programação orientada a objetos em comparação à programação funcional?

9

Eu estou passando por esta série . O autor menciona que, como o estado é mantido na programação orientada a objetos, é mais difícil escrever testes de unidade. Ele também diz que, como a programação funcional não mantém o estado (nem sempre), é mais fácil escrever testes de unidade. Não vi exemplos que demonstrem esse problema. Se isso for verdade, você poderia me fornecer um exemplo comparando o teste de unidade na programação orientada a objetos e funcional?

0112
fonte
2
Discordo da ideia de que a programação funcional é mais fácil de testar. Embora seja verdade que a função sem estado é mais fácil de testar do que um objeto com estado, a conversão de objeto com estado em função sem estado resulta em uma função de estado muito mais complexa, possivelmente com alguma emulação de estado complexa, como mônadas.
Euphoric
1
@Euphoric Uma versão funcional de uma solução para um problema provavelmente será bem diferente do que apenas substituir o estado por uma Statemônada (que emula o estado). Você tem parcialmente um ponto - o código escrito nesse estilo tem interações não locais - mas, para fins de teste de unidade, você ainda pode passar explicitamente no estado, não precisa zombar de nada. Por outro lado, com mônadas como STe IO(que possuem um estado mutável verdadeiro e implícito), você simplesmente está fazendo uma programação estável e o teste de unidade não será mais fácil do que em qualquer outro idioma.
Derek Elkins saiu de SE
10
Não há nada na definição de OO que exija estado mutável. É perfeitamente possível (e amplamente feito) escrever código OO puro e referencialmente transparente.
Jörg W Mittag
1
Todas as técnicas associadas à programação funcional são perfeitamente aplicáveis ​​na programação orientada a objetos. Programas OO bem projetados geralmente usam técnicas funcionais nos níveis mais baixos, no mínimo.
precisa
1
@ Fabio Se for esse o caso, faz mais sentido usar a programação FP. OO não requer estado mutável, mas a mutabilidade está implícita no exemplo que acabei de fornecer. O mesmo vale para f # - você pode marcar uma variável mutável para atender às necessidades. Mas se você vai usar muitas variáveis ​​mutáveis, use o c #.

Respostas:

17

Eu quero distinguir entre duas maneiras diferentes de abordar a programação orientada a objetos

  1. Simulacionista: seus objetos representam objetos de domínio real, você os programou para lidar com qualquer funcionalidade relacionada a esse domínio. Os objetos programados dessa maneira provavelmente têm muitos colaboradores mutáveis ​​e ocultos usados ​​para implementar essa funcionalidade.
  2. Registros + funções: seus objetos são apenas pacotes de dados e funções que operam sobre esses dados. Objetos programados dessa maneira são mais propensos a serem imutáveis, a assumir menos responsabilidades e permitir a injeção de colaboradores.

Uma regra prática é que um objeto programado na primeira maneira terá mais métodos e mais voidmétodos do que na segunda maneira. Digamos que íamos escrever um simulador de vôo e projetar uma classe de avião. Teríamos algo como:

class Plane {
    void accelerate();
    void deccelerate();
    void toggleRightFlaps();
    void toggleLeftFlaps();
    void turnRudderRight();
    void turnRudderLeft();
    void deployLandingGear();
    void liftLandingGear();
    // etc.
    void tick() throws PlaneCrashedException;
}

Talvez seja um pouco mais extremo do que se possa encontrar, mas é claro. Se você deseja implementar esse tipo de interface, você deve manter dentro do objeto:

  1. Toda a informação sobre o estado do equipamento do avião.
  2. Toda a informação sobre a velocidade / aceleração do avião.
  3. A taxa de atualização da nossa simulação (para implementar o tick).
  4. Detalhes completos sobre o modelo 3d da simulação e sua física para implementar o tick.

A gravação de um teste de unidade para um objeto gravado no modo é extremamente difícil porque:

  1. Você precisa fornecer todos os diferentes bits de dados e colaboradores que esse objeto precisa no início de seu teste (instatá-los pode ser realmente tedioso).
  2. Quando você deseja testar um método, enfrenta dois problemas: a) a interface frequentemente não expõe dados suficientes para você testar (então você acaba tendo que usar zombarias / reflexões para verificar as expectativas) b) existem muitos componentes ligados em um que você deve verificar se comportou em cada teste.

Basicamente, você começa com uma interface que parece razoável e se encaixa bem no domínio, mas a gentileza da simulação o levou a criar um objeto que é realmente difícil de testar.

No entanto, você pode criar objetos que atendem ao mesmo objetivo. Você gostaria de frear Planeem pedaços menores. Tenha um PlaneParticleque rastreie os bits físicos do avião, a posição, velocidade, aceleração, rotação, guinada, etc., etc., expondo e permitindo manipulá-los. Em seguida, um PlanePartsobjeto pode rastrear o status de. Você poderia enviar tick()para um lugar completamente diferente, digamos, ter um PlanePhysicsobjeto parametrizado por, por exemplo, a força da gravidade, que sabe como PlaneParticlee PlanePartscomo cuspir um novo PlaneParticle. Tudo isso pode ser totalmente imutável, embora não seja necessário, por exemplo.

Agora você tem essas vantagens em termos de teste:

  1. Cada componente individual tem menos a fazer e é mais fácil de configurar.
  2. Você pode testar seus componentes isoladamente.
  3. Esses objetos podem evitar a exposição de seus componentes internos (especialmente se forem imutáveis), portanto, não é preciso ter esperteza para medi-los.

Aqui está o truque: a segunda abordagem orientada a objetos que descrevi está muito próxima da programação funcional. Talvez em programas funcionais puros, seus registros e suas funções sejam separados e não unidos em objetos, definitivamente um programa funcional garantiria todas essas coisas. O que eu acho que realmente facilita o teste de unidade é

  1. Pequenas unidades (pequeno espaço de estado).
  2. Funções com entrada mínima (sem entradas ocultas).
  3. Funções com saída mínima.

A programação funcional incentiva essas coisas (mas é possível escrever programas ruins em qualquer paradigma), mas elas são alcançáveis ​​em programas orientados a objetos. E enfatizaria ainda que a programação funcional e a programação orientada a objetos não são incompatíveis.

Walpen
fonte
2
Não tenho certeza de que seja uma boa distinção. Mesmo ao usar a abordagem 1, muitos considerariam as técnicas que você descreve para a abordagem 2 como prática padrão. As duas abordagens não são mutuamente exclusivas. Os bits do plano podem ser compostos juntos para criar uma abstração maior, e agora você está na área da abordagem 1. Eu diria que a abordagem 1, como você a descreve, não é lógica.
precisa
3
"E eu enfatizaria ainda que a programação funcional e a programação orientada a objetos não são incompatíveis.": Alguns diriam que a programação orientada a objetos é caracterizada apenas por despacho dinâmico. Assim, você pode escrever no estilo OOP imperativo : objetos mutáveis ​​+ despacho dinâmico, ou no estilo OOP funcional : objetos imutáveis ​​+ despacho dinâmico.
Giorgio
3
Quebrar um objeto grande em partes menores não é uma programação funcional. E a solução para um grande objeto não é separar a lógica dos dados e torná-la anêmica, o que é funcional . Eu posso estar entendendo mal toda a sua parte, no entanto.
Chris Wohlert