Desenvolvimento Android Baseado Em Testes – Parte 2

On 24 de abril de 2013 by Bernardo Reis


android_thumbnail

Introdução

Como continuação da parte 1 do Desenvolvimento Android Baseado em Testes, faremos o exemplo proposto para mostrar como deve ser feito esse tipo de desenvolvimento. O exemplo citado na parte 1 era a criação de um conversor de temperaturas.

Recapitulação:

O Desenvolvimento baseado em testes, ou Test Driven Development (TDD), consiste basicamente dos seguintes passos:

  1. Escrever um caso de teste
  2. Executar todos os testes
  3. Se não passar, modifique o código e volte ao passo 1
  4. Se necessitar de refatoração, refatore e volte a passo 1

Vamos, agora, analisar novamente a lista de requisitos que nosso conversor de temperaturas deve cobrir.

  • Conversão Celsius para Fahrenheit e vice-versa.
  • A interface de usuário deverá possuir dois campos para temperatura, sendo um para temperatura em Celsius e o outro para temperatura em Fahrenheit.
  • Quando for digitada o valor em um dos campos o outro deve ser atualizado com o valor convertido.
  • Se ocorrer erros os mesmos devem ser exibidos ao usuário, preferencialmente nos mesmos campos.
  • A aplicação dever ter um espaço da tela reservado da tela para o teclado virtual não esconder algum dos campos.
  • Os campos são inicializados com 0.
  • A entrada será dada por valores decimais com 2 dígitos após a virgula.
  • Dígitos devem ficar alinhados a direita.
  • O ultimo valor digitado deve ser mantido quando o programa entra em estado de pausa.

Mãos à Obra

Agora que já demos uma olhada nos requisitos vamos criar os projetos e desenvolver nosso conversor com o método de desenvolvimento apresentado. Serão dois projetos, um principal onde o aplicativo será desenvolvido e outro que será o projeto de testes. No Eclipse você deverá criar um projeto, na janela de novo projeto após preencher os dados básicos você deverá clicar em Next para poder criar o projeto de testes. Selecione a opção Create a Test Project e a Use default location. Em seguida, selecione o projeto a ser testado e clique em Next. Escolha a versão do android e clique em Finish para criar o projeto de testes.

Repare que o projeto de testes foi criado. Agora, vá ao pacote da pasta ‘src’ do projeto e crie um JUnit Test Case. Coloque o nome da classe de testes (geralmente coloca-se o nome da Activity testada acrescentada de “Test”, por exemplo, se a Activity se chama TemperatureConverter, o teste teria o nome de TemperatureConverterTest).

Nessa parte do tutorial, aprenderemos a testar o layout de uma activity, então, em Superclass, coloque android.test.ActivityInstrumentationTestCase2<T>, onde T é a referência à activity a ser testada. Marque as opções “setUp()”, “tearDown()” e “constructor”. Em seguida, adicione novamente a referência à activity a ser testada em “Class under test”. Pressione Next.

Nessa nova página, estão disponíveis os métodos implementados na classe a ser testada, e, nesse caso, como nada foi implementado ainda, apenas o método onCreate estará disponível, porém, não será testado nesse tutorial. Nessa página, apenas marque “Create final method stubs”, para prevenir que os testes sejam modificados por subclasses.

Você verá que a classe criada contém alguns erros. Primeiro, pressione Shift+Ctrl+O para importar pacotes que estejam faltando. Em seguida, corrija o construtor da classe, que no meu caso, tem o nome de TemperatureConverterTest da seguinte maneira:

De:

[code language=”java” collapse=”false”]

public TemperatureConverterTest (String name) {
super(name);
} [/code]

Para:

[code language=”java” collapse=”false”]

public TemperatureConverterTest (String name) {
super(TemperatureConverter.class);
setName(name);
}

[/code]

Devemos criar também, um construtor que não exija argumentos. Este chamará o construtor com a String como argumento referente ao nome:

[code language=”java” collapse=”false”]

public TemperatureConverterTest () {
this("TemperatureConverterTest");
}

[/code]

Assim que os erros foram todos corrigidos, implementaremos o início do teste, o método setUp, que é sempre o primeiro a ser chamado pelos testes do Android, similar ao método onCreate de uma activity. Devemos criar uma variável global da activity a ser testada e, dentro do método setUp, usar o getActivity para inicializar a activity testada.

[code language=”java” collapse=”false”]

TemperatureConverter mActivity;

protected void setUp() throws Exception {
super.setUp();
mActivity = getActivity();
}

[/code]

Para que um método criado seja um teste, ele deve ser público e começar com “test”. Faremos então um método para testar as pré-condições do teste, ou seja, vamos testar se a activity que estamos testando não é nula. Criaremos o método como “final” para evitar alterações futuras indesejadas.

[code language=”java” collapse=”false”]

public final void testPreconditions(){
assertNotNull(mActivity);
}

[/code]

Agora, executamos o primeiro teste. O resultado será positivo, pois, provavelmente, sua única activity do projeto já foi criada juntamente com o mesmo.

Para começar a desenvolver os testes específicos, devemos primeiro relembrar como deve ser nossa aplicação. Vamos rever a tela que o projeto deve ter:

Design proposto para tela do aplicativo

Design proposto para tela do aplicativo

 

Analisando a tela, veremos que iremos precisar de 3 TextView(“Entre com a temperatura:”, “Celsius”, e “Fahrenheit”)  e de 2 EditText para a inserção das temperaturas. Criaremos então, as variáveis globais para cada uma dessas Views.

[code language=”java” collapse=”false”]
EditText mCelsius;
EditText mFahrenheit;
TextView mCelsiusLabel;
TextView mFahrenheitLabel;
TextView mMessage;
[/code]

Depois de criados os objetos, devemos referenciá-los aos respectivos ID’s do xml do layout de nossa tela. Faremos isso dentro do método setUp, depois de recebermos a activity. Então, nosso método setUp atualizado ficará dessa forma:

[code language=”java” collapse=”false”]

protected void setUp() throws Exception {
super.setUp();
mActivity = getActivity();
testPreconditions();
mCelsius = (EditText) mActivity.findViewById(com.example.temperatureconverter.R.id.celsius);
mFahrenheit = (EditText) mActivity.findViewById(com.example.temperatureconverter.R.id.fahrenheit);
mCelsiusLabel = (TextView) mActivity.findViewById(com.example.temperatureconverter.R.id.celsius_label);
mFahrenheitLabel = (TextView) mActivity.findViewById(com.example.temperatureconverter.R.id.fahrenheit_label);
mMessage = (TextView) mActivity.findViewById(com.example.temperatureconverter.R.id.main_message);

}

[/code]

Depois de referenciados, testaremos a existência de cada objeto, separando EditText de TextView, dessa forma:

[code language=”java” collapse=”false”]

public final void testHasInputFields(){
assertNotNull(mCelsius);
assertNotNull(mFahrenheit);
}

public final void testHasTextViews(){
assertNotNull(mCelsiusLabel);
assertNotNull(mFahrenheitLabel);
assertNotNull(mMessage);
}

[/code]

Ainda não podemos executar o teste, pois ainda não implementamos o xml do projeto principal. Seguimos, assim, o método de desenvolvimento proposto pela primeira parte do tutorial, que é criarmos o um teste que falhe (nesse caso, o teste ainda não pode nem ser compilado), para depois implementar o código que passe no teste. Então, depois de ajustado o xml (no exemplo, o layout utilizado foi LinearLayout) referente ao que foi implementado nos testes (no caso, as views devem apenas ser criadas, sem nenhum elemento específico citado, como por exemplo, posição da view na tela), poderemos compilar e executar os testes, que serão bem sucedidos, pois, agora, todos os elementos citados existem.

Vamos testar agora se os campos começam vazios. Para isso, criamos um novo método de teste:

[code language=”java” collapse=”false”]

public final void testFieldsShouldStartEmpty(){
assertEquals("", mCelsius.getText().toString());
assertEquals("", mFahrenheit.getText().toString());
}

[/code]

Ao executar o teste, veremos que teremos sucesso, pois nenhum valor inicial foi atribuído aos EditText quando criados, caso tenham sido criados diretamente pelo xml. Se você criou a tela pelo assistente de criação e o teste falhou, confira se o assistente não adicionou um texto aos campos automaticamente e remova-o.

Agora que sabemos que as views existem e os campos iniciais estão nulos, verificaremos se todas as views estão na tela do aparelho. Faremos um método de teste da seguinte maneira, utilizando asserts de ViewAsserts:

[code language=”java” collapse=”false”]

public final void testFieldsOnScreen(){
final Window window = mActivity.getWindow();
final View view = window.getDecorView();
ViewAsserts.assertOnScreen(view, mCelsius);
ViewAsserts.assertOnScreen(view, mFahrenheit);
ViewAsserts.assertOnScreen(view, mCelsiusLabel);
ViewAsserts.assertOnScreen(view, mFahrenheitLabel);
ViewAsserts.assertOnScreen(view, mMessage);
}

[/code]

Nesse método, criamos uma View auxiliar(que nesse caso é toda a tela da aplicação) e verificamos se cada View que criamos inicialmente está dentro da View auxiliar.

Esse teste é apenas para confirmar o que já achamos que sabemos, pois vemos os elementos na tela de nosso assistente durante a criação, mas é aconselhável a criação desse teste para ter a confirmação e se certificar que isso não mudará durante as alterações feitas no programa.

Nosso próximo passo será testar o alinhamento de cada View criada. Criamos, então, o método:

[code language=”java” collapse=”false”]

public final void testAlignment() {
ViewAsserts.assertLeftAligned(mCelsiusLabel, mCelsius);
ViewAsserts.assertLeftAligned(mFahrenheitLabel, mFahrenheit);
ViewAsserts.assertLeftAligned(mCelsius, mFahrenheit);
ViewAsserts.assertRightAligned(mCelsius, mFahrenheit);
}

[/code]

Usando novamente a ViewAsserts, testamos o alinhamento de duas Views, comparando se ambas estão com suas bordas na mesma posição do eixo x. Por isso verificamos se os dois EditText estão alinhandos tanto pela esquerda quanto pela direita.

Nesse teste, estaremos novamente bem sucedidos porque o LinearLayout já arranja suas Views da forma que esperamos, mas caso você não tenha feito com LinearLayout, o teste poderá falhar e você terá que ajustar o xml da aplicação.

Agora que verificamos o alinhamos relativo entre as Views, vamos nos certificar de que os EditText estão com a mesma largura da tela. Repare que foram utilizadas mensagens personalizadas para auxiliar na identificação de um erro, caso ocorra.

[code language=”java” collapse=”false”]

public final void testInputFieldsCoverEntireScreen(){
final int expected = LayoutParams.MATCH_PARENT;
final LayoutParams lp = mCelsius.getLayoutParams();
assertEquals("mCelsius não cobre a tela ", expected, lp.width);
lp = mFahrenheit.getLayoutParams();
assertEquals("mFahrenheit não cobre a tela", expected,lp.width);
}

[/code]

Dessa vez, o teste irá falhar quando executado, pois as Views do xml ainda não foram configuradas corretamente. Devemos então, ajudar o android:layout_width para “match_parent”. Podemos agora ir para o próximo passo.

Vamos testar o tamanho das fontes:

[code language=”java” collapse=”false”]

public final void testFontSizes(){
final float expected = 24.0f;
assertEquals("Erro na fonte do Celsius Label", expected, mCelsiusLabel.getTextSize());
assertEquals("Erro na fonte do Fahrenheit Label", expected, mFahrenheitLabel.getTextSize());</pre>
}
[/code]

O teste falhará, pois a fonte padrão não é 24px. Então, devemos fazer as devidas alterações no xml para que o teste seja bem sucedido.

Hora de testar as margens.

[code language=”java” collapse=”false”]

public final void testMargins(){
LinearLayout.LayoutParams lp;
final int expected = 6;
lp = (LinearLayout.LayoutParams) mCelsius.getLayoutParams();
assertEquals(expected, lp.leftMargin);
assertEquals(expected, lp.rightMargin);
lp = (LinearLayout.LayoutParams) mFahrenheit.getLayoutParams();
assertEquals(expected, lp.leftMargin);
assertEquals(expected, lp.rightMargin);
}

[/code]

Neste teste, testamos se a margem dos EditText é 6px, o que ainda não foi configurado e, por isso, o teste falhará. Então, seguindo a metodologia do desenvolvimento, vamos novamente ao xml consertar os erros acusados pelos testes.

Testaremos, agora, se os textos inseridos nos EditText estão à direita, como na figura da aplicação. Para isso, testaremos a justificação dos EditText da seguinte forma:

[code language=”java” collapse=”false”]

public final void testJustification(){
final int expected = Gravity.RIGHT|Gravity.<i>CENTER_VERTICAL</i>;
int actual = mCelsius.getGravity();
assertEquals(String.format("Expected 0x%02x but was 0x%02x", expected, actual), expected, actual);
actual = mFahrenheit.getGravity();
assertEquals(String.format("Expected 0x%02x but was 0x%02x", expected, actual), expected, actual);
}

[/code]

As mensagens personalizadas convertem o valor expressado para hexadecimal, já que a classe Gravity armazena seus dados nessa base.

O teste falhará quando executado, pois a propriedade gravity ainda não foi configurada e o padrão do android não é o que estamos esperando. Logo, mais uma vez precisamos ir ao xml e implementar as mudanças corretamente para que o teste não falhe. Para isso, basta colocarmos o comando android:gravity=“right|center_vertical” nos EditText correspondentes ao teste.

Por último, vamos testar se o espaço da tela reservado para o teclado virtual não foi invadido, já que nos requisitos do nosso projeto, está especificado que todos os seus elementos devem ser visíveis durante todo o tempo de execução. Para isso, o último elemento não pode estar mais baixo que o limite do teclado virtual, que, no nosso caso, será 280px. O método de teste é:

[code language=”java” collapse=”false”]

public final void testVirtualKeyboardSpaceReserved(){
final int expected = 280;
final int actual = mFahrenheit.getBottom();
assertTrue(actual <= expected);
}

[/code]

O teste passará se o valor referente ao eixo y do elemento mFahrenheit for menor que o espaço reservado para o teclado, que é 280. Este teste deve ser bem sucedido diretamente.

Conclusão

Repare que durante o andamento deste tutorial, os requisitos do projeto eram primeiramente convertidos em testes que provavelmente falhavam, uma vez que o requisito testado ainda não tinha sido tratado. Depois que víamos as falhas dos testes, corrigíamos o problema, seja alterando algo que já existia ou implementando algo novo. Assim, o teste tinha um resultado positivo e o requisito implementado pelo teste fora satisfeito. Aprendemos na prática, então, como desenvolver uma aplicação utilizando o Desenvolvimento baseado em testes, ou Test Driven Development (TDD), e também a testar o layout das telas do android.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *