TDD no Android

Esse artigo é um passo-a-passo de como eu consegui escrever os primeiros testes unitários para meu projeto. Escrever os testes até que não é algo complicado para quem já tem o costume, mas se você ainda está perdido e nunca fez, então é capaz que irei te ajudar.

Preparando o ambiente

Estou usando Kotlin no meu projeto, mas não muda nada em relação ao Java, apenas a forma de escrever o código.
Para quem já possui Kotlin configurado, basta adicionar essas duas bibliotecas no arquivo build.gradle: 
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
testCompile "com.nhaarman:mockito-kotlin:1.5.0"

O que preciso para começar?

Antes de tudo, seu código precisa estar com uma arquitetura bem estruturada para poder ser testado. Se você tiver uma activity ou fragment que faz tudo, vai ser praticamente impossível escrever testes. Veja até o final desse artigo para entender como que um código bem organizado torna tudo mais simples, e depois começe a repensar na arquitetura do seu projeto.

Basicamente, recomendo usar os princípios do SOLID no seu projeto. Se você não sabe o que é isso, recomendo dar uma lida:

Decida o que precisa ser testado

No meu caso, estou usando MVP, e decidi testar a camada do Presenter. Pode até parecer que não faz muito sentido, mas os testes ajudarão bastante caso venha a ter mudanças de requisitos e seu presenter passe a realizar novas funções. Qualquer refactor que venha a ser feito não irá simplesmente estragar o que já funciona. E se mesmo assim causar algum bug novo, então é porquê seus testes não cobriram aquele caso, mas aí vai depender do quão bem você deseja tratar todos os cenários possíveis.

Outra opção que eu tinha, seria testar a camada de “UseCases”, responsável por fazer validações e chamar (ou não) a camada de dados. Mas por enquanto quis focar apenas no Presenter.

Estamos quase lá

Só para certificar que estamos na mesma página, sugiro que você organize seu código de acordo com o seguinte:
1- Crie um contrato, definindo a interface para o presenter com ações que o usuário pode tomar + eventos importantes do sistema, e definindo a interface para a view;
2- Cuidado com as dependências do seu Presenter. Se você usa algo para controle de injeção de dependências, como o Dagger, então está tudo ok. Mas se não é seu caso, certifique que você não está cometendo o erro de criar a instância dessas dependências no construtor do seu Presenter (ou em algum outro lugar). O ideal é passar tudo por parâmetro no construtor, de forma que o responsável por definir a implementação dessas dependências é quem cria seu presenter.

Agora sim, vamos começar

Agora está tudo OK para escrever testes. Mas por que esperar o final do desenvolvimento se já podemos começar pelos testes? Tudo o que precisamos fazer é:

  • Criar o contrato, definindo as interfaces do presenter e da view
  • Criar a implementação do presenter, com todos os métodos em TODO
  • Mockar todas as dependências do seu presenter

No construtor do presenter, já devemos saber com quais outras classes ele deve interagir. Por exemplo, vamos considerar uma tela de Login. Sabemos que para realizar o login de fato, vai precisar chamar um “UseCases” que é responsável por validar os inputs de e-mail/senha válidos e fazer ou não a request para o servidor.

Como fica o código?

Vamos considerar o seguinte caso:

interface LoginPresenter {
fun onClickToLogin(login: String, password: String)
}

interface LoginView {
fun showInvalidUsername()
fun showInvalidPassword()
fun showInternetConnectionIssue()
fun navigateToHomeScreen()
}
class MyLoginPresenter(val view: LoginView, val loginUseCases: LoginUseCases): LoginPresenter {
override fun onClickToLogin(login: String, password: String) {
TODO("not implemented")
}
}

Nesse cenário simples, está tudo pronto para começarmos a escrever os testes.

class LoginPresenterTest {
private lateinit var presenter: LoginPresenter
@Mock lateinit var view: LoginView
@Mock lateinit var loginUseCases: LoginUseCases

@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
presenter = MyLoginPresenter(view, loginUseCases)
}
}

Agora só falta de fato você pensar nos possíveis comportamentos do método onClickToLogin(...)

@Test
fun `given an invalid username, when the user attempts to login, it should show failure`() {
doThrow(InvalidEmailException()).`when`(loginUseCases).performLogin(any(), any())

presenter.onClickToLogin("any login", "any password")

verify(view).showInvalidUsername()
}

Simples assim

Basta agora você explorar outros comportamentos para tratar a maior quantidade de casos possíveis, e assim irá possuir uma boa cobertura de testes.

Quando os testes estiverem prontos, seu código estará falhando para todos eles, e aí sim pode começar a implementar. Após a definição do contrato, podemos paralelizar o trabalho, tendo alguem para cuidar da view e outra pessoa para cuidar do presenter. E se forem criar STUBs para o “UseCases” (uma interface para definir os métodos, e uma classe que implementa de fato, mas inicialmente com tudo em TODO), poderia até ter uma terceira pessoa responsável por isso em paralelo.

Espero que esse artigo tenha sido útil. Os testes unitários não foram muito complexos, mas não gastam tanto tempo assim para serem escritos, e com certeza é melhor do que não ter nada!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.