Seite 1 von 2
Unittests für Methoden einer Klasse
Verfasst: Dienstag 12. Februar 2019, 22:59
von Atalanttore
Hallo
Ich würde gerne die Methoden einer Klasse mit Unittests ausstatten, aber es funktioniert leider nicht.
main.py
Code: Alles auswählen
class App:
def __init__(self):
for i in range(0, 100):
print(self._multi(i))
def _multi(self, number):
return number ** 2
if __name__ == "__main__":
app = App()
test_main.py
Code: Alles auswählen
import unittest
import main
class TestApp(unittest.TestCase):
def __setUp__(self):
self.app = main.App()
def test__multi(self):
self.assertEqual(self.app._multi(5), 25)
Fehlermeldung:
Code: Alles auswählen
Testing started at 22:52 ...
/usr/bin/python3.6 /snap/pycharm-community/112/helpers/pycharm/_jb_unittest_runner.py --path /home/ata/source/unittest/test_main.py
Launching unittests with arguments python -m unittest /home/ata/source/unittest/test_main.py in /home/ata/source/unittest
Error
Traceback (most recent call last):
File "/usr/lib/python3.6/unittest/case.py", line 59, in testPartExecutor
yield
File "/usr/lib/python3.6/unittest/case.py", line 605, in run
testMethod()
File "/home/ata/source/unittest/test_main.py", line 11, in test__multi
self.assertEqual(self.app._multi(5), 25)
AttributeError: 'TestApp' object has no attribute 'app'
Ran 1 test in 0.001s
FAILED (errors=1)
Process finished with exit code 1
Was muss man anders machen, damit es funktioniert?
Gruß
Atalanttore
Re: Unittests für Methoden einer Klasse
Verfasst: Dienstag 12. Februar 2019, 23:25
von __blackjack__
Wie bist Du denn auf den Namen `__setUp__()` gekommen?
Re: Unittests für Methoden einer Klasse
Verfasst: Mittwoch 13. Februar 2019, 08:40
von Sirius3
@Atalanttore: statt unittest solltest Du Dir pytest anschauen. unittest ist von der Syntax her eher Java.
Re: Unittests für Methoden einer Klasse
Verfasst: Mittwoch 13. Februar 2019, 16:30
von Atalanttore
__blackjack__ hat geschrieben: Dienstag 12. Februar 2019, 23:25
Wie bist Du denn auf den Namen `__setUp__()` gekommen?
Ursprünglich war das eine `__init__()`-Methode. Beim Austausch des Namens habe ich die Unterstriche dann nicht wieder entfernt

. Danke für den Tipp. Ohne Unterstriche funktioniert der Test.
Code: Alles auswählen
import unittest
import main
class TestApp(unittest.TestCase):
def setUp(self):
self.app = main.App()
def test__multi(self):
self.assertEqual(self.app._multi(5), 25)
@Sirius3: Danke für den Hinweis auf 'pytest'. Kann man mit 'unittest' und 'pytest' das Gleiche machen oder muss man je nach Anwendungsfall mal das eine und mal das andere nehmen?
Gruß
Atalanttore
Re: Unittests für Methoden einer Klasse
Verfasst: Mittwoch 13. Februar 2019, 16:36
von __blackjack__
@Atalanttore: pytest kann auch `unittest`-Tests finden und ausführen. Umgekehrt geht nicht, weil `unittest` halt wirklich die Java-Unittest-API hat.
Edit: So könnte das mit pytest aussehen:
Code: Alles auswählen
from pytest import fixture
import main
@fixture
def app():
return main.App()
def test_app_multi(app):
assert app._multi(5) == 25
Re: Unittests für Methoden einer Klasse
Verfasst: Mittwoch 13. Februar 2019, 17:52
von __blackjack__
Nachtrag: Die `__init__()` könnte man mit pytest so testen:
Code: Alles auswählen
def test_app_init(capsys):
app = main.App()
captured = capsys.readouterr()
assert captured.out == '''\
0
1
4
9
16
25
36
49
64
81
100
121
144
169
196
225
256
289
324
361
400
441
484
529
576
625
676
729
784
841
900
961
1024
1089
1156
1225
1296
1369
1444
1521
1600
1681
1764
1849
1936
2025
2116
2209
2304
2401
2500
2601
2704
2809
2916
3025
3136
3249
3364
3481
3600
3721
3844
3969
4096
4225
4356
4489
4624
4761
4900
5041
5184
5329
5476
5625
5776
5929
6084
6241
6400
6561
6724
6889
7056
7225
7396
7569
7744
7921
8100
8281
8464
8649
8836
9025
9216
9409
9604
9801
'''
Allerdings ist es eher unüblich das eine `__init__()` so viele Ausgaben tätigt.
Re: Unittests für Methoden einer Klasse
Verfasst: Mittwoch 13. Februar 2019, 21:26
von Atalanttore
@__blackjack__: Der `pytest`-Code unten generiert bei mir auf der Konsole keinerlei Ausgaben. Auch wenn ich beide Testfälle zu einem Fehler mache oder den `@fixture`-Dekorator, der mich sehr an `@property` erinnert, einfach durch `@property` ersetze, erscheint weiterhin nichts auf der Konsole. Fehlt da noch ein Methodenaufruf?
Code: Alles auswählen
from pytest import fixture
import main
@fixture
def app():
return main.App()
def test_app_multi(app):
assert app._multi(5) == 25
def test_app_init(capsys):
app = main.App()
captured = capsys.readouterr()
assert captured.out == '''\
0
1
4
9
16
25
36
49
64
81
100
121
144
169
196
225
256
289
324
361
400
441
484
529
576
625
676
729
784
841
900
961
1024
1089
1156
1225
1296
1369
1444
1521
1600
1681
1764
1849
1936
2025
2116
2209
2304
2401
2500
2601
2704
2809
2916
3025
3136
3249
3364
3481
3600
3721
3844
3969
4096
4225
4356
4489
4624
4761
4900
5041
5184
5329
5476
5625
5776
5929
6084
6241
6400
6561
6724
6889
7056
7225
7396
7569
7744
7921
8100
8281
8464
8649
8836
9025
9216
9409
9604
9801
'''
Gruß
Atalanttore
Re: Unittests für Methoden einer Klasse
Verfasst: Mittwoch 13. Februar 2019, 21:32
von __blackjack__
@Atalanttore: Wie führst Du die Tests denn aus? Das Modul selbst ist ja nicht ausführbar.
Re: Unittests für Methoden einer Klasse
Verfasst: Mittwoch 13. Februar 2019, 21:49
von Atalanttore
@__blackjack__: Ich führe die Tests in PyCharm aus wie
hier beschrieben. Die Tests werden von PyCharm auch erkannt (grüner Startbutton erscheint links neben jedem Test), aber es gibt leider keine Ausgabe über das Testergebnis.
Gruß
Atalanttore
Re: Unittests für Methoden einer Klasse
Verfasst: Mittwoch 13. Februar 2019, 22:12
von __blackjack__
@Atalanttore: Dann ist das wohl mehr eine PyCharm-Frage. Wie bei Programmen selbst auch: Ich führe das immer ohne eine IDE aus.
Re: Unittests für Methoden einer Klasse
Verfasst: Mittwoch 13. Februar 2019, 22:27
von Atalanttore
@__blackjack__: Wie machst du das genau?
Gruß
Atalanttore
Re: Unittests für Methoden einer Klasse
Verfasst: Mittwoch 13. Februar 2019, 23:14
von __blackjack__
@Atalanttore: Ich rufe pytest auf, wie in dessen Dokumentation beschrieben.
Re: Unittests für Methoden einer Klasse
Verfasst: Freitag 15. Februar 2019, 15:00
von Atalanttore
@__blackjack__: `pytest` aufzurufen ging ziemlich einfach. Im Verzeichnis der Tests ein Terminal öffnen, `pytest` eintippen und mit Enter bestätigen. Danach liefen die Tests durch.
Dein `pytest`-Code funktionierte wegen eines Bugs in PyCharm nicht auf Anhieb. Mittlerweile habe ich vom JetBrains Support aber einen Workaround (run configuration für Testdatei entfernen) erhalten.
Bug:
https://youtrack.jetbrains.com/issue/PY-30052
Gruß
Atalanttore
Re: Unittests für Methoden einer Klasse
Verfasst: Freitag 15. Februar 2019, 15:38
von Atalanttore
Andere Frage: Gibt es zur Kommentierung der einzelnen Testfälle in einem `pytest` eigene Ausdrücke oder nutzt man dafür einfache Python-Kommentare?
Gruß
Atalanttore
Re: Unittests für Methoden einer Klasse
Verfasst: Freitag 15. Februar 2019, 15:47
von __blackjack__
@Atalanttore: Man kann genau wie bei Code auch DocStrings verwenden und das dann auch wie normale Dokumentation weiterverarbeiten.
Re: Unittests für Methoden einer Klasse
Verfasst: Freitag 15. Februar 2019, 16:31
von Atalanttore
@__blackjack__: Okay. Dann weiß ich wieder etwas mehr.
Ich habe nun die Klasse `App` um einen Parameter erweitert, der bei der Instanziierung eines Objektes übergeben werden muss. In der Testdatei (nennt man das so?) habe ich für diesen Parameter einen Wert global definiert. Ist das ein guter Stil?
main.py:
Code: Alles auswählen
class App:
def __init__(self, exponent):
self.factor = exponent
for i in range(0, 10):
print(self._multi(i, exponent))
def _multi(self, number, factor):
return number ** factor
if __name__ == "__main__":
app = App(10)
test_main.py:
Code: Alles auswählen
from pytest import fixture
import main
exponent = 10
@fixture
def app():
return main.App(exponent)
def test_app_multi(app):
assert app._multi(5, exponent) == 9765625
def test_app_init(capsys):
app = main.App(exponent)
captured = capsys.readouterr()
assert captured.out == '''\
0
1
1024
59049
1048576
9765625
60466176
282475249
1073741824
3486784401
'''
Gruß
Atalanttore
Re: Unittests für Methoden einer Klasse
Verfasst: Freitag 15. Februar 2019, 16:55
von __blackjack__
@Atalanttore: Auch in Modulen mit Unittests sollte man sich an die üblichen Konventionen halten und Konstanten komplett gross schreiben. Die Module selbst würde ich mal sagen haben keinen speziellen Namen, denn Unittests müssen ja auch nicht zwingend in extra Modulen stecken. Es gibt auch Leute die sie gleich in die Module mit dem Code hinein schreiben. Mache ich zum Beispiel meistens wenn das kleine Werkzeug sowieso nur aus einem Modul, also eigentlich einer Skriptdatei besteht, und nicht schon zu lang ist.
Ob `EXPONENT` nun semantisch tatsächlich eine (sinnvolle) Konstante ist, kann man schlecht sagen, weil dieses Beispiel an sich ja gar keinen Sinn hat, ausser halt ein Beispiel zu sein. Ist das denn nur für die Tests ein besonderer, konstanter Wert?
Re: Unittests für Methoden einer Klasse
Verfasst: Sonntag 17. Februar 2019, 12:19
von Atalanttore
@__blackjack__: Wie lang ist "nicht schon zu lang"? Ist eine Skriptdatei mit 500 Codezeilen schon zu lang?
`EXPONENT` bzw. `exponent` ist nur ein Beispielwert, weil man beim Instanziieren eines Objektes der Klasse `App` ja einen Wert dafür angeben muss.
Gruß
Atalanttore
Re: Unittests für Methoden einer Klasse
Verfasst: Sonntag 17. Februar 2019, 18:47
von Atalanttore
Momentan komme ich bei der Ergründung der Ursache einer Fehlermeldung bei einem pytest nicht voran. Auf
Stack Overflow schrieb jemand zu der Fehlermeldung, dass der Speicher ausgeht, aber der Arbeitsspeicher meines Rechners (8 GB) ist nur bis zur Hälfte belegt.
Fehlermeldung:
Code: Alles auswählen
Testing started at 18:35 ...
/usr/bin/python3.6 /snap/pycharm-community/112/helpers/pycharm/_jb_pytest_runner.py --path /home/ata/source/test_bmi.py
Launching pytest with arguments /home/ata/source/test_bmi.py in /home/ata/source
============================= test session starts ==============================
platform linux -- Python 3.6.7, pytest-4.2.1, py-1.7.0, pluggy-0.8.1
rootdir: /home/ata/source, inifile:collected 1 item
test_bmi.py
Process finished with exit code 134 (interrupted by signal 6: SIGABRT)
test_bmi.py:
Code: Alles auswählen
from pytest import fixture
import main
@fixture
def main_window():
return main.MainWindow()
def test_bmi_main_window(main_window):
assert main_window._calculate_BMI(180, 80) == 24.691358024691358
bmi.py:
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.uic import loadUi
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
loadUi("bmi.ui", self)
self._write_size()
self._write_weight()
self._write_BMI()
self.horizontalSlider_size.sliderMoved.connect(self._write_BMI)
self.horizontalSlider_size.sliderMoved.connect(self._write_size)
self.horizontalSlider_weight.sliderMoved.connect(self._write_BMI)
self.horizontalSlider_weight.sliderMoved.connect(self._write_weight)
@property
def _size(self):
return self.horizontalSlider_size.value()
@property
def _weight(self):
return self.horizontalSlider_weight.value()
def _write_size(self):
self.output_size.setText(f"{self._size} cm")
def _write_weight(self):
self.output_weight.setText(f"{self._weight} kg")
def _calculate_BMI(self, size, weight):
return weight / (size / 100) ** 2
def _write_BMI(self):
BMI = self._calculate_BMI(self._size, self._weight)
self.output_BMI.setText(str(round(BMI, 2)))
def main():
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
bmi.ui:
Code: Alles auswählen
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Berechnungen</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QTabWidget" name="tabWidget">
<property name="geometry">
<rect>
<x>50</x>
<y>40</y>
<width>681</width>
<height>451</height>
</rect>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>BMI</string>
</attribute>
<widget class="QFrame" name="frame">
<property name="geometry">
<rect>
<x>50</x>
<y>50</y>
<width>591</width>
<height>151</height>
</rect>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_size">
<property name="text">
<string>Größe:</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="3">
<widget class="QSlider" name="horizontalSlider_size">
<property name="toolTipDuration">
<number>-1</number>
</property>
<property name="minimum">
<number>100</number>
</property>
<property name="maximum">
<number>250</number>
</property>
<property name="sliderPosition">
<number>175</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::NoTicks</enum>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QLabel" name="output_size">
<property name="text">
<string>x cm</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_weight">
<property name="text">
<string>Gewicht:</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QSlider" name="horizontalSlider_weight">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>300</number>
</property>
<property name="singleStep">
<number>1</number>
</property>
<property name="sliderPosition">
<number>80</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::NoTicks</enum>
</property>
</widget>
</item>
<item row="1" column="3" colspan="2">
<widget class="QLabel" name="output_weight">
<property name="text">
<string>x kg</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>257</x>
<y>220</y>
<width>191</width>
<height>41</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_BMI">
<property name="text">
<string>BMI:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="output_BMI">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</widget>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
Gruß
Atalanttore
Re: Unittests für Methoden einer Klasse
Verfasst: Sonntag 17. Februar 2019, 19:04
von __deets__
Und du benutzt auch die 64 Bit Version von Python, damit der Adressraum über die mit 32 Bit verfügbaren 4GB hinaus geht....?