Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testbarkeit (in Ruby)

elitau
August 23, 2014

Testbarkeit (in Ruby)

Tests steigern die Qualität und das Vertrauen in die Software. Wie muss nun Code gestaltet werden, so dass die Tests dazu einfacher geschrieben werden können? Was sind die typischen Schwachstellen von Ruby Programmen? Und wie schreibt man Code, der es leicht macht, diese Schwachstellen durch Tests offen zu legen? In diesem Vortrag gehe ich anhand von Beispielen auf die Attribute von schwer testbarem und fehleranfälligem Ruby Code ein und stelle Patterns und Refactorings vor, um die Tests zu vereinfachen.

elitau

August 23, 2014
Tweet

More Decks by elitau

Other Decks in Programming

Transcript

  1. QUALITÄTEN & TESTS Unit + Integration Tests external quality quality

    in use internal quality process quality influences influences influences depends on Acceptance Tests User Tests depends on depends on Retrospective Source: ISO/IEC SQuaRE 25010
  2. TESTEN VON SOFTWARE “Program testing can be used to show

    the presence of bugs, but never to show their absence!”
 Edsger W. Dijkstra • Reviews, Code-Metriken • Überblick über große Systeme • Potentielle Designprobleme • Einfache Anwendung • Schwierige Interpretation • Ausführen des Testobjekts • Black- und Whitebox Verfahren • Verifiziert Spezifikationen • Deckt Fehler auf • Einsatz aufwändig • Einfache Interpretation Dynamisches Testen Statisches Testen
  3. VORTEILE DURCH TESTBARKEIT • Reduziert Aufwand für Tests, damit auch

    Kosten und • Erleichtert das frühe Auffinden von Fehlern • Testbare Software erfüllt meist auch etablierte Gestaltungsprinzipien für OOP • Diagnose von Fehlern wird erleichtert
  4. TESTBARKEIT • Beobachtbarkeit • Kontrollierbarkeit • Softwaredesign „The ease with

    which test criteria can be established for a system or component and tests can be performed to determine whether those criteria have been met.“ [ISO 25010]
  5. KONTROLLIERBARKEIT • Wie leicht kann das Testobjekt ausgeführt werden? •

    Hindernisse: • Abhängigkeiten • Komplexer Code Testobject Input Output System (-zustand)
  6. BEOBACHTBARKEIT • Wie ist der Zustand des Systems nach einem

    Test? • (Test-)Schnittstellen • Notwendig für Automatisierung Testobject Input Output System (-zustand)
  7. FEHLERANALYSE • Analyse der Bugreports und Exceptions von simfy und

    Spree • Kategorisierung von Fehlern und Extraktion von Schwachstellen
  8. FEHLERVERTEILUNG 3 % 3 % 9 % 15 % 21

    % 21 % 29 % Fehlerhafte Objektreferenzen Methode nicht vorhanden Vertragsverletzung Falsche oder fehlende Parameter Methode hat geringe Kohäsion Inkonsistente Komponente Inkorrekte Typ-Überführung
  9. NIL • NoMethodError: undefined method `foo' for nil:NilClass • Viele

    Quellen für nil: Hash#[], Enumerable#map, etc. • [GOOS]: „Never Pass Null between Objects“ • Tell, Don’t Ask
  10. BEISPIEL class TeaserRepository ! def initialize(user, params) @current_user = user

    @params = params end ! def locale @params[:locale].presence || @current_user.locale end end
  11. DESIGN BY CONTRACT • Entwurfskonzept nach Bertrand Meyer • Vor-,

    Nachbedingungen & Invarianten • Ausführbare Kommentare & Tests in Einem • Fail Early
  12. DESIGN BY CONTRACT class TeaserRepository include Assertions def initialize(user, params)

    assert { params || current_user } @current_user = user @params = params end ! def locale @params[:locale].presence || @current_user.locale end end module Assertions class AssertionFailedError < StandardError; end def assert(&condition, message = "Assertion failed") raise AssertionFailedError.new(message) unless condition.call end end
  13. NULL-OBJECT PATTERN • „Use Special Case / Nullobject whenever you

    have multiple places in the system that have the same behavior after a conditional check for a particular class instance, or the same behavior after a null check.“ [Fowler, 2002, S. 497] • Tell, Don't Ask • Ersetzt nil-Objekte durch explizit benannte Logik • Fördert Kapselung
  14. NULL OBJECT BEISPIEL def greeting_for_user(user) if user && user.name "Hello

    #{user.name}" end end class NotLoggedInUser def name(name) "Gast" end end def greeting_for_user(user) if user.name "Hello #{user.name}" end end
  15. MODIFIER-/QUERY-METHOD • Methoden haben entweder beobachtbare Nebeneffekte oder geben einen

    Wert zurück • Reduziert Verantwortlichkeit von Methoden und damit Kontrollierbarkeit • Query - Methoden steigern Beobachtbarkeit
  16. MODIFIER-/QUERY BEISPIEL class Subscription def check_status_and_send_mail if invoice.paid? self.status =

    "paid" MailService.send_paid_subscription(self) else self.status = "pending" end return status end end describe Subscription do let(:subscription) { Subscription.new } it "should return status 'paid' and send mail if invoice paid" do allow(subscription.invoice).to receive(:paid?).and_return(true) expect(MailService).to receive(:send_paid_subscription) expect(subscription.check_status_and_send_mail).to eql('paid') end it "should return status 'pending' and if invoice not paid" do allow(subscription.invoice).to receive(:paid?).and_return(false) expect(subscription.check_status_and_send_mail).to eql('pending') end end
  17. MODIFIER-/QUERY BEISPIEL class Subscription def status invoice.paid? ? 'paid' :

    'pennding' end def send_mail_if_paid MailService.send_paid_subscription(self) if status == 'paid' end end describe Subscription do let(:subscription) { Subscription.new } it "should return status 'paid' if invoice is paid" do allow(subscription.invoice).to receive(:paid?).and_return(true) expect(subscription.status).to eql('paid') end ! it "should return status 'pending' if invoice is not paid" do allow(subscription.invoice).to receive(:paid?).and_return(false) expect(subscription.status).to eql('pending') end it "should send mail if status is 'paid'" do subscription = Subscription.new(status: 'paid') expect(MailService).to receive(:send_paid_subscription) subscription.send_mail_if_paid end end
  18. BEDINGTE AUSFÜHRUNG DURCH BLÖCKE • Entscheidung zur Ausführung von Code

    an die aufgerufene Methode verlagern • Reduziert nil Checks • Hilft bei Trennung von Methoden mit und ohne Nebeneffekt
  19. BLÖCKE BEISPIEL class UserStore def find_by_name(name, &block) if user =

    Database.find_record(:type => :user, :name => name) block.call(user) end end end class UsersController def update UserStore.find_by_name(params[:username]) do |user| user.updated_at = Time.now # ... end end end
  20. WEITERE REFACTORINGS • Extract Method, Class und Module - Refactorings

    steigern Kohäsion • Immutability • In-Out Funktionen ohne Seiteneffekte
  21. WAS TESTEN? • Neben dem reinen Outside-In Development sollten Edgecases

    nicht vergessen werden, v.a. auf/mit nil testen oder besser nil verhindern
  22. FAZIT • Wahrgenommene Qualität hängt von der inneren Qualität und

    dem Entwicklungsprozess ab • Testbare Software folgt OOP Designprinzipien • nil vermeiden hilft beim Fehler reduzieren
  23. QUELLEN • [GOOS]: Growing Object-Oriented Software, Guided by Tests -

    Steve Freeman and Nat Pryce • ISO/IEC 25010 SQuaRE (successor to ISO 9126) • [Fowler 2002] Patterns of Enterprise Application Architecture - Martin Fowler • [Binder]: Testing Object-Oriented Systems: Models, Patterns, and Tools - Robert Binder