Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Geleitwort des Fachgutachters
Einleitung
1 Einführung
2 Installation
3 Erste Schritte
4 Einführung in Ruby
5 Eine einfache Bookmarkverwaltung
6 Test-Driven Development
7 Rails-Projekte erstellen
8 Templatesystem mit ActionView
9 Steuerzentrale mit ActionController
10 Datenbankzugriff mit ActiveRecord
11 E-Mails verwalten mit ActionMailer
12 Nützliche Helfer mit ActiveSupport
13 Ajax on Rails
14 RESTful Rails und Webservices
15 Rails mit Plug-ins erweitern
16 Performancesteigerung
17 Sicherheit
18 Veröffentlichen einer Rails-Applikation auf einem Server
Ihre Meinung?

Spacer
 <<   zurück
Ruby on Rails 2 von Hussein Morsy, Tanja Otto
Das Entwickler-Handbuch
Buch: Ruby on Rails 2

Ruby on Rails 2
geb., mit DVD
699 S., 39,90 Euro
Rheinwerk Computing
ISBN 978-3-89842-779-1
Online bestellenPrint-Version Jetzt Buch bestellen
* versandkostenfrei in (D) und (A)
Pfeil 6 Test-Driven Development
  Pfeil 6.1 Was ist TDD?
  Pfeil 6.2 Vorstellung des Projekts
  Pfeil 6.3 Projekt erstellen und konfigurieren
  Pfeil 6.4 Unit-Tests
  Pfeil 6.5 Functional-Tests erstellen
  Pfeil 6.6 Autotest
  Pfeil 6.7 Referenz


Rheinwerk Computing - Zum Seitenanfang

6.5 Functional-Tests erstellen  Zur nächsten ÜberschriftZur vorigen Überschrift

Functional Tests testen im Prinzip die Controller und die Views. Jedoch ist zu beachten, dass, wenn ein Model (Unit-getestet) einen Bug hat, der Functional Test fehlschlägt. Daher ist zu empfehlen, vorher sicherzustellen, dass die Unit-Tests einwandfrei laufen.

Mit Functional-Tests können wir z. B. Folgendes konkret testen:

  • Testen, ob eine bestimmte Zeichenkette angezeigt wird.
  • Testen, ob der Controller ein bestimmtes Template anzeigt.
  • Testen, ob der Controller richtig weiterleitet.
  • Testen, ob die Seite korrekt geladen worden ist.
  • Testen, ob die richtigen Parameter übergeben worden sind.
  • Testen, ob das Routing zu dem Controller korrekt funktioniert.
  • Testen, ob die richtige Flash-Message angezeigt wird.
  • u. v. m.

Die Erstellung von Functional-Tests zeigen wir anhand des Flights-Controllers, den wir bereits im letzten Abschnitt mit dem scaffold -Generator erstellt haben. Der scaffold -Generator hat neben dem Flight-Model auch den passenden Controller und die Views erstellt, um die Flüge (»Flight«) zu verwalten. Folgende Template- und Controller-Dateien wurden u. a. generiert:

  • app/views/flights/index.html.erb
    Template für die Anzeige aller Flüge
  • app/views/flights/show.html.erb
    Template zum Anzeigen eines bestimmten Fluges (Detailansicht)
  • app/views/flights/edit.html.erb
    Formular zum Editieren der Flüge
  • app/views/flights/new.html.erb
    Formular, um eine neuen Flug anzulegen
  • app/views/layouts/flights.html.erb
    Legt das Layout für sämtliche Flight-Templates fest. Ist anfangs leer. In der Datei app/views/layouts/application.html.erb kann das Layout für sämtliche Templates festgelegt werden.
  • app/controllers/flights_controller.rb
    Controller für das Flight-Model. Die Klasse ist die Schaltzentrale zwischen dem Benutzer und dem Model. Der Controller nimmt die Benutzereingaben entgegen und führt die entsprechenden Model-Befehle aus. Der Controller nimmt beispielsweise die Daten für einen neuen Flug an, speichert sie mit Hilfe des Flight-Models und leitet den Benutzer auf die Detailseite des neu erstellten Fluges weiter.
  • test/functional/flights_controller_test.rb
    Der Functional-Test ist größtenteils schon fertig generiert und leicht erweiterbar.

Rheinwerk Computing - Zum Seitenanfang

Der Functional-Test des Flights-Controllers  Zur nächsten ÜberschriftZur vorigen Überschrift

Wenn Sie sich den Functional-Test flights_controller_test.rb im Verzeichnis test/functional anschauen, werden Sie feststellen, das er bereits vollständig implementiert ist. Hier werden die Grundfunktionen wie Anzeigen, Hinzufügen, Ändern und Löschen, die der Controller zur Verfügung stellt, getestet. Den Functional-Test müssen wir erst dann anpassen, wenn wir besondere Anforderungen implementieren möchten.

Bevor wir den Functional-Test anpassen und ergänzen, werden wir uns zunächst die generierten Testmethoden im Detail anschauen. Die Methoden des Controllers werden im Folgenden auch als Action bezeichnet.

  • Testen der Index-Action
    def test_should_get_index
      get :index
      assert_response :success
      assert_not_nil assigns(:flights)
    end

    get :index

    Die Methode get :index ruft die Index-Action des Flights-Controller auf. Mit assert_response :success wird überprüft, ob die vorherige Aktion erfolgreich ausgeführt werden konnte. Dies ist genau dann der Fall, wenn der HTTP-Rückgabecode 200 (O.K.) ist. Anschließend wird mit assert_not_nil sichergestellt, dass die Instanzvariable @flight mit einem Wert gesetzt ist (nicht nil). Die Instanzvariable wird vom Template index.html.erb zur Anzeige sämtlicher Flight-Objekte verwendet.
  • Testen der New-Action
    def test_should_get_new
      get :new
      assert_response :success
    end
    Diese Testmethode ruft die New-Action auf und überprüft, ob der Aufruf erfolgreich war.
  • Testen der Create-Action
    def test_should_create_flight
      assert_difference('Flight.count') do
        post :create, :flight => { }
      end
      assert_redirected_to flight_path(assigns(:flight))
    end
    Diese Testmethode besteht aus zwei Assert-Methoden.Die Erste ist etwas komplexer, da sie einen Block verwendet. Der Befehl post :create, :flight => { } ruft mit der HTTP-Methode POST die Action :create auf, mit dem leeren Parameter flight . Die Assert-Methode assert_difference('Flight.count') überprüft, ob die Anzahl der Flight-Objekte Flight.count sich nach Ausführung des Blocks ( post ) um eins erhöht hat. Im Prinzip wird einfach überprüft, ob die Action create die Anzahl der Flight-Objekte verändert hat. Die zweite Assert-Methode assert_redirected_to überprüft, ob der Controller zur Detailseite ( flight_path ) des Flight-Objektes, das in der Instanzvariable assigns(:flight) gespeichert wurde, weiterleitet.
  • Testen der show-Action
    def test_should_show_flight
      get :show, :id => flights(:one).id
      assert_response :success
    end
    Die Show-Action wird mit dem Parameter id=flights(:one).id aufgerufen, wobei flights(:one).id die ID des Fixtures mit dem Namen one aus der Fixture-Datei test/fixtures/flights.yml zurückgibt. Da wir jedoch die Fixture-Datei verändert haben, gibt es kein Fixture mehr mit dem Namen one . Wir werden das später korrigieren. Anschließend wird überprüft, ob die Seite erfolgreich, ohne einen Fehler ausgeführt werden konnte.
  • Testen der Update-Action
    def test_should_update_flight
      put :update, :id => flights(:one).id, :flight => { }
      assert_redirected_to flight_path(assigns(:flight))
    end
    Zunächst wird die Update-Action mit der HTTP-Methode put und mit den Parametern id und flight aufgerufen. Anschließend wird überprüft, ob zur Detailseite des geänderten flight-Objekts weitergeleitet wurde.
  • Testen der Destroy-Action
    def test_should_destroy_flight
      assert_difference('Flight.count', -1) do
        delete :destroy, :id => flights(:one).id
      end
    
      assert_redirected_to flights_path
    end
    Dieser Test funktioniert im Prinzip wie der Test für die Create-Action, nur dass hier die Action destroy des Contollers mit der HTTP-Methode delete aufgerufen und überprüft wird, ob sich die Anzahl der Flight-Objekte nach der Ausführung um eins verringert hat.

Test korrigieren

Um den Functional-Test auszuführen, verwenden wir entweder den allgemeinen Befehl rake test, um alle Tests auszuführen, oder wir rufen

rake test:functionals

auf, um nur die Functional-Tests auszuführen.

Fünf Fehler

Die Ausgabe liefert fünf verschiedene Fehler:

1) Failure:
test_should_create_flight(FlightsControllerTest)
...
<3> expected but was
<2>.
...
2)Error:
test_should_destroy_flight(FlightsControllerTest):
StandardError: No fixture with name 'one' found ...
3)Error:
test_should_get_edit(FlightsControllerTest):
StandardError: No fixture with name 'one' found ...
4)Error:
test_should_show_flight(FlightsControllerTest):
StandardError: No fixture with name 'one' found ...
5)Error:
test_should_update_flight(FlightsControllerTest):
StandardError: No fixture with name 'one' found ...
...
7 tests, 4 assertions, 1 failures, 4 errors

Fehlertypen

In der Fehlerausgabe kommen zwei Typen von Fehlern vor, Failure und Error . Diese haben allgemein folgende Bedeutung:

  • Failure:
    Ein Failure liegt vor, wenn die Ausführung einer Assert-Methode nicht das gewünschte Ergebnis liefert. In unserem Beispiel ist die Assert-Methode assert_difference derMethode test_should_create_flight gescheitert, da diese 3 Datensätze statt 2 Datensätze erwartet hat. Da wir in den Fixtures flights.yml zwei Flüge definiert haben und nach Ausführung der Create-Action immer noch zwei Datensätze vorliegen, bedeutet dies, dass kein neuer flight-Datensatz erstellt werden konnte. Der Grund dafür liegt darin, dass wir die Flugnummer nr im Model als Pflichtfeld festgelegt haben. Wir beheben das Problem, indem wir den Aufruf die Create-Action nicht mit einem leeren flight-Parameter aufrufen, sondern mit Beispielwerten. Wir können dazu die Testhelper- Methode valid_flight_attributes nutzen: Wir ersetzen dazu
    post :create, :flight => { }

    durch

    post :create, :flight => valid_flight_attributes
  • Error:

    Errors liegen vor, wenn ein Ruby bzw. Rails-Befehl fehlgeschlagen ist. In unserem Beispiel erhalten wir vier Errors, die alle dieselbe Ursache haben. In den Fixutures flights.yml haben wir die Bezeichnungen geändert. Um den Fehler zu korrigieren, ersetzen wir in den Testmethoden sämtliche :one durch :dus_muc, wie z. B.:

    get :show, :id => flights(:dus_muc).id

Fehlerfrei

Der Befehl rake test:functionals sollte nach den Korrekturen nun keine Errors und Failures mehr liefern.


Rheinwerk Computing - Zum Seitenanfang

Fixtures in die Entwicklungsdatenbank laden  Zur nächsten ÜberschriftZur vorigen Überschrift

Bevor wir weitere Ergänzungen durchführen, starten wir den lokalen Server mit ruby script/server und rufen die URL

http://localhost:3000/flights

auf.

Abbildung  http://localhost:3000/flights ohne Fixtures

Beispieldatensätze hinzufügen

Anfangs liegen jedoch noch keine Datensätze vor. Der einfachste Weg Beispieldatensätze hinzuzufügen ist, die Fixtures, die wir für die Tests angelegt haben, in die Entwicklungsdatenbank (development) zu laden.

rake db:fixtures:load
Fixtures einzeln laden

Es ist zu beachten, dass rake db:fixtures:load alle Tabellen leert, zu denen es Fixtures im Verzeichnis test/fixtures gibt. Anschließend werden die Daten aus den Fixtures in die Datenbanktabellen importiert.

In unserem Beispiel werden die Datenbanktabellen countries, airports und flights geleert und mit den Daten aus den entsprechenden Fixtures geladen.

Möchte man hingegen nicht alle Fixtures, sondern nur bestimmte Fixtures laden (z. B. nur countries und airports), so kann man den folgenden Befehl aufrufen:

rake db:fixtures:load FIXTURES=countries,airports 

Ein erneuter Aufruf der Seite http://localhost:3000/flights zeigt, dass die Fixtures korrekt geladen worden sind.

Abbildung  http://localhost:3000/flights mit Fixtures

Fixtures eignen sich daher also nicht nur für die Testklassen, sondern auch, um auf einfache Weise Testdaten zur Verfügung zu stellen.


Rheinwerk Computing - Zum Seitenanfang

Anzeigen der Flughafen-Codes  Zur nächsten ÜberschriftZur vorigen Überschrift

Als Erstes möchten wir gerne die Liste der Flights verbessern. Neben den unformatierten Datumsangaben fällt auf, dass die IDs der Abflughäfen (»Departure Airport«) und die IDs der Ankunftsflughäfen (»Arrival Airport«) statt der dreistelligen Codes der Flughäfen, wie z. B. DUS und MUC, angezeigt werden.

Bevor wir uns direkt an die Lösung des Problems machen, formulieren wir zunächst unseren Wunsch in Form einer Testmethode in der Datei flights_controller_test.rb:

def test_should_show_airport_names_in_index
  get :index
  assert_select 'td', 'DUS'
  assert_select 'td', 'MUC'
end

assert_select

In der Testmethode wird zunächst die Index-Action mit der GET-HTTP-Methode aufgerufen und dann mit den beiden Assert-Methoden assert_select überprüft, ob sich die Codes DUS und MUC jeweils innerhalb eines <td> -Tags befinden. Die Codes sollen nämlich in der Tabelle angezeigt werden.

Der Test rake test:functionals schlägt fehl. Um ihn erfolgreich zu machen, ändern wir die Templatedatei index.html.erb wie folgt ab:

Ersetze die Zeilen

Listing  *%app/views/flights/index.html.erb

<td><%=h flight.departure_airport_id %></td>
<td><%=h flight.arrival_airport_id %></td>

durch

Listing  *%app/views/flights/index.html.erb

<td><%=h flight.departure_airport.code %></td>
<td><%=h flight.arrival_airport.code %></td>

Die Tests werden nun erfolgreich ausgeführt.

Abbildung  http://localhost:3000/flights mit den Flughafen-Codes


Rheinwerk Computing - Zum Seitenanfang

Datumswerte formatieren  Zur nächsten ÜberschriftZur vorigen Überschrift

Als Nächstes kümmern wir uns um die Datumsformatierung. Anstatt Wed Jun 11 09:50:00 +0200 2008 soll 11.06.2008 auf der Index-Seite angezeigt werden. Der Fixture-Datei flights.yml entnehmen wir die Datumswerte, deren gewünschte Formatierung wir in einer Testmethode formulieren:

def test_should_format_dates_in_index
  get :index
  assert_select 'td', '10.06.2008 16:10'
  assert_select 'td', '10.06.2008 16:10'
  assert_select 'td', '11.06.2008 09:50'
  assert_select 'td', '11.06.2008 10:50'
end

Damit die Testmethode nicht mehr fehlschlägt, ändern wir die Templatedatei index.html.erb wie folgt ab:

Ersetze die Zeilen

Listing  *%/app/views/flights/index.html.erb

<td><%=h flight.departure_datetime %></td>
<td><%=h flight.arrival_datetime %></td>

durch

Listing  *%/app/views/flights/index.html.erb

<td>
  <%=h flight.departure_datetime.strftime('%d.%m.%Y %H:%M') %>
</td>
<td>
  <%=h flight.arrival_datetime.strftime('%d.%m.%Y %H:%M') %>
</td>

Die Tests sollten nun erfolgreich laufen. Wir könnten jetzt den Code refaktorisieren, indem wir die Formatierungsbefehle in einen Helper auslagern. Laufe die Tests nach dieser Änderung immer noch, ist die Refaktorisierung erfolgreich durchgeführt worden.

Die Datumsformatierung für die Detailseite (Show-Action) erfolgt analog.


Rheinwerk Computing - Zum Seitenanfang

Select-Felder zur Flughafenauswahl  Zur nächsten ÜberschriftZur vorigen Überschrift

Als nächstes widmen wir uns den Formularen zum Editieren und Erstellen von Flügen.

Wenn wir die Editieren-Seite eines Datensatzes durch Klick auf den Edit-Link öffnen, fällt auf, dass die Felder für die Flughäfen lediglich Textfelder sind.

Abbildung  http://localhost:3000/flights/219740562/edit ohne Select-Felder

Select-Felder

Anstelle des Textfeldes zur Eingabe der Flughafen-IDs hätten wir gerne Select-Felder für die Auswahl der Flughäfen. Der HTML-Code für solch ein Select-Feld könnte beispielsweise so aussehen:

<select id="flight_departure_airport_id">
  <option value="859882751">DUS</option>
  <option value="893487900">HND</option>
  <option value="936559827">MUC</option>
  <option value="986663167">SFO</option>
</select>

Es ist zu beachten, dass wir die IDs nicht kennen, da sie beim Importieren der Fixtures in die Datenbank automatisch gesetzt werden. In der folgenden Test-Methode wird überprüft, ob ein <select> -Tag mit der ID flight_departure_airport_id ein <option> -Tag enthält mit dem Wert »DUS« und dem value-Attribut mit der entsprechenden ID.

def test_should_have_select_fields_in_edit
  get :edit, :id => flights(:dus_muc).id
  assert_select 'select#flight_departure_airport_id' do
    assert_select "option",'DUS'
    assert_select 'option[value=?]', airports(:dus).id
  end
end

Bei dem Test ist es ausreichend, nur auf das Vorkommen eines option -Tags (für »DUS«) zu prüfen.

Fehlerfreie Tests

Die Testmethode läuft fehlerfrei, wenn wir folgende Zeilen in der Template-Datei edit.html.erb

<p><b>Departure airport</b><br />
<%= f.text_field :departure_airport_id %></p>

<p><b>Arrival airport</b><br />
<%= f.text_field :arrival_airport_id %></p>

durch

<p><b>Departure airport</b><br />
  <%= f.collection_select :departure_airport_id,
      Airport.find(:all), :id, :code %></p>

<p><b>Arrival airport</b><br />
  <%= f.collection_select :arrival_airport_id,
      Airport.find(:all), :id, :code %> </p>

ersetzen.

Abbildung  http://localhost:3000/flights/219740562/edit mit Select-Feldern


Rheinwerk Computing - Zum Seitenanfang

Weitere Funktionen zur Übung  Zur nächsten ÜberschriftZur vorigen Überschrift

Folgende offene Punkte seien Ihnen zur Übung überlassen:

  1. Anzeige der Flughafen-Codes auf der Detailseite (Show-Action)
  2. Datumsformatierung auf der Detailseite (Show-Action)
  3. Select-Felder für die Auswahl der Flughäfen beim Erstellen einen neuen Flughafendatensatzes (New-Action)
  4. Zusammenfassen der Formulare aus den Templates edit.html.erb und new.html.erb in ein Partial _form.html.erb

Gehen Sie auch hier Schritt für Schritt vor. Formulieren Sie für jeden Punkt zunächst die Testmethode, und implementieren Sie die Anforderung, bis der Test erfolgreich läuft. Erst dann widmen Sie sich dem nächsten Punkt.

Beispielprojekt auf CD
Auf der beiliegenden CD finden Sie das vollständige Beispielprojekt »railsair« mit sämtlichen Tests.

Rheinwerk Computing - Zum Seitenanfang

Integration-/Acceptance-Tests erstellen  Zur nächsten ÜberschriftZur vorigen Überschrift

Der Hauptanwendungszweck für Integration-Tests ist das Testen der Applikation aus der Sicht eines Benutzers, indem der Integration-Test mehrere Seiten aufruft und jeweils überprüft, ob die geladene Seite korrekt aufgerufen wird. Dies wird auch als »Szenario« oder »Story« bezeichnet.

Dabei ist es möglich, dass ein Integration-Test mehrere Controller involviert. Der Integration-Test kümmert sich dabei im Gegensatz zu den Functional- und den Unit-Tests nicht um Details, wie z. B. ob der Controller bestimmte Instanzvariablen gesetzt hat.

Ein Integration-Test könnte beispielsweise so aussehen:

  1. Öffne die Seite /login.
  2. Logge dich mit dem Usernamen »kara« und dem Passwort »geheim« ein.
  3. folge der Weiterleitung.
  4. Überprüfe, ob zur Home-Seite weitergeleitet wurde.
  5. Öffne die Seite /flights.
  6. Überprüfe, ob ein Delete-Link vorhanden ist.
  7. Betätige den ersten Delete-Link.
  8. Überprüfe, ob zur Übersichtsseite aller Flüge gewechselt wurde.

Story

Es handelt sich hierbei um eine »Story«, die beschreibt, wie ein Benutzer sich zunächst anmeldet und anschließend auf eine andere Seite wechselt, um einen Datensatz zu löschen.

Anhand von mehreren »Stories« kann die Funktionalität einer Webapplikation beschrieben werden. Dies wird dann auch als Acceptance-Test bezeichnet.

Es gibt verschiedene Technologien, mit denen man Integration-Tests durchführen kann.

  • Integration-Test in Rails
    Ohne eine Erweiterung zu installieren, kann man mit Rails Integration-Tests ausführen.
  • Selenium
    Selenium funktioniert auf Basis des Firefox-Browsers. Der Integration-Test läuft dabei direkt auf diesem Browser. Eine Programmierung des Integration-Tests in Ruby ist nicht notwendig (aber möglich). Selenium eignet sich hervorragend, um größere Applikationen zu testen, insbesondere wenn Ajax verwendet wird. Für weitere Informationen siehe http://www.openqa.org/selenium. Das Rails-Plug-in wird auf der Seite http://www.openqa.org/selenium-on-rails beschrieben.

Rheinwerk Computing - Zum Seitenanfang

Integration-Test mit Rails  topZur vorigen Überschrift

Mit dem in Rails integrierten Generator script/generate integration_test wird eine Datei für Integration-Tests generiert. Als Parameter wird der Name der Integration-Test-Klasse angegeben.

ruby script/generate integration_test GeneralStories

create  test/integration/general_stories_test.rb

Die Integration-Test-Datei general_stories_test.rb sieht nach der Generierung wie folgt aus:

Listing  test/integration/general_stories_test.rb

require "#{File.dirname(__FILE__)}/../test_helper"

class GeneralStoriesTest < ActionController::IntegrationTest
  # fixtures :your, :models
  # Replace this with your real tests.
  def test_truth
    assert true
  end
end

fixtures

Im Gegensatz zu Unit- und Functional-Tests werden die Fixtures nicht automatisch geladen. Mit dem Befehl fixtures kann man explizit angeben, welche Fixtures geladen werden sollen.

Da wir in unserer Beispielapplikation keine Login-Controller haben, werden wir uns bei unserem Integration-Test nur um den Flight-Controller kümmern.

  1. Seite »flights/new« aufrufen.
  2. Überprüfen, ob die Seite fehlerfrei aufgerufen werden konnte (HTTP-Status = 200).
  3. Überprüfen, ob das Template »flights/new« geladen wurde.
  4. Flight-Formulardaten an die Seite »/flights« schicken mit der HTML-Methode POST.
  5. Der Weiterleitung folgen.
  6. Überprüfen, ob die Seite ohne Fehler aufgerufen werden konnte (HTTP-Status = 200).
  7. Überprüfen, ob das Template »flights/show« geladen wurde.

Die Implementierung des Tests sieht wie folgt aus:

Listing  test/integration/general_stories_test.rb

require "#{File.dirname(__FILE__)}/../test_helper"

class GeneralStoriesTest < ActionController::IntegrationTest
  fixtures :countries, :airports

  # Replace this with your real tests.
  def test_new_flight
    get '/flights/new'
    assert_equal 200, status
    assert_template 'flights/new'

    post '/flights', :flight => valid_flight_attributes
    follow_redirect!
    assert_equal 200, status
    assert_template 'flights/show'
  end
end

URL aufrufen

Im Gegensatz zu den Functional-Tests fällt auf, dass wir nicht direkt eine Action-Methode des Controllers im Test aufrufen, sondern die URL, wie z. B. get '/flights/new' statt get :new.



Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

 <<   zurück
  Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Ruby on Rails 2
Ruby on Rails 2
Jetzt Buch bestellen
 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchtipps
Zum Rheinwerk-Shop: Ruby on Rails 3.1






 Ruby on Rails 3.1


Zum Rheinwerk-Shop: Responsive Webdesign






 Responsive Webdesign


Zum Rheinwerk-Shop: Suchmaschinen-Optimierung






 Suchmaschinen-
 Optimierung


Zum Rheinwerk-Shop: JavaScript






 JavaScript


Zum Rheinwerk-Shop: Schrödinger lernt HTML5, CSS3 und JavaScript






 Schrödinger lernt
 HTML5, CSS3
 und JavaScript


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2008
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern