Seria dinamicamente, em vez de estaticamente digitado. A digitação de pato faria então o mesmo trabalho que as interfaces em idiomas de tipo estaticamente. Além disso, suas classes seriam modificáveis em tempo de execução para que uma estrutura de teste pudesse facilmente stub ou zombar de métodos em classes existentes. Ruby é uma dessas línguas; O rspec é sua principal estrutura de teste para TDD.
Como a digitação dinâmica ajuda no teste
Com a digitação dinâmica, você pode criar objetos simulados simplesmente criando uma classe que tenha a mesma interface (assinaturas de método) que o objeto colaborador que você precisa imitar. Por exemplo, suponha que você tenha alguma classe que enviou mensagens:
class MessageSender
def send
# Do something with a side effect
end
end
Digamos que temos um MessageSenderUser que usa uma instância do MessageSender:
class MessageSenderUser
def initialize(message_sender)
@message_sender = message_sender
end
def do_stuff
...
@message_sender.send
...
@message_sender.send
...
end
end
Observe o uso aqui da injeção de dependência , um grampo dos testes de unidade. Voltaremos a isso.
Você deseja testar se as MessageSenderUser#do_stuff
chamadas são enviadas duas vezes. Assim como você faria em um idioma estaticamente digitado, você pode criar um MessageSender falso que conta quantas vezes send
foi chamado. Mas, diferentemente de uma linguagem de tipo estaticamente, você não precisa de nenhuma classe de interface. Você apenas cria e cria:
class MockMessageSender
attr_accessor :send_count
def initialize
@send_count = 0
end
def send
@send_count += 1
end
end
E use-o em seu teste:
mock_sender = MockMessageSender.new
MessageSenderUser.new(mock_sender).do_stuff
assert_equal(mock_sender.send_count, 2)
Por si só, a "digitação de pato" de uma linguagem digitada dinamicamente não acrescenta muito aos testes em comparação com uma linguagem digitada estaticamente. Mas e se as classes não forem fechadas, mas puderem ser modificadas em tempo de execução? Isso é um divisor de águas. Vamos ver como.
E se você não tivesse que usar injeção de dependência para tornar uma classe testável?
Suponha que o MessageSenderUser apenas use o MessageSender para enviar mensagens, e você não precisa permitir a substituição do MessageSender por outra classe. Dentro de um único programa, esse geralmente é o caso. Vamos reescrever o MessageSenderUser para que ele simplesmente crie e use um MessageSender, sem injeção de dependência.
class MessageSenderUser
def initialize
@message_sender = MessageSender.new
end
def do_stuff
...
@message_sender.send
...
@message_sender.send
...
end
end
O MessageSenderUser agora é mais simples de usar: ninguém a criar precisa criar um MessageSender para ele usar. Não parece uma grande melhoria neste exemplo simples, mas agora imagine que o MessageSenderUser seja criado em mais de uma vez ou que possua três dependências. Agora, o sistema tem muitas instâncias de passagem apenas para fazer os testes de unidade felizes, não porque necessariamente melhora o design.
Classes abertas permitem testar sem injeção de dependência
Uma estrutura de teste em uma linguagem com digitação dinâmica e classes abertas pode tornar o TDD bastante agradável. Aqui está um trecho de código de um teste rspec para MessageSenderUser:
mock_message_sender = mock MessageSender
MessageSender.should_receive(:new).and_return(mock_message_sender)
mock_message_sender.should_receive(:send).twice.with(no_arguments)
MessageSenderUser.new.do_stuff
Esse é o teste completo. Se MessageSenderUser#do_stuff
não chamar MessageSender#send
exatamente duas vezes, este teste falhará. A classe MessageSender real nunca é invocada: dissemos ao teste que sempre que alguém tenta criar um MessageSender, ele deve receber nosso MessageSender falso. Não é necessária injeção de dependência.
É bom fazer muito em um teste tão simples. É sempre melhor não ter que usar injeção de dependência, a menos que isso faça sentido para o seu design.
Mas o que isso tem a ver com aulas abertas? Observe a chamada para MessageSender.should_receive
. Não definimos #should_receive quando escrevemos o MessageSender, então quem o fez? A resposta é que a estrutura de teste, fazendo algumas modificações cuidadosas nas classes do sistema, é capaz de fazer com que ela apareça, já que #should_receive é definido em cada objeto. Se você acha que modificar classes de sistema como essa requer algum cuidado, você está certo. Mas é a coisa perfeita para o que a biblioteca de testes está fazendo aqui, e as classes abertas tornam isso possível.