Cucumber für Python?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Samstag 4. April 2009, 09:37

Cucumber ist ein IMHO interessanter Ansatz, Akzeptanztests zu schreiben. Leider muss man dazu Ruby benutzen. Gibt es etwas ähnliches auch für Python?

Cucumber ist eine externe DSL. Hier ist ein Beispiel:

Code: Alles auswählen

Scenario: Add two numbers
  Given I have entered 4 into the calculator
    And I have entered 3 into the calculator
   When I press add
   Then I should see 7 as result
Durch `Scenario:` wird ein Test eingeleitet. Mit `Given`, das wie jeder andere Abschnitt auch mit `And` verlängert werden kann, definiere ich ein Setup, mit `When` führe ich eine Aktion durch, deren Ergebnis ich dann mit `Then` überprüfe.

Man muss nun Cucumber sagen, was "I have entered 4 into the calculator" eigentlich bedeutet. In Ruby sieht das so aus:

Code: Alles auswählen

Given /I have entered (\d+) into the calculator/ do |n|
  @calculator ||= Calculator.new
  @calculator.push(n.to_i)
end
Für die anderen Schritte geht man ähnlich vor. Man implementiert jetzt "testdriven" den eigentlichen Taschenrechner und freut sich, dass der Test schließlich fehlerfrei läuft.

In Python könnte ich so was schreiben:

Code: Alles auswählen

@Given(r"I have entered (\d+) into the calculator")
def do(world, n):
    if not hasattr(world, 'calculator'):
        world.calculator = Calculator()
    world.calculator.push(int(n))
Aber elegant sieht das IMHO nicht aus. Hat jemand einen besseren Vorschlag? Vielleicht auch hier noch eine externe DSL?

Ein Parser, der die oben erwähnten Schlüsselwörter erkennt, ist jedenfalls nicht weiter schwer. Mit jeder Zeile geht er dann in eine von drei Listen mit den durch die Dekoratoren registrieren Funktionen und führt sie dann im Kontext eines "World"-Objekts aus.

Wie würde man das "should" zusichern?

Code: Alles auswählen

@Then(r"I should see (\d+) as result")
def do(world, n):
    assert world.calculator.pop() == int(n)
Das gibt leider sehr schlechte Fehlermeldungen. Statt einfach nur "AssertionError" sollte da natürlich etwas in der Art "expected 3, got 4" ausgegeben werden.

Stefan
audax
User
Beiträge: 830
Registriert: Mittwoch 19. Dezember 2007, 10:38

Samstag 4. April 2009, 10:31

Das sieht sehr ümständlich aus, also das gesamte Konzept. Ich will doch keine Romane fürs Testen schreiben...
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

Samstag 4. April 2009, 10:35

Warum nicht sowas?

Code: Alles auswählen

with Before():
    global calculator = Calculator()
with Given(r"I have entered (\d+) into the calculator") as n:
    calculator.push(int(n))
with Then(r"the result should be (.*) on the screen") as result:
    result.should == float(result)
    
Edit: Ähh nee geht nicht... benutze with so selten, das funktioniert natürlich nicht wie gedacht. Der Code wird ja sofort ausgeführt.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Samstag 4. April 2009, 12:30

audax hat geschrieben:Das sieht sehr ümständlich aus, also das gesamte Konzept. Ich will doch keine Romane fürs Testen schreiben...
Du bist auch nicht die Zielgruppe. Die Idee ist, derartige Akzeptanztests als Abnahmekriterium zu vereinbaren und da ist es wichtig, dass der Auftraggeber versteht oder besser noch selbst definiert, worum es gehen soll. Wenn du dich als Entwickler mit dem Auftraggeber zusammen setzt und vereinbaren willst, was deine Software machen soll, dann ist es schon extrem praktisch, wenn man eine gemeinsame Sprache spricht.

Meinst du wirklich, dass der Nicht-Python-Kundige dieses hier besser versteht?

Code: Alles auswählen

class CalculatorTests(test.unittest):
    def setUp(self):
        self.calculator = Calculator()

    def add_two_numbers(self):
        self.calculator.push(3)
        self.calculator.push(4)
        sefl.assertEquals(7, self.calculator.pop())
Auf deutsch funktioniert das ganze übrigens genau so gut:

Code: Alles auswählen

Szenario: Addiere zwei Zahlen
  Angenommen ich tippe die Zahl 4 in den Taschenrechner ein
  Und ich tippe die Zahl 3 in den Taschenrechner ein
  Wenn ich "add" drücke
  Dann sollte ich 7 als Ergebnis sehen
Stefan
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Samstag 4. April 2009, 13:00

Darii hat geschrieben:Edit: Ähh nee geht nicht... benutze with so selten, das funktioniert natürlich nicht wie gedacht. Der Code wird ja sofort ausgeführt.
Das Problem ist (mal wieder) dass Python keine Blöcke wie Ruby kennt und der einzige Weg, den ich kenne, ist, Funktionen zu benutzen.

Wie ich aber gerade entdecke, gibt es Pycucumber und das geht genauso vor, ich ich's vorgestellt habe. Könnte allerdings ein bisschen Dokumentation vertragen..

Stefan

PS: Perl und Java werden auch bedacht...
BlackJack

Samstag 4. April 2009, 13:22

Also ich bin bei so etwas immer skeptisch. Wir hatten hier schon mal so etwas ähnliches aus der Ruby-Welt in der Diskussion. Irgendwas mit "Spec" im Namen war's glaube ich.

Das ist bei den Spielzeugbeispielen immer hübsch lesbar, aber oft klingen die Formulierungen bei komplexeren Sachen dann nicht mehr so natürlich und das der Auftraggeber so etwas *schreiben* kann, ist IMHO auch alles andere als gegeben. Die übliche Gefahr bei "natürlichen" DSLs, dass es eben doch einen strikten Formalismus gibt, an den man sich halten muss, und die Leute einfach schreiben was sie meinen, das aber anders interpretiert wird, schlägt da zu.

Und so sehr man sich auch bemüht, Python bietet sich einfach nicht für solche DSLs an. Man sollte IMHO gar nicht erst versuchen "natürliche" Sprache in Python-Syntax zu zwingen.
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Samstag 4. April 2009, 23:32

BlackJack hat geschrieben:Also ich bin bei so etwas immer skeptisch. Wir hatten hier schon mal so etwas ähnliches aus der Ruby-Welt in der Diskussion. Irgendwas mit "Spec" im Namen war's glaube ich.
RSpec wars. Ich muss aber zugeben dass mich der RSpec-Ansatz eher verwirrt als dass er Sachen einfacher macht. Vielleicht bin ich aber auch inzwischen zu doof normalen Text zu lesen, das würde zumindest meine Verständnisprobleme klären 8)

Eigentlich weiß ich nicht, ob man unbedingt Tests so eng mit der Sprache verkuppeln will. Ich habe mit Akzeptanztests zu wenig Erfahrung, aber die entsprechenden Tester sind soweit ich gehört habe oft recht zufrieden mit ihren FIT-Wikis oder Excel-Tabellen oder ähnlichem.

DSLs sind eben so eine Sache. Selbst in der Lisp-Welt gibt es eine Bewegung gegen Makros (siehe ILC09) und Makros wie LOOP Spalten die Meinungen schon seit Jahren.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
Benutzeravatar
keppla
User
Beiträge: 483
Registriert: Montag 31. Oktober 2005, 00:12

Montag 6. April 2009, 10:25

RSpec wars. Ich muss aber zugeben dass mich der RSpec-Ansatz eher verwirrt als dass er Sachen einfacher macht. Vielleicht bin ich aber auch inzwischen zu doof normalen Text zu lesen, das würde zumindest meine Verständnisprobleme klären 8)
das schöne an RSpec ist imho gar nicht mal, dass man es lesen kann wie normalen Text (wie schon erwähnt wurde, auch ich halte das für einen irrweg), sondern, dass es an den richtigen stellen Freitext erlaubt und schachtelbar ist.

Code: Alles auswählen

describe "my object" do

  before :each do
    @obj = MyObject.new
  end

  it "should have a name" do
    @obj.name.should == 'fred'
  end

  it "should rauise an exception" do
    lambda{ @obj.epicfail }.should raise(Exception)
  end

end
vs

Code: Alles auswählen

class TestMyObject(unittest.TestCase):
    def setUp(self):
        self.obj = MyObject()

    def testHasName(self):
        self.assert_equals(self.obj, 'fred')

    def testRaisesError(self):
        try:
            self.obj.epicfail()
        except Exception:
            pass
        else:
            self.fail()
man sieht,

describe <=> class TestCase,
before :each <=> setUp
it "should behave" <=> testMethod

cool finde ich an rspec "shared tests" (sowas wie vererbung/delegation von tests) und die Schachtelung.
Mittlerweile scheint mir auch das am Anfang gewöhnungsbedürftige Format nicht mehr komisch, oder zumindest nicht komischer, als die Tatsache, dass man sonst Tests in Klassen tut (wo ist da der Zustand, den man kapseln will?), und das dort Magisch die Methoden rausgesucht werden, deren Namen mit "test" anfangen.

P.S: dieser "WTF?! was passiert da?" effekt hebt sich größtenteils auf, wenn man sich erstmal daran gewöhnt hat, dass man bei ruby eigentlich alles weglassen kann, von Klammern über Argumente zur Lesbarkeit ;)
bracki
User
Beiträge: 4
Registriert: Montag 27. Oktober 2008, 11:13

Sonntag 14. Juni 2009, 23:51

Es gibt auch noch http://www.pyccuracy.org/. Ob das jetzt ein Clone oder eine eigenständige Entwicklung ist, bleibt unklar.
Antworten