Introdução ao Sistema Sob Teste (SUT)
Ao concluirmos esta aula, estaremos familiarizados com as classes que serão utilizadas nas demais aulas deste tópico para demonstrar diferentes técnicas de criação de dados de teste.
O objetivo desta aula não é criticar a forma como essas classes foram originalmente projetadas. Contudo, se você quiser estudar mais a fundo as decisões de design discutidas aqui (como o antipadrão de construtor telescópico e o padrão JavaBeans versus imutabilidade em Kotlin), recomendamos a leitura dos materiais complementares indicados nas caixas de nota ao longo da aula.
Introdução à Classe MailingAddress
A classe MailingAddress armazena as informações de um único endereço de correspondência. Como a nossa aplicação precisa lidar tanto com pedidos domésticos (enviados para a Finlândia) quanto com pedidos internacionais, a classe MailingAddress deve conter as seguintes informações:
- O nome do destinatário (receiver)
- Endereço de rua (street address)
- Caixa postal (post office box)
- Código postal / CEP (post code)
- Cidade (city)
- Estado (state)
- País (country)
A estrutura inicial de propriedades da classe MailingAddress em Kotlin tem a seguinte aparência:
|
|
Há três regras de domínio importantes que devemos observar sobre esses atributos:
- Um endereço pode ter um endereço de rua (
streetAddress) ou uma caixa postal (postOfficeBox). - A informação de estado (
state) não é obrigatória em todos os países (como na Finlândia ou na Suécia). Entretanto, se quisermos entregar um pedido nos Estados Unidos, precisamos obrigatoriamente fornecer essa informação. - A informação de país (
country) não é obrigatória se quisermos entregar pedidos em endereços domésticos (Finlândia).
Os desenvolvedores originais da nossa aplicação decidiram utilizar o (anti)padrão do construtor telescópico (telescoping constructor (anti)pattern) para criar novos objetos MailingAddress. Eles escreveram três construtores sobrecarregados, que são descritos passo a passo a seguir:
Primeiro, se quisermos entregar um pedido nos Estados Unidos, precisamos utilizar o construtor que nos permite definir todos os campos do objeto MailingAddress criado. O código-fonte da classe com este construtor completo tem a seguinte aparência:
|
|
Podemos utilizar este construtor de 7 parâmetros para criar novos objetos MailingAddress que representam os seguintes endereços nos Estados Unidos (um com rua e outro com caixa postal):
|
|
Segundo, se quisermos entregar um pedido em um país estrangeiro que não exige a informação de estado (como a Suécia), podemos utilizar o construtor secundário de 6 parâmetros, que delega para o construtor principal passando null no lugar de state. O código-fonte deste construtor tem a seguinte aparência:
|
|
Podemos utilizar este construtor para criar novos objetos MailingAddress que contêm os seguintes endereços na Suécia:
|
|
Terceiro, se quisermos entregar um pedido em um endereço doméstico na Finlândia, podemos utilizar o construtor secundário de 5 parâmetros, que delega para o construtor de 6 parâmetros passando null para o país (country). O código-fonte completo com o terceiro construtor tem a seguinte aparência:
|
|
Podemos utilizar este terceiro construtor para criar novos objetos MailingAddress que contêm os seguintes endereços domésticos na Finlândia:
|
|
Nota sobre Kotlin vs. Java Clássico:
Em Java, não havia suporte a valores padrão de parâmetros ou argumentos nomeados, o que levava desenvolvedores a encadear múltiplos construtores (this(...)) — o chamado construtor telescópico. Em Kotlin 2.4, mesmo quando interagimos com classes legadas ou modelos que possuem sobrecargas telescópicas (ou anotadas com @JvmOverloads), podemos declarar um construtor primário conciso e tirar proveito de argumentos nomeados, como exploraremos em detalhes na próxima aula.
Leitura Adicional:
A seguir, examinaremos em detalhes a segunda classe de domínio do nosso sistema sob teste: a classe Task.
Introdução à Classe Task
A classe Task é utilizada em um rastreador de tarefas simples (task tracker) e armazena as informações de uma única tarefa. Por esse motivo, a classe Task deve conter as seguintes informações:
- Identificador (
id) - O responsável pela tarefa (
assignee— usuário a quem a tarefa foi atribuída) - O usuário que fechou a tarefa (
closer) - O usuário que criou a tarefa (
creator) - Descrição (
description) - O usuário que modificou a tarefa (
modifier) - Título (
title) - Status da tarefa (
status) - Resolução da tarefa (
resolution)
A estrutura de atributos da classe Task tem a seguinte aparência:
|
|
As classes Assignee, Closer, Creator e Modifier são objetos de valor (value objects) imutáveis que armazenam o userId do usuário correspondente em cada papel.
O código-fonte da classe Assignee tem a seguinte aparência:
|
|
O código-fonte da classe Closer tem a seguinte aparência:
|
|
O código-fonte da classe Creator tem a seguinte aparência:
|
|
O código-fonte da classe Modifier tem a seguinte aparência:
|
|
O enum TaskStatus especifica os possíveis status de uma tarefa. Seu código-fonte tem a seguinte aparência:
|
|
O enum TaskResolution especifica as possíveis resoluções de uma tarefa quando ela é encerrada. Seu código-fonte tem a seguinte aparência:
|
|
O diagrama de classes a seguir ilustra os relacionamentos entre todos esses componentes:
Os desenvolvedores da nossa aplicação de rastreamento de tarefas decidiram utilizar o padrão JavaBeans ao criar a classe Task. Isso significa que:
- Podemos criar um novo objeto
Taskutilizando um construtor sem argumentos (Task()). - Podemos definir os valores das propriedades do objeto criado utilizando métodos setter (ou atribuição direta às propriedades
varem Kotlin, que invocam os setters na JVM). - Podemos obter os valores das propriedades do objeto criado utilizando métodos getter (ou leitura direta das propriedades em Kotlin).
Em Java tradicional, a classe Task exigia dezenas de linhas de métodos get*() e set*() explícitos. Em Kotlin, propriedades mutáveis (var) geram automaticamente os getters e setters compatíveis com a especificação JavaBeans no bytecode da JVM, embora também possamos visualizar o equivalente completo com construtor vazio e métodos explícitos quando lidamos com classes legadas:
|
|
Caso você esteja inspecionando uma classe Task vinda de código legado estilo JavaBeans com getters e setters explícitos traduzidos literalmente, ela expõe o seguinte contrato completo:
|
|
Leitura Adicional:
Agora estamos familiarizados com as classes que serão utilizadas nas demais aulas deste tópico. Vamos resumir o que aprendemos nesta aula.
Evidência de Execução dos Modelos de Domínio
Para verificar que todas as classes do Sistema Sob Teste (SUT) e seus construtores funcionam corretamente no runtime do Kotlin e JUnit Jupiter 6, executamos o teste de sanidade:
|
|
Initialized native services in: /home/wc/.gradle/native
Initialized jansi services in: /home/wc/.gradle/native
Gradle Daemon started in 1 s 105 ms
Starting Build
Settings evaluated using settings file '/home/wc/test-with-spring/code/01-starter-junit6/settings.gradle.kts'.
Projects loaded. Root project using build file '/home/wc/test-with-spring/code/01-starter-junit6/build.gradle.kts'.
Included projects: [root project 'starter-junit6']
> Configure project :
Evaluating root project 'starter-junit6' using build file '/home/wc/test-with-spring/code/01-starter-junit6/build.gradle.kts'.
Using Kotlin Gradle Plugin 2.4.21
Using Java Toolchain: Temurin 21.0.6 (/home/wc/.sdkman/candidates/java/21.0.6-tem)
Spring Boot 4.1.2 plugin applied.
All projects evaluated.
Selected primary task 'test' from project :
Tasks to be executed: [task ':compileKotlin', task ':compileJava', task ':processResources', task ':classes', task ':compileTestKotlin', task ':compileTestJava', task ':processTestResources', task ':testClasses', task ':test']
Tasks that were skipped: [task ':compileJava', task ':compileTestJava']
> Task :compileKotlin
Compiling with Kotlin compiler version 2.4.21 (JVM target 21)
Compiling Kotlin files from [/home/wc/test-with-spring/code/01-starter-junit6/src/main/kotlin]
> Task :processResources NO-SOURCE
> Task :classes
> Task :compileTestKotlin
Compiling with Kotlin compiler version 2.4.21 (JVM target 21)
Compiling Kotlin files from [/home/wc/test-with-spring/code/01-starter-junit6/src/test/kotlin]
> Task :processTestResources NO-SOURCE
> Task :testClasses
> Task :test
Starting process 'Gradle Test Executor 1'. Working directory: /home/wc/test-with-spring/code/01-starter-junit6 Command: /home/wc/.sdkman/candidates/java/21.0.6-tem/bin/java -ea -cp ...
Successfully started process 'Gradle Test Executor 1'
com.testwithspring.starter.testdata.SutSanityTest > Deve criar MailingAddress com construtor de 7 parâmetros para endereço dos EUA PASSED
com.testwithspring.starter.testdata.SutSanityTest > Deve criar MailingAddress com construtor de 6 parâmetros para Suécia (sem state) PASSED
com.testwithspring.starter.testdata.SutSanityTest > Deve criar MailingAddress com construtor de 5 parâmetros para Finlândia (sem state e sem country) PASSED
com.testwithspring.starter.testdata.SutSanityTest > Deve criar e configurar objeto Task estilo JavaBeans PASSED
SUT Sanity - Verificação do Modelo de Domínio > 4 tests completed, 0 failed, 0 skipped
Finished generating test XML results into: /home/wc/test-with-spring/code/01-starter-junit6/build/test-results/test
Finished generating test html results into: /home/wc/test-with-spring/code/01-starter-junit6/build/reports/tests/test
BUILD SUCCESSFUL in 2s
5 actionable tasks: 5 executed
Resumo
Esta aula nos ensinou quatro pontos principais:
- A classe
MailingAddressarmazena as informações de um único endereço de correspondência (doméstico ou internacional). - Podemos criar novos objetos
MailingAddressutilizando o (anti)padrão do construtor telescópico. - A classe
Taskarmazena as informações de uma única tarefa no nosso sistema de acompanhamento de tarefas, relacionando-se com os objetos de valorAssignee,Closer,CreatoreModifiere com os enumsTaskStatuseTaskResolution. - A classe
Taskutiliza o padrão de construção estilo JavaBeans (construtor sem argumentos e mutação de estado por propriedades/setters).