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.
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
Die Fixtures können zu zahlreichen Problemen führen:
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.
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
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:
create_modellname(options = {}). Sie erzeugen ein Objekt des Typs Modellname in der Datenbank und geben es zurück.options-Hash mitgegeben werden.
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.
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.