TDD – Probleme vermeiden mit der AAA-Syntax

by Gregor Biswanger 9. Februar 2010 06:20

Immer mehr Prinzipien und Vorgehensweisen lenken die heutige Softwareentwicklung auf den korrekten Pfad. Das gleiche gilt natürlich auch für die Test-Ebene. Nur zählen hierbei ganz andere „Golden Rules“. Eines der wichtigsten Regeln beim Schreiben von Tests, ist die Einhaltung der AAA-Syntax.

tdd-red-green-feeling

Die AAA-Syntax

Beim Schreiben von Unit-Tests muss immer eines der wichtigsten Regel eingehalten werden: „Eine Test-Methode muss Isoliert und unabhängig von anderen Test-Methoden ausführbar sein.“. Die Test-Methoden dürfen daher nur Objekte testen, die explizit für sie erstellt wurden. Sobald eine Instanz von einem Objekt für mehrere Test-Methoden zugreifbar ist, wird bereits die soeben genannte Regel gebrochen.

Bei manchen Test-Frameworks, wie zum Beispiel NUnit, können mittels SetUp- und TearDown-Attribute eigene Methoden zum vorbereiten und aufräumen von Objekten verwendet werden. Das ist zum Beispiel sehr gut um ein neues Datenbank-Schema vor jedem Aufruf einer Test-Methode zu erzeugen. Allerdings wäre hier auch ein Nachteil, dass dieser Vorgang auch ausgeführt wird, wenn andere Test-Methoden keinen Datenbankzugriff benötigen. Daher werden auch Instanzen zur Verfügung gestellt, die eventuell von anderen Test-Methoden nicht verwendet werden dürfen. Folglich müssen eigene Instanzen bei jeder Test-Methode selbst zu Beginn erzeugt werden.

TDD-AAA-Syntax

Abb.1 – Eine Test-Methode die durch eigene Bereiche aufgeteilt wird.

Dazu wird eine Test-Methode in 3 Bereiche aufgeteilt. Jeder einzelne Bereich hat zudem auch seinen eigenen Aufgabenbereich. Die jeweiligen Bereiche sind durch die 3 x AAA geregelt (siehe Abb.1). Daher ist die AAA-Syntax entstanden und baut sich wie folgt auf:

 

Arrange – Instanzen für den Test vorbereiten

Aus der AAA-Syntax steht der erste Buchstabe A für Arrange, das so viel bedeutet wie „die Einrichtung“. Hierbei müssen alle Instanzen für den Test vorbereitet werden. Die Instanzen müssen direkt erzeugt werden, und zum Beispiel nicht von einem Microkernel (ServiceLocator). Um somit abhängichkeiten zu vermeiden. Die Einzige Ausnahme wäre hierbei ein Mocking-Framework. Hierbei wird explizit verlangt, dass ein gefälschtes Objekt erzeugt wird.

 

   1: [TestFixture]
   2: public class CustomerRepositoryTest
   3: {
   4:     [Test]
   5:     public void Get_Customers_from_Repository()
   6:     {
   7:         // Arrange
   8:         List<Customer> customers = new List<Customer>();
   9:  
  10:         Customer customerA = new Customer
  11:                                 {
  12:                                     Firstname = "Gregor", 
  13:                                     Lastname = "Biswanger"
  14:                                 };
  15:  
  16:         Customer customerB = new Customer
  17:                                 {
  18:                                     Firstname = "Robert",
  19:                                     Lastname = "Walter"
  20:                                 };
  21:  
  22:         customers.Add(customerA);
  23:         customers.Add(customerB);
  24:  
  25:         ICustomerRepository customerRepository = new CustomerRepository();
  26:  
  27:         // Act
  28:         // ...
  29:  
  30:         // Assert
  31:         // ...
  32:     }
  33: }

Listing 1 – Ein einfacher Test der mit Arrange beginnt.

   1: [TestFixture]
   2: public class CustomerRepositoryTest
   3: {
   4:     [Test]
   5:     public void Get_Customers_from_Repository()
   6:     {
   7:         // Arrange
   8:         List<Customer> customers = new List<Customer>();
   9:  
  10:         Customer customerA = new Customer
  11:                                 {
  12:                                     Firstname = "Gregor", 
  13:                                     Lastname = "Biswanger"
  14:                                 };
  15:  
  16:         Customer customerB = new Customer
  17:                                 {
  18:                                     Firstname = "Robert",
  19:                                     Lastname = "Walter"
  20:                                 };
  21:  
  22:         customers.Add(customerA);
  23:         customers.Add(customerB);
  24:  
  25:         ICustomerRepository customerRepository = MockRepository.GenerateMock<ICustomerRepository>();
  26:         customerRepository.Stub(repository => repository.GetCustomers()).Return(customers);
  27:  
  28:         // Act
  29:         // ...
  30:  
  31:         // Assert
  32:         // ...
  33:     }
  34: }

Listing 2 – Ein erweiterter Test mit einem Mock-Framework.

 
Act – Aktivitäten der Instanzen ausführen

Der zweite Buchstabe A aus der AAA-Syntax, steht für Act und bedeutet so viel wie „der Vorgang“. Beim „Act“ werden von den jeweiligen Instanzen deren Funktionen aufgerufen. Also beim TDD-Prozess wird hier deklariert, was von der späteren Implementierung ausgeführt werden soll.

 

   1: [TestFixture]
   2:  public class CustomerRepositoryTest
   3:  {
   4:      [Test]
   5:      public void Get_Customers_from_Repository()
   6:      {
   7:          // Arrange
   8:          List<Customer> customers = new List<Customer>();
   9:  
  10:          Customer customerA = new Customer
  11:                                  {
  12:                                      Firstname = "Gregor", 
  13:                                      Lastname = "Biswanger"
  14:                                  };
  15:  
  16:          Customer customerB = new Customer
  17:                                  {
  18:                                      Firstname = "Robert",
  19:                                      Lastname = "Walter"
  20:                                  };
  21:  
  22:          customers.Add(customerA);
  23:          customers.Add(customerB);
  24:  
  25:          ICustomerRepository customerRepository = MockRepository.GenerateMock<ICustomerRepository>();
  26:          customerRepository.Stub(repository => repository.GetCustomers()).Return(customers);
  27:  
  28:          // Act
  29:          List<Customer> customersResult = customerRepository.GetCustomers();
  30:  
  31:          // Assert
  32:          // ...
  33:      }
  34:  }

Listing 3 – Ein einfacher Test mit einem Act erweitert.

 

Assert – Die Erwartung definieren

Der dritte und letzte Buchstabe A steht für das Assert. Assert bedeutet „die Erwartung“. Dieser Bereich ist für das feststellen und versichern einer vom Act ausgeführten Anforderung. Die meisten Test-Frameworks bieten dazu statische Assert-Klassen bereit.

 

   1: [TestFixture]
   2: public class CustomerRepositoryTest
   3: {
   4:     [Test]
   5:     public void Get_Customers_from_Repository()
   6:     {
   7:         // Arrange
   8:         List<Customer> customers = new List<Customer>();
   9:  
  10:         Customer customerA = new Customer
  11:                                 {
  12:                                     Firstname = "Gregor", 
  13:                                     Lastname = "Biswanger"
  14:                                 };
  15:  
  16:         Customer customerB = new Customer
  17:                                 {
  18:                                     Firstname = "Robert",
  19:                                     Lastname = "Walter"
  20:                                 };
  21:  
  22:         customers.Add(customerA);
  23:         customers.Add(customerB);
  24:  
  25:         ICustomerRepository customerRepository = MockRepository.GenerateMock<ICustomerRepository>();
  26:         customerRepository.Stub(repository => repository.GetCustomers()).Return(customers);
  27:  
  28:         // Act
  29:         List<Customer> customersResult = customerRepository.GetCustomers();
  30:  
  31:         // Assert
  32:         CollectionAssert.AllItemsAreNotNull(customersResult);
  33:         CollectionAssert.AreEqual(customers, customersResult);
  34:     }
  35: }

Listing 4 – Ein einfacher Test mit seinen definierten Asserts.

 

Fazit

Mit der Verwendung der AAA-Syntax leiten sich mehrere Vorteile heraus. Zum einem wird hierbei verhindert, dass die erste und wichtigste Regel gebrochen wird. Des Weiteren verhindert man eine Unübersichtlichkeit. Durch die AAA-Syntax wird der Test-Code um einiges Strukturierter und somit leserlicher für andere Entwickler.



Wenn ihnen der Artikel gefallen hat oder er für sie hilfreich war, bitten "kicken" sie ihn.
kick it on dotnet-kicks.de

Kommentare

Powered by BlogEngine.NET 1.4.5.0
Theme by Extensive SEO

Über den Autor

Gregor Biswanger

Microsoft MVP für Client App Dev
XING

Gregor Biswanger (Microsoft MVP für Client App Dev) ist freier Consultant, Trainer, Autor und Speaker.


Seine Schwerpunkte liegen im Bereich der .NET-Architektur, agilen Prozessen und XAML. Er veröffentlichte vor kurzem seine DVD´s mit Video-Trainings zum Thema „Meine erste Windows 8 App“, „Windows Store Apps mit XAML und C#“ und „WPF 4.5 und Silverlight 5“ bei Addison-Wesley von video2brain.


Biswanger ist auch im Auftrag von Intel GmbH als Technologieberater für die Intel Developer Zone aktiv und ist Leader bei der Ingolstädter .NET Developers Group (INdotNET). 

 

Video über mich:
http://www.youtube.com/watch?v=mx_6SiiLxjk


Basta! 2011 Speaker

CLIPer

MCTS
Windows SharePoint Services 3.0 – Application Development (MCTS)