Ruby on Rails Fixtures – Fluch oder Segen?

Ruby on Rails baut auf einer Model-View-Controller (MVC)-Architektur auf. Die Modellschicht ActiveRecord verwendet das gleichnamige Pattern Active Record, das in Martin Fowlers Buch Patterns of Enterprise Application Architecture dokumentiert ist. Hierbei wird eine Datenbankrelation durch eine Klasse abgebildet, eine Zeile der Relation entspricht einer Instanz der Klasse. Eine Ausnahme von dieser Regel sind die Tabellen, die m:n-Abbildungen abbilden.

Test-Fixtures in Rails

Rails bietet Tests auf drei Ebenen an:

Alle drei Testarten verwenden also die Modellobjekte. Die meisten sinnvollen Tests benötigen Daten, auf denen sie operieren. Die Standardlösung dafür in Rails sind Fixtures. In diesen Dateien werden Objekte typischerweise im YAML-Format abgelegt:

john:
  id: 1
  name: John Doe
  login: johndoe
  password: nosecret

In den Testklassen werden dann die benötigten Fixtures über die fixtures-Methode eingebunden. Sie stellt sicher, dass die Daten aus den übergebenen Fixtures jedem Test der Testfallklasse in der Datenbank zur Verfügung stehen. Die Datensätze können dann im Test über eine Methode mit dem Namen der Fixture zugegriffen werden:

  fixtures :users	
	
  def setup
    @john = users(:john)	
  end  

Probleme durch Fixtures

Die Fixtures können zu zahlreichen Problemen führen:

Best Practices

Um die Probleme mit Fixtures in den Griff zu bekommen, bieten sich einige Techniken an:

Minimale Fixture

In den Fixtures sollten nur diejenigen Datensätze definiert werden, die für einen Großteil der Tests relevant sind. Wird zum Beispiel mit Authentifizierung gearbeitet, könnte ein Benutzerkonto zu diesen Daten gehören.

Damit wird die Menge an Fixture-Daten drastisch reduziert, und deutlich überschaubarer.

Echte Unit Tests schreiben

Für viele Tests ist es nicht notwendig, Datenbankobjekte in die Datenbank zu speichern. Nehmen wir als Beispiel diesen (durchwachsenen) Unit Test:

def test_card_validates_presence_of_front_and_back
  card = Card.new
  assert !card.save

  card.front = '1 + 3 = ?'
  assert !card.save

  card.front = ''
  card.back = '4'
  assert !card.save
  
  card.front = '1 + 3 = ?'
  assert card.save
end

Die Validations können hier ebensogut über den Aufruf der Methode valid? getestet werden, was auch erlaubt, den Test in zwei Fälle aufzuteilen:

def test_card_validates_presence_of_front
  card = Card.new
  card.valid?
  assert card.errors.include?(:front)
  card.front = 'front' 
  card.valid?
  assert !card.errors.include?(:front)
end

def test_card_validates_presence_of_back
  card = Card.new
  card.valid?
  assert card.errors.include?(:back)
  card.back = 'back' 
  card.valid?
  assert !card.errors.include?(:back)
end

Test Helper

Ist das Anlegen von testspezifischen Daten nicht vermeidbar, werden sie mit Hilfsmethoden im Testsetup oder direkt im Test angelegt. Ein gutes Schema für die Hilfsmethoden ist:

Mit den Hilfsmethoden können wir nun Tests schreiben, bei denen die Fixture in der setup-Methode der Testklasse oder in den einzelnen Testmethoden aufgebaut wird.

Fluch oder Segen?

Rails Fixtures sind ein Segen, da sie uns erlauben, häufig in Tests benötigte Datensätze einmalig anzulegen. Sie werden dann zum Fluch, wenn sich testspezifische Daten in die Fixtures verirren.