Não entendo a diferença entre Mock, Stub e Spy nos testes de Spock e os tutoriais que tenho visto online não os explicam em detalhes.
Não entendo a diferença entre Mock, Stub e Spy nos testes de Spock e os tutoriais que tenho visto online não os explicam em detalhes.
Respostas:
Atenção: vou simplificar demais e talvez até falsificar um pouco nos próximos parágrafos. Para obter informações mais detalhadas, consulte o site de Martin Fowler .
Um mock é uma classe fictícia que substitui uma classe real, retornando algo como nulo ou 0 para cada chamada de método. Você usa uma simulação se precisar de uma instância simulada de uma classe complexa que, de outra forma, usaria recursos externos como conexões de rede, arquivos ou bancos de dados ou talvez dezenas de outros objetos. A vantagem dos mocks é que você pode isolar a classe em teste do resto do sistema.
Um stub também é uma classe fictícia que fornece alguns resultados reproduzidos mais específicos, preparados ou pré-gravados, para certas solicitações em teste. Você poderia dizer que um esboço é uma simulação chique. Em Spock, você frequentemente lerá sobre métodos de stub.
Um espião é uma espécie de híbrido entre objeto real e stub, ou seja, é basicamente o objeto real com alguns (não todos) métodos ocultados por métodos stub. Os métodos sem stub são apenas roteados para o objeto original. Dessa forma, você pode ter um comportamento original para métodos "baratos" ou triviais e um comportamento falso para métodos "caros" ou complexos.
Atualização 06/02/2017: Na verdade, a resposta do usuário mikhail é mais específica para Spock do que a original acima. Portanto, no âmbito de Spock, o que ele descreve está correto, mas isso não falsifica minha resposta geral:
Agora, aqui está um exemplo de teste executável, demonstrando o que é possível e o que não é. É um pouco mais instrutivo do que os fragmentos de Mikhail. Muito obrigado a ele por me inspirar a melhorar minha própria resposta! :-)
package de.scrum_master.stackoverflow
import org.spockframework.mock.TooFewInvocationsError
import org.spockframework.runtime.InvalidSpecException
import spock.lang.FailsWith
import spock.lang.Specification
class MockStubSpyTest extends Specification {
static class Publisher {
List<Subscriber> subscribers = new ArrayList<>()
void addSubscriber(Subscriber subscriber) {
subscribers.add(subscriber)
}
void send(String message) {
for (Subscriber subscriber : subscribers)
subscriber.receive(message);
}
}
static interface Subscriber {
String receive(String message)
}
static class MySubscriber implements Subscriber {
@Override
String receive(String message) {
if (message ==~ /[A-Za-z ]+/)
return "ok"
return "uh-oh"
}
}
Subscriber realSubscriber1 = new MySubscriber()
Subscriber realSubscriber2 = new MySubscriber()
Publisher publisher = new Publisher(subscribers: [realSubscriber1, realSubscriber2])
def "Real objects can be tested normally"() {
expect:
realSubscriber1.receive("Hello subscribers") == "ok"
realSubscriber1.receive("Anyone there?") == "uh-oh"
}
@FailsWith(TooFewInvocationsError)
def "Real objects cannot have interactions"() {
when:
publisher.send("Hello subscribers")
publisher.send("Anyone there?")
then:
2 * realSubscriber1.receive(_)
}
def "Stubs can simulate behaviour"() {
given:
def stubSubscriber = Stub(Subscriber) {
receive(_) >>> ["hey", "ho"]
}
expect:
stubSubscriber.receive("Hello subscribers") == "hey"
stubSubscriber.receive("Anyone there?") == "ho"
stubSubscriber.receive("What else?") == "ho"
}
@FailsWith(InvalidSpecException)
def "Stubs cannot have interactions"() {
given: "stubbed subscriber registered with publisher"
def stubSubscriber = Stub(Subscriber) {
receive(_) >> "hey"
}
publisher.addSubscriber(stubSubscriber)
when:
publisher.send("Hello subscribers")
publisher.send("Anyone there?")
then:
2 * stubSubscriber.receive(_)
}
def "Mocks can simulate behaviour and have interactions"() {
given:
def mockSubscriber = Mock(Subscriber) {
3 * receive(_) >>> ["hey", "ho"]
}
publisher.addSubscriber(mockSubscriber)
when:
publisher.send("Hello subscribers")
publisher.send("Anyone there?")
then: "check interactions"
1 * mockSubscriber.receive("Hello subscribers")
1 * mockSubscriber.receive("Anyone there?")
and: "check behaviour exactly 3 times"
mockSubscriber.receive("foo") == "hey"
mockSubscriber.receive("bar") == "ho"
mockSubscriber.receive("zot") == "ho"
}
def "Spies can have interactions"() {
given:
def spySubscriber = Spy(MySubscriber)
publisher.addSubscriber(spySubscriber)
when:
publisher.send("Hello subscribers")
publisher.send("Anyone there?")
then: "check interactions"
1 * spySubscriber.receive("Hello subscribers")
1 * spySubscriber.receive("Anyone there?")
and: "check behaviour for real object (a spy is not a mock!)"
spySubscriber.receive("Hello subscribers") == "ok"
spySubscriber.receive("Anyone there?") == "uh-oh"
}
def "Spies can modify behaviour and have interactions"() {
given:
def spyPublisher = Spy(Publisher) {
send(_) >> { String message -> callRealMethodWithArgs("#" + message) }
}
def mockSubscriber = Mock(MySubscriber)
spyPublisher.addSubscriber(mockSubscriber)
when:
spyPublisher.send("Hello subscribers")
spyPublisher.send("Anyone there?")
then: "check interactions"
1 * mockSubscriber.receive("#Hello subscribers")
1 * mockSubscriber.receive("#Anyone there?")
}
}
A pergunta estava no contexto da estrutura de Spock e não acredito que as respostas atuais levem isso em consideração.
Com base nos documentos do Spock (exemplos personalizados, minha própria redação adicionada):
Stub: usado para fazer os colaboradores responderem às chamadas de método de uma determinada maneira. Ao fazer o stub de um método, você não se importa se e quantas vezes o método será chamado; você só quer que ele retorne algum valor, ou execute algum efeito colateral, sempre que for chamado.
subscriber.receive(_) >> "ok" // subscriber is a Stub()
Mock: Usado para descrever as interações entre o objeto sob especificação e seus colaboradores.
def "should send message to subscriber"() {
when:
publisher.send("hello")
then:
1 * subscriber.receive("hello") // subscriber is a Mock()
}
Um Mock pode atuar como um Mock e um Stub:
1 * subscriber.receive("message1") >> "ok" // subscriber is a Mock()
Spy: É sempre baseado em um objeto real com métodos originais que fazem coisas reais. Pode ser usado como um Stub para alterar os valores de retorno dos métodos selecionados. Pode ser usado como um mock para descrever interações.
def subscriber = Spy(SubscriberImpl, constructorArgs: ["Fred"])
def "should send message to subscriber"() {
when:
publisher.send("hello")
then:
1 * subscriber.receive("message1") >> "ok" // subscriber is a Spy(), used as a Mock an Stub
}
def "should send message to subscriber (actually handle 'receive')"() {
when:
publisher.send("hello")
then:
1 * subscriber.receive("message1") // subscriber is a Spy(), used as a Mock, uses real 'receive' function
}
Resumo:
Evite usar Mock () se Stub () for suficiente.
Evite usar Spy () se puder; fazer isso pode ser um cheiro e dicas de teste incorreto ou design incorreto do objeto em teste.
Em termos simples:
Simulação: Você zomba de um tipo e rapidamente cria um objeto. Os métodos neste objeto simulado retornam os valores padrão do tipo de retorno.
Stub: você cria uma classe de stub onde os métodos são redefinidos com definição de acordo com sua necessidade. Ex: No método de objeto real você chama uma API externa e retorna o nome de usuário contra e id. No método de objeto stubbed, você retorna algum nome fictício.
Espião: Você cria um objeto real e então o espia. Agora você pode simular alguns métodos e optar por não fazer isso para alguns.
Uma diferença de uso é que você não pode simular objetos de nível de método. enquanto você pode criar um objeto padrão no método e então espioná-lo para obter o comportamento desejado dos métodos no objeto espiado.
Os stubs são apenas para facilitar o teste de unidade, eles não fazem parte do teste. Mocks, são parte do teste, parte da verificação, parte da aprovação / reprovação.
Então, digamos que você tenha um método que recebe um objeto como parâmetro. Você nunca faz nada que altere este parâmetro no teste. Você simplesmente lê um valor dele. Isso é um esboço.
Se você mudar alguma coisa, ou precisar verificar algum tipo de interação com o objeto, então é uma simulação.