RSpec Stories - Ruby gerado a partir do texto!

Não, eu não quero que vocês gerem o código dos testes, isto iria apenas criar testes inúteis!
Mas eu acho muito chato ter certeza de que todos os possíveis passos de um arquivo txt com a user story estão presentes nos passos definidos no arquivo .rb
Claro que isto não esta considerando reutilização de passos através de Mixins ou outras técnicas semelhantes, mas este script me ajudou bastante ja, então resolvi compartilhar ele para quem estiver interessado :D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
name=ARGV[0]
all = []
last = ""
f = File.new(name)
f.each_line do |l|
  l.strip!
  l,command,params = *(/^(When|Then|Given|And) (.*)/.match l)
  if l
    command = last if command == "And"
    last = command
    params = params.gsub /"/, '\"'
    all << "#{last} \"#{params}\" do\n    pending\n  end"
  end
end
all.uniq!.sort!
sym_name = name[File.dirname(name).length+1..-5]
puts %Q{require 'stories/helper'
steps_for(:#{sym_name}) do
}
all.each { |l| puts "  #{l}"}
puts %Q{end 
 
with_steps_for :#{sym_name} do
  run "\#{dir = File.dirname(__FILE__)}/#{sym_name}.txt"
end
}

eu salvei este código em um arquivo txt_to_steps.rb, e para utilizar basta executar:
ruby txt_to_steps.rb

Um exemplo de user story (utilizando o formato do RSpec) seria este texto:

Story: new user
  As a company employee
  I want to register in the CRM
  So that I can see and manage company contacts

Scenario: user with no access to the system
  Given the username user1
  And the password mypassword
  When the login form is submited
  Then the login form should be shown again

Scenario: user registration
  Given the username user1
  And the password mypassword
  And the email user1@company.com
  When the registration form is submited
  And there is no other user with the same e-mail or email
  Then the registration should be OK
  And the user should be redirected to /

Scenario: repeated user registration
  Given the username user1
  And the password mypassword
  And the email user1@company.com
  When the registration form is submited
  And there is already another user with the same name or email
  Then the registration should fail
  And the registration form should apear again

Scenario: existing user login
  Given the username user1
  And the password mypassword
  When the login form is submited
  Then the user should be redirected to /


O txt_to_steps pode ser melhorado para tentar identificar alguns padrões, mas como esta agora ja me poupou bastante trabalho :D
Sei que não é o código ruby mais limpo que vocês ja leram, mas para algo escrito em 5 minutos até que ficou legal :D

Se ajudar mais alguem, a única exigência é deixar um comentário aqui dizendo o que poderia ser melhorado no script :D

PS.: sei que o blog anda meio parado demais, mas é por um bom motivo, acho que daqui a um mes aproximadamente volta tudo ao normal e eu posso contar aqui o motivo deste tempo quase sem posts :D

Se você gostou deste post, lembre-se de assinar o RSS feed do blog, para ser notificado de novos posts!

Plugins para rails e grails

Publiquei eles só no meu blog em ingles, mas acho que vale o link aqui pelo menos …

Para quem é tão preguiçoso quanto eu, eu criei um plugin para o rails que reutiliza tudo (ou quase) o que você ja definiu na migration para gerar o model, o nome do plugin é mydry.

E para quem trabalha com Grails, e estava triste até agora por não poder utilizar BDD de forma fácil, eu criei o plugin easyb-test que permite utilizar o easyb para escrever testes para a sua aplicação Grails.

Se quiserem um exemplo de como usar este plugin do easyb, é só olhar o material da minha palestra do FISL deste ano.

Se você gostou deste post, lembre-se de assinar o RSS feed do blog, para ser notificado de novos posts!

Pare de perder tempo escrevendo documentação do código que você escreveu (parte 1)

time_graphic.jpg

Não, eu não estou completamente louco, e não acho que documentação para o código escrito seja uma coisa completamente inútil.
Mas eu acredito que documentação desatualizada é pior do que nenhuma documentação …
E não estou incentivando a escrita de código completamente sem documentação …
E por último, só para constar, eu estou falando de documentação da regra de negócio implementada e não o manual do usuário …

Ok, mas como deixar de perder tempo escrevendo a tal da documentação?
A solução que me vem a cabeça é BDD, ou seja, Behavior Driven Development, junto com a tão falada, documentação executável …

Mas isto existe?
Sim existe, e o pessoal doRuby On Rails já esta bastante acostumado com isto, utilizando o RSpec.
Mas este post não é sobre Ruby On Rails e nem sobre RSpec, e sim sobre o Easyb, que é uma biblioteca para escrita de “Documentação Executável” utilizando Groovy, muito fácil de utilizar para testar código Java …

Para começar com a brincadeira, vou ir ao site do projeto e fazer o download do último release (atualmente 0.7)
criar um projeto java novo no eclipse.
criar uma pasta de nome lib e copiar os seguintes jars para a pasta:commons-cli-1.1.jar easyb-0.7.3.jar groovy-all-1.5.0.jar (todos vem no zip do easyb)
criar mais uma source folder de nome behavior
(O meu eclipse esta com o plugin para edição de código groovy instalado)

Crio um build.xml para executar os testes (ainda não existe um plugin para o eclipse, mas sinceramente, não achei necessário)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="UTF-8"?>
<project name="project" default="run_tests">
	<path id="all.path">
		<pathelement location="${basedir}/bin" />
		<pathelement location="${basedir}/bin-groovy" />
		<fileset dir="${basedir}/lib" includes="**/*.jar" />
	</path>
	<taskdef name="easyb" classname="org.disco.easyb.ant.BehaviorRunnerTask" classpathref="all.path" />
	<!-- ================================= 
          target: default              
         ================================= -->
	<target name="run_tests" description="--> description">
		<easyb failureProperty="easyb.failed">
			<classpath>
				<path refid="all.path" />
			</classpath>
			<behaviors dir="${basedir}/behavior">
				<include name="**/*Story.groovy" />
				<include name="**/*Specification.groovy" />
			</behaviors>
			<report location="story.txt" format="txtstory" />
			<report location="behavior.txt" format="txtbehavior" />
			<report location="easyb.xml" format="xmleasyb" />
		</easyb>
	</target>
</project>

Este build.xml declara a tag do easyb e executa todos os testes, alem de gerar os relatórios, que vocês vão gostar bastante …
Os tipos de relatórios variam bastante de acordo com a versão do easyb, a equipe de desenvolvimento esta melhorando bastante isto, eu estou utilizando aqui a versão do trunk do subversion, se quiserem baixar a mesma versão que uso aqui, podem baixar via git deste repositório : git clone git://github.com/urubatan/easyb.git
(Eu fiz um fork do projeto para adicioar algumas features, e ja estou enviando os patches para os desenvolvedores)

Agora vamos começar a brincadeira :D
Dentro do diretório behavior vou criar um diretório de nome users e um arquivo de nome PermitirRegistroDeUsuariosStory.groovy dentro deste último.
Ou seja, este arquivo vai conter os testes, e a documentação das regras de negócio sobre a criação de novos usuários no sistema …

Quero ter uma breve descrição da história nos meus relatórios, então adiciono uma tag description no inicio do arquivo (esta tag por enquanto só existe no meu fork).

1
2
3
description """Qualquer novo visitante do site deve poder se registrar no sistema, 
desde que as regras de validação definidas sejam atendidas.
Um usuário registrado podera alterar a sua senha sempre que desejar, mas a senha devera atender aos padrões minimos de segurança"""

As histórias são compostas basicamente de 4 comandos:
scenario - define um cenário da história, ou seja, um bloco contendo os outros 3 comandos
given - define a situação inicial
when - define um gatilho, uma ação do usuário que ira disparar alguma ação do sistema, este comando é opcional
then - define o que o sistema deve fazer, e o que vai ser testado

alem destes existe o “and” que atualmente é um comando que não executa nada, serve apenas para deixar o código mais legivel, mas isto vai mudar na próxima versão, o and vai repetir o comando anterior, semelhante ao que acontece no RSpec.

Conhecendo estes comandos podemos começar a escrever a nossa documentação :D
Vou adicionar alguns cenários a minha história e alguns detalhes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
scenario "Um novo usuário precisa confirmar a senha", {
	given "Um novo usuário", {
	}
	when "O usuário preenche o nome de usuário e a senha, mas sem confirmar a mesma", {
	}
	then "A validação do usuário deve falhar", {
	}
	when "O usuário preenche o nome de usuário e confirma a senha corretamente", {
	}
	then "A validação do usuário deve passar",{
	}
}
scenario "Um nome de usuário deve ser único no sistema", {
	given "Um usuário com o formulário preenchido corretamente", {
	}
	then "O registro deve criar o usuário no sistema", {
	}
	when "O nome de usuário ja existe no sistema", {
	}
	then "O registro do usuário deve falhar",{
	}
}
}

Com isto, podemos ver no diretório do projeto os seguintes arquivos criados:
behavior.txt que até o momento esta em branco

story.txt que contem o seguinte texto:

  Story: permitir registro de usuarios
    Qualquer novo visitante do site deve poder se registrar no sistema,
desde que as regras de validação definidas sejam atendidas.
Um usuário registrado podera alterar a sua senha sempre que desejar, mas a senha devera atender aos padrões minimos de segurança

    scenario Um novo usuário precisa confirmar a senha
      given Um novo usuário
      when O usuário preenche o nome de usuário e a senha, mas sem confirmar a mesma
      then A validação do usuário deve falhar
      when O usuário preenche o nome de usuário e confirma a senha corretamente
      then A validação do usuário deve passar

    scenario Um nome de usuário deve ser único no sistema
      given Um usuário com o formulário preenchido corretamente
      then O registro deve criar o usuário no sistema
      when O nome de usuário ja existe no sistema
      then O registro do usuário deve falhar

e easyb.xml que contem uma versão XML dos dois relatórios combinados …
Se você prestar atenção no story.txt, substituindo as palavras em ingles que fazem parte da DSL do easyb, por palavras em portugues, esta é uma ótima documentação para ser validada com o cliente, e com a garantia de que não vai ficar desatualizada por que este é o código fonte dos testes, por tanto sera alterado sempre que a regra de negócio for alterada …

Como assim?
Vamos seguir trabalhando no sistema, agora que ja temos o “behavior”, ou seja, o comportamento definido, vou adicionar algum código a esta história que devera ser testada sempre junto com o build automatizado do sistema:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package users;
 
description """Qualquer novo visitante do site deve poder se registrar no sistema, 
desde que as regras de validação definidas sejam atendidas.
Um usuário registrado podera alterar a sua senha sempre que desejar, mas a senha devera atender aos padrões minimos de segurança"""
 
scenario "Um novo usuário precisa confirmar a senha", {
	given "Um novo usuário", {
		user = new User()
	}
	when "O usuário preenche o nome de usuário e a senha, mas sem confirmar a mesma", {
		user.setName "validName"
		user.setPassword "aPassword"
	}
	then "A validação do usuário deve falhar", {
		ensureThrows(PasswordValidationException){
			user.validate()
		}
	}
	when "O usuário preenche o nome de usuário e confirma a senha corretamente", {
		user.setName "validName"
		user.setPassword "aPassword"
		user.setPasswordConfirmation "aPassword"
	}
	then "A validação do usuário deve passar",{
		user.validate()
	}
}
scenario "Um nome de usuário deve ser único no sistema", {
	given "Um usuário com o formulário preenchido corretamente", {
		user = new User('userName','password','password')
		userManager = UserManager.getInstance()
	}
	then "O registro deve criar o usuário no sistema", {
		userManager.create(user)
	}
	when "O nome de usuário ja existe no sistema", {
		user = new User('userName','otherPassword','otherPassword')
	}
	then "O registro do usuário deve falhar",{
		ensureThrows(UserAlreadyExistsException){
			userManager.create(user)
		}
	}
}

Pronto, este é todo o código desta história, e foi apenas o que eu escrevi até agora.
Agora preciso criar as classes User, UserManager e implementar os métodos corretamente, para que os testes passem., por que no momento os testes não estão nem rodando, ja que estas classes não existem ainda …

Criada a classe User:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package users;
 
public class User {
	private String name;
	private String password;
	private String passwordConfirmation;
 
	public User() {
		super();
	}
 
	public User(String name, String password, String passwordConfirmation) {
		super();
		this.name = name;
		this.password = password;
		this.passwordConfirmation = passwordConfirmation;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String userName) {
		this.name = userName;
	}
 
	public String getPassword() {
		return password;
	}
 
	public void setPassword(String password) {
		this.password = password;
	}
 
	public String getPasswordConfirmation() {
		return passwordConfirmation;
	}
 
	public void setPasswordConfirmation(String passwordConfirmation) {
		this.passwordConfirmation = passwordConfirmation;
	}
 
	public void validate() {
		throw new RuntimeException();
	}
}

E a classe UserManager:

1
2
3
4
5
6
7
8
9
10
11
package users;
 
public class UserManager {
	private static UserManager instance = new UserManager();
	public static UserManager getInstance(){
		return instance;
	}
	public void create(User user){
		throw new RuntimeException();
	}
}

Os testes agora executam mas falham.

Implementando corretamente o método “validate” da classe User, ja temos 2 testes passando:

1
2
3
4
	public void validate() throws PasswordValidationException {
		if(password==null || passwordConfirmation==null || !password.equals(passwordConfirmation))
			throw new PasswordValidationException();
	}

E agora, finalizando a implementação do método create da classe UserManager.

1
2
3
4
5
6
7
8
	public void create(User user) throws InvalidUserException, PasswordValidationException, UserAlreadyExistsException{
		if(user==null)
			throw new InvalidUserException();
		user.validate();
		if(users.containsKey(user.getName()))
			throw new UserAlreadyExistsException();
		users.put(user.getName(), user);
	}

Todos os testes passam, e o nosso código funciona perfeitamente.

Claro que a DSL do easyb é bem mais completa que isto, é possível testar apenas a especificação de uma classe em vez de uma história completa (eu uso histórias para as regras de negócio importantes para o cliente, e especificações para classes internas do sistema), existem diversos “ensure” para validar o estado desejado, tudo isto vocês podem ver na página do projeto: http://www.easyb.org

E como eu falei antes, este é o código dos testes do sistema, desta forma, quando o usuário solicitar uma alteração, se altera o teste, depois se altera o código.
Quando o seu chefe pedir para você documentar o código escrito até agora, você executa os testes, e copia os txts gerados e envia para ele por e-mail.
Os testes você seria obrigado a escrever de qualquer forma, ja que acredito eu que se você leu até aqui, você acredita que testes de código são importantes.
Como você iria escrever os testes de qualquer forma, agora você precisa fazer uma coisa a menos, que é documentar aquilo que escreveu, por que algum dia você vai querer tirar férias, e outro desenvolvedor vai precisar alterar o código que você escreveu :D

Depois de ler até aqui, o que você acha do easyb?
O que você acha de Behavior Driven Development?
Você concorda comigo, quando digo que o easyb tornou possível BDD em Java? (Sim eu sei que existe o JBehave, mas eu acho o trabalhoso demais, e sei que é possível trabalhar com BDD utilizando qualquer xUnit, mas ai eu perco muito da vantagem da documentação executável)
Você vai pelo menos testar isto no seu próximo projeto?

Eu estou escrevendo alguns pequenos patches e enviando para eles sempre que sinto falta de alguma coisa, e fora isto o projeto vai ter diversas melhorias bastante significativas em pouco tempo!
Parabens aos desenvolvedores, e viva o Groovy, tornando o desenvolvimento Java mais divertido e menos trabalhoso!

Se você gostou deste post, lembre-se de assinar o RSS feed do blog, para ser notificado de novos posts!