homeASCIIcasts

11: Refactoreando el nombre de usuario – Parte 2 

(view original Railscast)

Other translations: En It Fr

Other formats:

Written by Aldo Escudero (aldoescudero.com.ar)

En el episodio anterior, hablamos sobre refactorear y como podemos mover código de las vistas a un método en el correspondiente modelo para eliminar duplicación de código. Aunque, podemos mejorar mas nuestro código en el modelo User.

class User < ActiveRecord::Base  
  def full_name  
    name = first_name + ' '  
    name += "#{middle_initial}. " unless middle_initial.nil?  
    name += last_name  
  end  
end  

Nuestro modelo User como estaba al terminar el último episodio.

Testing

Antes de comenzar a refactorear nuestro modelo User, observemos un poco de testing. Testing y refactoreo van de la mano, ya que el propósito de refactorear es mejorar su código sin cambiar su comportamiento y tener el código testeado asegura que nuestro refactoreo no afecta la funcionalidad del código. Al crear un modelo en una aplicación Rails, automáticamente se crea un archivo de test. Veamos el archivo user_test.rb que se encuentra en la carpeta /test/unit.1

require 'test_helper'  
  
class UserTest < ActiveSupport::TestCase  
  # Replace this with your real tests.  
  test "the truth" do  
    assert true  
  end  
end  

El código de testing predeterminado que Rails genera.

Hay un test predeterminado que vemos en el código de arriba. Simplemente hace una afirmación (assert) que true == true (true: verdadero). Vamos a reemplazarlo con nuestro primer test. Crearemos un nuevo usuario sin su inicial del nombre del medio y verificaremos el nombre completo que esperamos que devuelva.

test "full name without middle initial" do  
  user = User.new(:first_name => "John", :last_name => "Smith")  
  assert_equal 'John Smith', user.full_name  
end  

Nuestro primer test verifica que haya un nombre sin la inicial del segundo nombre.

Para poder ejecutar nuestros test, podríamos ir a la carpeta de la aplicación desde la linea de comando y deberíamos correr rake test. Pero vamos a utilizar AutoTest 2. Podemos instalarlo con la gema ZenTest (sudo gem install ZenTest). AutoTest tiene la ventaja que continuamente correrá nuestros tests para que sea mas fácil ver cuando alguno falle.

Laa-Laa:ep11 eifion$ autotest
loading autotest/rails
/usr/local/bin/ruby -I.:lib:test -rtest/unit -e "%w[test/functional/users_controller_test.rb test/unit/user_test.rb].each { |f| require f }" | unit_diff -u
Loaded suite -e
Started
.
Finished in 0.046945 seconds.

1 tests, 1 assertions, 0 failures, 0 errors

Salida de AutoTest

Nuestro primer test paso!!. Ahora crearemos otro test para un nombre que tenga la inicial del segundo nombre. Para ello, agregaremos un método dentro de la clase UserTest.

test "full name with middle initial" do  
  user = User.new(:first_name => "Paul", :middle_initial => "P", :last_name => "Hughes")  
  assert_equal 'Paul P. Hughes', user.full_name  
end  

Al guardar el archivo de testing, AutoTest debería automáticamente volver a correr los tests. Ambos tests debieran ahora pasar.

Refactoreo

Ahora que sabemos que nuestro método full_name se comporta de la forma esperada, podemos empezar a refactorearlo. El uso de la variable local en el método y la concatenación de cadenas dentro de un arreglo pareciera innecesario. Así que los sacaremos. En vez, podríamos poner cada parte del nombre de usuario en un arreglo y juntarlas mediante un join.

class User < ActiveRecord::Base  
  def full_name  
    [first_name, middle_initial, last_name].join(' ')  
  end  
end  

Primer refactoreo hecho a la clase User.

Cuando guardamos el archivo, los tests debieran correr automáticamente y deberíamos poder ver que ambos fallan.

  1) Failure:
  test_full_name_with_middle_initial(UserTest)
  [./test/unit/user_test.rb:11:in `test_full_name_with_middle_initial'
  /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in `run']:
  --- /var/folders/yD/yDkhXjIsHAqkCTKsBbUlC++++TI/-Tmp-/expect31666.0    2009-01-04 11:23:35.000000000 +0000
  +++ /var/folders/yD/yDkhXjIsHAqkCTKsBbUlC++++TI/-Tmp-/butwas31666.0    2009-01-04 11:23:35.000000000 +0000
  @@ -1 +1 @@
  -Paul P. Hughes
  +Paul P Hughes

  2) Failure:
  test_full_name_without_middle_initial(UserTest)
  [./test/unit/user_test.rb:6:in `test_full_name_without_middle_initial'
  /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in `run']:
  --- /var/folders/yD/yDkhXjIsHAqkCTKsBbUlC++++TI/-Tmp-/expect31666.1    2009-01-04 11:23:35.000000000 +0000
  +++ /var/folders/yD/yDkhXjIsHAqkCTKsBbUlC++++TI/-Tmp-/butwas31666.1    2009-01-04 11:23:35.000000000 +0000
  @@ -1 +1 @@
  -John Smith
  +John  Smith

  2 tests, 2 assertions, 2 failures, 0 errors

Salida de AutoTest mostrando dos unit test que fallan.

El primer test falla debido a que no hay un punto a continuación de la inicial del segundo nombre. El segundo test falla debido a que hay dos espacios en blanco entre el primer nombre y el apellido. Para empezar, intentaremos corregir el segundo caso. Hay dos espacios porque la inicial del nombre del medio es nil y estamos uniendo cada parte del arreglo con un espacio. Podemos corregir esto haciendo una llamada al método compact en el arreglo antes de hacer un join (juntar) con las partes. (compact elimina los valores nil de un arreglo.)

def full_name  
  [first_name, middle_initial, last_name].compact.join(' ')  
end  

Ahora, solamente nuestro primer test falla: necesitamos el punto después de la inicial del segundo nombre. Vamos a corregirlo creando un nuevo método en la clase User llamado middle_initial_with_full_stop (inicial_del_medio_con_punto)

def full_name  
  [first_name, middle_initial_with_full_stop, last_name].compact.join(' ')  
end    
  
def middle_initial_with_full_stop  
  "#{middle_initial}." unless middle_initial.blank?  
end  

Nuestros tests ahora pasan. Pero hay una condición que no hemos testeado. Y si la inicial del segundo nombre es una cadena vacía?. Podemos escribir un test para eso y ver si pasa.

test "full name with empty middle initial" do  
  user = User.new(:first_name => "John", :middle_initial => "", :last_name => "Jones")  
  assert_equal 'John Jones', user.full_name  
end  

Esto también pasa. El método blank? en cadenas devuelve true para tanto un valor nil como para una cadena vacía. Por lo que nuestro nombre con una inicial vacía sigue funcionando como lo esperado.

Nuestro código en el modelo User ahora se ve mucho mejor que antes. Existe aun duplicación en el código de testing, que veremos en el próximo episodio.

Código final

class User < ActiveRecord::Base  
  def full_name  
    [first_name, middle_initial_with_full_stop, last_name].compact.join(' ')  
  end    
  
  def middle_initial_with_full_stop  
    "#{middle_initial}." unless middle_initial.blank?  
  end  
end  

La clase User después de refactorear.

require 'test_helper'  
class UserTest < ActiveSupport::TestCase  
  test "full name without middle initial" do  
    user = User.new(:first_name => "John", :last_name => "Smith")  
    assert_equal 'John Smith', user.full_name  
  end  
  
  test "full name with middle initial" do  
    user = User.new(:first_name => "Paul", :middle_initial => "P", :last_name => "Hughes")  
    assert_equal 'Paul P. Hughes', user.full_name  
  end  
  
  test "full name with empty middle initial" do  
    user = User.new(:first_name => "John", :middle_initial => "", :last_name => "Jones")  
    assert_equal 'John Jones', user.full_name  
  end  
end 

Los tests para la clase User.

Notas

  1. El Railscast en el que se basa este episodio esta basado en Rails 1. El código del episodio fue escrito utilizando Rails 2.2.
  2. http://rubyforge.org/projects/zentest/