Orientação a Objetos – Criação de Classes
Programação 2 –
Aulas 6, 7, 8 e 9
Aprender a pensar em testes antes de definir novas classes
Aprender a definir novas classes
◦ Entender conceitos de atributos, construtores, métodos, parâmetros e valor de retorno, encapsulamento, métodos accessor e mutator, this, métodos-função e métodos-procedimento, aninhamento de métodos, this(), escopo de atributos e variáveis locais, sobrecarga de métodos, métodos de classe, atributos de classe, escopo de atributos de classe, constantes
Apresentar Test-Driven Development (TDD)
A primeira classe que escreveremos é uma ContaSimples, que já usamos anteriormente
◦ Na realidade, vamos fazer uma versão um pouco diferente, e é por isso que se chama ContaSimples1
A documentação da classe que queremos escrever está aqui
Vamos adotar uma postura chamada "Test-Driven Development" (TDD) e escrever os testes antes de escrever o código da classe
◦ Antes?!?!?!?
◦ Sim, antes, não depois!
Vamos supor que a classe esteja pronta (foi escrita por alguém) e que queremos testá-la
◦ Como faríamos?
Eis alguns testes que podemos fazer, escritos em português
Cria um conta com nome "Jacques
Sauve", cpf 123456789-01, e número 123 Verifique que o nome da conta é
Jacques Sauve Verifique que o cpf da conta é
123456789-01 Verifique que o número da conta é 123 Verifique que o saldo da conta é 0.0 Deposite R$100,00 Verifique que o saldo é R$100,00 Saque R$45,00 Verifique que o saldo é R$55,00 Tente sacar R$56,00 e verifique que
não é possível Verifique que o saldo continua em
R$55,00 |
Veremos adiante que estes testes não estão completos, mas vamos começar com eles
◦ Você pode pensar em mais testes que deveriam ser feitos?
Queremos agora fazer com que os testes sejam realizados de forma automática
◦ Ter testes automáticos é muito, muito bom
◦ Permite que você execute os testes a qualquer momento
i) Você pode repetir os testes centenas de vezes sem custo adicional
ii)Imagine a situação se os testes fossem "manuais"
◦ Permite saber exatamente quando a implementação está terminada (quando os testes rodam)
◦ Permite fazer alterações ao código ao longo do tempo e assegurar-se de que nada quebrou
◦ Os próprios testes servem de documentação para a classe
i) Se você quiser saber como uma classe funciona ou como pode/deve ser usada, examine os testes
Vamos automatizar os testes usando um testador chamado JUnit
◦ JUnit ajuda a fazer "testes de unidade" (uma unidade = uma classe)
◦ Tem outros tipos de testes que veremos em outro momento
Vejamos uma classe de testes
package p2.exemplos.tests; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import p2.exemplos.ContaSimples1; public class TestaContaSimples1 { private ContaSimples1 umaConta; @Before public void criaConta() { umaConta = new ContaSimples1("Jacques Sauve", "123456789-01", 123); } @Test public void TestContaSimples1() { Assert.assertEquals("Nome errado", "Jacques Sauve", umaConta.getNome()); Assert.assertEquals("cpf errado", "123456789-01", umaConta.getCPF()); Assert.assertEquals("Número errado", 123, umaConta.getNúmero()); Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005); umaConta.depositar(100.0); Assert.assertEquals("Saldo errado", 100.0, umaConta.getSaldo(), 0.005); Assert.assertTrue("Nao consegui
sacar", umaConta.sacar(45.0)); Assert.assertEquals("Saldo errado", 55.0, umaConta.getSaldo(), 0.005); Assert.assertFalse("Consegui sacar
demais", umaConta.sacar(56.0)); Assert.assertEquals("Saldo errado", 55.0, umaConta.getSaldo(), 0.005); } } |
assertEquals, assertTrue, assertFalse são métodos do pacote JUnit e servem para realizar testes
◦ assertEquals("Mensagem de erro se o teste falhar", string esperado, string a testar)
◦ assertEquals("Mensagem de erro se o teste falhar", valor double esperado, valor double a testar, precisão)
◦ assertTrue("Mensagem de erro se o teste falhar", valor a testar que deve retornar true)
◦ assertFalse("Mensagem de erro se o teste falhar", valor a testar que deve retornar false)
Examine os testes com muita atenção antes de continuar
Tente rodar os testes com JUnit e a classe ContaSimples1.java pronta
Os testes devem rodar (veja figura abaixo)
Introduza erros nos testes e veja o que ocorre
Vamos fazer de conta que a classe ainda não existe (remova-a!) e precisa ser escrita do zero
Precisamos fazer os testes passarem
◦ Havendo Eclipse disponível na hora da aula, o professor pode construir a classe aos poucos para fazer os testes passarem, um teste de cada vez
package p2.exemplos; /** * Classe de conta bancária simples. * * @author Jacques Sauvé * @version 1.0 */ public class ContaSimples1
{ private String nome; private String cpf; private int numero; private double saldo; // construtor /** * Cria
uma conta a partir de um nome e cpf de pessoa física, e um número * de conta. * * @param nome * O nome do titular da conta. * @param cpf * O CPF do titular da conta. * @param número * O número da conta. */ public ContaSimples1(String nome, String cpf, int numero) { this.nome = nome; this.cpf = cpf; this.numero = numero; saldo = 0.0; } //
métodos /** * Recupera
o nome do titular da conta. * * @return O nome do titular da conta. */ public Object getNome() { return nome; } /** * Recupera
o CPF do titular da conta. * * @return O CPF do titular da conta. */ public Object getCPF() { return cpf; } /** * Recupera
o número da conta. * * @return O número da conta. */ public Object getNúmero() { return numero; } /** * Recupera
o saldo da conta. * * @return O saldo da conta. */ public double getSaldo() { return saldo; } /** * Efetua
um depósito numa conta. * * @param valor * O valor a depositar. */ public void
depositar(double valor) { saldo += valor; } /** * Efetua
sacada na conta. * * @param valor * O valor a sacar. * @return O sucesso ou não da operação. */ public boolean sacar(double valor) { if (saldo >= valor) { saldo -= valor; return true; } return false; } /** * Transforma
os dados da conta em um String. * * @return O string com os dados da conta. */ public String toString() { return "numero
" + numero
+ ", nome " + nome + ",
saldo " + saldo; } } |
Há vários comentários iniciando com /**, por exemplo:
/**
* Classe de conta bancária simples.
*
* @author Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.0
* <br>
* Copyright (C) 1999 Universidade Federal de Campina Grande.
*/
Esses comentários servem para criar documentação automática do seu programa através de uma ferramenta chamada javadoc
◦ Exemplo: ao rodar o seguinte comando: javadoc -d docContaSimples1 -version -author ContaSimples1.java
A saída é esta
Observe como os tags (@author, @version, @param, @return) saíram na documentação
public class ContaSimples1 {
Novas classes são definida com:
public class
ContaSimples1 { corpo da classe } |
private String nome; private String cpf; private int numero; private double saldo; |
Atributos são definidos depois do primeiro { e fora de qualquer corpo de método
"Método" significa função ou subrotina ou sub-programa
Normalmente, atributos são colocados no início da definição da classe, ou talvez bem no final, antes do } final
Os atributos de uma classe são equivalentes aos campos de um "record" ou "struct" em outras linguagens
A diferença básica é que com OO, a classe
conterá também código para manipular esses dados
O adjetivo de visibilidade "private" significa que o atributo só pode ser "visto" (usado) pelo código dentro da classe
"public" significa que todo mundo "vê", mesmo fora do corpo da classe
Falaremos mais sobre visibilidade adiante
Os atributos possuem um valor diferente para cada objeto instanciado
Cada ContaSimples1 tem um valor diferente (armazenado em lugar diferente da memória) para o nome de titular, CPF, número de conta e saldo
/** * Cria
uma conta a partir de um nome e cpf de pessoa física, e um * número
de conta. * * @param nome * O nome do titular da conta. * @param cpf * O CPF do titular da conta. * @param numero * O número da conta. */ public ContaSimples1(String nome, String cpf, int numero) { this.nome = nome; this.cpf = cpf; this.numero = numero; saldo = 0.0; } |
Ao chamar "new ContaSimples(...)", o método construtor da classe é chamado
◦ Como qualquer método, pode ter parâmetros (aqui tem 3)
◦ Porém, o construtor nunca retorna um valor com "return"
O construtor é normalmente usado para inicializar atributos
◦ this é uma referência especial a este objeto
◦ Portanto, this.nome se refere ao atributo "nome" do presente objeto ContaSimples1
Se o parâmetro nome se chamasse outra coisa, digamos nomeTitular, a linha poderia ser mudada para:
nome = nomeTitular; |
Observe que, aqui, nome referencia o atributo sem precisar usar this
//
métodos /** * Recupera
o nome do titular da conta. * * @return O nome do titular da conta. */ public Object getNome() { return nome; } /** * Recupera
o CPF do titular da conta. * * @return O CPF do titular da conta. */ public Object getCPF() { return cpf; } /** * Recupera
o número da conta. * * @return O número da conta. */ public Object getNúmero() { return numero; } /** * Recupera
o saldo da conta. * * @return O saldo da conta. */ public double getSaldo() { return saldo; } |
Estamos vendo 4 métodos acima
Observe como um método retorna um valor
◦ "return expressão" automaticamente pára a execução do método e retorna o valor da expressão para o chamador do método
Como todos esses métodos fazem apenas retornar o valor de um atributo, eles são chamados "accessor methods"
/** * Efetua
um depósito numa conta. * * @param valor * O valor a depositar. */ public void
depositar(double valor) { saldo += valor; } /** * Efetua
sacada na conta. * * @param valor * O valor a sacar. * @return O sucesso ou não da operação. */ public boolean sacar(double valor) { if (saldo >= valor) { saldo -= valor; return true; } return false; } |
Os dois métodos acima mostram idéias importantes
◦ Primeiro, a passagem de parâmetros (nos dois métodos)
◦ Segundo, o método que não retorna nada (indicando com tipo de retorno void)
◦ Terceiro, o fato de que o saldo da conta não é mexido "de fora"
Quem sabe mexer com o saldo é a ContaSimples1, em si
◦ Quem usa o objeto "de fora", não tem acesso direto aos atributos do objeto
◦ Só tem acesso aos métodos que definem o "comportamento" de objetos dessa classe
i) Neste caso, uma ContaSimples1 deixa que se façam depósitos e saques na Conta
Esses métodos, juntamente com os outros métodos declarados como public, definem a interface da classe
/** * Transforma
os dados da conta em um String. * * @return O string com os dados da conta. */ public String toString() { return "numero
" + numero
+ ", nome " + nome + ",
saldo " + saldo; } |
Em Java, todo objeto deve ter uma representação como String
◦ Facilita a impressão com System.out.println()
◦ Facilita a depuração
Definimos no método toString() o String a retornar para representar o objeto como String
Normalmente, imprimem-se todos os atributos do objeto, em algum formato
Além de Javadoc, uma outra forma de mostrar o que a classe faz é de usar uma representação gráfica numa linguagem chamada Unified Modeling Language (UML)
Também podemos mostrar apenas a parte pública, sem revelar detalhes internos que não interessam aos clientes da classe
UML é também chamada de linguagem de "modelagem visual"
◦ Um modelo é uma representação do mundo real que nos interessa
◦ UML permite criar modelos visuais
◦ Um programa é um modelo mais elaborado que consegue executar
O fato de definir uma classe como ContaSimples1 não significa que haja objetos desta classe em existência: alguém precisa fazer new ContaSimples1()
Seguem dois exemplos de programas que usam a classe ContaSimples1
O primeiro exemplo está a seguir:
package p2.exemplos; /* * Movimentação simples de uma conta bancária */ //
Programa Exemplo1 public class
Exemplo1 { //
O programa sempre tem um "método" main que é onde começa a execução public static void main(String args[]) { // Abra uma conta de número 1 para João com CPF
309140605-06 //
A conta será "referenciada" com a variável umaConta ContaSimples1 umaConta = new ContaSimples1("Joao", "30914060506", 1); //
Nesta conta, deposite R$1000,00 umaConta.depositar(1000.0); //
Imprima o saldo da conta de João double saldo = umaConta.getSaldo(); System.out.print("Saldo da conta de Joao antes do saque:
"); System.out.println(saldo); //
Saque R$300,00 desta conta umaConta.sacar(300.0); //
Imprima o objeto umaConta System.out.println(umaConta); //
Imprima o saldo da conta de João System.out.println("Saldo da conta de Joao depois do saque:
" + umaConta.getSaldo()); } //
fim do método main }
// fim da classe Exemplo1 |
O exemplo acima é praticamente idêntico ao exemplo Banco1.java
Observe que o método main está em Exemplo1 e não em ContaSimples1
O próximo exemplo está em ContaSimples1.java e consiste em colocar o main diretamente na classe que definimos acima
package p2.exemplos; /** * Classe de conta bancária simples. * * @author Jacques Sauvé * @version 2.0 */ public class ContaSimples1
{ private String nome; private String cpf; private int numero; private double saldo; // construtor /** * Cria
uma conta a partir de um nome e cpf de pessoa física, e um número de * conta. * * @param nome * O nome do titular da conta. * @param cpf * O CPF do titular da conta. * @param número * O número da conta. */ public ContaSimples1(String nome, String cpf, int numero) { this.nome = nome; this.cpf = cpf; this.numero = numero; this.saldo = 0.0; } //
métodos /** * Recupera
o nome do titular da conta. * * @return O nome do titular da conta. */ public Object getNome() { return this.nome; } /** * Recupera
o CPF do titular da conta. * * @return O CPF do titular da conta. */ public Object getCPF() { return this.cpf; } /** * Recupera
o número da conta. * * @return O número da conta. */ public Object getNúmero() { return this.numero; } /** * Recupera
o saldo da conta. * * @return O saldo da conta. */ public double getSaldo() { return this.saldo; } /** * Efetua
um depósito numa conta. * * @param valor * O valor a depositar. */ public void
depositar(double valor) { this.saldo += valor; } /** * Efetua
sacada na conta. * * @param valor * O valor a sacar. * @return O sucesso ou não da operação. */ public boolean sacar(double valor) { if (this.saldo
>= valor) { this.saldo -= valor; return true; } return false; } /** * Transforma
os dados da conta em um String. * * @return O string com os dados da conta. */ public String toString() { return "numero
" + numero
+ ", nome " + nome + ",
saldo " + saldo; } //
O programa sempre tem um "método" main que é onde começa a execução public static void main(String args[]) { // Abra uma conta de número 1 para João com CPF
309140605-06 //
A conta será "referenciada" com a variável umaConta ContaSimples1 umaConta = new ContaSimples1("Joao", "30914060506", 1); //
Nesta conta, deposite R$1000,00 umaConta.depositar(1000.0); //
Imprima o saldo da conta de João double saldo = umaConta.getSaldo(); System.out.print("Saldo da conta de Joao antes do saque:
"); System.out.println(saldo); //
Saque R$300,00 desta conta umaConta.sacar(300.0); //
Imprima o objeto umaConta System.out.println(umaConta); //
Imprima o saldo da conta de João System.out.println("Saldo da conta de Joao depois do saque:
" + umaConta.getSaldo()); } //
fim do método main } |
Observe que "main" é um método de classe (por causa da palavra "static")
◦ Pode executar sem ter objeto em existência ainda
◦ É assim que a bola começa a rolar e objetos são criados, etc.
Como na programação não OO, o “método” é uma técnica de ocultação de informação
◦ Para poder diminuir a complexidade, focando o programador numa coisa só
Observe como os métodos escodem os detalhes internos do objeto
◦ Para quem está “fora”, só usando o objeto, sabemos que podemos fazer um saque e um depósito mas nada sabemos sobre como isso ocorre, internamente
◦ Isso é uma chave da programação!
Também estamos vendo a técnica de encapsulamento em ação
◦ Dados (saldo, etc.) foram encapsulados numa caixa preta e a caixa disponibiliza uma interface (os métodos) para manipular os dados que estão dentro da caixa
◦ Isso é melhor do que acessar diretamente os dados para manipulação
◦ É melhor perguntar a alguém o que ele tomou no café da manhã ou enfiar sua mão goela abaixo e puxar a gosma para descobrir ...?
Observe como os métodos são pequenos
◦ Isso é normal na orientação a objeto
◦ Você deve desconfiar de métodos grandes: devem ser complicados demais e deveriam ser quebrados
Os testes da classe ContaSimples1 não estão completos
Principalmente, as condições de “exceção” não foram testadas
Exemplos:
◦ Construtor
i) O que ocorre se o nome for nulo ou vazio?
ii)O que ocorre se o CPF for nulo ou vazio?
iii) O que ocorre se o CPF for inválido?
iv) O que ocorre se o número da conta não for positivo?
◦ depositar
i) O que ocorre se o valor 0.0 for depositado?
ii)O que ocorre se um valor negativo for depositado?
iii)
Depositar
centavos funciona?
iv) O que ocorre se depositar frações de centavos?
◦ sacar
i) O que ocorre se o valor 0.0 for sacado?
ii)O que ocorre se um valor negativo for sacado?
iii)
Sacar
centavos funciona?
iv) O que ocorre se sacar frações de centavos?
◦ toString
i) toString não foi testado
ii)Tem que testar com que valores de saldo?
(1) Zero
(2) Positivo
(3) Com centavos
Muitos testes são necessários para garantir que tratamos adequadamente de todas as situações
◦ Para que testar se não for assim?
É o que veremos agora ...
…Mas antes precisamos aprender como lidar com os erros que possam ocorrer em nossos programas de forma elegante.
É importante diferenciar o descobrimento do erro e o tratamento do erro
◦ É muito frequente descobrir algo errado em um lugar mas querer tratar o erro em outro lugar
◦ Por exemplo, tratar o erro de nome vazio em ContaSimples1() é ruim porque é um método de "baixo nível" que não sabe sequer que tipo de interface está sendo usada (gráfica, a caractere), etc.
i) Não seria apropriado fazer um println e exit
A solução OO: Exceções
Vamos usar um mecanismo novo para retornar erros
◦ O retorno normal de valores por um método usa "return"
◦ O retorno anormal (indicando erro) usa outra palavra para retornar do método
i) A palavra é throw
◦ Da mesma forma que "return", "throw" retorna imediatamente do método
◦ Diferentemente de "return", "throw" só retorna objetos especiais chamados exceções
◦ A exceção pode conter uma mensagem indicando o erro que ocorreu para que o cliente da classe tenha informação suficiente para tratar o erro adequadamente
"throw" faz com que todos os métodos chamados retornem, até o ponto em que algum método captura a exceção para tratar o erro
◦ Essa captura é feita com um bloco try-catch
“throws” é uma palavra reservada usada para indicar que um método pode lançar uma exceção
◦ Assim, o código que chama este método pode se preparar para capturar a exceção com try-catch ou relançar a exceção (neste caso deve também indicar isso em sua assinatura)
Segue um exemplo do uso de exceções para você fuçar
package p2.exemplos; /** * Classe que mostra o uso de exceções. * * @author Jacques Philippe Sauvé * */ public class
TesteDeExcecoes { public static void main(String[]
args) { new TesteDeExcecoes().doIt(); System.out.println("bye, bye"); } private void doIt() { try { System.out.println("doIt: chama a()"); a(false); System.out.println("doIt: a() retornou"); System.out.println("main: chama b()"); b(false); System.out.println("doIt: b() retornou"); System.out.println("doIt: nao capturou excecao"); }
catch (Exception ex) { System.out.println("doIt: capturou excecao: " +
ex.getMessage()); } } private void a(boolean lanca) throws Exception { System.out.println("a:
chama c(" + lanca + ")"); c(lanca); System.out.println("a:
c() retornou"); } private void c(boolean lanca) throws Exception { System.out.println("c: inicio"); if (lanca) { System.out.println("c: vai lancar"); throw new Exception("bomba!"); // System.out.println("c:
lancou"); // causaria erro de compilação } System.out.println("c: fim"); } private void b(boolean lanca) throws Exception { try { System.out.println("b:
chama c(" + lanca + ")"); c(lanca); System.out.println("b:
c() retornou sem excecao"); } catch (Exception ex) { System.out.println("b: capturou excecao: " + ex.getMessage()); // tire o comentário abaixo para ver o
comportamento // throw ex; // ou então // throw new
Exception("granada!"); }
finally { System.out.println("b: finally"); } } } |
Agora, vamos ver como montar esse circo
◦ Vamos brincar com a classe teste de exceções, até entender bem como o lançamento e a captura de exceções ocorre em Java
Eis a classe de testes com novos testes
◦ Vamos testar a classe ContaSimples2 que trata erros adequadamente
◦ Os testes estão em TestaContaSimples2.java
package p2.exemplos.tests; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import p2.exemplos.ContaSimples2; public class
TestaContaSimples2 { private ContaSimples2 umaConta; @Before public void criaConta() throws Exception { umaConta = new ContaSimples2("Jacques Sauve", "123456789-01", 123); } @Test public void TestContaSimples1() throws Exception { Assert.assertEquals("Nome errado", "Jacques
Sauve", Assert.assertEquals("cpf errado", "123456789-01", umaConta.getCPF()); Assert.assertEquals("Número errado", 123, umaConta.getNúmero()); Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005); umaConta.depositar(100.0); Assert.assertEquals("Saldo errado", 100.0, umaConta.getSaldo(), 0.005); Assert.assertTrue("Nao consegui sacar", umaConta.sacar(45.0)); Assert.assertEquals("Saldo errado", 55.0, umaConta.getSaldo(), 0.005); Assert.assertFalse("Consegui sacar demais", umaConta.sacar(56.0)); Assert.assertEquals("Saldo errado", 55.0, umaConta.getSaldo(), 0.005); } @Test public void
testaErrosNoConstrutor() { try { ContaSimples2 umaConta = new ContaSimples2("", "123456789-01", 123); Assert.fail("Esperava excecao de nome vazio"); } catch (Exception ex) { Assert.assertEquals("Mensagem errada", "Nome
nao pode ser nulo ou vazio",
ex.getMessage()); } try { ContaSimples2 umaConta = new ContaSimples2(null, "123456789-01", 123); Assert.fail("Esperava excecao de nome nulo"); } catch (Exception ex) { Assert.assertEquals("Mensagem errada", "Nome
nao pode ser nulo ou vazio",
ex.getMessage()); } try { ContaSimples2 umaConta = new ContaSimples2("Jacques
Sauve", "",
123); Assert.fail("Esperava excecao de CPF vazio"); } catch (Exception ex) { Assert.assertEquals("Mensagem errada", "CPF
nao pode ser nulo ou vazio",
ex.getMessage()); } try { ContaSimples2 umaConta = new ContaSimples2("Jacques
Sauve", null, 123); Assert.fail("Esperava excecao de CPF nulo"); } catch (Exception ex) { Assert.assertEquals("Mensagem errada", "CPF
nao pode ser nulo ou vazio",
ex.getMessage()); } try { ContaSimples2 umaConta = new ContaSimples2("Jacques
Sauve", "123456789-01", 0); Assert.fail("Esperava excecao de numero de conta
errada"); } catch (Exception ex) { Assert.assertEquals("Mensagem errada", "Número
da conta deve ser positivo",
ex.getMessage()); } try { ContaSimples2 umaConta = new ContaSimples2("Jacques
Sauve", "123456789-01", -1); Assert.fail("Esperava excecao de numero de conta
errada"); } catch (Exception ex) { Assert.assertEquals("Mensagem errada", "Número
da conta deve ser positivo",
ex.getMessage()); } } @Test public void testDepositar() throws Exception { Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005); umaConta.depositar(100.0); Assert.assertEquals("Saldo errado", 100.0, umaConta.getSaldo(), 0.005); umaConta.depositar(0.0); Assert.assertEquals("Saldo errado", 100.0, umaConta.getSaldo(), 0.005); try { umaConta.depositar(-0.01); Assert.fail("Esperava excecao no deposito"); } catch (Exception ex) { Assert.assertEquals("Mensagem errada", "Deposito
nao pode ser negativo",
ex.getMessage()); } umaConta.depositar(0.01); Assert.assertEquals("Saldo errado", 100.01, umaConta.getSaldo(), 0.005); } @Test public void testSacar() throws Exception { Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005); Assert.assertTrue("Nao consegui sacar", umaConta.sacar(0.0)); Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005); umaConta.depositar(100.0); Assert.assertTrue("Nao consegui sacar", umaConta.sacar(23.10)); Assert.assertEquals("Saldo errado", 76.90, umaConta.getSaldo(), 0.005); Assert.assertFalse("Consegui sacar demais", umaConta.sacar(76.91)); Assert.assertTrue("Nao consegui sacar", umaConta.sacar(76.90)); Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005); try { umaConta.sacar(-0.01); Assert.fail("Esperava excecao no saque"); } catch (Exception ex) { Assert.assertEquals("Mensagem errada", "Saque
nao pode ser negativo", ex.getMessage()); } Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005); } @Test public void testToString() throws Exception { Assert.assertEquals("toString errado", "numero
123, nome Jacques Sauve, saldo 0.0",
umaConta .toString()); umaConta.depositar(1.23); Assert.assertEquals("toString errado", "numero
123, nome Jacques Sauve, saldo 1.23",
umaConta .toString()); } } |
Vamos escrever a classe ContaSimples2 com base nestes testes?
◦ Lembram que os testes são o roteiro de como a classe funciona?
A classe ContaSimples2 está a seguir
package p2.exemplos; /** * Classe de conta bancária simples. * * @author Jacques Sauvé * @version 2.0 */ public class ContaSimples2
{ private static final Object STRING_VAZIA = ""; private String nome; private String cpf; private int numero; private double saldo; // construtor /** * Cria
uma conta a partir de um nome e cpf de pessoa física, e um número de * conta. * * @param nome * O nome do titular da conta. * @param cpf * O CPF do titular da conta. * @param número * O número da conta. * @throws Exception * Se o nome for nulo ou vazio, o CPF for nulo ou vazio ou a * conta não for um número positivo */ public ContaSimples2(String nome, String cpf, int numero) throws Exception { if (nome == null || nome.equals(STRING_VAZIA)) { throw new
Exception("Nome
nao pode ser nulo ou vazio"); } if (cpf == null || cpf.equals(STRING_VAZIA)) { throw new
Exception("CPF
nao pode ser nulo ou vazio"); } if (numero <= 0) { throw new
Exception("Número
da conta deve ser positivo"); } this.nome = nome; this.cpf = cpf; this.numero = numero; this.saldo = 0.0; } //
métodos /** * Recupera
o nome do titular da conta. * * @return O nome do titular da conta. */ public String getNome() { return nome; } /** * Recupera
o CPF do titular da conta. * * @return O CPF do titular da conta. */ public String getCPF() { return cpf; } /** * Recupera
o número da conta. * * @return O número da conta. */ public int getNúmero() { return numero; } /** * Recupera
o saldo da conta. * * @return O saldo da conta. */ public double getSaldo() { return saldo; } /** * Efetua
um depósito numa conta. * * @param valor * O valor a depositar. * @throws Exception * Quando o valor a depositar é negativo. * */ public void depositar(double valor) throws Exception { if (valor < 0.0) { throw new
Exception("Deposito
nao pode ser negativo"); } saldo += valor; } /** * Efetua
saque na conta. * * @param valor * O valor a sacar. * @return O sucesso ou não da operação. * @throws Exception * Para um valor de saque negativo */ public boolean sacar(double valor) throws Exception { if (valor < 0.0) { throw new
Exception("Saque
nao pode ser negativo"); } if (saldo >= valor) { saldo
-= valor; return true; } return false; } /** * Transforma
os dados da conta em um String. * * @return O string com os dados da conta. */ public String toString() { return "numero
" + numero
+ ", nome " + nome + ",
saldo " + saldo; } //
O programa sempre tem um "método" main que é onde começa a execução public static void main(String args[]) { // Abra uma conta de número 1 para João com CPF
309140605-06 //
A conta será "referenciada" com a variável umaConta ContaSimples2 umaConta = null; try { umaConta = new ContaSimples2("Joao", "30914060506", 1); //
Nesta conta, deposite R$1000,00 umaConta.depositar(1000.0); } catch (Exception e) { System.out.println(e.getMessage()); } //
Imprima o saldo da conta de João double saldo = umaConta.getSaldo(); System.out.print("Saldo da conta de Joao antes do saque:
"); System.out.println(saldo); //
Saque R$300,00 desta conta try { umaConta.sacar(300.0); }
catch (Exception e) { System.out.println(e.getMessage()); } //
Imprima o objeto umaConta System.out.println(umaConta); //
Imprima o saldo da conta de João System.out.println("Saldo da conta de Joao depois do saque:
" + umaConta.getSaldo()); } //
fim do método main } |
Uma outra forma de escrever alguns métodos de teste que lidam com as exceções segue
package p2.exemplos; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import p2.exemplos.ContaSimples2; public class
TestaContaSimples2Outra { private ContaSimples2 umaConta; @Before public void criaConta() throws Exception { umaConta = new ContaSimples2("Jacques Sauve", "123456789-01", 123); } @Test public void TestContaSimples2() throws Exception { Assert.assertEquals("Nome errado", "Jacques
Sauve", umaConta.getNome()); Assert.assertEquals("cpf errado", "123456789-01", umaConta.getCPF()); Assert.assertEquals("Número errado", 123, umaConta.getNúmero()); Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005); umaConta.depositar(100.0); Assert.assertEquals("Saldo errado", 100.0, umaConta.getSaldo(), 0.005); Assert.assertTrue("Nao consegui sacar", umaConta.sacar(45.0)); Assert.assertEquals("Saldo errado", 55.0, umaConta.getSaldo(), 0.005); Assert.assertFalse("Consegui sacar demais", umaConta.sacar(56.0)); Assert.assertEquals("Saldo errado", 55.0, umaConta.getSaldo(), 0.005); } @Test(expected =
Exception.class) public void testaConstrutorNomeVazio() throws Exception { ContaSimples2
umaConta = new
ContaSimples2("", "123456789-01", 123); } @Test(expected =
Exception.class) public void testaConstrutorNomeNulo() throws Exception { ContaSimples2
umaConta = new
ContaSimples2(null, "123456789-01", 123); } @Test(expected =
Exception.class) public void testaConstrutorCPFVazio() throws Exception { ContaSimples2 umaConta = new ContaSimples2("Jacques
Sauve", "",
123); } @Test(expected =
Exception.class) public void testaConstrutorCPFNulo() throws Exception { ContaSimples2
umaConta = new
ContaSimples2("Jacques
Sauve", null,
123); } @Test(expected = Exception.class) public void
testaConstrutorContaNumeroZero() throws Exception { ContaSimples2 umaConta = new ContaSimples2("Jacques
Sauve", "123456789-01", 0); } @Test(expected = Exception.class) public void
testaConstrutorContaNumeroNegativo() throws Exception { ContaSimples2 umaConta = new ContaSimples2("Jacques
Sauve", "123456789-01", -1); } @Test public void testDepositar() throws Exception { Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005); umaConta.depositar(100.0); Assert.assertEquals("Saldo errado", 100.0, umaConta.getSaldo(), 0.005); umaConta.depositar(0.0); Assert.assertEquals("Saldo errado", 100.0, umaConta.getSaldo(), 0.005); try { umaConta.depositar(-0.01); Assert.fail("Esperava excecao no deposito"); } catch (Exception ex) { Assert.assertEquals("Mensagem errada", "Deposito
nao pode ser negativo",
ex.getMessage()); } umaConta.depositar(0.01); Assert.assertEquals("Saldo errado", 100.01, umaConta.getSaldo(), 0.005); } @Test public void testSacar() throws Exception { Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005); Assert.assertTrue("Nao consegui sacar", umaConta.sacar(0.0)); Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005); umaConta.depositar(100.0); Assert.assertTrue("Nao consegui sacar", umaConta.sacar(23.10)); Assert.assertEquals("Saldo errado", 76.90, umaConta.getSaldo(), 0.005); Assert.assertFalse("Consegui sacar demais", umaConta.sacar(76.91)); Assert.assertTrue("Nao consegui sacar", umaConta.sacar(76.90)); Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005); try { umaConta.sacar(-0.01); Assert.fail("Esperava excecao no saque"); } catch (Exception ex) { Assert.assertEquals("Mensagem errada", "Saque
nao pode ser negativo",
ex.getMessage()); } Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005); } @Test public void testToString() throws Exception { Assert.assertEquals("toString errado", "numero
123, nome Jacques Sauve, saldo 0.0",
umaConta .toString()); umaConta.depositar(1.23); Assert.assertEquals("toString errado", "numero
123, nome Jacques Sauve, saldo 1.23",
umaConta .toString()); } } |
Em primeiro lugar, queremos ver a implementação da classe Pessoa que usamos no passado
Exercício para casa:
◦ Escreva testes para a classe Pessoa
package p1.aplic.geral; import java.io.*; /** * Classe representando uma pessoa física. * * @author Jacques Philippe Sauvé, jacques@dsc.ufpb.br * @version 1.0 <br> * Copyright
(C) 1999 Universidade Federal de Campina Grande. */ public class Pessoa implements Serializable { private String nome; private String cpf; //
Construtores /** * Constroi
uma pessoa com nome e CPF dados. * * @param nome * O nome da pessoa. * * @param cpf * O CPF da pessoa. */ public Pessoa(String nome, String cpf) { this.nome = nome; this.cpf = cpf; } /** * Constroi
uma pessoa com nome dado e sem CPF. * * @param nome * O nome da pessoa. */ public Pessoa(String nome) { this(nome, ""); } /** * Recupera
o nome da pessoa. * * @return O nome da pessoa. */ public String getNome() { return nome; } /** * Recupera
o CPF da pessoa. * * @return O CPF associado à pessoa. */ public String getCPF() { return cpf; } /** * Ajusta
o nome da pessoa. * * @param nome * O nome da pessoa. */ public void setNome(String nome) { this.nome = nome; } /** * Ajusta
o CPF da pessoa. * * @param cpf * O CPF associado à pessoa. */ public void setCPF(String cpf) { this.cpf = cpf; } /** * Representa
a pessoa como string */ public String toString() { return "Nome " + nome + ", cpf
" + cpf; } /** * Testa
a igualdade de um objeto com esta pessoa. * * @param objeto * O objeto a comparar com esta pessoa. * @return true se o objeto for igual a esta pessoa, false caso contrário. */ public boolean equals(Object objeto) { if (!(objeto instanceof Pessoa)) { return false; } Pessoa outra = (Pessoa) objeto; return getNome().equals(outra.getNome()) &&
getCPF().equals(outra.getCPF()); } } |
Esqueça, por enquanto da palavra "Serializable"
◦ Por enquanto, significa apenas que queremos gravar objetos dessa classe em arquivo
A linha com “package” diz a qual pacote pertence a classe
Temos dois construtores
◦ Há um overload do nome Pessoa
//
Construtores /** * Constroi
uma pessoa com nome e CPF dados. * * @param nome * O nome da pessoa. * * @param cpf * O CPF da pessoa. */ public Pessoa(String nome, String cpf) { this.nome = nome; this.cpf = cpf; } /** * Constroi
uma pessoa com nome dado e sem CPF. * * @param nome * O nome da pessoa. */ public Pessoa(String nome) { this(nome, ""); } |
Uma Pessoa pode ser criada de duas formas: com e sem CPF
Pessoa
pessoa1 = new Pessoa("Raquel", "98765432-11"); Pessoa pessoa2
= new Pessoa("Raquel"); |
Observe também que o segundo construtor chama this() como se this fosse um método
◦ this(...) é a chamada a um construtor da classe, neste caso com dois argumentos
◦ Isto é, Pessoa(String) chama Pessoa(String, String), passando o string nulo como CPF
◦ É uma forma de não duplicar código (fatorando o que é igual)
Observe a existência de métodos "mutator" (que alteram o valor dos atributos)
Finalmente, é muito comum uma classe incluir um método equals() para testar se outro objeto qualquer é igual a este (que foi chamado)
/** * Testa
a igualdade de um objeto com esta pessoa. * * @param objeto * O objeto a comparar com esta pessoa. * @return true Se o objeto for igual a esta pessoa, false caso */ public boolean equals(Object objeto) { if (!(objeto instanceof Pessoa)) { return false; } Pessoa outra = (Pessoa) objeto; return getNome().equals(outra.getNome()) &&
getCPF().equals(outra.getCPF()); } |
◦ Dá para ver que dois objetos Pessoa são iguais se tiverem nome e CPF iguais
◦ Entenderemos adiante que Object é um objeto de classe geral e instanceof diz se um objeto “é” de uma certa classe
◦
Por que usamos getNome()
e getCPF() em vez de usar nome e cpf???
Agora vamos usar a classe Pessoa, através de uma outra classe ContaSimples3.java
package p2.exemplos; import p1.aplic.geral.Pessoa; /** * Classe de conta bancária simples. * * @author Jacques Sauvé * @author Raquel Lopes * @version 3.0 */ public class ContaSimples3
{ private static final Object STRING_VAZIA = ""; private Pessoa titular; private int
numero; private double saldo; //
construtores /** * Cria
uma conta a partir de uma pessoa e número de conta. * * @param titular * O titular da conta. * @param número * O número da conta. */ public ContaSimples3(Pessoa titular, int numero) throws Exception { if (titular.getNome() == null || titular.getNome().equals(STRING_VAZIA)) { throw new
Exception("Nome
nao pode ser nulo ou vazio"); } if (titular.getCPF() == null ||
titular.getCPF().equals(STRING_VAZIA)) { throw new
Exception("CPF
nao pode ser nulo ou vazio"); } if (numero <= 0) { throw new
Exception("Número
da conta deve ser positivo"); } this.titular = titular; this.numero = numero; this.titular = titular; saldo = 0.0; } /** * Cria
uma conta a partir de um nome e cpf de pessoa física, e um número * de conta. * * @param nome * O nome do titular da conta. * @param cpf * O CPF do titular da conta. * @param número * O número da conta. * @throws Exception * Se o nome for nulo ou vazio, o CPF for nulo ou vazio ou a * conta não for um número positivo */ public ContaSimples3(String nome, String cpf, int numero)
throws Exception { this(new
Pessoa(nome, cpf), numero); } //
métodos /** * Recupera
o nome do titular da conta. * * @return O nome do titular da conta. */ public String getNome() { return titular.getNome(); } /** * Recupera
o CPF do titular da conta. * * @return O CPF do titular da conta. */ public String getCPF() { return titular.getCPF(); } /** * Recupera
o número da conta. * * @return O número da conta. */ public int getNúmero() { return numero; } /** * Recupera
o titular da conta. * * @return O titular da conta. */ public Pessoa getTitular() { return titular; } /** * Recupera
o saldo da conta. * * @return O saldo da conta. */ public double getSaldo() { return saldo; } /** * Efetua
um depósito numa conta. * * @param valor * O valor a depositar. * @throws Exception * Quando o valor a depositar é negativo. * */ public void depositar(double valor) throws Exception { if (valor < 0.0) { throw new
Exception("Deposito
nao pode ser negativo"); } saldo += valor; } /** * Efetua
saque na conta. * * @param valor * O valor a sacar. * @return O sucesso ou não da operação. * @throws Exception * Para um valor de saque negativo */ public boolean sacar(double valor) throws Exception { if (valor < 0.0) { throw new
Exception("Saque
nao pode ser negativo"); } if (saldo >= valor) { saldo -= valor; return true; } return false; } /** * Transforma
os dados da conta em um String. * * @return O string com os dados da conta. */ public String toString() { return "numero
" + numero
+ ", nome " + getNome() + ",
saldo " + saldo; } } |
Observe particularmente os seguintes pontos:
◦ Os dois construtores com overload da palavra Pessoa como método
◦ Como o segundo construtor chama o primeiro
◦ Como variáveis temporárias são evitadas no segundo construtor
◦ O que getTitular() retorna
◦ Como getNome() e getCPF() fazem seu trabalho
Você vê por qual motivo toString() chama getNome() em vez de
usar titular.getNome()?
Em UML, a relação entre as duas classes pode ser vista assim:
◦ A linha é uma associação (ou relação) entre classes
◦ Neste caso, é uma associação de “conhecimento” (ContaSimples3 conhece uma Pessoa)
◦ A seta indica a navegabilidade