O documento discute a injeção de dependência e testes com dublês. A injeção de dependência permite que classes dependam de abstrações ao invés de implementações concretas, tornando o código mais fácil de testar e manter. Testes com dublês como stubs, spies e mocks substituem dependências reais por objetos controlados durante testes.
3. Exemplo
public List<Phone> phonesForCompany(String name) {
List<Phone> phones = finder.findAll();
Iterator<Phone> it = phones.iterator();
for (Phone phone: phones) {
Phone phone = it.next();
if (!phone.getCompany().equals(name)) {
phones.remove(phone);
}
}
return phones;
}
3
4. Continuando o Exemplo
• Criando interface
– public interface PhoneFinder {
List<Phone> findAll();
}
• Criação de instância continua obrigatória
– public class PhoneLister {
private PhoneFinder finder;
public PhoneLister() {
finder = new MySqlPhoneFinder();
}
}
4
7. Tipos de Injeção
• Injeção Por Construtor
public PhoneLister(PhoneFinder finder) {
this.finder = finder;
}
• Por Setter
class PhoneLister {
public PhoneLister(){…}
public void setPhoneFinder(PhoneFinder finder) {
this.finder = finder;
}
}
7
9. Usando na aplicação
public void test() {
Repository repository = new Repository("objects.xml");
PhoneLister lister = repository.object(“phoneLister");
List phones = lister.phonesForCompany("LW Telecom");
assertEquals(“1135443500",phones.get(0).getNumber());
}
9
10. Fábrica de Objetos
• Singleton
– Instância única compartilhada do objeto
– Uso padrão, mais comum
– Objetos de serviço sem estado (stateless)
• Protótipos
– Cada chamada cria um novo objeto
• Escopos de Objetos Customizados
– Objetos armazenados fora do controle do container (ex: request,
session em uma aplicação Web)
10
20. Vantagens de usar Injeção de Dependência
• Ajuda a escrever código que é fácil de testar: é só criar
objetos e atribuir as propriedades desejadas usando os
setters
11
21. Vantagens de usar Injeção de Dependência
• Ajuda a escrever código que é fácil de testar: é só criar
objetos e atribuir as propriedades desejadas usando os
setters
• Facilita boas práticas de programação: uso de interfaces no
lugar de classes
11
22. Vantagens de usar Injeção de Dependência
• Ajuda a escrever código que é fácil de testar: é só criar
objetos e atribuir as propriedades desejadas usando os
setters
• Facilita boas práticas de programação: uso de interfaces no
lugar de classes
• Não envasivo: o código do sistema depende o mínimo
possível da API do Framework
11
23. Vantagens de usar Injeção de Dependência
• Ajuda a escrever código que é fácil de testar: é só criar
objetos e atribuir as propriedades desejadas usando os
setters
• Facilita boas práticas de programação: uso de interfaces no
lugar de classes
• Não envasivo: o código do sistema depende o mínimo
possível da API do Framework
• Dependências são explícitas e evidentes
11
24. Vantagens de usar Injeção de Dependência
• Ajuda a escrever código que é fácil de testar: é só criar
objetos e atribuir as propriedades desejadas usando os
setters
• Facilita boas práticas de programação: uso de interfaces no
lugar de classes
• Não envasivo: o código do sistema depende o mínimo
possível da API do Framework
• Dependências são explícitas e evidentes
• Como os componentes não precisam procurar
colaboradores em tempo de execução, o código fica mais
fácil de escrever e manter
11
37. Construtor ou Setter?
• Construtor com parâmetros deixa claro o que é preciso
para criar o objeto
38. Construtor ou Setter?
• Construtor com parâmetros deixa claro o que é preciso
para criar o objeto
• Usando construtor evita campos imutáveis de serem
alterados
39. Construtor ou Setter?
• Construtor com parâmetros deixa claro o que é preciso
para criar o objeto
• Usando construtor evita campos imutáveis de serem
alterados
• Cuidado: construtores com muitos parâmetros podem ser
um indicativo de objeto com responsabilidades demais
40. Construtor ou Setter?
• Construtor com parâmetros deixa claro o que é preciso
para criar o objeto
• Usando construtor evita campos imutáveis de serem
alterados
• Cuidado: construtores com muitos parâmetros podem ser
um indicativo de objeto com responsabilidades demais
• Construtor é ruim se tiver parâmetros simples como
Strings: com setter você cria um método que identifica o
que a string significa
41. Construtor ou Setter?
• Construtor com parâmetros deixa claro o que é preciso
para criar o objeto
• Usando construtor evita campos imutáveis de serem
alterados
• Cuidado: construtores com muitos parâmetros podem ser
um indicativo de objeto com responsabilidades demais
• Construtor é ruim se tiver parâmetros simples como
Strings: com setter você cria um método que identifica o
que a string significa
• Receita geral: comece com construtor e mude para setter
se a coisa ficar complicada demais
42. Voltando ao nosso PhoneLister
public List<Phone> phonesForCompany(String name) {
List<Phone> phones = finder.findAll();
Iterator<Phone> it = phones.iterator();
for (Phone phone: phones) {
Phone phone = it.next();
if (!phone.getCompany().equals(name)) {
phones.remove(phone);
}
}
return phones;
}
15
43. Voltando ao nosso PhoneLister
public List<Phone> phonesForCompany(String name) {
Finder
List<Phone> phones = finder.findAll();
Iterator<Phone> it = phones.iterator();
for (Phone phone: phones) {
Phone phone = it.next();
if (!phone.getCompany().equals(name)) {
phones.remove(phone);
}
}
return phones;
}
15
44. Voltando ao nosso PhoneLister
public List<Phone> phonesForCompany(String name) {
Finder
List<Phone> phones = finder.findAll();
Iterator<Phone> it = phones.iterator();
for (Phone phone: phones) {
Preencher os dados
Phone phone = it.next(); no DB manualmente
if (!phone.getCompany().equals(name)) {
phones.remove(phone);
}
}
return phones;
}
15
45. Voltando ao nosso PhoneLister
public List<Phone> phonesForCompany(String name) {
Finder
List<Phone> phones = finder.findAll();
Iterator<Phone> it = phones.iterator(); E se eu não tiver
acesso ao banco?
for (Phone phone: phones) {
Preencher os dados
Phone phone = it.next(); no DB manualmente
if (!phone.getCompany().equals(name)) {
phones.remove(phone);
}
}
return phones;
}
15
46. Voltando ao nosso PhoneLister
public List<Phone> phonesForCompany(String name) {
Finder
List<Phone> phones = finder.findAll();
Iterator<Phone> it = phones.iterator(); E se eu não tiver
acesso ao banco?
for (Phone phone: phones) {
Preencher os dados
Phone phone = it.next(); no DB manualmente E se ele for lento
demais?
if (!phone.getCompany().equals(name)) {
phones.remove(phone);
}
}
return phones;
}
15
47. Voltando ao nosso PhoneLister
public List<Phone> phonesForCompany(String name) {
Finder
List<Phone> phones = finder.findAll();
Iterator<Phone> it = phones.iterator(); E se eu não tiver
acesso ao banco?
for (Phone phone: phones) {
Preencher os dados
Phone phone = it.next(); no DB manualmente E se ele for lento
demais?
if (!phone.getCompany().equals(name)) {
E se o custo for
phones.remove(phone); alto?
}
}
return phones;
}
15
48. Voltando ao nosso PhoneLister
public List<Phone> phonesForCompany(String name) {
Finder
List<Phone> phones = finder.findAll();
Iterator<Phone> it = phones.iterator(); E se eu não tiver
acesso ao banco?
for (Phone phone: phones) {
Preencher os dados
Phone phone = it.next(); no DB manualmente E se ele for lento
demais?
if (!phone.getCompany().equals(name)) {
E se o custo for
phones.remove(phone); alto?
}
E se não tiver
} banco ainda?
return phones;
}
15
56. Dummy Object
• Objetos que nunca são usados, só servem para preencher parâmetros
17
57. Dummy Object
• Objetos que nunca são usados, só servem para preencher parâmetros
public void testInvoice_addLineItem_noECS() {
final int QUANTITY = 1;
Product product = new Product("Dummy Product");
State state = new State("West Dakota", "WD");
City city = new City("Centreville", state);
Address address = new Address("123 Blake St.", city, "12345");
Customer customer= new Customer("Dummy Customer");
Invoice inv = new Invoice(customer);
// Add an item to the invoice
inv.addItemQuantity(product, QUANTITY);
// Verify that item was inserted
List lineItems = inv.getLineItems();
assertEquals("number of items", lineItems.size(), 1);
LineItem actual = (LineItem)lineItems.get(0);
LineItem expItem = new LineItem(inv, product, QUANTITY);
assertLineItemsEqual("",expItem, actual);
}
17
58. Dummy Object
• Objetos que nunca são usados, só servem para preencher parâmetros
public void testInvoice_addLineItem_noECS() {
final int QUANTITY = 1;
Dummy Product product = new Product("Dummy Product");
State state = new State("West Dakota", "WD");
City city = new City("Centreville", state);
Address address = new Address("123 Blake St.", city, "12345");
Customer customer= new Customer("Dummy Customer");
Invoice inv = new Invoice(customer);
// Add an item to the invoice
inv.addItemQuantity(product, QUANTITY);
// Verify that item was inserted
List lineItems = inv.getLineItems();
assertEquals("number of items", lineItems.size(), 1);
LineItem actual = (LineItem)lineItems.get(0);
LineItem expItem = new LineItem(inv, product, QUANTITY);
assertLineItemsEqual("",expItem, actual);
}
17
59. Dummy Object
• Objetos que nunca são usados, só servem para preencher parâmetros
public void testInvoice_addLineItem_noECS() {
final int QUANTITY = 1;
Dummy Product product = new Product("Dummy Product");
Dummy State state = new State("West Dakota", "WD");
City city = new City("Centreville", state);
Address address = new Address("123 Blake St.", city, "12345");
Customer customer= new Customer("Dummy Customer");
Invoice inv = new Invoice(customer);
// Add an item to the invoice
inv.addItemQuantity(product, QUANTITY);
// Verify that item was inserted
List lineItems = inv.getLineItems();
assertEquals("number of items", lineItems.size(), 1);
LineItem actual = (LineItem)lineItems.get(0);
LineItem expItem = new LineItem(inv, product, QUANTITY);
assertLineItemsEqual("",expItem, actual);
}
17
60. Dummy Object
• Objetos que nunca são usados, só servem para preencher parâmetros
public void testInvoice_addLineItem_noECS() {
final int QUANTITY = 1;
Dummy Product product = new Product("Dummy Product");
Dummy State state = new State("West Dakota", "WD");
Dummy City city = new City("Centreville", state);
Address address = new Address("123 Blake St.", city, "12345");
Customer customer= new Customer("Dummy Customer");
Invoice inv = new Invoice(customer);
// Add an item to the invoice
inv.addItemQuantity(product, QUANTITY);
// Verify that item was inserted
List lineItems = inv.getLineItems();
assertEquals("number of items", lineItems.size(), 1);
LineItem actual = (LineItem)lineItems.get(0);
LineItem expItem = new LineItem(inv, product, QUANTITY);
assertLineItemsEqual("",expItem, actual);
}
17
61. Dummy Object
• Objetos que nunca são usados, só servem para preencher parâmetros
public void testInvoice_addLineItem_noECS() {
final int QUANTITY = 1;
Dummy Product product = new Product("Dummy Product");
Dummy State state = new State("West Dakota", "WD");
Dummy City city = new City("Centreville", state);
Dummy Address address = new Address("123 Blake St.", city, "12345");
Customer customer= new Customer("Dummy Customer");
Invoice inv = new Invoice(customer);
// Add an item to the invoice
inv.addItemQuantity(product, QUANTITY);
// Verify that item was inserted
List lineItems = inv.getLineItems();
assertEquals("number of items", lineItems.size(), 1);
LineItem actual = (LineItem)lineItems.get(0);
LineItem expItem = new LineItem(inv, product, QUANTITY);
assertLineItemsEqual("",expItem, actual);
}
17
62. Dummy Object
• Objetos que nunca são usados, só servem para preencher parâmetros
public void testInvoice_addLineItem_noECS() {
final int QUANTITY = 1;
Dummy Product product = new Product("Dummy Product");
Dummy State state = new State("West Dakota", "WD");
Dummy City city = new City("Centreville", state);
Dummy Address address = new Address("123 Blake St.", city, "12345");
Dummy Customer customer= new Customer("Dummy Customer");
Invoice inv = new Invoice(customer);
// Add an item to the invoice
inv.addItemQuantity(product, QUANTITY);
// Verify that item was inserted
List lineItems = inv.getLineItems();
assertEquals("number of items", lineItems.size(), 1);
LineItem actual = (LineItem)lineItems.get(0);
LineItem expItem = new LineItem(inv, product, QUANTITY);
assertLineItemsEqual("",expItem, actual);
}
17
63. Stub
• Provê alguns dados estáticos que serão usados nos testes
• Não funciona para outras coisas além do que está no teste
18
64. Stub
• Provê alguns dados estáticos que serão usados nos testes
• Não funciona para outras coisas além do que está no teste
class OrderStateTester {
public void testOrderSendsMailIfUnfilled() {
Order order = new Order(TALISKER, 51);
order.fill(new Customer("techday@locaweb.com.br"));
assertEquals("techday@locaweb.com.br", order.getEmail());
}
}
18
65. Spy
• Muito parecido com Stub, mas com a diferença de que “grava” algumas coisas
19
66. Spy
• Muito parecido com Stub, mas com a diferença de que “grava” algumas coisas
class OrderStateTester {
public void testOrderSendsMailIfUnfilled() {
Order order = new Order(TALISKER, 51);
MailServiceSpy mailer = new MailServiceSpy();
order.setMailer(mailer);
order.fill(new Customer("techday@locaweb.com.br"));
assertEquals(1, mailer.numberSent());
}
}
19
67. Spy
• Muito parecido com Stub, mas com a diferença de que “grava” algumas coisas
class OrderStateTester {
public void testOrderSendsMailIfUnfilled() {
Order order = new Order(TALISKER, 51);
MailServiceSpy mailer = new MailServiceSpy();
order.setMailer(mailer);
order.fill(new Customer("techday@locaweb.com.br"));
assertEquals(1, mailer.numberSent());
}
}
public class MailServiceSpy implements MailService {
private List<Message> messages = new ArrayList<Message>();
public void send(Message msg) {
messages.add(msg);
}
public int numberSent() {
return messages.size();
}
}
19
68. O que são Mock Objects?
Years later, Mock objects are still quite controversial, often
misused and sometimes misunderstood.
“Pintside Thoughts: Mock Objects History”
—Tim McKinnon
69. O que são Mock Objects?
• Cada pessoa entende de um jeito diferente
• Terminologia ambígua
• Ficou famoso inicialmente na comunidade JAVA & TDD
Comportamento Estado
testa interação entre testa o resultado dessas
objetos interações
70. Código Mock
public class FTPUploaderTest {
public void shouldUploadOnePhoneConfig() throws Exception {
FTPUtils ftpUtils = createMock(FTPUtils.class);
FTPUploader uploader = new FTPUploader(ftpUtils);
FTPClient ftpClient = createMock(FTPClient.class);
expect(ftpUtils.getFTPConnection()).andReturn(ftpClient);
ftpUtils.delete(ftpClient, "/", "file-name.cfg");
ftpUtils.ftpUpload(ftpClient, "/polycom/", "/", "file-name.cfg");
expect(ftpClient.logout()).andReturn(true);
expect(ftpClient.isConnected()).andReturn(false);
expect(ftpClient.disconnect());
replay(ftpUtils, ftpClient);
List<String> fileNames = new ArrayList<String>();
fileNames.add("file-name.cfg");
uploader.sendFTPFiles(fileNames);
verify(ftpUtils, ftpClient);
}
}
71. Código Mock
public class FTPUploaderTest {
public void shouldUploadOnePhoneConfig() throws Exception {
FTPUtils ftpUtils = createMock(FTPUtils.class);
FTPUploader uploader = new FTPUploader(ftpUtils);
Criação dos Mocks
FTPClient ftpClient = createMock(FTPClient.class);
expect(ftpUtils.getFTPConnection()).andReturn(ftpClient);
ftpUtils.delete(ftpClient, "/", "file-name.cfg");
ftpUtils.ftpUpload(ftpClient, "/polycom/", "/", "file-name.cfg");
expect(ftpClient.logout()).andReturn(true);
expect(ftpClient.isConnected()).andReturn(false);
expect(ftpClient.disconnect());
replay(ftpUtils, ftpClient);
List<String> fileNames = new ArrayList<String>();
fileNames.add("file-name.cfg");
uploader.sendFTPFiles(fileNames);
verify(ftpUtils, ftpClient);
}
}
72. Código Mock
public class FTPUploaderTest {
public void shouldUploadOnePhoneConfig() throws Exception {
FTPUtils ftpUtils = createMock(FTPUtils.class);
FTPUploader uploader = new FTPUploader(ftpUtils);
Criação dos Mocks
FTPClient ftpClient = createMock(FTPClient.class);
expect(ftpUtils.getFTPConnection()).andReturn(ftpClient);
ftpUtils.delete(ftpClient, "/", "file-name.cfg");
ftpUtils.ftpUpload(ftpClient, "/polycom/", "/", "file-name.cfg");
Gravando
expect(ftpClient.logout()).andReturn(true);
Comportamentos
expect(ftpClient.isConnected()).andReturn(false);
expect(ftpClient.disconnect());
replay(ftpUtils, ftpClient);
List<String> fileNames = new ArrayList<String>();
fileNames.add("file-name.cfg");
uploader.sendFTPFiles(fileNames);
verify(ftpUtils, ftpClient);
}
}
73. Código Mock
public class FTPUploaderTest {
public void shouldUploadOnePhoneConfig() throws Exception {
FTPUtils ftpUtils = createMock(FTPUtils.class);
FTPUploader uploader = new FTPUploader(ftpUtils);
Criação dos Mocks
FTPClient ftpClient = createMock(FTPClient.class);
expect(ftpUtils.getFTPConnection()).andReturn(ftpClient);
ftpUtils.delete(ftpClient, "/", "file-name.cfg");
ftpUtils.ftpUpload(ftpClient, "/polycom/", "/", "file-name.cfg");
Gravando
expect(ftpClient.logout()).andReturn(true);
Comportamentos
expect(ftpClient.isConnected()).andReturn(false);
expect(ftpClient.disconnect());
replay(ftpUtils, ftpClient); Fim gravação
List<String> fileNames = new ArrayList<String>();
fileNames.add("file-name.cfg");
uploader.sendFTPFiles(fileNames);
verify(ftpUtils, ftpClient);
}
}
74. Código Mock
public class FTPUploaderTest {
public void shouldUploadOnePhoneConfig() throws Exception {
FTPUtils ftpUtils = createMock(FTPUtils.class);
FTPUploader uploader = new FTPUploader(ftpUtils);
Criação dos Mocks
FTPClient ftpClient = createMock(FTPClient.class);
expect(ftpUtils.getFTPConnection()).andReturn(ftpClient);
ftpUtils.delete(ftpClient, "/", "file-name.cfg");
ftpUtils.ftpUpload(ftpClient, "/polycom/", "/", "file-name.cfg");
Gravando
expect(ftpClient.logout()).andReturn(true);
Comportamentos
expect(ftpClient.isConnected()).andReturn(false);
expect(ftpClient.disconnect());
replay(ftpUtils, ftpClient); Fim gravação
List<String> fileNames = new ArrayList<String>();
fileNames.add("file-name.cfg"); Execução do método
uploader.sendFTPFiles(fileNames); a ser testado
verify(ftpUtils, ftpClient);
}
}
75. Código Mock
public class FTPUploaderTest {
public void shouldUploadOnePhoneConfig() throws Exception {
FTPUtils ftpUtils = createMock(FTPUtils.class);
FTPUploader uploader = new FTPUploader(ftpUtils);
Criação dos Mocks
FTPClient ftpClient = createMock(FTPClient.class);
expect(ftpUtils.getFTPConnection()).andReturn(ftpClient);
ftpUtils.delete(ftpClient, "/", "file-name.cfg");
ftpUtils.ftpUpload(ftpClient, "/polycom/", "/", "file-name.cfg");
Gravando
expect(ftpClient.logout()).andReturn(true);
Comportamentos
expect(ftpClient.isConnected()).andReturn(false);
expect(ftpClient.disconnect());
replay(ftpUtils, ftpClient); Fim gravação
List<String> fileNames = new ArrayList<String>();
fileNames.add("file-name.cfg"); Execução do método
uploader.sendFTPFiles(fileNames); a ser testado
verify(ftpUtils, ftpClient); Verificação Final
}
}
76. Fake Object
• Têm uma implementação completa, mas simples
• Não vai para produção
• Exemplo: banco de dados em memória
23
77. Fake Object
• Têm uma implementação completa, mas simples
• Não vai para produção
• Exemplo: banco de dados em memória
public void testReadWrite_inMemory() throws Exception {
// Setup:
FlightMgmtFacadeImpl facade = new FlightMgmtFacadeImpl();
facade.setDao(new InMemoryDatabase());
// Exercise:
BigDecimal yyc = facade.createAirport("YYC", "Calgary", "Calgary");
BigDecimal lax = facade.createAirport("LAX", "LAX Intl", "LA");
facade.createFlight(yyc, lax);
List flights = facade.getFlightsByOriginAirport(yyc);
// Verify:
assertEquals("# of flights", 1, flights.size());
Flight flight = (Flight) flights.get(0);
assertEquals("origin", yyc, flight.getOrigin().getCode());
}
23
78. Fake Object
• Têm uma implementação completa, mas simples
• Não vai para produção
• Exemplo: banco de dados em memória
public void testReadWrite_inMemory() throws Exception {
// Setup:
FlightMgmtFacadeImpl facade = new FlightMgmtFacadeImpl();
facade.setDao(new InMemoryDatabase());
// Exercise: Fake
BigDecimal yyc = facade.createAirport("YYC", "Calgary", "Calgary");
BigDecimal lax = facade.createAirport("LAX", "LAX Intl", "LA");
facade.createFlight(yyc, lax);
List flights = facade.getFlightsByOriginAirport(yyc);
// Verify:
assertEquals("# of flights", 1, flights.size());
Flight flight = (Flight) flights.get(0);
assertEquals("origin", yyc, flight.getOrigin().getCode());
}
23
79. Implementação de Fake Object
public class InMemoryDatabase implements FlightDao{
private List airports = new Vector();
public Airport createAirport(String airportCode, String name, String nearbyCity)
throws DataException, InvalidArgumentException {
Airport result = new Airport(getNextAirportId(), airportCode, name,
createCity(nearbyCity));
airports.add(result);
return result;
}
public Airport getAirportByPrimaryKey(BigDecimal airportId) throws DataException{
for (Airport airport: airports) {
if (airport.getId().equals(airportId)) {
return airport;
}
}
throw new DataException("Airport not found:"+airportId);
}
}
24
89. Testes com Doubles - Recapitulando
Double
Dummy
Stub Spy Mock Fake
Object
Provê
Preenche
dados para
parâmetros
os testes
26
90. Testes com Doubles - Recapitulando
Double
Dummy
Stub Spy Mock Fake
Object
Provê Provê
Preenche
dados para dados e
parâmetros
os testes grava
26
91. Testes com Doubles - Recapitulando
Double
Dummy
Stub Spy Mock Fake
Object
Provê Provê Verifica
Preenche
dados para dados e comporta-
parâmetros
os testes grava mentos
26
92. Testes com Doubles - Recapitulando
Double
Dummy
Stub Spy Mock Fake
Object
Provê Provê Verifica Implemen-
Preenche
dados para dados e comporta- tação
parâmetros
os testes grava mentos +simples
26
95. Referências
• http://www.springframework.org/documentation
• http://martinfowler.com/articles/injection.html
• http://martinfowler.com/articles/mocksArentStubs.html
• http://xunitpatterns.com/
• Anil Hemrajani, Agile Java Development with Spring, Hibernate and
Eclipse
96. Para aqueles que buscam sucesso em projetos e negócios, LOCAWEB oferece soluções
inovadoras em Hosted IT Services de alta performance e confiabilidade.
Data Center - Av. Juscelino Kubitschek, 1830, 10º andar – Itaim Bibi – São Paulo – SP
Escritório SP - Av. Juscelino Kubitschek, 1830, 10º andar – Itaim Bibi – São Paulo – SP | (11) 3544-0400
Escritório RS - Av. Carlos Gomes, 1340, 6º andar – Auxiliadora – Porto Alegre – RS | (51) 4062-0203
0800 726 7770 - Demais Estados