Code vor und nach Funktion schalten

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
iliggio
User
Beiträge: 6
Registriert: Montag 5. November 2012, 12:49

Hallo zusammen, ich habe das folgende Problem. Fuer einen Testfall sind mir vom Framework einige Funktionen vorgegeben. Ich wuerde gern vor bestimmten Funktionsaufrufen Code ausfuehren. Das untenstehende konkrete Beispiel ist von Selenium generiert.
Ich wuerde gern vor jedem Click-Aufruf z.B. die verstrichene Zeit speichern, vor jeder Aktion wuerde ich gern eine Pruefung auf vorhandene Popups durchfuehren etc. Natuerlich kann ich das im Code machen(Heisst: vor jeder zeile mit click action die zeit speichern etc), nur wuerde der Code dadurch sehr unuebersichtlich und nicht mehr wartbar werden.

Code: Alles auswählen

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
import unittest, time, re

class SeleniumTest(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Firefox()
        self.driver.implicitly_wait(30)
        self.base_url = "https://meine.baseurl.de/"
        self.verificationErrors = []
    
    def test_tc_w_d(self):
        driver = self.driver
        print("Der Test wird gestartet: {0}".format(time.strftime("%H:%M:%S",time.localtime())))
        start_time=time.time()
        driver.get(self.base_url + "/security/login")
        driver.find_element_by_id("site.login.username").clear()
        driver.find_element_by_id("site.login.username").send_keys("user")
        driver.find_element_by_id("site.login.password").clear()
        driver.find_element_by_id("site.login.password").send_keys("pw")
        driver.find_element_by_id("site.login.submit").click()
        if self.is_element_present(By.ID, "ui-site-alert"):
            driver.find_element_by_xpath("//button[@type='button']").click()
            print("Es wurde eine Notification weggeklickt")
        driver.find_element_by_xpath("//nav[@id='main-nav']/ul/li[2]/a").click()

...
Meine Idee war nun folgende: Ich schreibe alle Kommandos hintereinander in eine liste aus Tupeln der form [("command1", Action1, Action2), ("command2", action1),...)
wobei action1, ..., actionn eine Klasse ist, der ich das Kommando als String uebergebe.
Dies ist keine Gute Loesung, weil ich dann in der Liste aus Tupeln in den Kommando strings alle Anfuehrungszeichen escapen muss, weil es Probleme gibt mit den Variablen, die in setUp() definiert sind, usw. Der Code wird dadurch nicht leserlicher.

Hat Jemand ne Idee, wie ich das oben geschilderte Problem anders angehen koennte?

Danke schon mal
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Hallo illigio,

dass vor oder nach einer Funktion noch etwas anderes gemacht wird,
nennt sich in der Programmierphilosophie "decorator". Hier ein künstliches
Beispiel:

Code: Alles auswählen

def assert_positive(func):
    """das ist der Dekorator. Er testet, ob der
    an die Funktion func übergebene Parameter positiv ist"""
    def decorated_function(value):
        if value<0:
            raise ValueError("Parameter darf nicht negativ sein!")
        return func(value) # ursprüngliche Funktion wird aufgerufen
    return decorated_function

# der Dekorator wird auf die nächste Funktion angewendet
@assert_positive  
def wurzel(value):
    return value**0.5
Das läßt sich auch nachträglich auch auf Klassenmethoden anwenden:

Code: Alles auswählen

class Rucksack(object):
    def __init__(self):
        self.zeugs=[]

    def pack_ein(self, zeug):
        self.zeugs.append(zeug)

def ist_voll(func):  # Dekorator
    def decorated_function(self, *arg, **kw):
        if len(self.zeugs)>10:
            raise AssertionError("zu voll")
        return func(self, *arg, **kw)
    return decorated_function

# nachträglich dekoriert:
Rucksack.pack_ein=ist_voll(Rucksack.pack_ein)
Grüße
Sirius
iliggio
User
Beiträge: 6
Registriert: Montag 5. November 2012, 12:49

Hallo Sirius, danke fuer den Vorschlag. Das habe ich schon versucht, allerdings entsteht dabei das folgende Problem: Wie in deinem Code zu sehen, uebergibt man dem Decorator ein Funktionsobjekt. Ich habe jedoch eine Liste an Funktionscalls (von selenium generiert), also sowas wie:

Code: Alles auswählen

driver.find_element_by_id("site.login.username").send_keys("user")
Wenn ich nun an der Stelle etwas definiere wie:

Code: Alles auswählen

def do_before(func):
    print(time.time())
    func()
und dann den Aufruf mache

Code: Alles auswählen

do_before(driver.find_element_by_id("site.login.username").send_keys("user"))
wird die funktion

Code: Alles auswählen

 driver.find_element_by_id("site.login.username").send_keys("user") 
schon bei der Uebergabe ausgefuehrt, weil es ja eben ein Aufruf ist. Den kann ich auch nicht in die Funktion und die Argumente aufspalten.

Meine bisher eleganteste Loesung funktioniert so, dass ich saemtliche Befehle in eine Textdatei kopiere und dann dann mit exec in einer Klasse ausfuehre, also so

Code: Alles auswählen

import time

class CommandExecutor():
    timeCount=[]
    def __init__(self, filename, driver, base_url):
        self.fname=filename
        self.driver=driver
        self.base_url=base_url
        self.get_test_data(self.fname)
        
    def get_test_data(self,fname):
        with open(fname) as f:
            self.comList=[line for line in f]

    def do_before(self, entry):
# Was vor dem Funktionsaufruf geschieht
        self.timetemp=time.time()
        self.actName=entry

    def do_after(self, entry):
# Was nach dem Funktionsaufruf geschieht
        self.timeCount.append((self.actName[self.actName.find('(',1):].rstrip('()\n'), time.time()-self.timetemp))
                
    def exec_commands(self):
        driver=self.driver
        base_url=self.base_url
        
        for entry in self.comList:
            self.do_before(entry)
            eval(entry)
            self.do_after(entry)

        self.after_execution()
        
    def after_execution(self):
# ganz am Ende
        timesum=0
        for entry in self.timeCount:
            print("{0} : {1} s\n".format(entry[0],entry[1]))
            timesum+=entry
        print('==============================================')
        print('Gesamtdauer {0} s'.format(timesum))

Hierbei gibt es nur eine andere Unschoenheit: Wenn ich im eigentlichen Test diese Klasse instanziiere, die Instanz der Command executor Klasse keine Member der Test Klasse. Das habe ich hier unten mal verdeutlicht.

Code: Alles auswählen

class Outer_class():
    outer_var="outer Var"
    def outer_function(self):
        another_class_object=Another_class()
        another_class_object.Another_class_function()
        

class Another_class():
    def another_class_function(self):
        print(outer_var)

outer_object=Outer_class()
print(outer_object.outer_var)
outer_object.outer_function()

#NameError: global name 'outer_var' is not defined
Gibt es da vielleicht etwas?

Danke nochmals
iliggio
Antworten