Você está na página 1de 37

26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

Capítulo 10 : Juntando tudo

Neste capítulo, analisaremos o que fizemos nos capítulos anteriores e veremos


diferentes maneiras de melhorar as camadas do aplicativo. Mais tarde, explo-
raremos o benefício da arquitetura limpa quando integrarmos testes instru-
mentados ao aplicativo, onde trocaremos as dependências da fonte de dados
por dependências simuladas para garantir a confiabilidade dos testes.

Neste capítulo, abordaremos os seguintes tópicos:

Inspecionando dependências do módulo


Teste de instrumentação

Ao final do capítulo, você será capaz de identificar e remover dependências


externas na camada de caso de uso do aplicativo para aplicar o Princípio de
Fechamento Comum ( CCP ) e saber como criar testes instrumentados no An-
droid com fontes de dados simuladas.

Requerimentos técnicos

Os requisitos de hardware e software são os seguintes:

Android Studio Arctic Fox 2020.3.1 Patch 3

Os arquivos de código para este capítulo podem ser encontrados aqui:


https://github.com/PacktPublishing/Clean-Android-
Architecture/tree/main/Chapter10 .

Confira o vídeo a seguir para ver o Código em Ação: https://bit.ly/3sLr0HS

Inspecionando dependências do
módulo

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 1/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

Nissoseção, analisaremos as dependências usadas nos diferentes módulos do


aplicativo criado nos capítulos anteriores.

Seguindo o Exercício 09.01 – Transição para MVI do Capítulo 9 , Implementando


uma Arquitetura MVI , agora temos um aplicativo totalmente funcional divi-
dido em módulos separados, representando diferentes camadas. Podemos
analisar a relação entre os diferentesmódulos observando o bloco de depen‐
dências no arquivo build.gradle em cada módulo e focando em particular

nas linhas de implementação(project(path: "{module}")) . Se fôssemos dese-


nhar um diagrama, ficaria assim:

Figura 10.1 – Um diagrama de dependência do módulo para o exercício 09.01

Na figura anterior, podemos ver que o módulo :domain , que faz parte da ca-
mada de domínio, está no centro, com os módulos das outras camadas tendo
uma dependência em relação a ele. O módulo :app é responsável por montar
todas as dependências, e isso significa que ele terá uma dependência de todos
os outros módulos. Isso significaque estamos em uma boa posição de arquite-
tura limpa porque queremos que as entidades e os casos de uso tenham de-
pendências mínimas de outros componentes. Se continuarmos analisando os
arquivos build.gradle para cada módulo e incluirmos as dependências exter-

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 2/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

nas também, veremos dependências adicionais em bibliotecas externas para


cada módulo:

Figura 10.2 – Diagrama de dependência do módulo com dependências exter-


nas para o exercício 09.01

Na Figura 10.2 , podemos ver algumas das dependências externas relevantes


que nossos módulos usam. :data-remote usa dependências para Retrofit e
OkHttp para rede, o módulo :data-local tem dependências para Room e Da-
taStore, enquanto os módulos da camada de apresentação dependem de coisas
como Compose, ViewModel e a estrutura do Android. As dependências que fo-
ram usadas em todo o projeto foram corrotinas, fluxos e Hilt.

Tendodependências em Hilt e corrotinas podem representar um problema


para o :domain e :data-repositorymódulos. Queremos que esses dois módulos
sejam o mais estáveis ​possível, e ter dependências externas criará problemas
toda vez que atualizarmos as versões dessas bibliotecas. Decidimos usar flu-
xos por causa de seus benefícios de encadeamento, a abordagem reativa e por-

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 3/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

que eles foram desenvolvidos como uma extensão do framework Kotlin. Eles
ainda podem representar um problema se quisermos adaptar nossos casos de
uso para várias plataformas usando o Kotlin Multiplatform. Uma solução para
isso seria desenvolver um plugin reativo que abstraísse o uso de fluxos e
usasse essa abstração nos diferentes módulos. Isso nos permitiria trocar dife-
rentes bibliotecas reativas sem alterar o código dentro do módulo. Embora
esta solução resolva o problema,

Quando se trata da dependência Hilt, podemos remover as referências a Hilt


dos módulos :domain e :data-repository e mover os módulos Hilt para :app .
Outra solução seria criar novos módulos Gradle que seriam responsáveis ​por
fornecer a dependência necessária. Por exemplo, um módulo :domain-hilt po-
deria ser criado, onde teria uma classe anotada @Module que forneceria todas
as dependências que o módulo :domain precisaria expor. Essa abordagem pode
ser usada para outros módulos que desejamos exportar para aplicativos que
usam diferentes estruturas de injeção de dependência para evitar a dependên-
cia do Hilt nesses projetos.

As dependências de módulo aumentarão à medida que os aplicativos desen-


volverem novos recursos e evoluem; isso significa que devemos ter tempo e
avaliar as dependências usadas em um projeto. Isso nos ajudará a identificar
possíveis problemas e se podemos dimensionar um aplicativo corretamente.
Devemos também contabilizar as dependências externas e analisar a influên-
cia que elas têm sobre nosso projeto. Na seção a seguir, veremos um exercício
sobre como reduzir as dependências que os módulos de domínio e repositório
têm no Hilt.

Exercício 10.01 – Reduzir dependências

Modifique o Exercício 09.01 – transição para MVI no Capítulo 9 , Implemen-


tando uma Arquitetura MVI , entãoque os módulos de domínio e repositório de
dados não dependerão mais do Hilt e, em vez disso, fornecerão as dependên-

cias desses módulos dentro do módulo app .

Antes dacompletando este exercício, você precisará fazer o seguinte:

1. Remova o Hilt do módulo de domínio .

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 4/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

2. Remova a anotação @Inject das classes GetPostsWithUsersWithInteracti‐


onUseCase , GetPostUseCase , GetUserUseCase e UpdateInteractionUseCase .

3. Renomeie a classe AppModule UseCaseModule e use @Provides para fornecer


dependências aos objetos anteriores.
4. Remova o Hilt do módulo de repositório de dados e exclua o uso da anota-
ção @Inject .
5. Mova RepositoryModule do módulo de repositório de dados para app e use
@Provides para fornecer as dependências para PostRepository ,

UserRepository e InteractionRepository .

Siga estas etapas para concluir o exercício:

1. No arquivo build.gradle do módulo de domínio , remova o uso dos plugins


kapt e Hilt:
plug-ins {
    id 'com.android.library'
    id 'kotlin-android'
}
2. No mesmo arquivo, exclua os usos do Hilt do bloco de dependências :
dependências {
    implementação coroutines.coroutinesAndroid
    testImplementação test.junit
    testImplementação test.coroutines
    testImplementação test.mockito
}
3. Excluiro uso de @Inject de GetPostsWithUsersWithInteractionUseCase :
class GetPostsWithUsersWithInteractionUseCase(
    configuração: configuração,
    valor privado postRepository: PostRepository,
    valor privado userRepository: UserRepository,
    private val transactionRepository:
        InteractionRepository
): UseCase<GetPostsWithUsersWithInteractionUseCase.
    Solicitar,
        GetPostsWithUsersWithInteractionUseCase.
            Resposta>(configuração) {
    …

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 5/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

}
4. Exclua o uso de @Inject de GetPostUseCase :
class GetPostUseCase(
    configuração: configuração,
    valor privado postRepository: PostRepository
): UseCase<GetPostUseCase.Request, GetPostUseCase.
    Resposta>(configuração) {
    …
}
5. Exclua o uso de @Inject de GetUserUseCase :
class GetUserUseCase(
    configuração: configuração,
    valor privado userRepository: UserRepository
): UseCase<GetUserUseCase.Request, GetUserUseCase.
    Resposta>(configuração) {
    …
}
6. Excluiro uso de @Inject de UpdateInteractionUseCase :
class UpdateInteractionUseCase(
    configuração: configuração,
    private val transactionRepository:
        InteractionRepository
): UseCase<UpdateInteractionUseCase.Request,
   UpdateInteractionUseCase.Response>(configuração) {
    …
}
7. No módulo do aplicativo, renomeie AppModule UseCaseModule .
8. No módulo app na classe UseCaseModule , forneça uma dependência para
GetPostsWithUsersWithInteractionUseCase :
@Módulo
@InstallIn(SingletonComponent::class)
class UseCaseModule {
    …
    @Provides
    Diversão   
    provideGetPostsWithUsersWithInteractionUseCase(
        configuração: UseCase.Configuration,

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 6/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

        postRepository: PostRepository,
        userRepository: UserRepository,
        InteractionRepository: InteractionRepository
    ): GetPostsWithUsersWithInteractionUseCase =
       GetPostsWithUsersWithInteractionUseCase(
        configuração,
        postRepositório,
        userRepository,
        interaçãoRepositório
    )
}

Aqui nósprecisamos usar @Provides porque não estamos mais no mesmo mó-
dulo, o que significa que devemos tratar isso como uma dependência externa,
que precisa da anotação @Provides , semelhante à forma como fornecemos as
dependências Room e Retrofit.

9. Na mesma classe, forneça uma dependência para GetPostUseCase :


@Módulo
@InstallIn(SingletonComponent::class)
class UseCaseModule {
    …
    @Provides
    divertido fornecerGetPostUseCase(
        configuração: UseCase.Configuration,
        postRepository: PostRepository
    ): GetPostUseCase = GetPostUseCase(
        configuração,
        postRepository
    )
}

Neste trecho, seguimos a abordagem da etapa anterior.

10. Nomesma classe, forneça uma dependência para GetUserUseCase :


@Módulo
@InstallIn(SingletonComponent::class)

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 7/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

class UseCaseModule {
    …
    @Provides
    divertido fornecerGetUserUseCase(
        configuração: UseCase.Configuration,
        userRepository: UserRepository
    ): GetUserUseCase = GetUserUseCase(
        configuração,
        userRepository
    )
}

Neste trecho, seguimos a abordagem da etapa anterior.

11. Na mesma classe, forneça uma dependência para UpdateInteractionUse‐


Case :
@Módulo
@InstallIn(SingletonComponent::class)
class UseCaseModule {
    …
    @Provides
    divertido fornecerUpdateInteractionUseCase(
        configuração: UseCase.Configuration,
        InteractionRepository: InteractionRepository
    ): UpdateInteractionUseCase =
       UpdateInteractionUseCase(
        configuração,
        interaçãoRepositório
    )
}

Nissosnippet, seguimos a abordagem da etapa anterior.

12. No arquivo build.gradle do módulo data-repository , remova o uso dos


plugins kapt e Hilt:
plug-ins {
    id 'com.android.library'

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 8/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

    id 'kotlin-android'
}
13. No mesmo arquivo, exclua os usos do Hilt do bloco de dependências :
dependências {
    implementação(projeto(caminho: ":domínio"))
    implementação coroutines.coroutinesAndroid
    testImplementação test.junit
    testImplementação test.coroutines
    testImplementação test.mockito
}
14. Jogadaa classe RepositoryModule do pacote de injeção no módulo de repo‐
sitório de dados para o pacote de injeção no módulo de aplicativo e torne

a classe não abstrata.


15. Exclua o uso de @Inject de InteractionRepositoryImpl :
class InteraçãoRepositórioImpl(
    valor privado transactionDataSource:
LocalInteractionDataSource
): Repositório de Interação {
    …
}
16. Exclua o uso de @Inject de PostRepositoryImpl :
class PostRepositoryImpl(
    valor privado remotePostDataSource:
        RemotePostDataSource,
    valor privado localPostDataSource:
        LocalPostDataSource
): : PostRepository {
    …
}
17. Exclua o uso de @Inject de UserRepositoryImpl :
class UserRepositoryImpl(
    valor privado remoteUserDataSource:
        RemoteUserDataSource,
    valor privado localUserDataSource:
        LocalUserDataSource
): UserRepository {
    …

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 9/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

}
18. Dentroa classe RepositoryModule , substitua o método bindPostRespository
por um método @Provides :
@Módulo
@InstallIn(SingletonComponent::class)
classe abstrata RepositoryModule {
    @Provides
    divertido fornecerPostRepository(
        remotePostDataSource: RemotePostDataSource,
        localPostDataSource: LocalPostDataSource
    ): PostRepository = PostRepositoryImpl(
        remotePostDataSource,
        localPostDataSource
    )
    …
}

Aqui, não podemos mais usar a anotação @Binds porque removemos a anota-
ção @Inject da classe PostRepositoryImpl e, por ser uma dependência ex-
terna, precisaremos usar @Provides .

19. Nomesmo arquivo, substitua o método bindUserRepository por um método


@Provides :
@Módulo
@InstallIn(SingletonComponent::class)
classe abstrata RepositoryModule {
    …
    @Provides
    divertido fornecerUserRepository(
        remoteUserDataSource: RemoteUserDataSource,
        localUserDataSource: LocalUserDataSource
    ): UserRepository = UserRepositoryImpl(
        remoteUserDataSource,
        localUserDataSource
    )
    …
}

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 10/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

20. No mesmo arquivo, substitua bindInteractionRepositorymethod por um mé-


todo @Provides :
@Módulo
@InstallIn(SingletonComponent::class)
classe abstrata RepositoryModule {
    …
    @Provides
    divertido provideInteractionRepository(
        interaçãoDataSource:
            LocalInteractionDataSource
    ): InteractionRepository =
       InteractionRepositoryImpl(
        interaçãoDataSource
    )
    …
}

Se executarmos o aplicativo,deve ver a mesma saída que obtivemos no Exercí-


cio 09.01 – Transição para MVI :

Figura 10.3 – O resultado do exercício 10.01

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 11/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

O projeto está agora em um estado em que os módulos de domínio e repositó‐


rio de dados não dependem mais do Hilt. Isso significa que todos os outros

módulos que dependem desses dois serão menosexpostos a possíveis proble-


mas causados ​por atualizações do Hilt. Isso também significa que, no futuro,
se quisermos alterar a estrutura de injeção de dependência usada em todo o
aplicativo, os módulos de domínio e repositório de dados permanecerão inal-
terados pela alteração. Na seção a seguir, veremos como podemos criar testes
de instrumentação com dados simulados para testar se os módulos estão bem
integrados e se os dados transmitidos são processados ​adequadamente.

Teste de instrumentação

Nesta seção, vamosveja como realizar testes de instrumentação para um apli-


cativo Android e como podemos aproveitar a injeção de dependência para in-
jetar dados simulados ou adicionar lógica relacionada ao teste sem modificar
a estrutura do código de um aplicativo.

Teste de instrumentaçãoé um conjunto de testes que são executados em um


dispositivo ou emulador Android e são representados pelos testes escritos no
diretório androidTest . Assim como outras partes do desenvolvimento do An-
droid, os testes de instrumentação evoluíram ao longo dos anos para melhorar
a qualidade do código de teste e fornecer a capacidade de criar melhores tes-
tes e declarações. Inicialmente, o teste era feito usando classes de teste como
ActivityTestCase , ContentProviderTestCase e ServiceTestCase , que eram usa-

das principalmente para testar componentes individuais de um aplicativo iso-


ladamente. A adição das bibliotecas de teste do Espresso nos permite testar fa-
cilmente várias atividades como parte da jornada que um usuário realizaria.

Para adicionar o Espresso e as bibliotecas associadas em um projeto, o se-


guinte precisará ser adicionado ao arquivo build.gradle de qualquer módulo:

dependências {
    …
    androidTestImplementação "androidx.test:core:1.4.0"
    androidTestImplementação "androidx.test:runner:1.4.0"
    androidTestImplementação "androidx.test:rules:1.4.0 "
    AndroidTestImplementação
https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 12/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

        "androidx.test.ext:junit:1.1.3 "
    AndroidTestImplementação
        "androidx.test.espresso:espresso-core:3.4.0 "
    androidTestImplementação "androidx.test.espresso.
        inatividade: inatividade simultânea: 3.4.0 "
}
A seguiré um exemplo de um teste escrito usando Espresso:

    @Teste
    divertido meuTeste(){
        ActivityScenario.launch(MainActivity::class.java)
.
            moveToState(Lifecycle.State.RESUMED)
        onView(withId(R.id.my_id))
            .perform(clique())
            .check(isExibido())
    }
No exemplo anterior, usamos o método de inicialização ActivityScenario para
iniciar MainActivity e fazer a transição de Activity para o estado RESUMED . Em
seguida, usamos onView , que requer ViewMatcher , e withId procura View por
seu ID e retorna ViewMatcher contendo essas informações. Temos então a op-
ção de usar perform , que requer ViewAction . Isso é para quando queremos in-
teragir com determinadas visualizações. Também podemos executar ViewAs‐
sertion usando a verificaçãométodo. Nesse caso, estamos verificando se uma

visualização é exibida.

Outra adição útil para ajudar nos testes é o orquestrador. O orquestrador é útil
quando queremos excluir os dados gerados pelos testes que podem ser manti-
dos na memória ou persistidos no dispositivo e que, por sua vez, podem afetar
outros testes e causar mau funcionamento. O que o orquestrador faz é desins-
talar o aplicativo antes de cada teste executado para que cada teste seja em
um aplicativo recém-instalado. Para adicionar o orquestrador ao aplicativo,
você precisará adicioná-lo ao arquivo build.gradle do módulo:

andróide {
    …
    configuração padrão {

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 13/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

        …
        testInstrumentationRunnerArgumentos
            clearPackageData: 'true'
        opções de teste {
            execução 'ANDROIDX_TEST_ORCHESTRATOR'
        }
        …
    }
}
Isso adicionará oconfiguração do orquestrador na execução do teste e passe a
instrução para excluir os dados do aplicativo após cada teste. Para adicionar a
dependência do orquestrador ao projeto, é necessário o seguinte:

dependências {
    …
    androidTestUtil "androidx.test:orchestrator: 1.4.1"
}
O Espresso também vem com muitas extensões, uma das quais é o conceito de
IdlingResource . Quando os testes locais (testes executados na máquina de de-

senvolvimento) e os testes instrumentados são executados, eles são executados


em um conjunto dedicado de encadeamentos para teste. A biblioteca de testes
Espresso monitorará o thread principal do aplicativo e, quando estiver ocioso,
fará as asserções necessárias. Se o aplicativo usar threads em segundo plano, o
Espresso precisará de uma maneira de ser informado por isso. Podemos usar
IdlingResource para indicar ao Espresso esperar que uma ação seja concluída

antes de continuar sua execução. Um exemplo de IdlingResource é CountingI‐


dlingResource, que conterá um contador para cada operação que o Expresso

precisará aguardar. O contador é incrementado antes de cada operação de


longa duração e depois decrementado após a conclusão da operação. Antes de
cada teste, IdlingResource iráprecisam ser registrados e, em seguida, desregis-
trados quando o teste terminar:

class MyClass(valor privado countIdlingResource:


    Contando Recurso Inativo) {
    diversão doOperation() {
        countIdlingResource.increment()
        // Realiza operação de longa duração

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 14/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

        countIdlingResource.decrement()
    }
}
No exemplo anterior, temos CountingIdlingResource sendo incrementado no
início do método doOperation e decrementado após a operação longa que pre-
tendemos realizar. Para registrar e cancelar o registro de IdlingResource , po-
demos fazer o seguinte:

    lateinit var countingIdlingResource :


CountingIdlingResource
    @Antes da
    configuração divertida(){
        IdlingRegistry.getInstance().register
            (ContandoRecurso Inativo)
    }
    @Depois
    divertido tearDown(){
        IdlingRegistry.getInstance().
            unregister(countingIdlingResource)
    }
NissoPor exemplo, registramos IdlingResource no método setUp , que é cha-
mado antes de cada teste por causa da anotação @Before, e cancelamos o re-
gistro no método tearDown , que é chamado após cada teste por causa da anota-
ção @After .

Como IdlingResource faz parte do Espresso, mas precisa ser usado quando as
operações dentro do código do aplicativo são executadas, queremos evitar o
uso de IdlingResource junto com esse código. Uma solução para isso é decorar
a classe que contém a operação e então usar injeção de dependência para inje-
tar a dependência decorada no teste. Para decorar o código, precisaremos ter
uma abstração para a operação. Um exemplo disso é o seguinte:

interface MinhaInterface {
    diversão doOperation()
}
class MinhaClasse: MinhaInterface {
    substituir fun doOperation() {

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 15/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

        // Implementa operação de longa duração


    }
}
No exemplo anterior, criamos uma interface que define o método doOperation
e, em seguida, implementamos a interface com a operação de longa duração
em uma classe. Agora podemos criar uma classe que pertencerá à pasta an‐
droidTest , queirá decorar a implementação atual da classe:

class MinhaClasseDecorada(
    valor privado minhaInterface: MinhaInterface,
    valor privado countIdlingResource:
        Recurso de contagem em marcha lenta
): MinhaInterface {
    substituir fun doOperation() {
        countIdlingResource.increment()
        minhaInterface.doOperation()
        countIdlingResource.decrement()
    }
}
Aqui, temos outra implementação de MyInterface , que conterá uma referência
à abstração e CountingIdlingResource . Quando doOperation for chamado, in-
crementaremos IdlingResource , chamaremos a operação e, quando terminar,
diminuiremos IdlingResource .

Se quisermos injetar a nova dependência no teste, precisaremos primeiro defi-


nir uma nova classe que estenda Application , que conterá o gráfico de depen-
dência contendo as dependências do teste. Se estivermos usando Hilt, ele já
fornece tal classe na forma de HiltTestApplication . Se quisermos integrar o
Hilt nos testes instrumentados, precisaremos que as seguintes dependências
sejam adicionadas ao arquivo build.gradle do módulo:

dependências {
    androidTestImplementation "com.google.dagger:hilt-
        teste android: 2.40.5"
    kaptAndroidTest "com.google.dagger:hilt-android-
        compilador: 2.40.5"
}

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 16/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

Parafornecer a classe HiltTestApplication para o teste, precisaremos alterar o


executor de teste instrumentado. Um exemplo de um novo executor de testes
terá a seguinte aparência:

class MyTestRunner : AndroidJUnitRunner() {


    substituir fun newApplication(cl: ClassLoader?, name:
        String?, contexto: Contexto?): Aplicação {
        return super.newApplication(cl,
            HiltTestApplication ::class.java.name,
context)
    }
}
Neste exemplo, estamos estendendo de AndroidJUnitRunner , e no método
newApplication , invocamos o super método e passamos HiltTestApplication

como o nome . Isso significa que quando o teste for executado, HiltTestApplica‐
tion será usado no lugar da classe Application que definimos em nosso código

principal. Agora precisaremos alterar a configuração no arquivo build.gradle


do módulo para usar o executor anterior:

andróide {
    …
    configuração padrão {
        …
        testInstrumentationRunner "com.test.MyTestRunner"
        …
        }
    }
}
Isso permite que o teste instrumentado use o runner que criamos. Vamos
agora supor que temoso módulo a seguir, que fornecerá a dependência inicial:

@Módulo
@InstallIn(SingletonComponent::class)
classe abstrata MyModule {
    @Binds
    abstract fun bindMyClass(myClass: MyClass):
MyInterface

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 17/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

}
Aqui, estamos usando uma ligação simples para conectar uma implementação
à abstração. Na pasta androidTest , podemos criar um novo módulo no qual
substituímos esta instância pela decorada:

@Módulo
@TestInstallIn(
    componentes = [SingletonComponent::class],
    substitui = [MyModule::class]
)
class MyDecoratedModule {
    @Provides
    diversão fornecerIdlingResource() =
        CountingIdlingResource("meu-recurso-ocioso")
    @Provides
    fun provideMyDecoratedClass(countingIdlingResource:  
        CountingIdlingResource) =
        MyDecoratedClass(MyClass(), countIdlingResource)
}
NissoPor exemplo, usamos a anotação @TestInstallIn , que tornará as depen-
dências deste módulo ativas enquanto o aplicativo de teste e substituir as de-
pendências no módulo anterior. Podemos então fornecer dependências para
IdlingResource e MyDecoratedClass , que envolverão MyClass e usarão Idlin‐

gResource . Se quisermos que essas alterações entrem em vigor nos testes, pre-

cisaremos das seguintes alterações:

@HiltAndroidTest
class MinhaAtividadeTeste {
    @get:Regra(ordem = 0)
    var hiltAndroidRule = HiltAndroidRule(this)
    @Injetar
    lateinit var idlingResource: CountingIdlingResources
    @Antes da
    configuração divertida(){
        hiltAndroidRule.inject()
        IdlingRegistry.getInstance().register
            (recurso inativo)

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 18/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

    }
    @Depois
    divertido tearDown(){
       IdlingRegistry.getInstance().unregister
           (recurso inativo)
    }
}
NissoPor exemplo, usamos a anotação @HiltAndroidTest porque queremos in-
jetar CountingIdlingResources no teste. Em seguida, usamos HiltAndroidTes‐
tRule para realizar a injeção. Também demos a mais alta prioridade em ter-

mos de ordem de execução para regras de teste. Por fim, conseguimos regis-
trar e cancelar o registro de CountingIdlingResources para cada teste da classe.

O Jetpack Compose vem com suas próprias bibliotecas de teste, que exigem a
seguinte configuração para o arquivo build.gradle do módulo:

dependências {
    androidTestImplementation "androidx.compose.ui:ui-
test-
        junit4:1.0.5"
    debugImplementation "androidx.compose.ui:ui-test-
        manifesto: 1,0,5"
}
Para escrever testes para componentes do Jetpack Compose, precisaremos de-
finir uma regra de teste do Compose usando createComposeRule quando qui-
sermos testar métodos composáveis ​individuais ou createAndroidComposeRule
se quisermos testar o conteúdo do Compose de uma atividade inteira. Um
exemplo ficaria assim:

class MeuTeste {
    @get:Regra
    var composeTestRule = createAndroidComposeRule
        (MinhaAtividade::class.java)
}
NoNo exemplo anterior, definimos uma regra de teste que será responsável
por testar o conteúdo do Compose dentro de MyActivity . Se quisermos que o

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 19/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

teste interaja com a interface do usuário ou afirme que ele exibe as informa-
ções corretas, temos a seguinte estrutura:

    @Teste
    divertido testDisplayList() {
        composeTestRule.onNode()
            .assertIsDisplayed()
            .performClick()
    }
Neste exemplo, usamos o método onNode para localizar um elemento especí-
fico, como Text ou Button . Temos então o método assertIsDisplayed , que é
usado para verificar se o nó é exibido. Por fim, temos o método performClick ,
que irá clicar no elemento. O Jetpack Compose usa seu próprio tipo IdlingRe‐
source , que pode ser registrado na regra de teste Compose, semelhante ao

exemplo a seguir:

    lateinit var idlingResource: IdlingResource


    @Antes da
    configuração divertida() {
        composeTestRule.registerIdlingResource
            (recurso inativo)
    }
    @Depois
    divertido desmontar() {
        composeTestRule.unregisterIdlingResource
            (recurso inativo)
    }
A partir de umperspectiva de arquitetura limpa, devemos nos esforçar para
tornar o código do nosso aplicativo o mais testável possível. Isso se aplica a
testes locais, como testes de unidade e testes instrumentados. Queremos ser
capazes de garantir que os testes sejam confiáveis; isso geralmente significa
que precisaremos remover a dependência de chamadas de rede, o que signi-
fica que precisaremos fornecer uma maneira de injetar dados fictícios no apli-
cativo sem modificar o código do aplicativo. Também precisamos ser capazes
de injetar IdlingResourcesna aplicação ou use dependências decoradas para
verificar se os dados inseridos pelo usuário são os dados corretos recebidos na
camada de dados. Isso também envolve a capacidade de decorar essas depen-

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 20/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

dências para adicionar lógica extra sem modificar o código do aplicativo. Na


seção a seguir, veremos um exercício no qual injetaremos várias dependên-
cias contendo lógica de teste no aplicativo e avaliaremos a dificuldade neces-
sária para introduzi-las.

Exercício 10.02 - Testes instrumentados

Adicione um teste instrumentado ao Exercício 10.01 – Reduzir dependências ,


que irá afirmar que os seguintes dados são exibidos na tela:

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 21/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

Figura 10.4 – O resultado esperado do exercício 10.02

Para conseguir isso, vocêprecisará criar uma nova implementação de Remote‐


PostDataSource , que retornará uma lista de quatro posts; dois posts pertence-

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 22/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

rão a um usuário e os outros dois pertencerão a outro usuário. A mesma coisa


precisará ser feita para RemoteUserDataSource , que retornará os dois usuários.
Essas implementações precisarão ser injetadas no teste. Para garantir que o
teste aguarde a conclusão do trabalho em segundo plano, você precisará deco-
rar cada repositório com IdlingResource , que também precisará ser injetado
no teste.

Antes de concluireste exercício, você precisará fazer o seguinte:

1. Integre as bibliotecas de teste no módulo do aplicativo.


2. Crie PostAppTestRunner , que será usado para fornecer HiltTestApplication
ao executor de teste de instrumentação do Android.
3. Crie uma classe ComposeCountingIdlingResource , que envolverá um Es-
presso CountingIndlingResource e implementará o Compose IdlingResource
.
4. Crie MockRemotePostDataSource e MockRemoteUserDataSource , que serão res-
ponsáveis ​por retornar os usuários e posts apresentados na Figura 10.4 .
5. Crie IdlingInteractionRepository , IdlingUserRepository e
IdlingPostRespository , que decorarão InteractionRepository ,

UserRepository e PostRepository , e use o ComposeCountingIdlingResource ,

que será incrementado quando novos dados forem carregados e diminuído


quando o carregamento de dados for concluído.
6. Crie IdlingRepositoryModule e MockRemoteDataSourceModule , que substitui-
rão RepositoryModule e RemoteDataSourceModule respectivamente nos testes.
7. Crie MainActivityTest , que terá um teste, e use createAndroidComposeRule
para afirmar que a lista de dados fictícios é exibida.

Siga estas etapas para concluir o exercício:

1. No arquivo build.gradle de nível superior , adicione as seguintes versões


de biblioteca:
script de construção {
    extensão {
        …
        versões = [
                …
                androidTestCore: "1.4.0",

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 23/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

                androidTestJunit : "1.1.3",
                orquestrador: "1.4.1"
        ]
        …
}
2. No mesmo arquivo, façacertifique-se de que as seguintes dependências an‐
droidTest sejam adicionadas:
script de construção {
    extensão {
       …
        teste android = [
                junit: "androidx.test.ext
                    :junit:${versions.espressoJunit}",
                espressoCore : "androidx.test.
                    espresso:espresso-core:${versions.
                        espressoCore}",
                idlingResource: "androidx.test.
                    espresso:espresso-idling-resource
                        :${versions.espressoCore}",
                composeUiTestJunit: "androidx.compose.
                    ui:ui-test-junit4:$
                        {versions.compose}",
                composeManifest : "androidx.compose
                    .ui:ui-test-manifest:$
                        {versions.compose}",
                hilt: "com.google.
                    punhal:hilt-android-testing:$
                        {versions.hilt}",
                hiltCompiler : "com.google.
                     punhal:hilt-android-compiler:$
                        {versions.hilt}",
                core: "androidx.test:
                    core:${versions.androidTestCore}",
                corredor: "androidx.test:
                    corredor:$
                        {versions.androidTestCore}",
                regras: "androidx.test:

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 24/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

                   regras:${versions.androidTestCore}"
,
                orquestrador: "androidx.test:
                    orquestrador:$
                        {versions.orchestrator}"
        ]
    }
    …
}

Aqui, estamos definindo os mapeamentos para todas as bibliotecas de teste


que usaremos para que estejam disponíveis em vários módulos.

3. No arquivo build.gradle deo módulo do aplicativo, adicione as dependên-


cias de teste necessárias:
dependências{
    …
    androidTestImplementação androidTest.junit
    androidTestImplementação androidTest.espressoCore
    AndroidTestImplementação
        androidTest.idlingResource
    androidTestImplementação androidTest.core
    androidTestImplementação androidTest.rules
    androidTestImplementação androidTest.runner
    androidTestImplementação androidTest.hilt
    kaptAndroidTest androidTest.hiltCompiler
    AndroidTestImplementação
        androidTest.composeUiTestJunit
    debugImplementação androidTest.composeManifest
    androidTestUtil androidTest.orchestrator
}
4. Na pasta androidTest do módulo app, crie a classe PostAppTestRunner den-
tro da pasta java/{package-name} :
class PostAppTestRunner : AndroidJUnitRunner() {
    substituir fun newApplication(cl: ClassLoader?,
        nome: String?, contexto: Contexto?): Aplicação
{

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 25/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

        return super.newApplication(cl,
            HiltTestApplication::class.java.name,
                contexto)
    }
}
5. No arquivo build.gradle do módulo do aplicativo, defina a seguinte confi-
guração de teste. Façocertifique-se de substituir {package-name} pelo pacote
em que o PostAppTestRunner está:
andróide {
    …
    configuração padrão {
        …
        testInstrumentationRunner "{package-name}.
            PostAppTestRunner"
        testInstrumentationRunnerArgumentos
            clearPackageData: 'true'
        opções de teste {
            execução 'ANDROIDX_TEST_ORCHESTRATOR'
        }
    }
}
6. Na pasta androidTest do módulo app, crie os seguintes pacotes dentro da
pasta java/{package-name} – idling , injection , remote , repository e test .
7. Lado de dentroo pacote idling , crie uma nova classe chamada Compose‐
CountingIdlingResource :
class ComposeCountingIdlingResource(name: String) :
    Recurso Inativo {
    valor privado countIdlingResource =
        CountingIdlingResource(name)
    substituir val isIdleNow: booleano
        get() = countIdlingResource.isIdleNow
    fun increment() = countIdlingResource.
        incremento()
    fun decrement() = countIdlingResource.
        diminuir()
}

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 26/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

Aqui, usamos a classe CountingIdlingResource do Espresso para executar a ló-


gica para incrementar, decrementar e fornecer seu estado de inatividade atual
por meio do método isIdleNow , que é usado pelo Jetpack Compose.

8. No mesmo pacote, crie um arquivo chamado IdlingUtils com o seguinte


método:
divertido <T> Fluxo<T>.attachIdling(
    countIdlingResource:  
        Recurso ComposeCountingIdling
): Fluxo<T> {
    retornar emIniciar {
        countIdlingResource.increment()
    }.em cada {
        countIdlingResource.decrement()
    }
}

Isto éuma função de extensão que podemos usar para incrementar IdlingRe‐
source antes que Flow seja coletado e decrementá-lo quando o primeiro valor

de Flow estiver sendo emitido.

9. No pacote do repositório , crie uma classe chamada


IdlingInteractionRepository :
class IdlingInteractionRepository(
    valor privado transactionRepository:
InteractionRepository,
    valor privado countIdlingResource:
ComposeCountingIdlingResource
): Repositório de Interação {
    override fun getInteraction(): Flow<Interaction> {
        retornar interaçãoRepository.getInteraction()
            .attachIdling(countingIdlingResource)
    }
    substituir fun saveInteraction(interaction:
        Interação): Fluxo<Interação> {
        retornar interaçãoRepository.
            salvarInteração(interação)

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 27/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

            .attachIdling(countingIdlingResource)
    }
}

Esta classe tem uma referência ao objeto ComposeCountingIdlingResourcee o


método attachIdling criado anteriormente para incrementar quando os da-
dos são carregados ou salvos e para decrementar quando terminar de realizar
essas operações.

10. No mesmo pacote, crie uma classe chamada IdlingPostRepository :


class IdlingPostRepository(
    valor privado postRepository: PostRepository,
    valor privado countIdlingResource:
        Recurso ComposeCountingIdling
): : PostRepository {
    substituir fun getPosts(): Flow<List<Post>> =
        postRepository.getPosts().attachIdling
            (ContandoRecurso Inativo)
    override fun getPost(id: Long): Flow<Post> =
        postRepository.getPost(id).
            attachIdling(countingIdlingResource)
}

Neste trecho, seguimos a abordagem da etapa anterior.

11. Nomesmo pacote, crie uma classe chamada IdlingUserRepository :


class IdlingUserRepository(
    valor privado userRepository: UserRepository,
    valor privado countIdlingResource:
        Recurso ComposeCountingIdling
): UserRepository {
    substituir fun getUsers(): Flow<List<User>> =
        userRepository.getUsers()
            .attachIdling(countingIdlingResource)
    override fun getUser(id: Long): Flow<User> =
        userRepository.getUser(id)
            .attachIdling(countingIdlingResource)

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 28/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

Neste trecho, seguimos a abordagem da etapa anterior.

12. No pacote de injeção , crie a classe IdlingRepositoryModule :


@Módulo
@TestInstallIn(
    componentes = [SingletonComponent::class],
    substitui = [RepositoryModule::class]
)
class IdlingRepositoryModule {
}
13. Na classe IdlingRepositoryModule , forneça uma dependência para Compose‐
CountingIdlingResource , que será uma única instância em todos os

repositórios:
@Módulo
@TestInstallIn(
    componentes = [SingletonComponent::class],
    substitui = [RepositoryModule::class]
)
class IdlingRepositoryModule {
    @Singleton
    @Provides
    diversão fornecerIdlingResource():
        ComposeCountingIdlingResource =
        Recurso ComposeCountingIdling
            ("repositório-inativo")
}

Neste snippet, estamos fornecendo uma única instância de ComposeCountingI‐


dlingResource para que, quando vários repositórios carregarem dados emao

mesmo tempo, o mesmo contador será usado para todos eles.

14. No mesmo arquivo, forneça uma dependência para IdlingPostRepository :


@Módulo
@TestInstallIn(
    componentes = [SingletonComponent::class],

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 29/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

    substitui = [RepositoryModule::class]
)
class IdlingRepositoryModule {
    …
    @Provides
    divertido fornecerPostRepository(
        remotePostDataSource: RemotePostDataSource,
        localPostDataSource: LocalPostDataSource,
        countIdlingResource:
            Recurso ComposeCountingIdling
    ): PostRepository = IdlingPostRepository(
        PostRepositoryImpl(
            remotePostDataSource,
            localPostDataSource
        ),
        recurso de contagem
    )
}

Neste trecho, nósestão fornecendo uma instância de IdlingPostRepository ,


que envolverá uma instância de PostRepositoryImpl e terá uma referência à
instância ComposeCountingIdlingResource definida anteriormente.

15. No mesmo arquivo, forneça uma dependência para IdlingUserRepository :


@Módulo
@TestInstallIn(
    componentes = [SingletonComponent::class],
    substitui = [RepositoryModule::class]
)
class IdlingRepositoryModule {
    …
    @Provides
    divertido fornecerUserRepository(
        remoteUserDataSource: RemoteUserDataSource,
        localUserDataSource: LocalUserDataSource,
        countIdlingResource:
            Recurso ComposeCountingIdling

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 30/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

    ): UserRepository = IdlingUserRepository(


        UserRepositoryImpl(
            remoteUserDataSource,
            localUserDataSource
        ),
        recurso de contagem
    )
}

Neste trecho, nósestão fornecendo uma instância de IdlingUserRepository ,


que envolverá uma instância de UserRepositoryImpl e terá uma referência à
instância ComposeCountingIdlingResource definida anteriormente.

16. No mesmo arquivo, forneça uma dependência para


IdlingInteractionRepository :
@Módulo
@TestInstallIn(
    componentes = [SingletonComponent::class],
    substitui = [RepositoryModule::class]
)
class IdlingRepositoryModule {
    …
    @Provides
    divertido provideInteractionRepository(
        interaçãoDataSource:
            LocalInteractionDataSource,
        countIdlingResource:
            Recurso ComposeCountingIdling
    ): InteractionRepository =
       IdlingInteractionRepository(
        InteractionRepositoryImpl(
            interaçãoDataSource
        ),
        recurso de contagem
    )
}

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 31/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

Neste trecho, estamosfornecendo uma instância de


IdlingInteractionRepository , que envolverá uma instância de

InteractionRepositoryImpl e terá uma referência à instância ComposeCountin‐

gIdlingResource definida anteriormente.

17. No pacote remoto , crie uma classe chamada MockRemoteUserDataSource e


crie uma lista de objetos User representandoos dados do teste:
class MockRemoteUserDataSource @Inject constructor() :
    RemoteUserDataSource {
    valor privado users = listOf(
        Do utilizador(
            ID = 1L,
            nome = "nome1",
            usuario = "usuario1",
            e-mail = "email1"
        ),
        Do utilizador(
            ID = 2L,
            nome = "nome2",
            usuario = "usuario2",
            e-mail = "email2"
        )
    )
    substituir fun getUsers(): Flow<List<User>> =
flowOf
        (usuários)
    override fun getUser(id: Long): Flow<User> =
        flowOf(usuários[0])
}

Aqui, criamos uma lista na qual retornamos dois usuários e colocamos no Flow
para o método getUsers .

18. Nomesmo pacote, crie uma classe chamada MockRemotePostDataSource e


crie uma lista de objetos Post representando os dados de teste:
class MockRemotePostDataSource @Inject constructor() :
    RemotePostDataSource {

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 32/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

    mensagens de valor privado = listOf(


        Publicar(
            ID = 1L,
            ID do usuário = 1L,
            titulo = "titulo1",
            corpo = "corpo1"
        ),
        Publicar(
            ID = 2L,
            ID do usuário = 1L,
            titulo = "titulo2",
            corpo = "corpo2"
        ),
        Publicar(
            ID = 3L,
            ID do usuário = 2L,
            titulo = "titulo3",
            corpo = "corpo3"
        ),
        Publicar(
            ID = 4L,
            ID do usuário = 2L,
            titulo = "titulo4",
            corpo = "corpo4"
        )
    )
    substituir fun getPosts(): Flow<List<Post>> =
        flowOf(posts)
    override fun getPost(id: Long): Flow<Post> =
        flowOf(posts[0])
}

Igual ao que fizemos com os usuários, criamos uma lista de posts e conecta-
mos os dois primeiros posts ao primeiro usuário e os dois últimos posts ao se-
gundo usuário.

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 33/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

19. No pacote de injeção , crie uma classe chamada MockRemoteDataSourceMo‐


dule , que será responsável por vincular as duas implementações anterio-

res às abstrações:
@Módulo
@TestInstallIn(
    componentes = [SingletonComponent::class],
    substitui = [RemoteDataSourceModule::class]
)
classe abstrata MockRemoteDataSourceModule {
    @Binds
    diversão abstrata bindPostDataSource(
        postDataSourceImpl: MockRemotePostDataSource):
            RemotePostDataSource
    @Binds
    diversão abstrata
bindUserDataSource(userDataSourceImpl
          : MockRemoteUserDataSource):
              RemoteUserDataSource
}
20. No pacote de teste , crieuma classe chamada MainActivityTest :
@HiltAndroidTest
class MainActivityTest {
    @get:Regra(ordem = 0)
    var hiltAndroidRule = HiltAndroidRule(this)
    @get:Regra(ordem = 1)
    var composeTestRule = createAndroidComposeRule
        (MainActivity::class.java)
    @Injetar
    lateinit var idlingResource:
        Recurso ComposeCountingIdling
    @Antes da
    configuração divertida() {
        hiltAndroidRule.inject()
        composeTestRule.
            registerIdlingResource(idlingResource)
    }
    @Depois

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 34/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

    divertido desmontar() {
        composeTestRule.unregisterIdlingResource
            (recurso inativo)
    }
}

Aqui nósestamos inicializando nossas regras de teste, que são para Hilt e Com-
pose, nessa ordem exata. Em seguida, injetamos ComposeCountingIdlingRe‐
source na classe de teste para que possamos registrá-la na regra de teste

Compose.

21. Na classe MainActivityTest , adicione um teste que confirmará que os da-


dos necessários são exibidos na tela:
@HiltAndroidTest
class MainActivityTest {
    …
    @Teste
    divertido testDisplayList() {
        composeTestRule.onNodeWithText("Total de
cliques
            contagem: 0")
            .assertIsDisplayed()
        composeTestRule.onAllNodesWithText("Autor:
            nome1")
            .assertCountEquals(2)
        composeTestRule.onAllNodesWithText("Autor:
            nome2")
            .assertCountEquals(2)
        composeTestRule.onNodeWithText("Título:
            título1")
            .assertIsDisplayed()
        composeTestRule.onNodeWithText("Título:
            título2")
            .assertIsDisplayed()
        composeTestRule.onNodeWithText("Título:
            título3")
            .assertIsDisplayed()

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 35/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

        composeTestRule.onNodeWithText("Título:
            título4")
            .assertIsDisplayed()
    }
}

Aqui, adicionamos um teste que afirma que o texto do cabeçalho é exibido, os


dois usuários sãoexibido para cada uma de suas postagens e que cada posta-
gem seja exibida.

Se executarmos o teste, devemos ver a seguinte saída:

Figura 10.5 – A saída do teste para o exercício 10.02

Como parte deste exercício, conseguimos fornecer dados simulados para o


aplicativo sem alterar nenhum código existente, alterando as fontes de dados
remotas e aproveitando a funcionalidade existente, adicionando IdlingRe‐
sources aos nossos repositórios. Ambas as técnicas foram possíveis usando in-

jeção de dependência e, por causa das abstrações, introduzimos diferentes ca-


madas da aplicação quando realizamos a inversão de dependência. Isso torna
o código do aplicativo testável e nos dá a oportunidade de testar diferentes ce-
nários e criar vários tipos de testes para garantir a integração de diferentes
componentes.

Resumo

Neste capítulo, analisamos os exercícios


Apoiar que
Sairfizemos nos capítulos anteriores

e encontramos possíveis problemas com as dependências que os módulos do


© 2022 O'REILLY MEDIA, INC.  TERMOS DE SERVIÇO POLÍTICA DE PRIVACIDADE
aplicativo possuem. Analisamos possíveis soluções para esses problemas. Em
seguida, analisamos uma aplicação prática de arquitetura limpa, que é a im-
https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 36/37
26/10/2022 21:47 Chapter 10: Putting It All Together | Clean Android Architecture

plementação de testes instrumentados, e como podemos alterar as fontes de


dados de uma aplicação para garantir a confiabilidade dos testes. Vimos como
podemos implementar testes instrumentados usando Jetpack Compose e Hilt
para fornecer injeção de dependência e, em seguida, os aplicamos em um
exercício no qual alteramos as dependências dos testes. Isso serve apenas
como um exemplo dos benefícios da arquitetura limpa. Outros benefícios vi-
rão em situações em que vários tipos são usados ​para publicar aplicativos se-
melhantes e queremos injetar diferentes implementações ou configurações
para cada aplicativo que queremos construir. Outro benefício vem ao lidar
com várias plataformas (como Android e iOS), onde podemos definir entida-
des, casos de uso e repositórios de forma agnóstica das plataformas usando
frameworks de plataforma cruzada e depois injetar as implementações para
recuperar e persistir os dados para cada plataforma.

No Capítulo 9 , Implementando uma Arquitetura MVI, mostramos como pode-


mos alterar a camada de apresentação de um aplicativo sem afetar outras ca-
madas. Em um aplicativo limpo, isso também deve ser possível para a camada
de dados. Vimos como as bibliotecas mudaram e evoluíram ao longo do
tempo. Quando as bibliotecas de rede mudam, devemos ser capazes de fazer a
transição para novas bibliotecas sem causar problemas nos outros módulos de
um aplicativo. O mesmo princípio pode ser aplicado ao armazenamento local.
Devemos ser capazes de mudar de Room para outras formas de persistência
de dados localmente. Uma boa regra de como os módulos devem ser criados é
ver cada módulo como uma biblioteca que pode ser lançada e imaginar-se
como o usuário final. Agora você deve ter uma boa ideia de como a arquite-
tura limpa deve funcionar, os problemas que ela está tentando resolver e
como você pode aplicá-la a um aplicativo Android.

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_10_ePub.xhtml 37/37

Você também pode gostar