Codemountain, Paulo Suzart's Blog

Especificando validações com Scala, Specs e Lift – Parte I

with one comment

Pra variar um post sem ligação alguma com o anterior. Mas vamos lá.

Há pouco mais de um ano que comecei a estudar Scala, e durante este tempo me dediquei quase que puramente à linguagem. O máximo que brinquei foi com Scala SBT, o Dispatch do n8han e outras coisinhas. Acredito que até mesmo por não ser meu foco profissional (por hora) demorei pra estudar o Lift, o framework MVC para Scala.

Completei o getting started do framework e comecei a fazer uma segunda aplicação de estudo com @lucastex e @r4f4e1. Mas o motivo desse post é um processo de desenvolvimento que me chamou a atenção depois que vi o Cucumber. BDD, ou BehaviourDrivenDevelopment é uma metodologia ágil que vem como um complemento – ou mesmo evolução – ao TDD (Test Driven Development) e se transformando em uma ferramenta primordial para o DDD (DomainDrivenDesign).

Existem muitos frameworks BDD como RSpec, Specs, Scala Test, Cucumber, JBehave, jsspec, e escolhi o Specs por algum motivo obscuro.

Sendo bem objetivo e tentando reduzir a quantidade de palavras no post (deixando o máximo de código possível), o foco do BDD é a construção de comportamentos esperados para o sistema ao invés de testes. A construção de testes, em última instância, tem interesse em verificar o comportamento que o sistema deve (should) ter em diversos cenários (examples), por isso BDD.

Outro ponto importante é a tão falada linguagem ubíqua, ou seja, uma linguagem que todos os envolvidos na construção do software consigam entender. Frameworks BDD permitem que esta linguagem seja executável e escrita na propria linguagem de construção do sistema. Scala é muito poderosa na construção de DSLs (Domain Specific Languages), chegando ao ponto do código se parecer com uma escrita em ingles, não em uma linguagem de programação. Neste post, o comportamento que escolhi especificar foi de validação de dados de domínio:

"Create/Update a ToDo item" should {
   "show the following error messages:" >> {
     "Description must be 3 characters" >> {
       "if description field length is less than 3" in {
         val todo = ToDo.create.owner(User.currentUser)
         todo.desc("").validate must
         contain(FieldError(todo.desc, Text("Description must be 3 characters")))
       }
     }
     "Priority must be 1-10" >> {
       "if priority field is not between, including, 1 and 10" in {
         val todo = ToDo.create.owner(User.currentUser)
         todo.priority(-1).validate must
         contain(FieldError(todo.priority, <b>Priority must be 1-10</b>))
        }
     }
   }
 }

Nos parágrafos acima note em negrito as palavras should e example. Indicamos com should o que o sistema (ToDo app) deve fazer nos (in) exemplos, ou cenários, que seguem.

Dada cada uma das mensagens esperadas, o trecho de código delimitado por in {…} é implementado com um ou mais matchers.  o matcher must contain é usado aqui por que o método de Validação de uma classe mapper no lift retorna um List[FieldError]. FieldError é uma case class de construção FieldError[Identifier, NodeSeq]. Logo, o matcher must contain verifica a existência de um objeto com o formato especificado na lista de erros retornados na validação.

Logo, os matcher são os responsáveis por assegurar que a especificação descrita no texto será atendida pelo resultado do código nos exemplos.

O guia de matchers apresenta uma listagem completa desta DSL que pode ser aplicada em Iterables, String, Objects, Maps, Numer, Options, ScalaChecks, XML, Arquivos e grafos de objetos. E para ilustrar o uso de matchers para string.

Ao executar a especificação acima, temos o resultado:

-------------------------------------------------------
Running com.liftworkshop.todo.TodoSpecTest
Tests run: 3, Failures: 3, Errors: 0, Skipped: 0, Time elapsed: 2.694 sec <<< FAILURE!
Running com.liftworkshop.todo.AppTest
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.042 sec
Results :
Failed tests:
  Create/Update a ToDo item should show the following error messages: Description must be 3 characters if description field length is less than 3
  Create/Update a ToDo item should show the following error messages: Priority must be 1-10 if priority field is not between, including, 1 and 10
  Create/Update a ToDo item should show the following error messages: Priority must be 1-10 if priority field is not between, including, 1 and 10
Tests run: 5, Failures: 3, Errors: 0, Skipped: 0

Isto por que os campos desc e priority (mostrados abaixo) não implementam nenhuma validação:

class ToDo extends LongKeyedMapper[ToDo] with IdPK {
    lazy val priorityList = (1 to 10).map(v =>; (v.toString, v.toString))
    def getSingleton = ToDo
    object done extends MappedBoolean(this)
    object owner extends MappedLongForeignKey(this, User)
    object priority extends MappedInt(this) {
        override def defaultValue = 5
        override def _toForm = Full(select(ToDo.priorityList,
                                           Full(is.toString),
                                           f => set(f.toInt)))
    }
    object desc extends MappedPoliteString(this, 128)
}
object ToDo extends ToDo with LongKeyedMetaMapper[ToDo]

E aqui chegamos a um ponto interessante do BDD, certamente herdado do TDD: vamos escrever o código estritamente necessário para atender a nossa especificação. Supondo que você tenha o mínimo de conhecimento com Lift e quem sabe tenha feito o getting started, adicionemos as validações assim:

    object desc extends MappedPoliteString(this, 128) {
        override def validations = valMinLen(3, &quot;Description must be 3 characters&quot;) _ :: super.validations
    } // para o mapeamento da descrição e ...
    object priority extends MappedInt(this) {
    ...
        override def validations = validPriority _ :: super.validations
        def validPriority(in : Int) : List[FieldError] =
        if (in &gt; 0 && in >= 10) Nil
        else List(FieldError(this, <b>Priority must be 1-10</b>))
    ...
    }
<p>

O método validations é redefinido em cada propriedade mepada que necessida validação. O campo desc apenas obriga o tamanho mínimo para 3 com a mensagem especificada definido pela função utilitária valMinLen. Já o campo priority instancia diretamente um FieldError passando o campo contendo o erro junto com a mensagem em negrito.

Este projeto foi montado usando o NetBeans junto com o Maven conforme descrito no getting started do Lift. Fazendo um pequeno ajuste (descrito no próximo post), temos a execução da especificação integrada ao JUnit na IDE NetBeans.

Executando outra vez a especificação, teremos todos os testes ok como na figura a seguir. Um print do Netbeans.

Especificação atendida!

Especificação atendida!

Esta foi talvez a parte mágica da coisa. Onde tudo já está funcionando e precisamos só curtir o BDD num exemplo sofrível com intuito de ilustrar esta metodologia, o framework Specs, o framework Lift e a linguagem Scala.

O código completo do projeto estará disponível no próximo Post desta série. As versões utilizadas no projeto são: Lift 1.0, Scala Specs 1.6.1, EasyMock 2.5.2 (instalada no repositório maven local). E no próximo Post teremos: Configurando seu projeto Maven para o uso de Scala Specs; Explorando mais matchers; Configurando Contexto Specs para execução da especificação em uma LiftSession acessando a base de dados; Especificando Snippets Lift, e mais. Será que cabe tudo em mais um Post? Vai ter que caber.

Se puder visite o site do BDD e conheça mais sobre esta metodologia no mínimo interessante. Dado que não sou especialista em nenhum metodologia ágil, se você tem ou teve alguma experiência prática com BDD, TDD ou DDD deixe seu comentário ou sua sugestão sobre como abordar o tema de maneira mais prática.

Veja a Parte II deste post.

Written by paulosuzart

outubro 14, 2009 às 3:21 am

Publicado em bdd, lift, scala

Tagged with , , ,

Uma resposta

Subscribe to comments with RSS.

  1. […] validações com Scala, Specs e Lift – Parte II No último Post tivemos uma visão inicial do uso de Specs, BDD e um exemplo de utilização do framework. Mas como […]


Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: