Pacotes, diagramas de classes e testes unitários em Java

Aprenda a organizar um programa Java maior para que continue fácil de entender, alterar e verificar. Agrupe classes em pacotes com importações e níveis de acesso adequados, esboce a estrutura em diagramas de classes UML e separe a interface do usuário, a lógica do programa e o armazenamento de dados em camadas. Depois, verifique a lógica com testes JUnit, escreva os testes antes do código e use Maven ou Gradle para compilar, testar e empacotar todo o projeto com um único comando.

BásicoAula 10 de 11 do curso Curso de Programação orientada a objetos com Java

O que você vai aprender

  1. Agrupe classes relacionadas em pacotes, armazene cada arquivo-fonte onde o compilador e as ferramentas de construção esperam encontrá-lo e use classes entre pacotes com importações e o nível de acesso adequado.
    • Declare pacotes e organize as pastas correspondentes
      Escreva uma declaração de pacote, escolha um nome de pacote convencional, armazene o arquivo na pasta correspondente e refira-se a classes por seus nomes totalmente qualificados.
    • Use classes de outros pacotes por meio de importações
      Importe classes individuais ou pacotes inteiros, saiba quais classes não precisam de importação, resolva conflitos entre classes com o mesmo nome e use importações estáticas.
    • Controle o acesso entre pacotes
      Escolha entre public, protected, acesso de pacote e private para que cada pacote exponha apenas o que os outros pacotes precisam.
  2. Interprete e desenhe diagramas de classes que mostram classes, seus membros e as relações entre elas, e passe de um diagrama para o código Java e vice-versa.
    • Desenhe e interprete a representação de uma classe
      Mostre o nome, os atributos e as operações de uma classe com tipos e visibilidade e marque membros estáticos, classes abstratas e interfaces.
    • Represente associações e multiplicidade
      Conecte classes por associações, interprete e escreva multiplicidades e navegabilidade e distinga composição, agregação e dependência.
    • Modele herança e transite entre diagramas e código
      Mostre herança e implementação de interfaces, escreva código Java a partir de um diagrama e desenhe um diagrama a partir de código existente.
  3. Divida um programa de modo que a interface do usuário, a lógica da aplicação e o armazenamento de dados fiquem em classes e pacotes separados, com dependências apontando em uma única direção.
    • Separe a interface do usuário da lógica do programa
      Mantenha a leitura de dados de entrada e a exibição de saídas em uma classe de interface do usuário, mantenha as regras do programa em classes de lógica e conecte as duas no método main.
    • Organize camadas, pacotes e dependências
      Organize classes em uma camada de apresentação, uma camada de lógica e uma camada de acesso a dados, mantenha as dependências apontando para baixo e conecte as camadas por interfaces.
  4. Escreva testes unitários automatizados com JUnit, escolha casos de teste que revelem defeitos, mantenha os testes independentes e legíveis e desenvolva código novo escrevendo os testes primeiro.
    • Escreva e execute seu primeiro teste JUnit
      Crie uma classe de teste, escreva métodos de teste com asserções, execute-os e interprete o resultado de um teste que passa e de um que falha.
    • Escolha casos de teste que revelem defeitos
      Escolha dados de entrada típicos, de valor-limite e inválidos, teste exceções esperadas e dê a cada teste um objetivo e um nome claros.
    • Mantenha os testes independentes e fáceis de manter
      Prepare novos objetos de teste antes de cada teste, evite estado compartilhado, execute um teste com várias entradas e teste a lógica separadamente da entrada e da saída.
    • Desenvolva código escrevendo os testes primeiro com TDD
      Siga o ciclo vermelho–verde–refatorar a partir de uma lista de testes, dê passos pequenos e saiba o que o desenvolvimento orientado por testes promete e o que não promete.
  5. Use uma ferramenta de construção para gerenciar bibliotecas, seguir a estrutura padrão do projeto e compilar, testar e empacotar um programa com um único comando.
    • Configure e construa um projeto Maven
      Descreva um projeto em pom.xml com coordenadas e dependências, siga a estrutura padrão de pastas e execute o ciclo de vida da construção.
    • Configure um projeto Gradle e corrija falhas de construção
      Escreva o script de construção Gradle equivalente, execute testes JUnit na JUnit Platform, compare as duas ferramentas e encontre a causa de uma falha de construção.

Guia de estudo de Pacotes e testes unitários em Java

5 capítulos

1

Organize um programa Java em pacotes

À medida que um programa cresce, nomes de classes e pastas precisam de uma estrutura que tanto as pessoas quanto Java possam seguir. Pacotes agrupam tipos relacionados e definem limites de acesso. Importações tornam esses tipos mais fáceis de usar sem expor seus detalhes internos. Você relacionará uma declaração ao caminho de seu código-fonte, resolverá nomes entre pacotes e decidirá quais classes, construtores e métodos outra parte do programa deve poder chamar.

1.1 Nomes de pacotes e pastas de código-fonte

Um pacote agrupa classes e interfaces relacionadas sob um mesmo nome. Ele também separa nomes: duas classes chamadas Parcel podem coexistir quando seus nomes de pacote são diferentes. Um pacote oferece ao Java um limite de acesso, além de uma forma de organizar arquivos.

Declare o pacote primeiro

Um arquivo-fonte comum começa com sua declaração de pacote, seguida das importações e depois da classe. Comentários podem aparecer antes da declaração. Por exemplo:

package com.example.parcels.domain;

public class Parcel {
    private int weightGrams;

    public Parcel(int weightGrams) {
        this.weightGrams = weightGrams;
    }
}

Todo tipo de nível superior nesse arquivo pertence a com.example.parcels.domain. Um arquivo tem no máximo uma declaração de pacote. A classe pública Parcel deve ficar em Parcel.java; alterar apenas a pasta não altera o pacote declarado.

Transforme pontos em pastas

O diretório raiz do código-fonte é a pasta onde começa o caminho do pacote. Em um projeto Maven ou Gradle padrão, o código Java principal começa em src/main/java. Substitua cada ponto no nome do pacote por uma barra e depois acrescente o nome da classe:

src/main/java/
  com/example/parcels/domain/Parcel.java

O pacote é com.example.parcels.domain, não src.main.java.com.example.parcels.domain. As três primeiras pastas descrevem a estrutura da construção; elas não fazem parte do nome Java. Quando uma IDE informa uma incompatibilidade de pacote, compare a declaração com o caminho abaixo do diretório raiz do código-fonte. Mova o arquivo ou corrija a declaração para que correspondam.

Nomes de pacotes usam letras minúsculas. Organizações geralmente invertem seu domínio: example.com torna-se com.example, seguido do projeto e de sua área. Um projeto sem domínio pode usar parcels.domain. Se um componente do domínio invertido contiver um hífen, substitua-o por um sublinhado, como em org.example.hyphenated_name. Evite o espaço de nomes java e javax da plataforma.

Use o nome completo da classe

Um nome totalmente qualificado une o pacote e o nome simples da classe: com.example.parcels.domain.Parcel. Ele identifica a classe mesmo quando outro pacote também tem uma Parcel. O código pode nomear uma classe acessível por extenso sem importação:

java.util.ArrayList<String> names = new java.util.ArrayList<>();

Para executar uma classe principal em um pacote, forneça seu nome completo e um caminho de classes apontando para a raiz da árvore de pacotes compilada. Na pasta do projeto:

javac -d out src/main/java/com/example/parcels/Main.java src/main/java/com/example/parcels/domain/Parcel.java
java -cp out com.example.parcels.Main

Aqui, javac -d out cria a árvore de saída, e java -cp out busca a partir da raiz dessa árvore. Iniciar java Main de uma pasta de pacote perde a parte do pacote na identidade da classe. Ferramentas de construção e IDEs normalmente gerenciam esses comandos.

Distinga pacotes próximos

com.example.parcels e com.example.parcels.domain são pacotes separados. Compartilhar um prefixo de nome não concede direitos de acesso adicionais. Um arquivo sem declaração pertence ao pacote sem nome. Pacotes nomeados não podem importar suas classes, portanto use pacotes nomeados em todo programa com várias partes.

Principais conclusões

  • Coloque a declaração de pacote antes das importações e da classe.
  • Faça o caminho do pacote corresponder ao caminho abaixo do diretório raiz do código-fonte; não inclua src/main/java no nome do pacote.
  • Use nomes de pacotes em minúsculas e o nome totalmente qualificado da classe quando necessário.
  • Um prefixo compartilhado no nome dos pacotes não concede acesso de pacote entre eles.
  • Execute classes em pacotes a partir da raiz da árvore compilada usando seus nomes totalmente qualificados.

1.2 Importações e conflitos de nomes de classes

Uma importação permite que um arquivo-fonte use o nome simples de um tipo acessível. Ela informa ao compilador o significado do nome. Não copia a classe, não baixa uma biblioteca nem torna pública uma classe oculta.

Leia o início de um arquivo-fonte

A interface do usuário pertence a um pacote e usa classes de outros dois:

package com.example.parcels.ui;

import com.example.parcels.domain.Parcel;
import com.example.parcels.domain.ShippingCalculator;
import java.util.Scanner;

public class TextUI {
    private Scanner scanner;
    private ShippingCalculator calculator;
}

Parcel e ShippingCalculator vêm do pacote de domínio do programa; Scanner vem da biblioteca Java. Cada importação se aplica apenas a esse arquivo. Outro arquivo que usa Scanner precisa de sua própria importação ou do nome completo java.util.Scanner.

Escolha um tipo único ou um curinga

import java.util.Scanner; é uma importação de tipo único. Sua classe exata fica visível para quem lê. import java.util.*; é uma importação sob demanda: tipos acessíveis nesse pacote podem ser nomeados quando usados. O curinga não carrega classes não utilizadas no programa nem o torna mais lento.

O curinga se limita àquele pacote. Ele não inclui java.util.function.Function nem java.util.stream.Collectors, que estão em pacotes separados. A mesma regra se aplica ao seu próprio código: import com.example.parcels.*; não alcança com.example.parcels.domain.Parcel.

Três casos não precisam de importação: tipos no mesmo pacote, tipos disponíveis automaticamente em java.lang e tipos nomeados por extenso. Por isso, String, System, Math, Integer e Object aparecem frequentemente sem importações.

Resolva um nome simples compartilhado

Tanto java.util quanto java.awt têm uma classe chamada List. Duas importações de tipo único para essas classes diferentes causam erro de compilação. Duas importações com curinga podem coexistir, mas o nome simples List torna-se ambíguo quando usado. Uma importação de tipo único tem precedência sobre uma importação com curinga.

Se ambas as classes forem necessárias, importe uma e escreva a outra por extenso:

import java.util.List;

class NameDisplay {
    private List<String> names;
    private java.awt.List widget;
}

As importações Java não oferecem uma sintaxe de apelido como import ... as .... O nome totalmente qualificado é a maneira de distinguir o segundo tipo.

Importe um membro estático

Uma importação estática dispensa o nome da classe proprietária na chamada de um membro estático:

import static java.lang.Math.sqrt;

O arquivo agora pode chamar sqrt(16.0). O código de teste frequentemente usa import static org.junit.jupiter.api.Assertions.assertEquals; para que as asserções sejam lidas como verificações diretas. No código comum do programa, use importações estáticas com critério para que quem lê ainda consiga ver de onde vêm as operações.

Siga o que o erro informa

“Cannot find symbol” pode indicar uma importação ausente ou um tipo escrito incorretamente. “Package ... does not exist” aponta para um problema de grafia do pacote, diretório raiz do código-fonte ou dependência. “Is not public ... cannot be accessed from outside package” significa que Java encontrou o tipo, mas o acesso está bloqueado. Adicionar outra importação não corrige esse erro de acesso; examine a declaração da classe e os membros de que você precisa.

Principais conclusões

  • Uma importação resolve nomes em um arquivo e não concede direitos de acesso.
  • Um curinga inclui tipos de um pacote, sem seus subpacotes.
  • Tipos do mesmo pacote e tipos de java.lang não precisam de importação.
  • Use uma importação de tipo único e um nome totalmente qualificado quando duas classes necessárias compartilham um nome.
  • Uma importação estática permite chamar um membro estático sem o nome de sua classe.
  • Distinga um erro de resolução de nomes de um erro de acesso.

1.3 Uma interface pública pequena

Um pacote deve expor as classes e os métodos de que outras partes precisam, mantendo ocultos os detalhes de implementação. Modificadores de acesso permitem impor esse limite. Uma importação pode encurtar um nome, mas não pode atravessar um limite fechado pelas regras de acesso do Java.

Escolha o acesso para um membro de classe
AcessoQuem pode usar o membro?
publicCódigo que pode acessar o tipo que o declara
protectedO mesmo pacote, além do acesso de subclasses entre pacotes
Sem modificadorExatamente o mesmo pacote
privateA classe que o declara e seus tipos aninhados

A ausência de modificador significa acesso de pacote para membros comuns de classes e construtores. Não se trata de uma palavra-chave chamada default. Uma classe de nível superior tem apenas duas opções: pública ou com acesso de pacote. Uma classe pública de nível superior é armazenada em um arquivo com seu nome, como ShippingCalculator.java.

Verifique o tipo e a operação

Suponha que a interface do usuário precise construir uma encomenda. Uma classe pública, por si só, não é suficiente:

package com.example.parcels.domain;

public class Parcel {
    private int weightGrams;

    Parcel(int weightGrams) {
        this.weightGrams = weightGrams;
    }
}

O construtor tem acesso de pacote. O código em com.example.parcels.ui pode nomear Parcel, mas não pode chamar new Parcel(1200). Torne esse construtor public se a criação de encomendas fizer parte do serviço público do pacote. Mantenha weightGrams privado e exponha uma operação apropriada em vez de abrir o campo.

O acesso de pacote compara o nome completo do pacote. Um pacote que parece ser pai ou filho não é o mesmo pacote. O código em com.example.parcels não recebe acesso especial às classes auxiliares em com.example.parcels.domain.

Entenda o acesso protegido

protected permite acesso de classes sem relação no pacote que declara o membro, além de subclasses. Entre pacotes, um membro de instância é acessado no contexto de herança da subclasse. Uma subclasse pode usar seu próprio campo herdado, mas não pode usar um objeto arbitrário da superclasse para contornar o limite do pacote.

Por exemplo, se Item declara protected int weightGrams, uma Letter extends Item em outro pacote pode usar this.weightGrams. Dentro de Letter, um parâmetro declarado apenas como Item não permite item.weightGrams; uma referência declarada como Letter permite o acesso herdado. Essa restrição impede que os privilégios da subclasse se tornem acesso geral a cada Item.

Oculte classes auxiliares deliberadamente

Torne ShippingCalculator e feeInCents públicos se a interface os chamar. Uma classe auxiliar WeightBands pode manter acesso de pacote. Assim, o código fora do pacote de domínio depende do comportamento da calculadora, não de como ela armazena a tabela. Substituir essa classe auxiliar exige menos alterações em outros lugares.

Testes podem usar o mesmo nome de pacote da classe testada enquanto ficam em src/test/java. Isso lhes dá acesso de pacote sem colocar código de teste entre arquivos de produção. Isso não concede acesso a campos privados. Tornar cada membro público apenas para satisfazer um teste espalha dependências de detalhes internos; em vez disso, teste o comportamento público ou uma operação útil com acesso de pacote.

Principais conclusões

  • Membros de classe com acesso de pacote são acessíveis apenas no mesmo pacote exato.
  • Quem faz uma chamada precisa ter acesso tanto à classe quanto ao construtor ou método que usa.
  • O acesso protegido inclui acesso no mesmo pacote e acesso restrito de subclasses entre pacotes.
  • Mantenha campos privados e exponha apenas as operações de que outro pacote precisa.
2

Esboce a estrutura do programa com diagramas de classes UML

Um diagrama de classes oferece uma visão compacta de um programa antes que você leia os corpos de seus métodos. Caixas descrevem tipos e operações; linhas descrevem ligações, quantidades e relações entre subtipos. Você interpretará essas marcações com precisão e as traduzirá em declarações Java. O programa de encomendas fornece exemplos de classes individuais, coleções de objetos associados e implementações de repositório intercambiáveis.

2.1 O que a caixa de uma classe UML informa

Um diagrama de classes UML mostra a estrutura estática: classes, seus membros e relações. Ele ajuda quem lê a ver que tipos de objetos existem e o que oferecem. Não acompanha a ordem das chamadas de métodos durante a execução.

Leia os três compartimentos

Uma caixa de classe tem o nome da classe no topo, os atributos no meio e as operações na parte inferior. Um atributo geralmente representa um campo Java; uma operação representa a assinatura visível de um método. Um esboço pode omitir compartimentos que não atendem ao seu objetivo.

Para uma encomenda, uma representação textual da caixa é:

Parcel
-----------------------------
- weightGrams: int
- recipient: String
-----------------------------
+ Parcel(weightGrams: int, recipient: String)
+ getWeightGrams(): int
+ isHeavierThan(limitGrams: int): boolean

O construtor tem o nome da classe e não tem tipo de retorno. O método isHeavierThan aceita um inteiro chamado limitGrams e retorna um booleano.

Traduza a ordem de nomes e tipos

UML coloca o nome antes do tipo: weightGrams: int. Java coloca o tipo primeiro: int weightGrams. Uma operação segue o mesmo padrão de nome primeiro para parâmetros e depois coloca o tipo de retorno após o parêntese de fechamento.

private int weightGrams;

public boolean isHeavierThan(int limitGrams) {
    return weightGrams > limitGrams;
}

Esse código corresponde a - weightGrams: int e + isHeavierThan(limitGrams: int): boolean. O corpo de um método geralmente não aparece no diagrama; a operação declara o que pode ser chamado, não o algoritmo.

Preserve a visibilidade e as marcações especiais

Os símbolos de visibilidade são + public, - private, # protected e ~ acesso de pacote. Coloque o símbolo antes do nome do membro. Se um diagrama fornecido omitir um símbolo, não invente sua visibilidade; adicione o símbolo pretendido ao documentar o código.

Um membro estático é sublinhado. Ele pertence à classe, e não a cada objeto. Uma classe ou operação abstrata é escrita em itálico ou marcada com {abstract} quando o itálico não é conveniente. A marcação de abstrato significa que uma subclasse precisa fornecer o comportamento ausente antes que um objeto possa ser criado a partir de uma classe concreta.

Uma caixa de interface traz «interface» acima do nome. Por exemplo, ParcelRepository pode oferecer + save(parcel: Parcel): void e + findAll(): List<Parcel>. A caixa declara um contrato; ela não escolhe uma implementação de armazenamento.

Escolha os detalhes de que quem lê precisa

Um esboço de planejamento pode mostrar apenas Parcel, Shipment e suas principais operações. Um diagrama para implementar uma classe precisa de tipos e visibilidade. Listar cada método de acesso rotineiro pode ocultar as relações que o diagrama deveria mostrar. Selecione membros úteis e mantenha-os consistentes com o código: um campo Java privado não deve aparecer com um símbolo público +, e um método estático precisa ser sublinhado.

Principais conclusões

  • Uma caixa de classe separa o nome, os atributos e as operações.
  • UML usa name: Type; Java usa Type name.
  • Use +, -, # e ~ para as visibilidades public, private, protected e de pacote.
  • Sublinhe membros estáticos; marque elementos abstratos com itálico ou {abstract}.
  • Uma caixa de interface mostra «interface» e as operações de seu contrato.

2.2 Leia as extremidades das linhas de relacionamento

Uma associação mostra uma ligação estrutural entre objetos. Em Java, um objeto geralmente mantém uma referência a outro em um campo. A linha também pode mostrar quantos objetos são permitidos e em que direção a referência pode ser seguida.

Conte a partir da extremidade oposta

A multiplicidade fica ao lado da classe que ela conta. Se 1..* aparece ao lado de Parcel e 1 ao lado de Shipment, uma remessa tem uma ou mais encomendas, e cada encomenda pertence a exatamente uma remessa. Leia cada extremidade separadamente, começando por um objeto na extremidade oposta.

MultiplicidadeSignificado
1Exatamente um
0..1Zero ou um
* ou 0..*Qualquer quantidade, incluindo zero
1..*Pelo menos um
2..4De dois a quatro

Uma referência única pode representar 1 ou 0..1; uma coleção representa uma extremidade com vários valores. Uma List Java, por si só, não impõe um limite mínimo. Uma Shipment com uma regra de 1..* encomendas deve rejeitar uma lista inicial vazia e impedir que operações posteriores a deixem vazia. Da mesma forma, uma referência única obrigatória precisa de uma verificação contra null.

Siga a referência

Uma ponta de seta aberta em Parcel significa que uma referência pode ser seguida de Shipment até Parcel. Um diagrama de classes pode deixar a navegabilidade não especificada. Na convenção didática usada aqui, setas marcam referências em uma só direção; uma linha sem marcação pode significar acesso bidirecional ou direção não especificada. Não deduza um campo de volta apenas de uma seta em direção a Parcel.

Um nome de papel como parcels identifica o significado da ligação a partir da remessa. Mostre essa referência como uma associação ou como - parcels: List<Parcel> na caixa. Exibir ambas sem motivo pode fazer um campo parecer dois elementos de estado diferentes.

Escolha o significado de todo–parte

A composição usa um losango preenchido no todo. Uma parte pertence a no máximo um todo composto por vez e normalmente é removida com esse todo, como um pedido e suas linhas de pedido. A agregação usa um losango vazado para um agrupamento mais flexível cujas partes podem existir independentemente. Uma equipe pode agrupar jogadores sem controlar seu tempo de vida.

Java não tem uma palavra-chave de losango e não impõe a propriedade UML apenas porque existe uma lista. Modele composição apenas quando o projeto incluir essa regra de propriedade. Quando o projeto diz apenas que um objeto conhece outro, uma associação simples é mais clara que um losango sem fundamento.

Mostre um uso sem afirmar propriedade

Uma dependência é uma linha tracejada com uma ponta de seta aberta em direção ao tipo usado. Uma calculadora com feeInCents(Parcel parcel) depende de Parcel porque a assinatura de seu método usa esse tipo. Uma dependência é uma relação geral de uso; ela não se limita a código sem campos. Uma associação fornece informações estruturais mais específicas quando um campo é importante para o modelo.

Antes de desenhar uma linha, pergunte o que o código ou o requisito estabelece: uma referência, uma quantidade permitida, um uso ou propriedade. Uma coleção estabelece armazenamento de vários valores, mas por si só não estabelece propriedade exclusiva nem uma quantidade mínima.

Principais conclusões

  • A multiplicidade ao lado de uma classe conta seus objetos para um objeto na outra extremidade.
  • Um tipo de coleção não impõe uma multiplicidade mínima ou máxima.
  • Uma seta de associação aponta para o tipo que a origem pode acessar por navegação.
  • Coloque um losango de composição ou agregação na extremidade do todo.
  • Uma dependência é uma seta tracejada de uso; uma associação descreve uma ligação estrutural.
  • Mostre uma referência uma vez, a menos que o diagrama precise deliberadamente das duas representações.

2.3 De um diagrama para declarações Java

Herança e implementação de interface conectam um tipo específico a um mais geral. UML usa um triângulo vazado no tipo geral, mas o estilo da linha distingue as duas relações. Essa distinção indica qual declaração Java escrever.

Leia o triângulo e sua linha

Uma linha contínua com um triângulo vazado em Item significa que uma subclasse como Letter estende Item. Isso é generalização. Uma linha tracejada com um triângulo vazado em ParcelRepository significa que uma classe como InMemoryParcelRepository implementa essa interface. Isso é realização.

O triângulo sempre toca a superclasse ou a interface. Uma seta aberta tracejada, sem o triângulo, indica dependência. O estilo da linha e a ponta da seta juntos transmitem o significado; uma linha tracejada sozinha não basta.

Construa o esqueleto em uma ordem útil
  1. Crie cada classe ou interface em seu pacote.
  2. Traduza atributos em campos, preservando nomes, tipos e visibilidade.
  3. Traduza associações navegáveis em campos de referência ou de coleção.
  4. Adicione extends ou implements a partir das linhas de generalização ou realização.
  5. Traduza operações em cabeçalhos de métodos e depois forneça seus corpos.

Suponha que uma interface ofereça save e findAll. Uma implementação tem uma associação de vários valores com Parcel, com o papel parcels:

public class InMemoryParcelRepository implements ParcelRepository {
    private List<Parcel> parcels = new ArrayList<>();

    @Override
    public void save(Parcel parcel) {
        parcels.add(parcel);
    }

    @Override
    public List<Parcel> findAll() {
        return new ArrayList<>(parcels);
    }
}

O triângulo com linha tracejada fornece implements ParcelRepository. A associação fornece o campo de lista. As assinaturas das operações fornecem os dois métodos públicos. Os corpos dos métodos são uma escolha de implementação: aqui save acrescenta um objeto, e findAll retorna uma nova lista para que quem chama não possa adicionar ou remover entradas diretamente na lista armazenada. Os próprios objetos de encomenda continuam compartilhados.

Desenhe no sentido inverso

Comece com uma caixa para cada classe ou interface. Um campo de valor simples torna-se um atributo. Uma referência a uma classe do programa torna-se uma associação; uma coleção sugere uma extremidade com vários valores. Examine as verificações de construtores e métodos antes de afirmar que o limite mínimo é um. Traduza extends em uma linha contínua com triângulo e implements em uma linha tracejada com triângulo.

Campos e operações herdados geralmente ficam na caixa da superclasse. Coloque membros novos ou sobrescritos na subclasse. Adicione uma dependência para um parâmetro, tipo de retorno ou uso local quando essa relação ajudar a explicar o projeto.

Pacotes podem envolver suas classes em caixas com formato de pasta e uma pequena aba. Esses grupos ajudam a revelar quais áreas dependem de quais outras. Mantenha atualizado um diagrama de documentação quando o código mudar; identifique um esboço inicial de planejamento para que quem lê compreenda seu objetivo.

Uma Shipment que contém encomendas não é um tipo de Parcel. Sua lista fornece uma relação de “tem”, portanto desenhe uma associação. Use herança para uma relação de “é um tipo de” que o sistema de tipos e o comportamento Java realmente sustentem.

Principais conclusões

  • A generalização usa uma linha contínua e um triângulo vazado na superclasse.
  • A realização usa uma linha tracejada e um triângulo vazado na interface.
  • Traduza campos, associações, herança e assinaturas de métodos antes de escrever os corpos.
  • Preserve os tipos e a visibilidade do diagrama ao traduzi-lo.
  • O tipo declarado de uma coleção, por si só, não estabelece uma quantidade mínima.
  • Use associações para objetos contidos e herança para relações entre subtipos.
  • Mantenha os diagramas de documentação consistentes com o código que descrevem.
3

Estruture um programa em camadas

Um programa fica mais fácil de testar e alterar quando seu diálogo, suas regras e seu armazenamento têm responsabilidades separadas. Você acompanhará um comando de encomenda por essas partes, moverá os cálculos para fora do código de console e passará colaboradores por construtores. Interfaces permitem que a lógica trabalhe com armazenamento real ou com um substituto em memória, enquanto uma leitura cuidadosa das importações revela os tipos dos quais cada camada depende.

3.1 Mantenha o diálogo fora dos cálculos

A interface do usuário lê comandos e exibe resultados. A lógica da aplicação contém as regras e os dados sobre os quais esses comandos atuam. Separá-las permite que um teste chame um cálculo diretamente e que outra interface o reutilize sem copiar a regra.

Dê a cada parte uma tarefa clara

TextUI cuida da interação: pede um peso, lê uma linha com Scanner, transforma o texto em um número, chama a lógica e exibe o resultado. ShippingCalculator recebe um peso em gramas e retorna uma tarifa em centavos. Ela não pergunta nada ao usuário nem exibe uma resposta.

Um método chamado printFee mistura essas tarefas se ele calcula e exibe o resultado. Altere a operação da lógica para feeInCents(int weightGrams). A interface pode então exibir "Fee: " + calculator.feeInCents(weightGrams) + " cents". Um teste pode verificar o inteiro retornado com uma asserção sem capturar a saída do console.

Escreva a regra como um valor de retorno

A regra de encomendas cobra 500 centavos para pesos positivos até 2.000 g, 900 centavos até 10.000 g e depois mais 100 centavos para cada bloco iniciado de 1.000 g acima de 10.000 g.

public int feeInCents(int weightGrams) {
    if (weightGrams <= 0) {
        throw new IllegalArgumentException("weight must be positive");
    }
    if (weightGrams <= 2000) {
        return 500;
    }
    if (weightGrams <= 10000) {
        return 900;
    }
    long excess = (long) weightGrams - 10000;
    long startedBlocks = (excess + 999) / 1000;
    return (int) (900 + 100 * startedBlocks);
}

Os dois primeiros ramos que retornam um valor incluem seus limites superiores. Para 12.500 g, o excesso é de 2.500 g. A divisão inteira após a adição de 999 conta três blocos iniciados, resultando em 1.200 centavos. O cálculo intermediário com long também lida com pesos positivos grandes do tipo int sem estouro ao somar 999.

Um peso inválido provoca uma IllegalArgumentException: o método rejeita um argumento que viola sua regra. A interface pode capturar essa exceção e escolher uma mensagem legível. A regra pertence à lógica; a redação da mensagem pertence à interface.

Conecte os objetos em main

Passe o leitor de entrada e a calculadora ao construtor da interface:

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    ShippingCalculator calculator = new ShippingCalculator();
    TextUI ui = new TextUI(scanner, calculator);
    ui.start();
}

Crie um scanner para o fluxo de entrada e compartilhe-o com a interface. Vários scanners sobre System.in podem interferir na entrada armazenada em buffer uns dos outros. O método main monta o programa; start executa o laço de diálogo.

Ao revisar uma classe, observe suas responsabilidades. Um Scanner ou System.out dentro de uma calculadora de tarifas revela uma mistura de entrada e saída com regras. Uma fórmula de tarifa dentro da interface revela o mesmo problema pelo outro lado. Mova o cálculo para um método com entrada explícita e um resultado; mantenha a interpretação de comandos e o texto de exibição juntos na interface.

Principais conclusões

  • A interface lê e exibe; métodos de lógica recebem valores e retornam resultados.
  • Mantenha regras de tarifa e validação na lógica para que os testes possam chamá-las diretamente.
  • Deixe a interface escolher como exibir um resultado ou uma entrada rejeitada.
  • Crie colaboradores em main e passe-os ao construtor da interface.
  • Conte um bloco de peso parcialmente usado como um bloco iniciado quando a regra exigir.

3.2 Conecte camadas por um repositório

Um programa em camadas agrupa classes por responsabilidade. A camada de apresentação cuida do diálogo, a camada de lógica aplica regras e a camada de acesso a dados salva e recupera informações. Uma classe usa outra camada por meio de uma operação clara, sem acessar diretamente seus campos internos.

Posicione as classes de encomendas

O pacote de interface contém TextUI. O pacote de domínio contém Parcel, ShippingCalculator e ParcelService. O pacote de dados contém ParcelRepository e suas implementações de armazenamento. Main monta os objetos acima dessas camadas.

Um comando segue este caminho: o usuário digita “add 1200 Alex”; a interface o interpreta e chama service.register(1200, "Alex"); o serviço valida o peso, cria uma encomenda, salva-a por meio do repositório e retorna 500 centavos; a interface exibe a tarifa. A leitura de texto, a decisão sobre o que é permitido e o armazenamento do resultado têm lugares separados.

Dependa de um contrato

O serviço precisa do comportamento de armazenamento, não de um formato de arquivo específico. Expresse esse comportamento por uma interface:

public interface ParcelRepository {
    void save(Parcel parcel);
    List<Parcel> findAll();
}

O serviço armazena uma referência do tipo ParcelRepository e a recebe no construtor:

public class ParcelService {
    private final ParcelRepository repository;

    public ParcelService(ParcelRepository repository) {
        this.repository = repository;
    }
}

Isso é injeção pelo construtor, uma forma de injeção de dependência: quem chama fornece um colaborador. Um serviço que constrói new FileParcelRepository(...) dentro de seus próprios métodos escolhe o armazenamento em arquivo para si e dificulta testes isolados.

Altere a implementação na montagem

O programa em execução pode criar new ParcelService(new FileParcelRepository("parcels.csv")). Um teste pode fornecer new InMemoryParcelRepository(). Ambas as implementações atendem à mesma interface. Chamadas por meio da interface usam polimorfismo para chegar aos métodos do objeto real.

Com o contrato do repositório inalterado, trocar o armazenamento exige uma nova implementação e uma alteração na montagem. As regras de tarifa e o código de diálogo não precisam saber como os dados são armazenados.

Verifique a direção das dependências com precisão

O caminho descendente de uso dos serviços vai da interface para a lógica e depois para o armazenamento. O domínio não deve importar nem chamar classes da interface. Se a lógica precisar informar um resultado ao usuário, retorne-o à interface. Se a interface salvar diretamente pelo repositório, ela poderá contornar a validação do serviço.

As referências a tipos Java também importam: uma assinatura de repositório que nomeia Parcel depende desse tipo de domínio. Com ParcelRepository no pacote de dados e o serviço no pacote de domínio, esses pacotes se referem um ao outro. Para remover esse ciclo de dependências entre pacotes, coloque a interface do repositório junto aos tipos de domínio e deixe que as implementações de dados a importem. O serviço continua usando o mesmo contrato; apenas sua localização muda. Examine tanto o caminho das chamadas de métodos quanto as importações ao verificar o projeto.

Programas maiores podem primeiro agrupar pacotes por funcionalidade, como pedidos ou clientes, e depois separar responsabilidades dentro de cada funcionalidade. Nomes de pacotes ajudam quem lê a localizar responsabilidades; parâmetros de construtores e assinaturas de métodos mostram as dependências reais.

Principais conclusões

  • Separe as responsabilidades de apresentação, lógica e acesso a dados.
  • Passe uma interface de repositório ao construtor do serviço.
  • Escolha uma implementação em arquivo ou em memória no ponto em que o programa é montado.
  • Encaminhe ações da interface pela lógica para que a validação seja aplicada.
  • Retorne resultados à interface em vez de chamar seu código a partir da lógica.
  • Distinga a direção de uso dos serviços das dependências de tipos Java ao verificar ciclos entre pacotes.
4

Verifique o código com testes JUnit

Testes automatizados transformam o comportamento esperado em verificações que você pode executar novamente após cada alteração. Escrever uma verificação é apenas o começo: entradas úteis expõem limites, conjuntos de teste novos mantêm os testes independentes e falhas legíveis ajudam você a encontrar a causa. Você criará testes Jupiter para a lógica de encomendas, controlará seus dados e colaboradores e usará um ciclo curto com testes primeiro para desenvolver uma regra, um caso por vez.

4.1 Um teste que verifica um resultado

Um teste unitário chama uma pequena unidade de código com dados de entrada escolhidos e verifica o resultado automaticamente. Ele torna um requisito executável. Executar novamente os testes após uma alteração ajuda a encontrar uma regressão, um defeito em um comportamento que funcionava antes.

Crie um teste Jupiter

JUnit Jupiter é o modelo de programação usado pelo JUnit 5 e 6. JUnit 6 precisa de Java 17 ou posterior. Anotações e asserções de teste usam org.junit.jupiter.api.

Coloque ShippingCalculatorTest.java em src/test/java/com/example/parcels/domain, com a mesma declaração de pacote da calculadora. O diretório raiz separado mantém os testes afastados do código do programa entregue, preservando o acesso ao mesmo pacote.

package com.example.parcels.domain;

import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;

class ShippingCalculatorTest {
    @Test
    void lightParcelCostsBaseFee() {
        ShippingCalculator calculator = new ShippingCalculator();

        int fee = calculator.feeInCents(1500);

        assertEquals(500, fee);
    }
}

A classe e o método de teste podem ter acesso de pacote. Um método de teste retorna void e não pode ser privado. Use o @Test do Jupiter; org.junit.Test do JUnit 4 pertence a outro modelo de programação e não é encontrado pelo mecanismo Jupiter. Asserções do JUnit 4 podem funcionar quando sua biblioteca está disponível, mas importações consistentes do Jupiter mantêm a configuração clara.

Leia preparar–agir–verificar

A primeira parte prepara uma calculadora. A segunda age pedindo a tarifa. A asserção final compara os 500 centavos esperados com o valor retornado. Linhas em branco tornam essas tarefas fáceis de identificar. Exibir apenas fee deixaria para uma pessoa a decisão sobre sua correção; a asserção torna essa decisão parte do teste.

assertEquals(expected, actual) coloca o requisito primeiro e o resultado observado em segundo. Uma mensagem opcional vem por último, como em assertEquals(500, fee, "fee for 1,500 g"). Inverter os dois primeiros argumentos geralmente mantém o resultado de aprovação ou falha para esses valores, mas inverte o significado do relatório de falha.

Escolha a asserção adequada

Use assertTrue ou assertFalse para um booleano e assertNull ou assertNotNull quando a presença de um objeto for o resultado. Verificações de igualdade de objetos usam equals; sem uma sobrescrita, dois objetos distintos com campos correspondentes ainda podem ser considerados diferentes.

Resultados em ponto flutuante precisam de uma tolerância apropriada. O cálculo 0.1 + 0.2 produz 0.30000000000000004 em um double Java:

assertEquals(0.3, 0.1 + 0.2, 1e-9);

O terceiro argumento permite uma diferença absoluta de até 0.000000001. Escolha uma tolerância adequada ao cálculo; uma tolerância excessivamente grande pode ocultar um erro significativo. Cálculos com centavos inteiros não precisam de tolerância decimal.

Leia uma falha

Execute um teste pela IDE ou pela ferramenta de construção. Um relatório como “expected: <500> but was: <900>” informa os resultados exigido e observado; o rastreamento da pilha aponta para a asserção. Uma exceção inesperada também faz um teste falhar. Verde significa que os casos verificados passaram; escolher esses casos continua sendo uma parte separada do teste.

Principais conclusões

  • Use anotações e asserções do Jupiter de org.junit.jupiter.api.
  • Coloque testes em src/test/java, normalmente no pacote da classe testada.
  • Prepare os objetos, aja sobre eles e verifique o resultado com uma asserção.
  • Coloque o valor esperado primeiro em assertEquals.
  • Use uma tolerância justificada para comparações em ponto flutuante.
  • Leia o nome do teste que falhou, os valores esperado e real e a localização da asserção.

4.2 Escolha entradas nos limites das regras

Um conjunto útil de testes verifica como a especificação divide as entradas em casos. Uma entrada comum raramente testa uma regra inteira. Casos de valor-limite no limite e imediatamente ao seu lado são especialmente bons para encontrar uma condição escrita com o operador relacional errado.

Divida o espaço de entradas

Uma classe de equivalência agrupa entradas que devem ser tratadas da mesma forma. A regra de envio tem pesos inválidos em zero ou abaixo, uma faixa de 500 centavos até 2.000 g, uma faixa de 900 centavos até 10.000 g e um cálculo por bloco iniciado acima disso. Escolha entradas representativas das classes e depois examine seus limites.

Peso em gramasResultado esperado
0, -5Rejeitar com IllegalArgumentException
1, 2000500 centavos
2001, 10000900 centavos
10001, 110001.000 centavos
110011.100 centavos

Alterar <= 2000 para < 2000 afeta exatamente 2.000 g. Um teste com 1.500 g continua passando. Acima de 10.000 g, teste tanto um bloco inteiro quanto o primeiro grama do bloco seguinte para verificar o arredondamento para cima.

Os valores esperados vêm do requisito e do cálculo manual. Para 12.500 g, o excesso é de 2.500 g, exigindo três blocos iniciados, portanto o resultado esperado é 1.200 centavos. Executar o método e copiar sua resposta para o teste também copiaria qualquer defeito.

Teste entradas rejeitadas

Um teste de exceção fornece ao JUnit a operação a executar:

assertThrows(IllegalArgumentException.class,
    () -> calculator.feeInCents(0));

A expressão () -> ... é uma lambda: um pequeno trecho de código fornecido ao JUnit. JUnit o executa dentro da verificação. Chamar o método antes de fornecê-lo à asserção deixaria a exceção escapar antes que a verificação pudesse examiná-la.

assertThrows aceita o tipo de exceção nomeado ou uma subclasse. Ele falha se a operação terminar normalmente ou lançar um tipo incompatível. Ele retorna a exceção, portanto uma mensagem exigida pode ser verificada:

IllegalArgumentException error = assertThrows(
    IllegalArgumentException.class,
    () -> calculator.feeInCents(0));
assertEquals("weight must be positive", error.getMessage());
Dê um nome legível a cada cenário

Nomes como zeroWeightIsRejected e feeIsBaseFeeAtExactly2000Grams identificam o comportamento pretendido. Um teste pode ter várias asserções quando elas verificam o mesmo resultado. Após adicionar encomendas de 1.200 g e 800 g a uma remessa, verificar uma contagem de duas e um peso total de 2.000 g descreve esse único cenário. assertAll pode agrupar verificações para que todas as falhas sejam relatadas.

Verifique objetos alterados por meio de seu comportamento, como parcelCount() e totalWeightGrams(), em vez de expor campos privados para testes. Assim, uma refatoração pode alterar detalhes de armazenamento preservando as verificações. Cobertura completa de linhas significa que os testes executaram as linhas; ela ainda pode deixar passar um limite incorreto ou uma entrada inválida não testada.

Principais conclusões

  • Divida as entradas conforme a especificação e teste casos representativos e limites.
  • Verifique o próprio limite e valores próximos para detectar erros de uma unidade.
  • Calcule os resultados esperados independentemente da implementação.
  • Passe a operação a assertThrows como uma lambda.
  • Nomeie um cenário por teste; várias asserções relacionadas podem verificar esse cenário.
  • Teste o estado alterado por meio do comportamento observável por quem chama.

4.3 Testes que funcionam sozinhos e em qualquer ordem

Cada teste deve preparar tudo de que precisa. Um teste que só passa depois que outro adicionou dados depende de um histórico oculto. Esses testes tornam-se difíceis de confiar quando um executor altera a ordem ou executa apenas um método.

Crie um novo conjunto de teste

Um conjunto de dados e objetos de teste é o conjunto de objetos e dados que um teste usa. Coloque a preparação repetida de cada teste em @BeforeEach:

class ShipmentTest {
    private Shipment shipment;

    @BeforeEach
    void setUp() {
        shipment = new Shipment();
    }

    @Test
    void newShipmentIsEmpty() {
        assertEquals(0, shipment.parcelCount());
    }

    @Test
    void addingParcelIncreasesCount() {
        shipment.add(new Parcel(1200, "Alex"));
        assertEquals(1, shipment.parcelCount());
    }
}

A preparação é executada antes de cada método. O segundo teste não altera a remessa usada pelo primeiro. Por padrão, Jupiter também cria uma nova instância da classe de teste para cada método de teste. Campos estáticos mutáveis continuam compartilhados, portanto uma lista estática ainda pode transmitir estado entre essas instâncias.

@AfterEach faz a limpeza após cada teste. @BeforeAll e @AfterAll são executados uma vez para a classe e são estáticos no ciclo de vida padrão. Dados de referência compartilhados somente para leitura podem ser adequados para uma preparação feita uma vez por classe; objetos que mudam geralmente precisam ser criados novamente para cada teste. Os testes devem funcionar individualmente, sem depender da ordem no arquivo-fonte.

Use um corpo de teste para uma tabela

Um teste parametrizado executa a mesma verificação de comportamento para várias linhas:

@ParameterizedTest
@CsvSource({"1, 500", "2000, 500", "2001, 900",
            "10000, 900", "10001, 1000"})
void feeMatchesTable(int grams, int expected) {
    assertEquals(expected,
        new ShippingCalculator().feeInCents(grams));
}

Cada linha é uma invocação separada com seu próprio relatório. @ValueSource(ints = {0, -5}) fornece um inteiro por invocação para testes de entradas inválidas. Anotações de testes parametrizados vêm de org.junit.jupiter.params; CsvSource e ValueSource estão em seu pacote provider. A dependência agregadora junit-jupiter inclui o módulo de testes parametrizados.

@DisplayName adiciona um rótulo legível ao relatório. @Disabled registra um teste ignorado com um motivo; uma verificação ignorada não fornece evidência de aprovação para o comportamento que testaria.

Controle o ambiente

Um teste de serviço pode receber um InMemoryParcelRepository, uma implementação simplificada para testes funcional que armazena encomendas em memória. Isso evita o conteúdo de arquivos reais e isola a lógica. Passe um Scanner construído a partir de uma cadeia de caracteres conhecida a uma interface de texto ao testar seu tratamento de entrada. Uma data fixa ou um Random com semente fixa também torna previsível uma entrada externa.

Um teste instável muda seu resultado entre execuções sem uma alteração relevante no código. Dados mutáveis compartilhados, o horário atual, valores aleatórios não controlados e arquivos remanescentes são causas comuns. Dê ao teste controle dessas entradas. Mantenha métodos auxiliares pequenos e a preparação legível; um conjunto de teste que constrói muitos objetos sem relação pode revelar que a classe testada tem tarefas demais.

Principais conclusões

  • Use @BeforeEach para fornecer novos conjuntos de teste aos testes.
  • Uma nova instância da classe de teste não reinicia o estado estático mutável.
  • Testes parametrizados executam um método com linhas de entrada relatadas separadamente.
  • Use implementações simplificadas para testes e entradas fornecidas para manter testes de lógica independentes do armazenamento real e do relógio.
  • Mantenha cada teste executável sozinho, sem uma ordem obrigatória de métodos.

4.4 Desenvolva comportamento com vermelho, verde e refatorar

O desenvolvimento orientado por testes (TDD) começa uma mudança de comportamento com um teste. Um ciclo curto torna visível o comportamento ausente, implementa-o e depois melhora a estrutura do código. Uma lista de testes guarda os casos que ainda precisam ser explorados sem exigir que todos sejam escritos de uma vez.

Escolha um item da lista de testes

Para um verificador de anos bissextos gregorianos, liste quatro comportamentos: um ano comum não bissexto, um ano divisível por quatro, um ano de século que não é divisível por 400 e um ano de século que é divisível por 400. Esses casos representam partes diferentes da regra.

Vermelho: escreva um teste e execute-o. Um método ausente pode causar um erro de compilação. Quando o método existir, verifique se o teste falha para o comportamento que você pretende adicionar. Uma asserção ausente, a chamada do método errado ou um teste não encontrado podem tornar enganoso um resultado aparentemente verde. Um novo caso também pode já ser satisfeito pelo código existente; examine esse resultado antes de alterar a implementação.

Verde: adicione o código mais simples que faça o novo teste e todos os anteriores passarem. Refatorar: melhore a estrutura sem alterar o comportamento e execute novamente os testes após cada pequena alteração. Depois, escolha o próximo caso.

Deixe os casos desenvolverem a regra

A primeira verificação é assertFalse(LeapYears.isLeapYear(2023)). Criar o método com return false; faz esse caso passar. É um primeiro passo deliberado, não uma regra de calendário concluída.

A próxima verificação, assertTrue(LeapYears.isLeapYear(2024)), falha. Altere o método para return year % 4 == 0;. Ambos os casos agora passam.

Em seguida, assertFalse(LeapYears.isLeapYear(1900)) expõe a exceção dos anos de século. Alterar a expressão para year % 4 == 0 && year % 100 != 0 faz esses três testes passarem.

Por fim, assertTrue(LeapYears.isLeapYear(2000)) expõe a exceção à regra dos anos de século. A implementação fica:

public static boolean isLeapYear(int year) {
    return year % 400 == 0
        || (year % 4 == 0 && year % 100 != 0);
}

Execute as quatro verificações após a alteração. Cada nova falha apontou para uma parte ausente do comportamento, em vez de pedir que você depurasse uma implementação grande e inacabada.

Melhore a estrutura enquanto os testes estão verdes

Refatoração altera nomes ou organização preservando o comportamento. Renomear um método, extrair um método auxiliar, remover lógica repetida e mover uma classe para um pacote mais adequado são refatorações quando o comportamento observável permanece igual. Alterar a tarifa cobrada em um limite é uma mudança de comportamento e precisa de um teste que descreva essa alteração.

Se uma limpeza fizer um teste falhar, restabeleça o comportamento pretendido antes de adicionar outra funcionalidade. Pequenas alterações ajudam a localizar a causa de uma falha. Pular a limpeza deixa código que passa nos testes, mas se torna mais difícil de ampliar; escrever uma implementação grande para muitos casos futuros cria mais código para examinar quando a próxima verificação falhar.

TDD depende da qualidade dos requisitos e dos casos selecionados. Uma regra mal compreendida pode aparecer tanto no teste quanto na implementação. A lógica com entradas e saídas claras é um ponto de partida útil porque o resultado esperado pode ser declarado independentemente e verificado rapidamente.

Principais conclusões

  • Mantenha uma lista de testes e implemente um comportamento por vez.
  • Vermelho torna visível o comportamento ausente; verde faz todos os testes atuais passarem.
  • Examine um teste que passou inesperadamente antes de supor que ele verificou a alteração pretendida.
  • Refatore a estrutura preservando o comportamento e execute novamente os testes.
  • Use casos sucessivos para expor regras comuns e exceções.
5

Construa e teste projetos com Maven ou Gradle

Um projeto precisa de mais do que arquivos-fonte: precisa de bibliotecas, configurações de compilação, um executor de testes e uma maneira de empacotar o resultado. Maven e Gradle descrevem essas escolhas para que um comando possa repetir a construção. Você usará suas pastas padrão, declarará dependências do Jupiter, executará o inicializador incluído e distinguirá um erro de código de um problema de descoberta de testes ou de dependência lendo a saída da construção.

5.1 Construa um projeto Maven a partir de sua descrição

Uma ferramenta de construção transforma arquivos-fonte em um resultado verificado e empacotado. Maven lê uma descrição do projeto, baixa bibliotecas declaradas, compila código, executa testes e cria um artefato. A mesma descrição fornece à linha de comando e à IDE uma visão compartilhada do projeto.

Use a estrutura padrão

Coloque o código Java de produção em src/main/java, os testes em src/test/java, os recursos de produção em src/main/resources e os dados de teste em src/test/resources. As pastas de pacotes começam abaixo de cada diretório raiz do código-fonte Java. Maven grava a saída gerada em target; edite os arquivos-fonte, não as cópias compiladas nessa pasta.

O arquivo pom.xml na raiz do projeto, o Project Object Model, identifica o projeto usando coordenadas: groupId, artifactId e version. Por exemplo, com.example:parcels:1.0-SNAPSHOT identifica uma versão de desenvolvimento do programa de encomendas. Uma dependência de biblioteca também tem coordenadas.

Declare JUnit para testes

A seguir está uma configuração Maven pequena e completa. Ela compila para Java 17 e seleciona um executor de testes compatível com Jupiter:

<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>parcels</artifactId>
  <version>1.0-SNAPSHOT</version>
  <properties>
    <maven.compiler.release>17</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>6.1.3</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.14.1</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.5.4</version>
      </plugin>
    </plugins>
  </build>
</project>

Maven obtém dependências de repositórios, normalmente o Maven Central, e as armazena em cache em .m2/repository dentro de sua pasta pessoal. Bibliotecas necessárias para uma dependência são dependências transitivas. O escopo de teste disponibiliza JUnit para compilar e executar testes, não para a compilação do código principal nem para o caminho de classes do programa em execução. Um JAR comum mantém os JARs de suas dependências separados, a menos que o empacotamento seja configurado para reuni-los.

Solicite uma fase do ciclo de vida

O ciclo de vida principal passa por compile, test, package e install, entre outras fases. Solicitar package também executa as fases anteriores. Com o inicializador, ./mvnw test compila e testa; ./mvnw package também cria o JAR em target; ./mvnw install adicionalmente coloca o artefato no repositório local. O ciclo de vida separado de limpeza, ./mvnw clean, remove a saída gerada.

Uma falha de teste interrompe a construção normal antes do empacotamento de um novo artefato. Um JAR antigo ainda pode estar presente até a limpeza, portanto sua presença não comprova que a construção mais recente foi bem-sucedida.

Torne visível a descoberta de testes

Os padrões do Surefire incluem por padrão nomes que começam com Test ou terminam com Test, Tests ou TestCase. ShippingChecks não corresponde a eles; use ShippingCalculatorTest e examine a quantidade de testes informada. Testes colocados em src/main/java não podem usar uma dependência com escopo de teste.

O Maven Wrapper, mvnw ou mvnw.cmd no Windows, executa a versão do Maven especificada pelo projeto. Use um projeto que inclua seus arquivos de inicialização; um POM sozinho não cria os scripts. A partir da raiz do projeto, o inicializador fornece um comando de construção consistente sem uma instalação separada do Maven.

Principais conclusões

  • Mantenha código principal, testes e recursos em suas pastas de origem padrão.
  • Identifique projetos e bibliotecas com coordenadas de grupo, artefato e versão.
  • Declare JUnit com escopo de teste e use um executor Surefire compatível.
  • Uma fase Maven solicitada executa as fases anteriores de seu ciclo de vida.
  • Examine as quantidades de testes; uma construção bem-sucedida ainda pode ter deixado de executar um teste com nome inadequado.
  • A saída gerada fica em target e pode ser reconstruída.
  • O inicializador executa a versão do Maven selecionada pelo projeto.

5.2 Execute testes Gradle e acompanhe erros de construção

Gradle descreve uma construção com um script e executa tarefas com suas dependências. Um script Kotlin chama-se build.gradle.kts; um script Groovy chama-se build.gradle. Plugins fornecem tarefas, e configurações de dependência indicam onde as bibliotecas são necessárias.

Configure uma aplicação e Jupiter

Um settings.gradle.kts na raiz do projeto pode conter rootProject.name = "parcels". Um script de construção Kotlin correspondente é:

plugins {
    application
}

repositories {
    mavenCentral()
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

dependencies {
    testImplementation(platform("org.junit:junit-bom:6.1.3"))
    testImplementation("org.junit.jupiter:junit-jupiter")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

application {
    mainClass = "com.example.parcels.Main"
}

tasks.test {
    useJUnitPlatform()
}

O plugin application fornece tarefas de construção Java e adiciona run. mavenCentral() seleciona um repositório de dependências. A BOM, ou lista de materiais, alinha as versões dos módulos JUnit, portanto a versão aparece uma vez. testImplementation fornece bibliotecas necessárias para compilar e executar testes. testRuntimeOnly fornece o inicializador apenas durante sua execução. Bibliotecas do código principal usam implementation, que também as disponibiliza para o código de teste.

useJUnitPlatform() seleciona a plataforma que executa testes Jupiter. Adicionar bibliotecas Jupiter sem configurar o executor pode deixar esses testes sem serem encontrados. O Gradle atual falha quando há arquivos-fonte de teste, mas nenhum teste é encontrado sob sua configuração padrão; examine a saída e a configuração da tarefa de teste em vez de remover a verificação.

Execute tarefas com o inicializador

Use o inicializador incluído no projeto: ./gradlew test executa testes; ./gradlew build compila, testa e empacota; ./gradlew run inicia a classe principal configurada; e ./gradlew clean remove build. No Windows, use gradlew.bat. Gradle usa a mesma estrutura de código-fonte principal e de teste que Maven e grava a saída gerada em build.

Uma tarefa marcada como UP-TO-DATE tem entradas e saídas monitoradas inalteradas, portanto Gradle reutiliza seu resultado anterior. Essa mensagem é diferente de uma execução de teste recém-realizada. O relatório HTML de testes está em build/reports/tests/test/index.html.

Encontre a etapa que falhou

Comece pelo primeiro erro útil. “Cannot find symbol” durante a compilação principal aponta para o código, a importação ou a dependência mencionados. A ausência de org.junit.jupiter.api durante a compilação dos testes aponta para a configuração da dependência de teste; o mesmo erro durante a compilação principal pode significar que um teste foi colocado na pasta de código-fonte principal. Um erro de resolução de dependência aponta para coordenadas, acesso ao repositório ou disponibilidade da rede.

Uma falha durante a execução de um teste é diferente: o teste foi compilado e encontrado, mas sua verificação falhou. Se “expected: <500> but was: <900>” aparecer no limite de tarifa de 2.000 g, examine a comparação que escolhe a faixa. Alterar dependências não corrigirá esse cálculo.

Maven oferece uma descrição declarativa em XML com fases fixas de ciclo de vida. Gradle oferece scripts e tarefas configuráveis, incluindo a possibilidade de pular trabalho atualizado. Ambos automatizam construções Java repetíveis; use a ferramenta escolhida para o projeto e leia a primeira falha que permita agir antes de alterar o código.

Principais conclusões

  • Plugins adicionam tarefas Gradle; configurações controlam onde as dependências ficam disponíveis.
  • Alinhe versões do JUnit com uma BOM e execute Jupiter na JUnit Platform.
  • Use o Gradle Wrapper incluído para tarefas de teste, construção, execução e limpeza.
  • Distinga resultados de tarefas em cache, erros de compilação, erros de dependência e falhas de asserção.
  • Leia o nome do teste que falhou e os valores esperado e real antes de alterar a implementação.

Exercícios resolvidos de Pacotes e testes unitários em Java

5 Lições Interativas

1

Mova uma classe de encomenda para um pacote

13 passos
Relacione um caminho de código-fonte, importações e verificações de acesso ao separar o código da interface do código de domínio.
  1. 1
    IntroduçãoDê à classe um lugar definido

    Um programa de encomendas tem uma calculadora e uma interface de texto. Quando essas classes são movidas para pacotes separados, Java precisa de nomes, pastas e níveis de acesso que correspondam entre si.

    Acompanhe a mudança em três etapas: posicione a classe, permita que outro arquivo a nomeie e depois verifique se quem a chama pode usar seu construtor e seus métodos.

  2. 2
    ExemploComece abaixo do diretório raiz do código-fonte

    O diretório raiz do código-fonte é src/main/java. Para Parcel em com.example.parcels.domain, transforme os pontos do pacote em pastas e acrescente o nome do arquivo:

    src/main/java/com/example/parcels/domain/Parcel.java
    

    Escreva package com.example.parcels.domain; no início do arquivo. As pastas src/main/java do diretório raiz descrevem a estrutura da construção e ficam fora da declaração de pacote.

  3. 3
    ExplicaçãoMantenha intacta a identidade da classe

    O nome simples é Parcel; o nome totalmente qualificado é com.example.parcels.domain.Parcel. Mover o arquivo, por si só, não reescreve essa identidade.

    Uma classe pública chamada Parcel deve ficar em Parcel.java. Execute uma classe principal em um pacote a partir da raiz da árvore compilada usando um comando como java -cp out com.example.parcels.Main, para que Java possa encontrar as pastas de pacotes dentro de out.

  4. 4
    DesafioCorrija uma localização incompatível

    Uma classe pública chamada Receipt declara package com.example.parcels.domain;, mas está armazenada em src/main/java/com/example/parcels/ui/. O pacote deve permanecer inalterado. Para onde o arquivo deve ir?

    ---reveal---

    Mova-o para src/main/java/com/example/parcels/domain/Receipt.java. O caminho do pacote começa abaixo do diretório raiz do código-fonte, e o nome do arquivo corresponde à classe pública. Alterar uma importação em outro lugar não corrigiria a estrutura do código-fonte.

  5. 5
    ExemploFacilite o uso do nome do tipo

    A interface tem sua própria declaração e depois importa o tipo de domínio:

    package com.example.parcels.ui;
    
    import com.example.parcels.domain.Parcel;
    

    O corpo de sua classe agora pode usar o nome simples Parcel. A importação se aplica apenas a esse arquivo. Ela resolve um nome; não copia uma classe nem disponibiliza uma dependência para a construção.

  6. 6
    Ideia-chaveUm curinga se limita a um pacote

    import com.example.parcels.*; não inclui o pacote de domínio. O prefixo compartilhado no nome parece uma árvore genealógica, mas cada nome completo de pacote designa um pacote separado.

    A mesma regra explica por que java.util.* não fornece java.util.function.Function. Use uma importação de tipo único, o curinga do pacote adequado ou um nome totalmente qualificado para esse tipo.

  7. 7
    DesafioResolva o conflito entre duas listas

    Um arquivo precisa de java.util.List e java.awt.List. Atualmente ele tem importações de tipo único para ambas, e a compilação falha. Como o arquivo pode nomear os dois tipos sem ambiguidade?

    ---reveal---

    Mantenha uma importação de tipo único, como import java.util.List;, e escreva java.awt.List por extenso onde esse tipo for necessário. Duas importações de tipo único diferentes não podem dar ao mesmo nome simples dois significados em um arquivo.

  8. 8
    DicaReconheça nomes que não precisam de importação

    Um arquivo pode usar tipos de seu próprio pacote sem importá-los. Tipos acessíveis de java.lang, incluindo String, Math e System, também estão disponíveis automaticamente.

    Uma importação estática tem outra função: importar Assertions.assertEquals estaticamente permite uma chamada direta a assertEquals(...). Ela encurta uma referência a membro estático, não um nome de pacote.

  9. 9
    ExemploVerifique também o construtor

    O tipo é público, mas seu construtor não tem modificador:

    public class Parcel {
        Parcel(int grams, String recipient) { }
    }
    

    Esse construtor tem acesso de pacote. A interface consegue resolver Parcel por meio da importação, mas não pode construí-lo a partir do pacote da interface. Marque o construtor como público se criar encomendas fizer parte das operações públicas pretendidas do domínio; mantenha seus campos privados.

  10. 10
    DesafioAbra a operação e mantenha a classe auxiliar oculta

    A interface precisa chamar o método feeInCents de uma calculadora pública, atualmente declarado sem modificador de acesso. Apenas a calculadora usa sua classe auxiliar WeightBands, que tem acesso de pacote. Qual alteração de acesso é necessária?

    ---reveal---

    Torne feeInCents público e deixe WeightBands com acesso de pacote. A interface precisa da operação de cálculo, não da classe auxiliar da tabela. Importar qualquer uma das classes não altera seus direitos de acesso.

  11. 11
    ExplicaçãoUma subclasse tem privilégios específicos

    Membros protegidos são acessíveis a todo o pacote que os declara e a subclasses segundo as regras de herança entre pacotes. Em outro pacote, uma subclasse pode usar seu próprio membro de instância herdado.

    Ela não pode usar uma referência declarada com o tipo da superclasse para acessar esse membro em um objeto arbitrário da superclasse. Portanto, o acesso protegido é mais amplo que o acesso exclusivo de subclasses, mas mais restrito que o acesso público.

  12. 12
    DesafioDiagnostique todo o caminho de acesso

    TextUI importa corretamente uma Parcel pública. Sua chamada a new Parcel(800, "Jo") informa que o construtor não pode ser acessado de fora do pacote. O construtor não tem modificador. O que corrige essa criação pretendida entre pacotes?

    ---reveal---

    Torne o construtor público. O pacote e a importação já resolvem o tipo, portanto mover o arquivo ou adicionar uma importação com curinga não resolve a falha de acesso informada. Os campos privados da encomenda podem continuar privados.

  13. 13
    RecapitulaçãoUse três verificações separadas

    Verifique a localização comparando a declaração com as pastas abaixo do diretório raiz do código-fonte. Verifique a nomenclatura por meio de importações ou nomes totalmente qualificados. Verifique o acesso ao tipo e à operação que quem chama realmente usa.

    Essa ordem ajuda a transformar um problema vago de “Java não consegue usar esta classe” em uma correção específica da estrutura do código-fonte, da resolução de nomes ou da visibilidade.

2

Interprete um modelo de encomendas e construa seu esqueleto Java

13 passos
Traduza caixas UML, extremidades de relações e linhas de subtipos em declarações Java consistentes.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoLeia a estrutura antes de escrever os corpos
  2. 2
    ExemploTraduza um campo e um método
  3. 3
    ExplicaçãoIdentifique um membro da classe
  4. 4
    DesafioPreserve os símbolos
  5. 5
    ExemploConte encomendas a partir de uma remessa
  6. 6
    ExemploEscolha uma representação e imponha a regra
  7. 7
    Ideia-chaveSiga uma ligação em uma só direção
  8. 8
    DesafioVerifique uma quantidade mínima
  9. 9
    ExplicaçãoColoque a propriedade na extremidade correta
  10. 10
    ExemploLeia duas linhas com triângulo
  11. 11
    DicaConstrua as declarações em ordem
  12. 12
    DesafioCombine um contrato e uma coleção
  13. 13
    RecapitulaçãoMantenha distinta a função de cada símbolo
3

Acompanhe um comando por responsabilidades separadas

11 passos
Mova um cálculo de tarifa para fora da interface e conecte um serviço a um armazenamento substituível.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoAcompanhe um comando pelo programa
  2. 2
    ExemploExtraia a regra do código de console
  3. 3
    ExemploCalcule um bloco iniciado
  4. 4
    Ideia-chaveMantenha a validação junto da regra
  5. 5
    DesafioTorne um cálculo reutilizável
  6. 6
    ExemploPasse o armazenamento ao serviço
  7. 7
    ConexãoSubstitua arquivos por uma implementação simplificada funcional
  8. 8
    DicaVerifique chamadas e importações separadamente
  9. 9
    DesafioCorrija um atalho que contorna o serviço
  10. 10
    DesafioConfigure um teste de serviço controlado
  11. 11
    RecapitulaçãoColoque cada alteração onde sua tarefa pertence
4

Desenvolva um conjunto útil de testes Jupiter

19 passos
Escreva uma primeira asserção, escolha limites, isole o estado e desenvolva uma regra por pequenas alterações com testes primeiro.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoTransforme requisitos em verificações executáveis
  2. 2
    ExemploPrepare, aja e verifique
  3. 3
    ExplicaçãoInterprete corretamente o esperado e o real
  4. 4
    DicaPermita o arredondamento numérico de forma deliberada
  5. 5
    DesafioCorrija um teste que apenas exibe o resultado
  6. 6
    ExemploEncontre as mudanças de tarifa
  7. 7
    Ideia-chaveMantenha o resultado esperado independente
  8. 8
    ExemploColoque a chamada rejeitada dentro da verificação
  9. 9
    DesafioEscolha o caso que revela a alteração
  10. 10
    ExemploDê a cada teste uma nova remessa
  11. 11
    ExemploExecute uma tabela com um único corpo de teste
  12. 12
    ConexãoControle o colaborador, não a ordem dos testes
  13. 13
    DesafioDiagnostique dados deixados por outro teste
  14. 14
    ExemploComece o ciclo com um ano comum
  15. 15
    ExemploAdicione a regra geral de divisibilidade por quatro
  16. 16
    ExemploUse casos de anos de século para refinar a condição
  17. 17
    DesafioEscolha o próximo passo preservando as verificações
  18. 18
    DesafioCombine seleção de casos e isolamento
  19. 19
    RecapitulaçãoConfie em evidências específicas e repetíveis
5

Leve um projeto do arquivo de construção ao relatório de testes

13 passos
Configure dependências e descoberta de testes, execute inicializadores e diagnostique falhas na etapa em que ocorrem.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoFaça um comando repetir a construção
  2. 2
    ExplicaçãoSepare arquivos-fonte da saída gerada
  3. 3
    ExemploNomeie o projeto e sua biblioteca de teste
  4. 4
    ExemploSolicite a fase necessária
  5. 5
    DicaNão confunda zero testes com comportamento verificado
  6. 6
    DesafioEncontre um teste na pasta errada
  7. 7
    ExplicaçãoSelecione as tarefas Gradle
  8. 8
    ExemploMantenha os módulos Jupiter alinhados
  9. 9
    ExemploInforme à tarefa de teste como executar Jupiter
  10. 10
    DesafioCorrija a descoberta de testes
  11. 11
    ExemploAcompanhe a falha de um teste executado
  12. 12
    DesafioCombine a descoberta com uma nova construção
  13. 13
    RecapitulaçãoInterprete a construção como evidência em etapas

Questões de Pacotes e testes unitários em Java

50 Questões de Prática em 5 conjuntos

Conjunto 1: Nomes, pastas e acesso em Java10 Questões de Prática

Questão 1 de 10

Uma classe pública Ticket declara package org.example.events.model;. Qual caminho ela deve ter abaixo do diretório raiz do código-fonte Java principal?
  1. org/example/events/Ticket/model.java
  2. org/example/events/model/Ticket.java
  3. src/main/java/org/example/events/model/Ticket.java
  4. org.example.events.model/Ticket.java
Mostrar respostaOcultar resposta
Resposta: B
Cada ponto torna-se uma pasta abaixo do diretório raiz do código-fonte, e o nome da classe pública fornece Ticket.java. O diretório raiz já é o ponto de partida, portanto adicionar src/main/java novamente duplicaria a parte do caminho relativa à estrutura de construção.

Questão 2 de 10

Uma classe no pacote shop.ui precisa de uma classe auxiliar que atualmente não tem declaração de pacote. Como a classe auxiliar deve ser organizada para que a interface possa importá-la?
  1. Deixe-a sem nome e adicione import Helper; ao arquivo-fonte da interface.
  2. Coloque-a em uma pasta chamada shop sem adicionar declaração de pacote.
  3. Mantenha-a sem nome e qualifique seu nome com o caminho do diretório raiz principal.
  4. Coloque-a em um pacote nomeado com declarações acessíveis.
Mostrar respostaOcultar resposta
Resposta: D
Uma classe em um pacote nomeado não pode importar um tipo do pacote sem nome. Dê à classe auxiliar uma declaração de pacote e uma localização correspondente e depois forneça o acesso de que quem a chama precisa; mover o arquivo por si só não lhe dá uma identidade de pacote nomeado.

Questão 3 de 10

Um arquivo tem import java.util.*; e usa java.util.function.Predicate por seu nome simples. Qual importação adicional de tipo único fornece esse nome?
  1. import java.util.function.Predicate;
  2. import java.util.Predicate;
  3. import java.util.function;
  4. import static java.util.function.Predicate;
Mostrar respostaOcultar resposta
Resposta: A
Predicate está no pacote separado java.util.function, portanto seu nome completo de tipo é necessário na importação de tipo único. O curinga existente java.util.* não alcança subpacotes, e uma importação estática é usada para membros estáticos.

Questão 4 de 10

Um arquivo importa java.util.* e java.awt.* e depois declara List<String> names. Como ele pode fazer List referir-se à interface de coleção mantendo as duas importações com curinga?
  1. Adicione import static java.util.List;.
  2. Substitua o tipo da declaração por Object.
  3. Adicione import java.util.List;.
  4. Mova os dois curingas para antes da declaração de pacote.
Mostrar respostaOcultar resposta
Resposta: C
Uma importação explícita de tipo único tem precedência sobre importações sob demanda, resolvendo List como java.util.List. As duas importações com curinga podem permanecer; uma importação estática não identificaria esse tipo de nível superior como membro estático.

Questão 5 de 10

Um teste Jupiter chama assertTrue(valid) sem o prefixo da classe Assertions. Qual importação disponibiliza essa chamada direta?
  1. import org.junit.jupiter.api.Assertions.assertTrue;
  2. import static org.junit.jupiter.api.Test.assertTrue;
  3. import org.junit.jupiter.api.Assertions;
  4. import static org.junit.jupiter.api.Assertions.assertTrue;
Mostrar respostaOcultar resposta
Resposta: D
assertTrue é um membro estático de Assertions, portanto uma importação estática permite chamá-lo diretamente. Importar apenas a classe permite Assertions.assertTrue(...), mas não remove o nome da classe proprietária da chamada.

Questão 6 de 10

book.data.Record é pública e tem um construtor declarado como Record(int id). Uma classe sem relação em book.ui a importa, mas não consegue construí-la. Qual alteração de acesso permite essa chamada?
  1. Torne o construtor público.
  2. Torne o construtor protegido.
  3. Importe também book.data.*.
  4. Renomeie o pacote da interface para book.data.ui.
Mostrar respostaOcultar resposta
Resposta: A
O modificador omitido dá acesso de pacote ao construtor, e book.ui é um pacote diferente. O acesso público permite essa chamada por uma classe sem relação; o acesso protegido não permitiria, pois quem chama não está no pacote que declara o construtor nem usa privilégios de subclasse.

Questão 7 de 10

Uma classe auxiliar comum declarada como class RateTable não tem modificador de acesso. Onde outras classes de nível superior podem usá-la?
  1. Em pacotes que compartilham seu nome de nível superior.
  2. Apenas no pacote exato em que foi declarada.
  3. Em qualquer pacote após uma importação explícita.
  4. Apenas em subclasses, em qualquer pacote.
Mostrar respostaOcultar resposta
Resposta: B
Uma classe de nível superior sem public tem acesso de pacote, o que restringe seu uso ao mesmo pacote exato. Importações resolvem nomes sem conceder permissão, portanto uma importação explícita de outro pacote não pode abrir o acesso a essa classe auxiliar.

Questão 8 de 10

Uma caixa de classe contém # threshold: int e um + reset(): void sublinhado. Como esses membros são traduzidos para Java?
  1. Um campo inteiro privado e um método reset público abstrato.
  2. Um campo inteiro com acesso de pacote e um método reset público estático.
  3. Um campo inteiro protegido e um método reset público estático.
  4. Um campo inteiro protegido e um método reset público de instância.
Mostrar respostaOcultar resposta
Resposta: C
O símbolo # indica visibilidade protegida, enquanto o sublinhado marca um membro estático. O sinal de mais torna reset público e seu tipo de retorno é void; o sublinhado não significa abstrato, que usa itálico ou uma marcação de abstrato.

Questão 9 de 10

Qual operação UML descreve public boolean accepts(int grams)?
  1. + boolean accepts(int: grams)
  2. - accepts(grams: int): boolean
  3. + accepts(grams: boolean): int
  4. + accepts(grams: int): boolean
Mostrar respostaOcultar resposta
Resposta: D
UML mantém o nome da operação primeiro, escreve cada parâmetro com nome seguido de tipo e coloca o tipo de retorno após os parênteses. O sinal de mais preserva a visibilidade pública; inverter os tipos de parâmetro e resultado descreveria um método diferente.

Questão 10 de 10

O pacote base tem a classe Engine com acesso de pacote. Seu construtor declarado explicitamente e seu método run() não têm modificador de acesso. Uma Screen sem relação em base.ui precisa chamar new Engine().run(). Quais são as alterações mínimas de visibilidade que permitem essa chamada?
  1. Torne Engine pública; mantenha o restante com acesso de pacote.
  2. Torne Engine e run públicos, não o construtor.
  3. Torne Engine, seu construtor e run públicos.
  4. Torne apenas o construtor e run públicos.
Mostrar respostaOcultar resposta
Resposta: C
A interface precisa acessar o tipo, chamar seu construtor e invocar seu método. Cada um tem atualmente acesso de pacote, e base.ui é um pacote separado, portanto os três precisam de acesso público para essa classe sem relação. Abrir apenas o tipo e o método ainda deixa a construção bloqueada.
Conjunto 2: Relações entre classes e responsabilidades em camadas10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

Em uma associação entre biblioteca e livro, 0..* está escrito ao lado de Book. A quantos livros uma biblioteca pode estar ligada?
  1. A pelo menos um livro.
  2. A exatamente um livro.
  3. A zero ou mais livros.
  4. A zero ou um livro.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Um curso deve ter uma ou mais sessões. Seu campo List<Session> começa vazio, e nenhum método verifica seu tamanho. O que falta na implementação?
  1. Verificações que estabeleçam e preservem a quantidade mínima de sessões.
  2. Uma importação de lista diferente para que a coleção imponha um mínimo.
  3. Um campo estático de sessão compartilhado por cada instância de curso.
  4. Um campo de lista protegido que permita às sessões preenchê-lo diretamente.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Um modelo de pasta agrupa documentos que podem ser compartilhados e continuar existindo depois que a pasta é removida. O requisito sustenta uma composição dos documentos por essa pasta?
  1. Sim; toda coleção de referências estabelece propriedade por composição.
  2. Sim; uma pasta é o todo, portanto o tempo de vida dos documentos não importa.
  3. Não; composição é reservada a classes sem campos de coleção.
  4. Não; documentos compartilhados e independentes não têm um proprietário composto.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Uma classe tem uma linha UML tracejada que termina em um triângulo vazado em Printable. Qual declaração Java de subtipo essa linha representa?
  1. extends Printable
  2. implements Printable
  3. imports Printable
  4. new Printable()
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Basket contém private List<Product> products; e não estende Product. Como a relação principal deve ser representada?
  1. Uma associação em direção a Product, com vários valores na extremidade de Product.
  2. Uma linha contínua de generalização a partir de Basket, com triângulo em Product.
  3. Uma linha tracejada de realização a partir de Basket, com triângulo em Product.
  4. Uma associação navegável de Product para Basket, com vários valores na extremidade de Basket.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Uma calculadora de notas calcula uma média e a exibe. Uma interface web precisa do cálculo. Qual alteração torna essa lógica diretamente reutilizável?
  1. Copiar o cálculo para a interface web.
  2. Adicionar um formato de exibição web à calculadora.
  3. Retornar a média e deixar que as interfaces a formatem.
  4. Fazer a calculadora ler primeiro o nome da interface.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Uma interface de texto rejeita entradas não numéricas antes de chamar uma calculadora. A calculadora também deve rejeitar valores numéricos negativos para todos os chamadores. Onde essa regra deve ficar?
  1. Apenas na interface de texto, junto da interpretação numérica.
  2. Na calculadora, com as mensagens tratadas pela interface.
  3. No método main, depois de criar a calculadora.
  4. No repositório, depois de armazenar o resultado calculado.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

O construtor de um serviço aceita uma interface CatalogRepository. Como um teste pode evitar o banco de dados real?
  1. Passar um nome de arquivo de banco de dados à implementação real existente.
  2. Fazer o construtor do serviço criar o repositório de banco de dados.
  3. Mover o serviço para o pacote de apresentação para o teste.
  4. Injetar uma implementação de CatalogRepository em memória.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Uma tela de reservas salva diretamente por um repositório. O serviço contém uma verificação do limite de lugares e cria seu próprio repositório de arquivos. Os testes precisam verificar esse limite sem acesso ao disco. Qual par de alterações atende à tela e aos testes?
  1. Encaminhar solicitações da tela pelo serviço e injetar o repositório nele.
  2. Manter o salvamento direto da tela e injetar o repositório apenas nela.
  3. Encaminhar solicitações da tela pelo serviço e compartilhar um repositório estático de arquivos.
  4. Mover a verificação do limite para a tela e deixar que o serviço continue escolhendo arquivos.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

rules.OrderService importa storage.OrderRepository. Essa interface nomeia rules.Order em suas assinaturas, e implementações de armazenamento a implementam. Como o contrato pode ser realocado para remover o ciclo entre os pacotes rules e storage, mantendo o armazenamento intercambiável?
  1. Mover Order para storage ; OrderService importa um armazenamento concreto.
  2. Mover a interface para rules; storage implementa esse contrato.
  3. Mover OrderRepository para a interface do usuário; as duas camadas inferiores o importam.
  4. Manter a estrutura e substituir as importações por nomes totalmente qualificados.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Conjunto 3: Asserções, seleção de casos e testes independentes10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

Um método retorna actual, e seu resultado exigido é 42. Qual asserção de igualdade do Jupiter identifica corretamente os valores?
  1. assertEquals(actual, 42);
  2. assertTrue(actual);
  3. assertNotNull(42);
  4. assertEquals(42, actual);
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Um cálculo correto com double difere de 0,6 em cerca de 0,0000000000000001. A precisão numérica exigida é 0,000001. Como seu resultado esperado deve ser verificado?
  1. assertEquals(0.6, actual);
  2. assertEquals(0.6, actual, 1.0);
  3. assertEquals(0.6, actual, 1e-6);
  4. assertNotNull(actual);
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Uma regra de tamanho aceita inteiros de 1 a 500, inclusive. O 0 é uma entrada válida, uma entrada válida de valor-limite ou uma entrada inválida?
  1. Uma entrada inválida.
  2. Uma entrada válida de valor-limite.
  3. Uma entrada válida.
  4. Uma entrada não especificada.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Um sensor aceita leituras inteiras até 800, inclusive. Uma pessoa desenvolvedora altera sua verificação superior de reading <= 800 para reading < 800. Qual leitura válida revela o erro?
  1. 799
  2. 800
  3. 801
  4. 400
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Um método deve rejeitar uma cadeia de caracteres vazia com IllegalArgumentException. Como um teste Jupiter deve executar a chamada?
  1. assertThrows(IllegalArgumentException.class, () -> parser.parse(""));
  2. assertThrows(IllegalArgumentException.class, parser.parse(""));
  3. assertEquals(IllegalArgumentException.class, parser.parse(""));
  4. assertNotNull(() -> parser.parse(""));
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Uma tarifa cobra 250 centavos até 3.000 g e depois 50 centavos por bloco iniciado de 500 g acima disso. Sua implementação usa 250 + ((grams - 3000) / 500) * 50 acima do limite. Qual novo teste revela mais diretamente seu defeito de arredondamento?
  1. 3.500 g devem custar 300 centavos.
  2. 4.000 g devem custar 350 centavos.
  3. 3.001 g devem custar 300 centavos.
  4. 3.000 g devem custar 250 centavos.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Uma nova instância da classe de teste é criada para cada teste, mas uma lista estática mutável mantém entradas de um teste anterior. Qual preparação dá a cada cenário sua própria lista?
  1. Renomear os métodos de teste para forçar uma ordem alfabética.
  2. Usar uma lista não estática inicializada para cada teste.
  3. Criar uma lista em um método BeforeAll estático.
  4. Manter a lista estática e adicionar rótulos DisplayName.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Um teste de desconto lê a data atual e usa um repositório estático que contém clientes anteriores. Ele passa sozinho, mas muda de resultado entre execuções do conjunto. Como sua preparação deve ser reformulada?
  1. Manter a data de hoje e executar o teste primeiro em cada conjunto.
  2. Usar uma data fixa e reutilizar o repositório compartilhado preenchido.
  3. Recriar apenas o serviço, deixando as duas entradas externas inalteradas.
  4. Fornecer uma data fixa e um novo repositório para cada cenário.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Uma regra de tarifa tem oito linhas de entrada e resultado esperado que usam a mesma asserção. Como Jupiter pode manter casos relatados separadamente sem copiar oito corpos de métodos?
  1. Usar um teste parametrizado com as linhas como fonte de argumentos.
  2. Usar um teste que exibe cada resultado, mas verifica apenas o primeiro.
  3. Usar BeforeAll para executar o cálculo da tarifa uma vez para toda a classe.
  4. Usar Disabled nos métodos repetidos e manter um método ativo.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Todos os testes atuais passam. Você quer renomear um método auxiliar e extrair lógica repetida sem alterar nenhuma saída. Que atividade de TDD é essa?
  1. Adicionar um novo caso vermelho para outra funcionalidade.
  2. Alterar os resultados esperados para corresponder à saída do método auxiliar.
  3. Refatorar enquanto executa novamente os testes que passam.
  4. Pular os testes anteriores até que outro comportamento seja implementado.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Conjunto 4: Configuração de construção e relatórios de falhas10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

Onde um projeto Maven padrão coloca os arquivos-fonte Java de testes unitários?
  1. src/test/java
  2. src/main/java
  3. target/test-classes
  4. src/test/resources
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Um projeto usa JUnit apenas em testes. Qual escopo de dependência Maven o mantém disponível para compilação e execução de testes sem adicioná-lo aos caminhos de classes principais?
  1. compile
  2. test
  3. runtime
  4. provided
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Em um ciclo de vida Maven normal, sem configuração para pular etapas, o que acontece antes de a fase package criar um JAR?
  1. Apenas o código principal é compilado; os testes são executados separadamente.
  2. Um JAR instalado mais antigo é copiado para o código-fonte.
  3. Os códigos principal e de teste são compilados, e os testes são executados.
  4. O ciclo de vida clean sempre exclui a saída primeiro.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Um projeto Maven com Java 17 declara apenas JUnit Jupiter 6.1.3, mas configura Surefire 2.12.4. Seu arquivo de teste TotalsChecks.java importa org.junit.Test. Os padrões de inclusão do Surefire não foram alterados. Qual conjunto de correções permite que a classe seja compilada e executada como um teste Jupiter descoberto?
  1. Renomeá-la para TotalsTest e usar @Test do Jupiter, mas manter Surefire 2.12.4.
  2. Manter TotalsChecks, usar @Test do Jupiter e selecionar Surefire 3.5.4.
  3. Renomeá-la para TotalsTest e selecionar Surefire 3.5.4, mas manter org.junit.Test.
  4. Renomeá-la para TotalsTest, usar @Test do Jupiter e selecionar Surefire 3.5.4.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Qual é o nome usual de um arquivo de script de construção Gradle escrito em Kotlin?
  1. settings.gradle.kts
  2. build.gradle.kts
  3. build.gradle
  4. pom.xml
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Um projeto Gradle tem testes Jupiter e declara suas bibliotecas. Qual chamada dentro da tarefa de teste seleciona sua plataforma de execução?
  1. mavenCentral()
  2. testImplementation()
  3. mainClass.set()
  4. useJUnitPlatform()
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Uma BOM do JUnit é declarada com platform(...) do Gradle. Por que as declarações de dependências JUnit relacionadas podem omitir números de versão individuais?
  1. O compilador Java infere a versão instalada mais recente de cada módulo.
  2. O inicializador substitui cada versão de dependência por sua versão do Gradle.
  3. A BOM fornece restrições de versão alinhadas para os módulos JUnit.
  4. A tarefa de teste baixa qualquer módulo cujo nome de classe apareça em um teste.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Um projeto Gradle não consegue compilar um teste porque org.junit.jupiter.api está ausente. Sua única declaração do Jupiter é testRuntimeOnly("org.junit.jupiter:junit-jupiter"), e a tarefa de teste já usa a JUnit Platform. Qual correção de configuração resolve essa etapa?
  1. Declarar Jupiter com testImplementation para que a compilação de testes veja sua API.
  2. Adicionar outra chamada a useJUnitPlatform sem alterar a dependência.
  3. Mover o teste para o código-fonte principal e manter Jupiter apenas em execução.
  4. Manter Jupiter apenas em execução e adicionar uma anotação DisplayName ao teste.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Um teste de serviço reutiliza um repositório estático de arquivos. Ele passa na IDE após uma reinicialização, mas falha no conjunto completo do Gradle porque testes anteriores armazenaram registros extras. O serviço já aceita uma interface de repositório. Qual correção isola seu comportamento?
  1. Injetar um novo repositório em memória preparado apenas com os registros desse teste.
  2. Usar BeforeAll para compartilhar o mesmo repositório de arquivos em todo o conjunto.
  3. Executar Gradle clean e manter as suposições sobre os dados do repositório compartilhado.
  4. Tornar o teste público e depender da ordem atual de execução do conjunto completo.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Uma nova verificação de TDD passa imediatamente, embora você pretendesse que ela revelasse um comportamento ausente. Qual é a próxima investigação adequada?
  1. Adicionar código de produção até que o teste que passa comece a falhar.
  2. Confirmar que ela é executada, verifica o resultado pretendido e pode já estar satisfeita.
  3. Excluir a asserção porque um resultado positivo prova que o novo comportamento está ausente.
  4. Refatorar classes sem relação antes de verificar qual método o teste chama.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Conjunto 5: Diagnostique uma alteração Java completa10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

Um teste em execução informa “expected: <18> but was: <21>” em uma asserção de igualdade com a ordem correta. Qual valor o método retornou?
  1. 18
  2. 3
  3. 21
  4. 39
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Uma associação mostra 1 ao lado de Department e 0..* ao lado de Employee. O que o modelo permite para um departamento e exige para um funcionário?
  1. Um departamento exige um funcionário; cada funcionário pode não ter departamentos.
  2. Um departamento pode não ter funcionários; cada funcionário tem exatamente um departamento.
  3. Um departamento exige muitos funcionários; cada funcionário tem exatamente um departamento.
  4. Um departamento pode não ter funcionários; cada funcionário pode ter muitos departamentos.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Um método retorna uma lista contendo três valores especificados, mas o tipo da coleção interna pode mudar. Como o teste deve verificar o resultado?
  1. Ler o vetor interno privado e verificar sua capacidade exata.
  2. Verificar se sua classe de coleção concreta corresponde à atual.
  3. Exibir os elementos e deixar que o executor os compare automaticamente.
  4. Comparar o conteúdo retornado com os três valores exigidos.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

A classe base.Item tem um campo de instância protegido weight. Em outro pacote, Special extends Item tem um método de instância com os parâmetros Item a e Special b. Dentro desse método, quais expressões podem acessar diretamente o campo herdado?
  1. this.weight e b.weight
  2. this.weight e a.weight
  3. a.weight e b.weight
  4. this.weight, a.weight e b.weight
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Um diagrama mostra MemoryArchive realizando a interface Archive e mantendo um ou mais objetos Entry por meio de uma associação navegável. Uma classe gerada atualmente declara extends Archive e cria uma lista vazia sem verificações. Qual conjunto de correções preserva o modelo?
  1. Usar implements Archive, mas manter a lista vazia sem verificações, pois List impõe um ou mais.
  2. Usar implements Archive e rejeitar entradas vazias no construtor, mas permitir a remoção da última entrada.
  3. Usar implements Archive e manter a coleção de entradas não vazia na construção e nas atualizações.
  4. Usar implements Archive e substituir a coleção por uma referência opcional a Entry.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Um serviço de relatórios importa formatadores da interface do usuário e cria seu próprio armazenamento em arquivo. Seu cálculo é necessário para uma segunda interface e para testes sem acesso ao disco. Qual correção de projeto trata das duas dependências?
  1. Retornar texto formatado para o console e deixar o serviço continuar construindo o armazenamento em arquivo.
  2. Mover o armazenamento em arquivo para o pacote da interface e manter as chamadas aos formatadores no serviço.
  3. Tornar estáticos o formatador e o armazenamento em arquivo do serviço para as duas interfaces os compartilharem.
  4. Retornar dados do relatório, manter a formatação nas interfaces e injetar um contrato de armazenamento.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Um verificador desenvolvido com TDD atualmente retorna year % 4 == 0 e passa nos testes de 2031 e 2032. Um novo teste para 2100 deve rejeitar esse ano, e um teste para 2400 deve aceitá-lo posteriormente. Qual sequência de alterações de comportamento atende aos casos pendentes?
  1. Excluir anos divisíveis por 400 para 2100 e depois voltar a aceitar os divisíveis por 100 para 2400.
  2. Excluir anos divisíveis por 100 para 2100 e depois voltar a aceitar os divisíveis por 400 para 2400.
  3. Excluir primeiro anos divisíveis por 100 e depois exigir divisibilidade por 400 para todo ano bissexto.
  4. Excluir primeiro anos divisíveis por 100 e depois permitir múltiplos de 400 apenas quando não forem divisíveis por 100.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Uma regra de embalagem rejeita pesos não positivos, cobra 400 centavos até 2.500 g e 700 centavos acima disso. Seus testes cobrem apenas 1.000 g. Uma alteração aceita zero acidentalmente e usa < 2500 para a faixa inferior. Qual par adicional de verificações revela os dois defeitos?
  1. Zero deve lançar uma exceção; 2.500 g devem retornar 400 centavos.
  2. Zero deve lançar uma exceção; 2.501 g devem retornar 700 centavos.
  3. 1 g deve retornar 400 centavos; 2.501 g devem retornar 700 centavos.
  4. O peso de −1 g deve lançar uma exceção; 2.499 g devem retornar 400 centavos.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Um script Gradle contém testRuntimeOnly("org.junit.jupiter:junit-jupiter:6.1.3") e tasks.test { }. Uma classe de teste Jupiter está armazenada em src/main/java, e sua anotação não compila. Quais alterações permitem que a classe seja compilada e executada no Jupiter?
  1. Mantê-la no código-fonte principal, usar testImplementation para Jupiter e selecionar a JUnit Platform.
  2. Movê-la para o código-fonte de teste, manter Jupiter apenas em execução e selecionar a JUnit Platform.
  3. Movê-la para o código-fonte de teste, usar testImplementation para Jupiter e manter o executor padrão.
  4. Movê-la para o código-fonte de teste, usar testImplementation para Jupiter e selecionar a JUnit Platform.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Uma execução normal de package do Maven para em um teste que falhou, mas um JAR de ontem permanece em target. O que esse arquivo comprova sobre o código-fonte de hoje?
  1. Ele comprova que o código-fonte de hoje passou em seus testes.
  2. Ele comprova que o empacotamento ignorou a falha.
  3. Ele comprova apenas que existe um artefato mais antigo.
  4. Ele comprova que Maven recompilou o código-fonte de ontem.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Travou em uma questão sua? Ajuda com Tarefas

Termos-Chave

73 Termos-Chave, cada um com sua definição

A

Acesso de pacote
O acesso de pacote permite que um membro de classe comum ou tipo de nível superior seja usado apenas a partir do pacote exato em que foi declarado.
Uma classe auxiliar sem modificador público permanece indisponível para uma classe de interface em um pacote vizinho, mesmo com uma importação.
Acesso protegido
O acesso protegido permite o uso dentro do pacote que declara o membro e o acesso herdado restrito por subclasses em outros pacotes.
Uma subclasse em outro pacote pode ler seu próprio campo protegido herdado sem torná-lo público.
Agregação
Agregação é um agrupamento flexível de todo–parte, mostrado por um losango vazado, cujo significado detalhado depende do projeto modelado.
Uma equipe pode agrupar jogadores que também existem independentemente, sem assumir a propriedade de seus tempos de vida.
Agrupamento por pacote
Um agrupamento por pacote é uma área em formato de pasta em um diagrama UML que organiza caixas de tipos conforme os pacotes aos quais pertencem.
Agrupar separadamente classes de interface e de domínio facilita o exame de referências entre essas áreas.
Asserção
Uma asserção em um teste é uma verificação executável que faz o teste falhar quando o resultado observado não atende ao esperado declarado.
Uma asserção de igualdade faz uma tarifa incorreta falhar automaticamente em vez de apenas deixá-la na saída exibida.
Asserção de exceção
Uma asserção de exceção executa uma operação fornecida e verifica se ela lança o tipo de exceção exigido ou um subtipo aceito.
assertThrows pode verificar se um peso zero é rejeitado e retornar a exceção para uma verificação de mensagem.
Associação
Uma associação é uma relação estrutural que liga objetos de tipos diferentes, geralmente implementada por campos de referência armazenados.
A lista de encomendas de uma remessa pode ser desenhada como uma associação com o nome de papel parcels na extremidade da encomenda.
Atributo
Um atributo é uma propriedade nomeada mostrada em uma caixa de classe UML, geralmente correspondente a um campo Java com tipo e visibilidade.
A entrada - weightGrams: int representa um campo inteiro privado que armazena o peso da encomenda.

C

Camada
Uma camada agrupa classes com um tipo compartilhado de responsabilidade e oferece operações a outras partes por meio de um limite definido.
Um serviço de encomendas aplica a validação antes de pedir a uma camada de armazenamento que salve uma nova encomenda.
Camada de acesso a dados
A camada de acesso a dados implementa o salvamento e a recuperação de dados persistentes, mantendo detalhes de armazenamento separados das regras da aplicação.
Um repositório de arquivos cuida do formato do arquivo enquanto um serviço de encomendas cuida dos pesos válidos.
Camada de apresentação
A camada de apresentação traduz a interação do usuário em solicitações ao programa e apresenta os resultados retornados ao usuário.
Uma interface de texto pode formatar uma tarifa em uma frase sem colocar essa frase dentro do código que calcula a tarifa.
Caso de valor-limite
Um caso de valor-limite usa uma entrada no limite ou próxima do limite de uma regra especificada, onde uma pequena alteração pode mudar o comportamento esperado.
Quando uma faixa de tarifa termina em 2.000 g, testar exatamente 2.000 g revela uma comparação incorretamente estrita que um teste com 1.500 g não detectaria.
Ciclo de dependências entre pacotes
Um ciclo de dependências entre pacotes existe quando uma cadeia de dependências de tipos parte de um pacote e retorna a ele, ligando suas definições nas duas direções.
Colocar a interface do repositório junto aos tipos de domínio pode remover um ciclo de importações entre domínio e dados.
Ciclo de vida da construção
Um ciclo de vida da construção é a sequência ordenada de fases do Maven, na qual solicitar uma fase posterior executa as fases anteriores desse ciclo de vida.
Solicitar package normalmente executa a fase test antes de produzir o artefato empacotado do projeto.
Classe de equivalência
Uma classe de equivalência em testes é um grupo de entradas que a especificação trata da mesma forma, representado por casos de exemplo úteis e limites.
Vários pesos positivos na mesma faixa de peso compartilham uma tarifa, enquanto zero pertence a uma classe de entradas inválidas.
Composição
Composição é uma associação de todo–parte com propriedade exclusiva de uma parte por um todo composto por vez, mostrada por um losango preenchido no todo.
Um pedido e suas linhas podem usar composição quando remover o pedido também remove essas partes.
Configuração de dependência
Uma configuração de dependência define onde uma dependência Gradle está disponível, como na implementação principal, na compilação de testes ou na execução de testes.
Um inicializador da plataforma pode usar testRuntimeOnly porque os arquivos-fonte de teste não precisam dele durante a compilação.
Conflito de nomes
Um conflito de nomes ocorre quando um nome simples pode se referir a declarações conflitantes, exigindo que o código distinga o tipo pretendido.
Um arquivo que precisa de dois tipos chamados List pode importar um e usar o nome totalmente qualificado do outro.
Conjunto de dados e objetos de teste
Um conjunto de dados e objetos de teste é a coleção de objetos e dados preparados para um teste, que fornece o estado inicial controlado de que seu cenário precisa.
Uma nova remessa vazia antes de cada teste impede que adições de encomendas passem para outro cenário.
Contrato da interface
Um contrato da interface especifica operações que uma implementação oferece, permitindo que chamadores usem o tipo de interface sem escolher sua implementação.
Um serviço pode chamar save por meio do contrato do repositório, seja o armazenamento em memória ou em arquivo.
Coordenadas do artefato
As coordenadas do artefato identificam um projeto ou biblioteca Maven usando seu grupo, nome do artefato e versão, para que a construção possa resolver o artefato pretendido.
As coordenadas distinguem uma versão de biblioteca JUnit de outra sem copiar arquivos JAR manualmente.

D

Declaração de importação
Uma declaração de importação permite que um arquivo-fonte Java use um tipo ou membro estático por um nome mais curto sem alterar sua acessibilidade.
Uma classe de interface que importa Scanner pode usar o nome simples em sua declaração de campo em vez de java.util.Scanner.
Declaração de pacote
Uma declaração de pacote atribui os tipos de um arquivo-fonte a um pacote nomeado e aparece antes de suas importações e declarações de classes.
Escrever package com.example.parcels.domain; torna esse pacote parte do nome completo da classe.
Dependência
Uma dependência é uma relação de uso em que um elemento depende de outro, desenhada como uma linha tracejada com uma ponta de seta aberta.
Uma operação de tarifa que recebe um parâmetro Parcel cria uma dependência da definição do tipo de encomenda.
Dependência transitiva
Uma dependência transitiva é uma biblioteca exigida por outra dependência, resolvida pela ferramenta de construção junto com o artefato solicitado diretamente.
Declarar o agregador Jupiter também traz os módulos necessários para suas funcionalidades de teste.
Desenvolvimento orientado por testes
O desenvolvimento orientado por testes é um método de desenvolvimento que usa um teste que falha para orientar uma pequena alteração de comportamento antes de melhorar a estrutura do código.
Um caso de ano de século que falha orienta a próxima alteração em um verificador de anos bissextos em vez de uma grande reescrita.
Diagrama de classes UML
Um diagrama de classes UML modela a estrutura estática de software por meio de caixas de tipos, atributos, operações e relações entre tipos.
Um diagrama de encomendas mostra o que uma remessa armazena sem acompanhar a ordem dos comandos que um usuário digita.
Diretório raiz do código-fonte
Um diretório raiz do código-fonte é a pasta abaixo da qual começam os caminhos dos pacotes Java, como src/main/java para código de produção em uma construção padrão.
Para parcels.domain.Parcel, o caminho abaixo do diretório raiz do código-fonte é parcels/domain/Parcel.java.

E

Escopo da dependência
Um escopo da dependência controla quais caminhos de classes Maven incluem uma biblioteca, separando requisitos exclusivos de teste dos requisitos do programa principal.
O escopo de teste disponibiliza JUnit em src/test/java sem torná-lo uma dependência do código principal.
Expressão lambda
Uma expressão lambda fornece uma pequena operação como valor para que outro código possa decidir quando e como executá-la.
A lambda () -> calculator.feeInCents(0) permite que JUnit execute a chamada inválida dentro de sua asserção.

F

Ferramenta de construção
Uma ferramenta de construção automatiza tarefas do projeto, como resolver dependências, compilar código-fonte, executar testes e empacotar artefatos.
Um comando Maven pode compilar um projeto de encomendas, executar seus testes e produzir o JAR resultante.

G

Generalização
Generalização é uma relação UML de subtipo desenhada com uma linha contínua e um triângulo vazado apontando para o tipo mais geral.
Uma linha de Letter em direção a Item traduz-se em uma declaração Java na qual Letter estende Item.

I

Implementação simplificada para testes
Uma implementação simplificada para testes é uma implementação simples e funcional de uma dependência, usada em testes, que fornece comportamento utilizável com recursos controlados.
Um repositório em memória armazena encomendas para testes de serviço sem abrir ou alterar um arquivo real.
Importação com curinga
Uma importação com curinga disponibiliza sob demanda tipos acessíveis em um pacote, sem disponibilizar tipos em seus subpacotes.
Importar java.util.* ainda deixa java.util.function.Function precisando de sua própria importação ou nome completo.
Importação de tipo único
Uma importação de tipo único disponibiliza um tipo acessível por seu nome simples e declara explicitamente qual pacote o fornece.
A declaração import java.util.ArrayList; identifica exatamente qual ArrayList o arquivo usa.
Importação estática
Uma importação estática permite que um arquivo-fonte nomeie diretamente um membro estático acessível, omitindo o nome da classe que o declara.
Testes JUnit frequentemente importam assertEquals estaticamente para que cada verificação seja lida como uma chamada direta de asserção.
Independência dos testes
A independência dos testes significa que cada teste fornece seu próprio estado necessário e pode passar ou falhar corretamente quando executado sozinho ou em qualquer ordem.
Um teste de contagem cria suas próprias encomendas em vez de depender de um método anterior para adicioná-las.
Inicializador da ferramenta de construção
Um inicializador da ferramenta de construção é um lançador fornecido pelo projeto que obtém e executa a versão da ferramenta escolhida pelo projeto para comandos repetíveis.
Um inicializador Maven incluído no controle de versão permite que colaboradores executem a versão especificada do Maven com ./mvnw test.
Injeção de dependência
A injeção de dependência fornece os colaboradores de um objeto a partir de fora dele, permitindo que suas implementações ou entradas sejam selecionadas pelo chamador.
Passar um repositório ao construtor do serviço permite que o teste escolha armazenamento controlado.
Interface do usuário
Uma interface do usuário é a parte de um programa que aceita ações do usuário e apresenta resultados, como um diálogo de comandos de texto.
Uma interface de texto para uma calculadora de tarifas pede gramas, lê a resposta, chama a calculadora e exibe a tarifa retornada.
Interface pública do pacote
A interface pública do pacote é o conjunto de tipos e operações acessíveis que um pacote oferece deliberadamente ao código fora dele.
Um pacote de domínio pode expor uma calculadora de envio enquanto oculta a classe auxiliar que armazena suas faixas de peso.

J

JUnit Jupiter
JUnit Jupiter é o modelo de programação e mecanismo de testes do JUnit que fornece anotações, asserções e extensões modernas para testes Java.
Usar @Test do Jupiter mantém a anotação de teste consistente com o mecanismo selecionado pela construção.
JUnit Platform
A JUnit Platform é a base que encontra e inicia mecanismos de teste compatíveis, incluindo o mecanismo para testes Jupiter.
Gradle a seleciona com useJUnitPlatform() quando os testes do projeto usam anotações Jupiter.

L

Lista de materiais
Uma lista de materiais é um conjunto de metadados de dependência que alinha versões de módulos relacionados para que uma construção mantenha consistente uma família de bibliotecas.
A BOM do JUnit fornece versões correspondentes dos módulos depois que uma versão comum é selecionada.
Lista de testes
Uma lista de testes registra comportamentos e casos que ainda precisam ser cobertos, permitindo que a pessoa desenvolvedora selecione um pequeno próximo passo e adicione descobertas conforme necessário.
Uma lista de anos bissextos inclui anos comuns, anos divisíveis por quatro e os dois casos de anos de século.
Lógica da aplicação
A lógica da aplicação é o código que implementa as regras e operações de um programa sobre dados, independentemente de seu diálogo com o usuário.
Uma calculadora de tarifas escolhe a faixa de tarifa correta sem pedir dados de entrada ou escrever no console.

M

Membro estático
Um membro estático pertence a uma classe em vez de a um objeto individual e é mostrado pelo sublinhado de sua entrada de atributo ou operação UML.
Sublinhar uma constante de peso máximo distingue um valor compartilhado da classe do peso de cada encomenda.
Método de ciclo de vida
Um método de ciclo de vida é um método que o framework de testes executa em um ponto definido, como antes de cada teste ou uma vez antes de toda a classe.
@BeforeEach recria objetos que mudam, enquanto @AfterEach pode remover recursos temporários de teste.
Modificador de acesso
Um modificador de acesso é uma palavra-chave que controla qual código pode usar uma declaração, como uma classe, construtor, campo ou método.
Uma classe pública ainda pode ter um construtor que uma classe de interface em outro pacote não consegue chamar.
Montagem dos objetos
A montagem dos objetos é a criação e conexão de objetos colaboradores do programa, geralmente realizada perto do ponto de entrada da aplicação.
Um método main cria um scanner e uma calculadora, passa-os a um objeto de interface e inicia seu diálogo.
Multiplicidade
Multiplicidade indica a quantidade permitida de objetos ligados em uma extremidade de uma relação para um objeto na extremidade oposta.
Escrever 1..* ao lado de Parcel exige que cada remessa mantenha pelo menos uma encomenda ligada.

N

Navegabilidade
Navegabilidade descreve se um objeto pode seguir uma relação em direção a outro objeto, frequentemente mostrada com uma ponta de seta aberta.
Uma seta da remessa para a encomenda mostra que uma remessa pode alcançar suas encomendas por meio de referências.
Nome totalmente qualificado
Um nome totalmente qualificado identifica um tipo Java combinando o nome de seu pacote com seu nome simples, eliminando a ambiguidade entre nomes de pacotes.
Usar java.util.List distingue a interface de coleção de outro tipo também chamado List.

O

Operação
Uma operação é um comportamento declarado em um tipo UML, descrito por seu nome, tipos de parâmetros, visibilidade e tipo de retorno.
A operação + totalWeightGrams(): int descreve um método público sem fornecer seu algoritmo.

P

Pacote
Um pacote é um grupo nomeado de tipos Java que organiza código relacionado, separa nomes de tipos e estabelece um limite de acesso.
Pacotes separados de interface e de domínio mantêm o tratamento de comandos à parte das classes que calculam tarifas de encomendas.
Pacote sem nome
O pacote sem nome contém tipos de arquivos-fonte sem declarações de pacote; pacotes nomeados não podem importar esses tipos.
Mover classes auxiliares compartilhadas para um pacote nomeado permite que as outras partes nomeadas de um projeto se refiram a elas.
Plugin
Um plugin adiciona recursos e configurações de construção, frequentemente fornecendo tarefas e convenções para um tipo específico de projeto.
O plugin application do Gradle adiciona uma tarefa run para a classe principal configurada de encomendas.
Preparar–agir–verificar
Preparar–agir–verificar é um padrão de escrita de testes que separa a preparação de um cenário, a execução de sua ação e a verificação de seu resultado.
Criar uma remessa, adicionar uma encomenda e verificar sua contagem dá ao teste três partes claras.

R

Realização
Realização é uma relação em que um tipo cumpre um contrato de interface, mostrada por uma linha tracejada e um triângulo vazado.
Um repositório em memória realiza a mesma interface de repositório que um repositório baseado em arquivo realiza.
Refatoração
Refatoração altera a organização interna do código preservando seu comportamento observável, por exemplo ao renomear ou extrair um método.
Mover um método auxiliar de tarifa para um pacote mais adequado é uma refatoração quando a calculadora continua fornecendo os mesmos resultados.
Regressão
Uma regressão é um defeito introduzido em um comportamento que funcionava antes, frequentemente após uma alteração em outra parte do código.
Um teste existente de limite de tarifa detecta uma regressão quando uma comparação muda de inclusiva para estrita.
Repositório
Um repositório oferece operações para armazenar e recuperar objetos de domínio por trás de um contrato que pode ter implementações diferentes.
Um teste fornece um repositório em memória para evitar depender de um arquivo real de dados de encomendas.
Resultado esperado
Um resultado esperado é o resultado ou comportamento exigido pela especificação, determinado independentemente do código testado.
Quando encomendas pesadas são cobradas por bloco de peso iniciado, a tarifa esperada vem da contagem de blocos, não da cópia da saída do método.

S

Separação de responsabilidades
A separação de responsabilidades atribui tarefas diferentes a partes diferentes de um programa para que cada uma possa ser compreendida, alterada e testada mais facilmente.
Alterar a redação da mensagem de solicitação não deve exigir edição do método que calcula a tarifa de uma encomenda.
Símbolo de visibilidade
Um símbolo de visibilidade é um prefixo UML que marca o acesso: + public, - private, # protected ou ~ acesso de pacote.
Um campo mostrado com - deve manter a visibilidade privada quando o diagrama for traduzido para Java.
Superclasse
Uma superclasse é a classe da qual uma subclasse herda membros e comportamento; UML coloca o triângulo de generalização nessa classe.
Tanto cartas quanto encomendas podem herdar comportamento compartilhado de item sem repeti-lo em cada caixa de subclasse.
Surefire
Surefire é o plugin Maven que executa testes unitários durante a fase de testes usando frameworks de teste e regras de descoberta configurados.
Nomear uma classe de teste como ShippingCalculatorTest corresponde a um padrão de inclusão padrão do Surefire.

T

Tarefa do Gradle
Uma tarefa do Gradle é uma unidade de trabalho de construção, como compilação ou teste, que pode depender de outras tarefas e reutilizar resultados inalterados.
Executar a tarefa de construção também inclui o trabalho de compilação e teste exigido para essa construção.
Teste instável
Um teste instável produz resultados inconsistentes sem uma alteração relevante no código, frequentemente porque tempo, estado compartilhado ou dados externos não são controlados.
Um teste que lê a data atual pode mudar seu resultado quando o calendário muda durante a noite.
Teste parametrizado
Um teste parametrizado executa um método de teste para vários conjuntos de argumentos fornecidos, relatando cada invocação como um caso separado.
Uma tabela de pesos e tarifas pode verificar vários limites sem repetir o corpo do método de asserção.
Teste unitário
Um teste unitário é uma verificação automatizada de uma pequena unidade de código que compara o comportamento observado com um resultado especificado independentemente.
Um teste de calculadora chama o método de tarifa e verifica o número esperado de centavos sem iniciar a interface.
Tolerância
Uma tolerância é uma diferença numérica permitida usada por uma asserção quando a igualdade exata é inadequada para um cálculo em ponto flutuante.
Uma pequena tolerância justificada aceita uma diferença de arredondamento sem aceitar um resultado substancialmente incorreto.

V

Vermelho–verde–refatorar
Vermelho–verde–refatorar é o ciclo de TDD de revelar um comportamento ausente, fazer todos os testes atuais passarem e melhorar a estrutura preservando o comportamento.
Depois que o novo caso de tarifa passa, extrair código de cálculo repetido pertence à etapa de refatoração.

Sobre esta aula

Por StudeliRelatar um erro

Crie seus próprios Materiais de Estudo

Transforme suas anotações, slides e documentos em Guias de Estudo, Questões de Prática, Flashcards e mais 6 ferramentas.

Começar