Continuando o assunto como criar meu próximo teste, irei abordar os três tópicos seguintes do segundo post da série. Recomendo que os leiam em ordem:

Estou querendo testar um método privado

Quem nunca não é mesmo? Tudo pode começar com uma inocente vontade de verificar apenas um método privado em apenas uma classe do teu software. Após pesquisar, você concluí: ah, se a linguagem de programação X permite que eu modifique a visibilidade do método para public via reflexão (parte da metaprogramação) é porque isso
é bom.
Será ?

O primeiro ponto a se analisar é relembrar o que não é Test-Driven Development - spoiler: não é sobre testar se o software não tem bugs. Com a mente fresca, pode-se perceber que se TDD é uma prática de design de código que tem por objetivo guiar seu design baseando-se nos requisitos funcionais do software, por si só, TDD é contra teste de método privado.

Definição método privado por Sandi Metz: métodos privados são métodos unstable da sua API. Entende-se aqui por API todo e qualquer método público de quaisquer objetos. Os métodos privados contêm o necessário para auxiliar métodos de negócio a realizarem suas atividades e por si só, não desempenham quaisquer atividades de valor ao software.

Então porquê quero testar meu método privado? Por causa de uma má definição do design da classe ou feature que você está implementando. A dica chave aqui é repensar nas suas atribuições, ou seja, será que esse método na verdade não deveria ser uma nova classe? Extração em classe aqui geralmente ocorre, então se está travado nisso, pare e repense o design do código que entorna isso.

Quer ver como isso acontece e você nem percebe? Assista essa apresentação de Michael Feathers:

The Deep Synergy Between Testability and Good Design from Tim Yann on Vimeo.

Não consigo isolar a feature que vou criar. Os objetos que irão interagir com ela são complexos para mockar

Situação básica onde você se empolga codificando e quando vai ver virou Big Ball of Mud. Pode ser também que você ficou no deixa disso e não fez Test-First.

Nota para os deixa disso: não estou afirmando que Test-First é a salvação do mundo e que a paz será atingida quando todos o fizerem - meu alerta sobre Test-First é aquele aviso que seu pai/mãe te deu quando você tentou apertar um parafuso do tipo philips com uma chave de fenda, e ignorando-o(a) você seguiu. Foi doloroso, demorado e ficou mal apertado, não foi?

Geralmente, você faz mocks utilizando doubles ao invés de instanciar objetos reais para interagir com seu objeto alvo do teste. Se no teste você precisa que seu double chame um método que chame outro para no fim retornar algo, você simplesmente pode fazer seu double retornar o valor esperado do método que seu objeto em teste precisa e pronto, correto?

O problema escala quando você tem objetos métodos que não podem ser mockados. Por que isso acontece? Bem, quando você não injeta suas dependências. Veja só:

class DeixaDisso
    def tdd_eh_mimimi(params)
        Paypal::RestAPI.perform("/paymenow", params)
    end
end

Como é que você testa o DeixaDisso#tdd_eh_mimimi se ao testar isso ele chamará de verdade o Paypal::RestAPI.perform?

Supondo que o código acima seja uma abstração aceitável, o ideal a fazer é extrair o Paypal::RestAPI e torná-lo injetável via método, por exemplo:

class DeixaDisso
    def tdd_eh_mimimi(params, paypal_api: Paypal::RestAPI)
        paypal_api.perform("/paymenow", params)
    end
end

Com isso, no teste você pode passar um double para que responderá ao método perform e você pode fazer o mock dele criando a seguinte expectativa: __espero que seja chamado o método perform com os parametros: /paymenow e params uma vez.

Pronto. Isso é um teste de unidade baseado em expectativa muito útil e simples quando as dependências são injetáveis.

Houve uma vez que eu trabalhei com um framework PHP chamado Phalcon Framework. Quando fui fazer uns testes que envolvia Controllers, etc., descobri que ele disponibilizava os "Serviços" via di que na verdade era um container de injeção de dependência. Para chamar o serviço 'paypal' por exemplo, eu poderia fazer assim:

class FoobarController {

    public function indexAction() {
        $this->di->getService('paypal') # Objeto Paypal
    }
}

Do ponto de vista do Mr. M isso é fantástico, pois você magicamente tem acesso ao $this->di que te lista todos os serviços registrados. Mas... como testar isto sem cair na real implementação dos serviços? A mesma ideia funcionava para os parametros de POST/PUT. Vinha magicamente e não injetado. Essa filosofia seguia também para os Models e ORM.

Não consigo verificar se um evento/callback do framework que estou usando foi chamado

Ora, como teste de unidade testa a unidade de uma funcionabilidade do software, deixar que o callback/evento seja chamado tornaria o teste um teste de integração, concorda?

Buscando evitar que este callback seja chamado de verdade no teste, a melhor solução é verificar se ele foi chamado dado uma condição. Em miúdos, criar uma expectativa em um mock. Como callbacks diferentes podem e são implementados de formas diferentes, não há um recipe mágico aqui, apenas a dica de que devemos garantir que ele é invocado utilizando da técnica de setar a expectativa no mock (como no item acima eu mencionei). Alguns frameworks fornecem assertions expecificos para você testar a chamada a callbaks. Por exemplo, o Rails te permite testar se um e-mail foi enviado sem enviá-lo de facto. Isso permite que você crie testes isolados para estas condições de negócio sem seguir para os mocks.

Por fim e não menos importante, você pode criar um spy no teste que te ajudará a verificar se o estado de um objeto muda após a chamada do callback. O RSpec tem assertion baseado em evento/mudança.

expect {subject}.to change {subject.will_change_x_to_y}.from(x).to(y)

To be continued.

Continuação em: Parte 3 - Tópicos 7, 8 e 9