No post anterior, sobre Mockito, falei um pouco sobre ScalaTest, sem, no entanto, entrar em detalhes.
No post atual, pretendo então relatar alguma experiência que tive com a linguagem Scala e com a biblioteca ScalaTest.
No post atual, pretendo então relatar alguma experiência que tive com a linguagem Scala e com a biblioteca ScalaTest.
Um pouco sobre Scala
Conforme o site oficial:
Scala é uma linguagem de programação de propósito geral projetada para expressar padrões de programação comuns de uma forma concisa, elegante e type-safe. Suavemente integra recursos de linguagens orientadas a objetos e funcional, permitindo que programadores Java e outros a serem mais produtivos. Tamanhos de código são normalmente reduzidos por um fator de 2-3 em relação a uma aplicação Java equivalente.
Adicionalmente, Scala é uma linguagem que executa sobre a Java Virtual Machine, assim como a própria linguagem Java, apontando, na verdade, o que tem sido uma grande tendência no universo Java: Uma plataforma multi-linguagem com ferramentas adequadas para diversas situações, mantendo por baixo de tudo o seu mecanismo de OO conhecido como a plataforma mais madura atualmente.
Se você é desenvolvedor Java, vale a pena (se não for já um dever) conhecer essas linguagens que compartilham a JVM, pois em inúmeras situações Java não será a linguagem mais adequada, e aí, como bons desenvolvedores que fazem o "tema de casa", temos que saber discernir.
Algumas peculiaridades percebidas de cara são a ausência de ponto-e-vírgula, a desobrigação de () na chamada de métodos que não recebem parâmetros, o tratamento de classes como objetos, etc.
Se você é desenvolvedor Java, vale a pena (se não for já um dever) conhecer essas linguagens que compartilham a JVM, pois em inúmeras situações Java não será a linguagem mais adequada, e aí, como bons desenvolvedores que fazem o "tema de casa", temos que saber discernir.
Algumas peculiaridades percebidas de cara são a ausência de ponto-e-vírgula, a desobrigação de () na chamada de métodos que não recebem parâmetros, o tratamento de classes como objetos, etc.
ScalaTest
As devidas apresentações já foram feitas no post anterior então vamos à sua estrutura.
ScalaTest oferece algumas Traits padrão para serem implementadas/estendidas que abrangem diferentes abordagens de testes. Dificilmente você vai usar todas, ou até muitas delas, na maioria dos casos, vai utilizar uma ou duas. Uma dá uma flexibilidade maior de implementação das próprias assertions, outra é para testes semelhantes (quase idênticos) aos com RSpec para Ruby (com describe, it, etc.), outra é mais no estilo JUnit, outra xUnit, dentre outras. A lista completa com uma breve descrição pode ser conferida neste link.
ScalaTest oferece algumas Traits padrão para serem implementadas/estendidas que abrangem diferentes abordagens de testes. Dificilmente você vai usar todas, ou até muitas delas, na maioria dos casos, vai utilizar uma ou duas. Uma dá uma flexibilidade maior de implementação das próprias assertions, outra é para testes semelhantes (quase idênticos) aos com RSpec para Ruby (com describe, it, etc.), outra é mais no estilo JUnit, outra xUnit, dentre outras. A lista completa com uma breve descrição pode ser conferida neste link.
Scala e ScalaTest com Eclipse IDE
Para a IDE top-of-mind, Eclipse, é possível instalar plugins que tornam natural a criação de projetos Scala, testes com ScalaTest e tudo mais. Os dois plugins que utilizei para Scala na versão Juno da IDE seguem abaixo. Para instalá-los, utilize estas URLs no menu Help -> Install New Software.
- Scala IDE: http://download.scala-ide.org/nightly-update-juno-master-29x
- Scala Worksheet: http://download.scala-ide.org/nightly-build-worksheet-scalaide21-29/site
Para ScalaTest, siga as dicas do site oficial:
Finalmente, exemplos
No meu caso, achei conveniente, e extremamente interessante, explorar uma combinação de JUnit com should matchers.
A trait JUnitSuite estende org.scalatest.Suite e org.scalatest.junit.AssertionsForJUnit, e a trait ShouldMatchersForJUnit estende org.scalatest.matchers.ShouldMatchers e org.scalatest.junit.AssertionsForJUnit. Dessa forma, recursos valiosos da biblioteca ScalaTest são combinados para utilização com JUnit, como veremos em seguida.
A trait JUnitSuite estende org.scalatest.Suite e org.scalatest.junit.AssertionsForJUnit, e a trait ShouldMatchersForJUnit estende org.scalatest.matchers.ShouldMatchers e org.scalatest.junit.AssertionsForJUnit. Dessa forma, recursos valiosos da biblioteca ScalaTest são combinados para utilização com JUnit, como veremos em seguida.
package scalatest import java.nio.charset.Charset import java.nio.file.FileSystems import java.nio.file.Files import java.nio.file.StandardOpenOption import java.util.ArrayList import org.junit.AfterClass import org.junit.Before import org.junit.BeforeClass import org.junit.Test import org.mockito.Matchers.anyListOf import org.mockito.Matchers.anyObject import org.mockito.Matchers.anyString import org.mockito.Mockito.doCallRealMethod import org.mockito.Mockito.mock import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.scalatest.junit.JUnitSuite import org.scalatest.junit.ShouldMatchersForJUnit import com.adornes.somefun.Controller import com.adornes.somefun.Util import com.adornes.somefun.exception.MissingAgeException import com.adornes.somefun.exception.MissingNameException // Changing the object of the class // Equivalent to the 'static' Java approach. object ReaderTest { val TEST_FILE = "test_file" @BeforeClass def createTestFile { var lines = new ArrayList[String] lines.add("Alice 25 years old") lines.add("Bob 26 years old") lines.add("Carol 27 years old") Files.write(FileSystems.getDefault.getPath(".", TEST_FILE), lines, Charset.defaultCharset, StandardOpenOption.CREATE) } @AfterClass def deleteTestFile { var testFile = FileSystems.getDefault.getPath(".", TEST_FILE) Files.delete(testFile) } } ... ...
Nas linhas acima temos a primeira parte do nosso código de exemplo. Primeiro, uma porção de imports. Perceba já aí que os ponto-e-virgula não são necessários, e nem a palavra-chave static para importação de métodos estáticos.
Na linha 30, a definição object ReaderTest indica que o que vem em seguida são alterações no objeto da própria classe ReaderTest, o que na mentalidade da linguagem Java é equivalente à declaração de atributos e métodos estáticos. JUnit exige que os métodos marcados como @BeforeClass e @AfterClass sejam estáticos, por isso em Scala eles devem ser declarados dentro de um bloco object.
Na linha 32, a palavra-chave val indica, em Scala, uma constante. Nos métodos que seguem após a linha 32, podemos ver a utilização de annotations exatamente da mesma forma que faríamos em um código JUnit escrito em Java. Nas linhas 42 e 49, um pouquinho de NIO.2, nova biblioteca de I/O do Java 7.
Na linha 30, a definição object ReaderTest indica que o que vem em seguida são alterações no objeto da própria classe ReaderTest, o que na mentalidade da linguagem Java é equivalente à declaração de atributos e métodos estáticos. JUnit exige que os métodos marcados como @BeforeClass e @AfterClass sejam estáticos, por isso em Scala eles devem ser declarados dentro de um bloco object.
Na linha 32, a palavra-chave val indica, em Scala, uma constante. Nos métodos que seguem após a linha 32, podemos ver a utilização de annotations exatamente da mesma forma que faríamos em um código JUnit escrito em Java. Nas linhas 42 e 49, um pouquinho de NIO.2, nova biblioteca de I/O do Java 7.
Nas linhas abaixo, vemos a continuação do mesmo código acima:
... ... class ReaderTest extends JUnitSuite with ShouldMatchersForJUnit{ var controller : Controller = _ @Before def setUp { controller = new Controller } @Test def testProcess { var mockReader = mock(classOf[Reader]) doCallRealMethod.when(mockReader).readFile(anyString) doCallRealMethod.when(mockReader).readLines(anyListOf(classOf[String])) var lines = mockReader.readFile(ReaderTest.TEST_FILE) mockReader.readLines(lines) verify(mockReader, times(lines.size)).buildPerson(anyString) verify(mockReader, times(lines.size)).addPerson(anyObject()) } @Test def testBuildPersonFail { intercept[MissingAgeException]{ controller.buildPerson("Dave") // How old is he?? } intercept[MissingNameException]{ controller.buildPerson("100 years old") // Who is 100 years old?? } } @Test def testBuildPersonSuccess { var per1 = controller.buildPerson("John 30 years old") var per2 = controller.buildPerson("Peter 31 years old") per1.age should be (30) per2.age should be (31) } }
Dessa vez começamos com uma declaração class, o que significa que estamos definindo atributos e métodos para as instância dessa classe (e não mais estáticos como na declaração object). Na linha 14 temos um método com a mesma ideia que exemplifiquei no post anterior usando Mockito.
Nas linhas 28 e 31, a palavra-chave intercept verifica se a exceção especificada está sendo lançada.
E nas linhas 41 e 42, quase desapercebido, aparece o uso dos should matchers. Que por baixo dos panos traduz a validação para JUnit. Fantástico, não?
Nas linhas 28 e 31, a palavra-chave intercept verifica se a exceção especificada está sendo lançada.
E nas linhas 41 e 42, quase desapercebido, aparece o uso dos should matchers. Que por baixo dos panos traduz a validação para JUnit. Fantástico, não?
Performance
Todos os meus testes foram feitos inicialmente com JUnit e Java somente. Depois migrei para Scala com ScalaTest e pude ter a experiência que relatei aqui. No entanto, pelo que medi, o desempenho caiu pela metade na execução dos testes. Para minha aplicação, foi de mais ou menos 1 segundo para meio segundo, ou seja, perda insignificante, no entanto, para uma aplicação real com talvez centenas de cenários de teste pode haver uma perda significativa. Aí se alguém tiver essa experiência, pode compartilhar aí!
Conclusão
Este post misturou bastante coisa, e não aprofundou muito em algumas, mas espero que tenha gerado aquele interesse de olhar mais nos links que referenciei no decorrer do texto. Fica minha recomendação para a utilização da linguagem Scala e, para testes, acho que vale a pena dar uma olhada em ScalaTest sem abandonar o bom e velho JUnit.
Nenhum comentário:
Postar um comentário