[Gelöst] unittests: Problem mit Leerzeichen bei Tests auf generierten Code

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Antworten
buhtz
Beiträge: 1106
Registriert: 04.12.2015 17:54:49
Kontaktdaten:

[Gelöst] unittests: Problem mit Leerzeichen bei Tests auf generierten Code

Beitrag von buhtz » 23.03.2022 23:10:48

Konkret geht es hier um Python3 mit unittest, aber ich vermute, dass das Problem auch in anderen Sprachen mit anderen Testsuites vorhanden ist.

Eine Funktion spuckt im Ergebnis HTML-Code aus; also ein String mit HTML drin.
Nun muss ich testen, ob da raus kommt, was ich erwarte.

Code: Alles auswählen

 assertEqual(die_funktion(), meine_erwartung)
Mir ist eigentlich egal, ob das HTML mehrzeilig ist, Eingerückt ist, oder einfach alles in einer Zeile steht und zwischen den Tags keine Leerzeichen sind. Fakt ist jedoch, dass sich das im Laufe der Entwicklung durchaus ändern kann. Hier ein paar Varianten:

Code: Alles auswählen

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <link rel="stylesheet" href="styles.css">    <title>Foo</title>
</head>

Code: Alles auswählen

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<link rel="stylesheet" href="styles.css">    <title>Foo</title>
</head>

Code: Alles auswählen

<!DOCTYPE html><html><head lang="en"><meta charset="UTF-8"><link rel="stylesheet" href="styles.css"><title>Foo</title></head>

Code: Alles auswählen

<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="UTF-8">
    <link rel="stylesheet" href="styles.css">
    <title>
      Foo
    </title>
  </head>
Leerzeichen und Tabulatoren außerhalb der Tags interessieren mich nicht. Um Grunde müsste ich den Rückgabewert und meine Erwartung um alle solche Leerzeichen und Tabulatoren bei jedem Test-Run bereinigen, um das Problem zu umgehen. Trivial ist das aber nicht, wenn ich das mit String-Operationen mache.

Eine alternative wären HTML-parser wie BeautifulSoup. Das ist aber so ein dickes Ding und bringt so viele Seiteneffekte mit, dass ich das auch nur ungern machen möchte.

EDIT: Oder riecht das wieder nach einer RegEx Aufgabe? ;)
Zuletzt geändert von buhtz am 27.04.2022 09:29:17, insgesamt 1-mal geändert.
Debian 11 & 12; Desktop-PC, Headless-NAS, Raspberry Pi 4
Teil des Upstream Betreuer Teams von Back In Time (Debianbackintime)

eggy
Beiträge: 3331
Registriert: 10.05.2008 11:23:50

Re: unittests: Problem mit Leerzeichen bei Tests auf generierten Code

Beitrag von eggy » 24.03.2022 06:33:31

Code: Alles auswählen

assertEqual(die_funktion().replace(" ","").replace("\n",""), meine_erwartung.replace(" ","").replace("\n",""))
statt mehreren replace Funktionen kannst auch nen RegEx nehmen, der für Whitespaces und anderes zu ersetzendes steht,
wie das geht, sagt Dir die Suchmaschine Deiner Wahl.

Schöner und übersichtlicher wird's, wenn man nur "assert (machhuebsch(die_funktion()), machhuebsch(erwartung))" schreibt, und sich ne funktion baut, die das bereinigen übernimmt.

Code: Alles auswählen

def machhuebsch(eingabe):
    return eingabe.replace(regex, "")

Benutzeravatar
Meillo
Moderator
Beiträge: 8817
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: unittests: Problem mit Leerzeichen bei Tests auf generierten Code

Beitrag von Meillo » 24.03.2022 06:52:49

Genau genommen darf man aber bloss den Whitespace *zwischen* den Tags veraendern, weil `<h1>Eine Ueberschrift</h1>' ja unveraendert bleiben muss.

Ein XML-Parser/Beautifier waere schon der korrekte Ansatz.

Sonst, wenn du mit RegExps arbeiten willst ;-) , dann wird es etwas komplizierter wenn es moeglichst alle Faelle abdecken und zugleich robust sein soll. Du muesstest an folgende Dinge denken:

- Die Arbeitsweise muss den ganzen String betreffen und darf nicht zeilenweise sein. Du musst also Newlines matchen koennen. Das muss man ggf. einstellen. (Bei PHP gibt es dazu einen Modifier.)
- r'>\s+<' durch "><" ersetzen
- Dann noch den Whitespace ganz am Anfang und Ende des Strings entfernen.

Das sollte wohl schon eine recht einheitliche Form schaffen. Jedoch wuerden so Faelle wie:

Code: Alles auswählen

<a             href="#">
immer noch nicht normalisiert werden.

Wenn es solide und fuer alle Faelle funktionieren soll, dann wirst du um einen XML-Parser nicht drum rum kommen.


Edit:
Besser als r'>\s+<' waere wohl r'>\s+' plus r'\s+<' um auch den Whitespace um Werte in einem Tag zu entfernen.
Use ed once in a while!

eggy
Beiträge: 3331
Registriert: 10.05.2008 11:23:50

Re: unittests: Problem mit Leerzeichen bei Tests auf generierten Code

Beitrag von eggy » 24.03.2022 08:44:14

Meillo hat geschrieben: ↑ zum Beitrag ↑
24.03.2022 06:52:49
Genau genommen darf man aber bloss den Whitespace *zwischen* den Tags veraendern, weil `<h1>Eine Ueberschrift</h1>' ja unveraendert bleiben muss.
Ups, kleiner Fehler, nicht "" sondern " " als Ersetztung. Der Rest dürfte egal sein, weil mehrere Leerzeichen in HTML eh wie eins dargestellt werden.

Benutzeravatar
paedubucher
Beiträge: 856
Registriert: 22.02.2009 16:19:02
Lizenz eigener Beiträge: GNU Free Documentation License
Wohnort: Schweiz
Kontaktdaten:

Re: unittests: Problem mit Leerzeichen bei Tests auf generierten Code

Beitrag von paedubucher » 24.03.2022 09:24:54

Vielleicht wäre es noch sinnvoll, einen Schritt zurück zu gehen: Was soll denn die Funktion genau machen? Sie spuckt offenbar HTML aus, aber könnte es sein, dass hierzu andere Funktionen verwendet werden, die auf einer tieferen Ebene operieren? Vielleicht könntest du mit deinen Tests etwas tiefer ansetzen.

HTML mit RegEx parsen ist ein Himmelfahrtskommando, sofern es um mehr als nur einzelne Tags geht.

BeautifulSoup mag ja eine relativ grosse Abhängigkeit sein, aber du kannst ja dein Projekt so konfigurieren, dass diese Abhängigkeit nur fürs Testing miteinbezogen wird. (Und wenn du schon dabei bist: pytest ist auch recht schlank und macht v.a. deinen Testcode schlanker :wink: )
Habe nun, ach! Java
Python und C-Sharp,
Und leider auch Visual Basic!
Durchaus programmiert mit heissem Bemühn.
Da steh' ich nun, ich armer Tor!
Und bin so klug als wie zuvor.

buhtz
Beiträge: 1106
Registriert: 04.12.2015 17:54:49
Kontaktdaten:

Re: unittests: Problem mit Leerzeichen bei Tests auf generierten Code

Beitrag von buhtz » 24.03.2022 12:33:47

paedubucher hat geschrieben: ↑ zum Beitrag ↑
24.03.2022 09:24:54
spuckt offenbar HTML aus, aber könnte es sein, dass hierzu andere Funktionen verwendet werden, die auf einer tieferen Ebene operieren? Vielleicht könntest du mit deinen Tests etwas tiefer ansetzen.
Das ist ein sehr guter Einwand. Und du hast Recht, da sind Funktionen die einzelne Elemente des Gesamt-HTML generieren. Die bekommen natürlich auch ihren Test. Das beschriebene Problem besteht aber auch hier. Dass schließt aber nicht die Verpflichtung aus, am Ende auch das große Ganze (z.B. den Zusammenbau) zu testen.
paedubucher hat geschrieben: ↑ zum Beitrag ↑
24.03.2022 09:24:54
pytest ist auch recht schlank und macht v.a. deinen Testcode schlanker :wink: )
OK, ich habe Pytest noch nicht eingesetzt, viele Bespiele gesehen, aber so recht erschließt sich mir nicht der Sinn. Das für mich wirksamste Argument ist bisher, dass pytest sich mehr an objektorientierung und Pythonkonzepten orientiert. Unittest stammt ja wohl irgendwie aus der Java Welt oder so ähnlich.
Dennoch ist Pytest nicht Standard, weil es nicht mit Python geliefert wird. Deswegen ist die Nutzung von unittest schlanker, als die von pytest. Testcode muss nicht schlank i.S. von wenig Zeilen sein. Auch viele Zeilen kann man lesbar und wartbar gestalten. Unter Umstände sind viele Zeilen, sogar aussagekräftiger als weniger.
Wenn Python das pytest mal als inbuild Paket übernimmt, wovon ich ausgehe, lasse ich mich gerne darauf ein.
Debian 11 & 12; Desktop-PC, Headless-NAS, Raspberry Pi 4
Teil des Upstream Betreuer Teams von Back In Time (Debianbackintime)

Benutzeravatar
paedubucher
Beiträge: 856
Registriert: 22.02.2009 16:19:02
Lizenz eigener Beiträge: GNU Free Documentation License
Wohnort: Schweiz
Kontaktdaten:

Re: unittests: Problem mit Leerzeichen bei Tests auf generierten Code

Beitrag von paedubucher » 24.03.2022 16:23:45

buhtz hat geschrieben: ↑ zum Beitrag ↑
24.03.2022 12:33:47
paedubucher hat geschrieben: ↑ zum Beitrag ↑
24.03.2022 09:24:54
spuckt offenbar HTML aus, aber könnte es sein, dass hierzu andere Funktionen verwendet werden, die auf einer tieferen Ebene operieren? Vielleicht könntest du mit deinen Tests etwas tiefer ansetzen.
Das ist ein sehr guter Einwand. Und du hast Recht, da sind Funktionen die einzelne Elemente des Gesamt-HTML generieren. Die bekommen natürlich auch ihren Test. Das beschriebene Problem besteht aber auch hier. Dass schließt aber nicht die Verpflichtung aus, am Ende auch das große Ganze (z.B. den Zusammenbau) zu testen.
Es ist natürlich schon sinnvoll, alle Ebenen zu testen. Die Frage ist aber, ob du so nicht auf der obersten Ebene noch die Sachen mittestest, die du weiter unten schon getestet hast. Idealerweise soll die "höhere" Testfunktion ja nur die Aspekte testen, die deine produktive Funktion zusammenbringt. Und wenn du irgendwo einen Bug einfügst, sollte idealerweise auch nur ein einziger Test scheitern. Eine Funktion von automatisierten Tests ist nämlich auch das schnelle Lokalisieren von Fehlerursachen.

Da ich den Code nicht kenne, kann ich nur spekulieren. Evtl. musst du auch nicht den ganzen String vergleichen, sondern kannst dir bestehende Aspekte daraus rausgreifen, indem du etwa überprüfst ob das (gestrippte) Dokument mit <!DOCTYPE html> anfängt (oder wie auch immer es bei dir sein muss), und dass es mit </html> aufhört.

Eine ganz andere Möglichkeit wäre es, wenn du das HTML-Dokument nicht als String sondern als baumartige Struktur aufbaust. Das Rendern kannst du dann auf der Stufe der einzelnen Elemente testen. Die Baumstruktur kannst du dann per Unittest durchtesten. Hier wären natürlich ein paar Informationen hilfreich, was denn dein spezifischer Code genau so macht.
buhtz hat geschrieben:
paedubucher hat geschrieben: ↑ zum Beitrag ↑
24.03.2022 09:24:54
pytest ist auch recht schlank und macht v.a. deinen Testcode schlanker :wink: )
OK, ich habe Pytest noch nicht eingesetzt, viele Bespiele gesehen, aber so recht erschließt sich mir nicht der Sinn. Das für mich wirksamste Argument ist bisher, dass pytest sich mehr an objektorientierung und Pythonkonzepten orientiert. Unittest stammt ja wohl irgendwie aus der Java Welt oder so ähnlich.
Pytest ist eher etwas sparsamer was OOP angeht, bzw. verlangt es nicht von dir, (Test)Klassen zu schreiben. Du kannst auch nur einzelne Funktionen schreiben und darin mit assert arbeiten. Unittest ist dasjenige, das sich an JUnit orientiert. Das ganze setup/teardown-Konzept stammt von da. Die API von JUnit hat sich in den letzten ca. 20 Jahren doch recht stark gewandelt, sodass die Python Unittests mich mittlerweile stärker an meine alten Java-Tage erinnern als gegenwärtiger JUnit-Testcode :lol:
buhtz hat geschrieben: Dennoch ist Pytest nicht Standard, weil es nicht mit Python geliefert wird. Deswegen ist die Nutzung von unittest schlanker, als die von pytest. Testcode muss nicht schlank i.S. von wenig Zeilen sein. Auch viele Zeilen kann man lesbar und wartbar gestalten. Unter Umstände sind viele Zeilen, sogar aussagekräftiger als weniger.
Wenn Python das pytest mal als inbuild Paket übernimmt, wovon ich ausgehe, lasse ich mich gerne darauf ein.
Pytest dürfte wohl kaum Pythons Standard werden. Die Codemenge wird aufgrund des geringeren (objektorientierten) Overheads schlanker. Der eigentliche Testcode, der auch den dokumentierenden Charakter hat, bleibt wohl in etwa gleich lange. Mit Pytest kannst du auch die bestehenden Unittests mitausführen, ohne dass du dafür etwas spezielles tun musst.

Ich persönlich bereue den Umstieg nicht. Aber ob Pytest oder Unittest ist ja hier nicht das Hauptproblem...
Habe nun, ach! Java
Python und C-Sharp,
Und leider auch Visual Basic!
Durchaus programmiert mit heissem Bemühn.
Da steh' ich nun, ich armer Tor!
Und bin so klug als wie zuvor.

buhtz
Beiträge: 1106
Registriert: 04.12.2015 17:54:49
Kontaktdaten:

Re: unittests: Problem mit Leerzeichen bei Tests auf generierten Code

Beitrag von buhtz » 24.03.2022 16:54:50

Hi Paedubucher,
danke für deine Gedanken und geteilten Erfahrungen. Genau das hilft mir weiter. Da hast du mir ein paar gute Hausaufgaben beschert.
paedubucher hat geschrieben: ↑ zum Beitrag ↑
24.03.2022 16:23:45
Idealerweise soll die "höhere" Testfunktion ja nur die Aspekte testen, die deine produktive Funktion zusammenbringt. Und wenn du irgendwo einen Bug einfügst, sollte idealerweise auch nur ein einziger Test scheitern. Eine Funktion von automatisierten Tests ist nämlich auch das schnelle Lokalisieren von Fehlerursachen.
Ein interessanter Aspekt, den ich so noch nicht auf dem Schirm hatte. Doppeltgemoppelt hält besser, ist hier also nicht so gut. :D

Wie ich die höhere Ebene testen kann, ohne einen Aspekt der unteren Ebenen zu wiederholen, muss ich erstmal durchdenken.
paedubucher hat geschrieben: ↑ zum Beitrag ↑
24.03.2022 16:23:45
Eine ganz andere Möglichkeit wäre es, wenn du das HTML-Dokument nicht als String sondern als baumartige Struktur aufbaust. Das Rendern kannst du dann auf der Stufe der einzelnen Elemente testen.
So in der Art ist das eigentlich auch bereits der Fall.
Debian 11 & 12; Desktop-PC, Headless-NAS, Raspberry Pi 4
Teil des Upstream Betreuer Teams von Back In Time (Debianbackintime)

buhtz
Beiträge: 1106
Registriert: 04.12.2015 17:54:49
Kontaktdaten:

Re: unittests: Problem mit Leerzeichen bei Tests auf generierten Code

Beitrag von buhtz » 10.04.2022 10:22:02

Ich hatte den Gedanken Pythons HTMLParser zu nutzen, um den HTML Code einheitlicher zu formatieren und damit die resultierenden Strings vergleichbar zu machen.

Allerdings ist das so einfach, dass es am Ende wohl auch ohne den Parser geht.

Code: Alles auswählen

>>> print(html)
<html>
   <head>
       <meta something="bäm">
   </head>
   <body>
      text
      <p>puh</ br>yeah</p>
   </body>
</html>
>>> '\n'.join([line.strip() for line in html.split('\n')])
'<html>\n<head>\n<meta something="bäm">\n</head>\n<body>\ntext\n<p>puh</ br>yeah</p>\n</body>\n</html>'
Eigentlich passieren hier nur zwei Dinge: 1) Die führenden Leerzeichen jeder Zeile werden entfernt und 2) die Zeilen werden per new-line wieder zusammengeklebt.

Was haltet ihr davon?

Dürfte passen. Aber ich muss es dann erstmal in der Praxis bzw. den unittests probieren, ob das meinen Ansprüchen genügt.
Debian 11 & 12; Desktop-PC, Headless-NAS, Raspberry Pi 4
Teil des Upstream Betreuer Teams von Back In Time (Debianbackintime)

JTH
Moderator
Beiträge: 3023
Registriert: 13.08.2008 17:01:41
Wohnort: Berlin

Re: unittests: Problem mit Leerzeichen bei Tests auf generierten Code

Beitrag von JTH » 10.04.2022 19:36:09

buhtz hat geschrieben: ↑ zum Beitrag ↑
10.04.2022 10:22:02
Was haltet ihr davon?
Das hängt stark davon ab, wie sehr du dich darauf verlassen kannst, dass deine HTML produzierende Funktion auf dem Wege vergleichbare Ergebnisse produziert. Als Beispiel: Diese drei Schnippel führen letztendlich zum identischen Inhalt im Browser und sind aus dessen Sicht auch, meine ich – korrigiert mich gerne, absolut identisch:

Code: Alles auswählen

<html>
  <body>
    Zeile1
    Zeile2
  </body>
</html>

Code: Alles auswählen

<html>
  <body>
    Zeile1 Zeile2
  </body>
</html>

Code: Alles auswählen

<html>
  <body>
    Zeile1      Zeile2
  </body>
</html>
Die Umformatierung
buhtz hat geschrieben: ↑ zum Beitrag ↑
10.04.2022 10:22:02

Code: Alles auswählen

'\n'.join([line.strip() for line in html.split('\n')])
liefert für die aber schon nicht den selben String.


buhtz hat geschrieben: ↑ zum Beitrag ↑
10.04.2022 10:22:02
Ich hatte den Gedanken Pythons HTMLParser zu nutzen, um den HTML Code einheitlicher zu formatieren und damit die resultierenden Strings vergleichbar zu machen.
Ein Minifier wie Debianhtmlmin könnte nen Weg sein, vergleichbare Strings zu bekommen. Der liefert für obige drei Schnipsel z.B. dreimal identisch

Code: Alles auswählen

<!DOCTYPE html><html> <body> Zeile1 Zeile2 </body> </html> 
Das Paket hängt allerdings von Debianpython3-htmlmin ab. Damit würdest du also auch wieder nicht um eine (Python-) Abhängigkeit herumkommen ;)
Manchmal bekannt als Just (another) Terminal Hacker.

buhtz
Beiträge: 1106
Registriert: 04.12.2015 17:54:49
Kontaktdaten:

Re: unittests: Problem mit Leerzeichen bei Tests auf generierten Code

Beitrag von buhtz » 27.04.2022 09:28:50

Aktuell sieht die "Vereinheitlichungs-Funktion" bei mir so aus und tut erstmal seinen Dienst im Rahmen meiner Anforderungen.

Code: Alles auswählen

def unify_html(html):
    """Creates a unified and comparable HTML string.

    Args:
        html (str): The HTML string to unify.

    Returns:
        (str): The unified string

    Beside other modifications mainly newlines and indentions are removed.
    """
    unified_html = ' '.join([line.strip() for line in html.split('\n')])

    # remove trailing whitespace
    unified_html = unified_html.strip()

    # fix line breaks and (multiple) blanks between tags
    # e.g. "</body>     </html>"
    unified_html = re.sub(r'>\s+<', '><', unified_html)

    return unified_html
Debian 11 & 12; Desktop-PC, Headless-NAS, Raspberry Pi 4
Teil des Upstream Betreuer Teams von Back In Time (Debianbackintime)

Antworten