Hallo. Das ist leo1109. Auch dieses Mal werden wir nach dem vorherigen über das Testen sprechen.
Der im Artikel verwendete Code wurde auf GitHub hochgeladen.
Es geht darum, einen Test zu schreiben. Patch verwenden.
Wir haben das vorherige Skript verbessert und die folgenden Funktionen hinzugefügt.
Darüber hinaus wurden die Erfassung der Fibonacci-Nummer und die Erfassung der Gesamtsumme in die definierte Implementierung geändert.
# python 3.5.2
import time
from functools import wraps
def required_natural_number(func):
@wraps(func)
def wrapper(*args, **kwargs):
k = args[0]
if isinstance(k, bool):
raise TypeError
if not isinstance(k, int) or k < 1:
raise TypeError
return func(*args, **kwargs)
return wrapper
def _f(k):
if k == 1 or k == 2:
return 1
else:
return _f(k-1) + _f(k-2)
def _sum(k):
if k == 1:
return 1
else:
return k + _sum(k-1)
@required_natural_number
def fibonacci(k):
return _f(k)
@required_natural_number
def sum_all(k):
return _sum(k)
@required_natural_number
def delta_of_sum_all_and_fibonacci(k):
x = sum_all(k) - fibonacci(k)
return x if x >= 0 else -x
@required_natural_number
def surplus_time_by(k):
return int(time.time() % k)
Fügen Sie einen Test für "sum_all_minus_fibonacci (x)" hinzu, um die Differenz zwischen der Summe und der Anzahl der Fibonacci zu erhalten. Ich habe den Wert des Testfalls entsprechend gewählt.
my_math_2_test.py
# python 3.5.2
from unittest.mock import patch
import pytest
import my_math
class TestFibonacci:
def test(self):
assert my_math.fibonacci(1) == 1
assert my_math.fibonacci(5) == 5
assert my_math.fibonacci(10) == 55
assert my_math.fibonacci(20) == 6765
assert my_math.fibonacci(30) == 832040
assert my_math.fibonacci(35) == 9227465
class TestSumAll:
def test(self):
assert my_math.sum_all(1) == 1
assert my_math.sum_all(5) == 15
assert my_math.sum_all(10) == 55
assert my_math.sum_all(20) == 210
assert my_math.sum_all(30) == 465
assert my_math.sum_all(35) == 630
class TestDeltaOfSumAllAndFibonacci:
def test(self):
assert my_math.delta_of_sum_all_and_fibonacci(1) == 1 - 1
assert my_math.delta_of_sum_all_and_fibonacci(5) == 15 - 5
assert my_math.delta_of_sum_all_and_fibonacci(10) == 55 - 55
assert my_math.delta_of_sum_all_and_fibonacci(20) == -1 * (210 - 6765)
assert my_math.delta_of_sum_all_and_fibonacci(30) == -1 * (465 - 832040)
assert my_math.delta_of_sum_all_and_fibonacci(35) == -1 * (630 - 9227465)
Lassen Sie uns den Test ausführen.
$ pytest -v my_math_2_test.py
================================================================== test session starts ==================================================================
collected 3 items
my_math_2_test.py::TestFibonacci::test PASSED
my_math_2_test.py::TestSumAll::test PASSED
my_math_2_test.py::TestDeltaOfSumAllAndFibonacci::test PASSED
=============================================================== 3 passed in 21.01 seconds ===============================================================
Es dauerte 20 Sekunden. Es scheint, dass der Fibonacci-Zahlentest und der Differenztest jeweils etwa 10 Sekunden dauern.
Die Ursache ist, dass die Methode zum Abrufen der Fibonacci-Nummer rekursiv umgeschrieben wurde. (Da der Zweck dieses Artikels ein Test ist, werde ich die Erklärung zur Berechnungszeit weglassen, aber die Implementierung mit Wiederholung kann sehr viel Rechenzeit in Anspruch nehmen.)
Dieser Test beinhaltet jedoch tatsächlich unnötige Verarbeitung außer Implementierungsproblemen. Indem Sie diesen Punkt festlegen, können Sie den Test beschleunigen.
Es ist ein Bild, das von oben eingefügt werden soll.
Der Grund für den langsamen Test ist die Methode, um die Fibonacci-Zahl zu erhalten. Daher möchte ich vermeiden, sie so oft wie möglich auszuführen.
Wenn Sie sich den Testinhalt ansehen, können Sie sehen, dass der Fibonacci (x) -Test und das Delta_of_sum_all_and_fibonacci (x) beide Fibonacci (x) mit denselben Argumenten aufrufen. ... das ist ein bisschen Verschwendung.
Da fibonacci (x)
alleine getestet wurde, möchte ich es nicht tun, wenndelta_of_sum_all_and_fibonacci (x)
getestet wird. (Weil es Zeit braucht)
Verwenden wir dieses Mal "patch" und versuchen, "fibonacci (x)" nicht auszuführen.
Nachdem ich den vorhandenen Test übersprungen hatte, fügte ich einen Test hinzu, der Fibonacci (x) "patte".
class TestDeltaOfSumAllAndFibonacci:
@pytest.mark.skip
def test_slow(self):
assert my_math.delta_of_sum_all_and_fibonacci(1) == 1 - 1
assert my_math.delta_of_sum_all_and_fibonacci(5) == 15 - 5
assert my_math.delta_of_sum_all_and_fibonacci(10) == 55 - 55
assert my_math.delta_of_sum_all_and_fibonacci(20) \
== -1 * (210 - 6765)
assert my_math.delta_of_sum_all_and_fibonacci(30) \
== -1 * (465 - 832040)
assert my_math.delta_of_sum_all_and_fibonacci(35) \
== -1 * (630 - 9227465)
def test_patch(self):
with patch('my_math.fibonacci') as mock_fib:
mock_fib.return_value = 1
assert my_math.delta_of_sum_all_and_fibonacci(1) == 1 - 1
assert my_math.delta_of_sum_all_and_fibonacci(5) == 15 - 5
assert my_math.delta_of_sum_all_and_fibonacci(10) == 55 - 55
assert my_math.delta_of_sum_all_and_fibonacci(20) \
== -1 * (210 - 6765)
assert my_math.delta_of_sum_all_and_fibonacci(30) \
== -1 * (465 - 832040)
assert my_math.delta_of_sum_all_and_fibonacci(35) \
== -1 * (630 - 9227465)
Patch schreibt den Ausführungsinhalt der Methode des angegebenen Moduls mithilfe der with-Anweisung und des Decorators neu.
Als Bestätigung der Operation gibt fibonacci (x)
immer 1 zurück.
Lass es uns laufen.
$ pytest -v my_math_2_test.py::TestDeltaOfSumAllAndFibonacci
================================================================== test session starts ==================================================================
collected 2 items
my_math_2_test.py::TestDeltaOfSumAllAndFibonacci::test_slow SKIPPED
my_math_2_test.py::TestDeltaOfSumAllAndFibonacci::test_patch FAILED
======================================================================= FAILURES ========================================================================
_______________________________________________________ TestDeltaOfSumAllAndFibonacci.test_patch ________________________________________________________
self = <my_math_2_test.TestDeltaOfSumAllAndFibonacci object at 0x103abcf28>
def test_patch(self):
with patch('my_math.fibonacci') as mock_fib:
mock_fib.return_value = 1
assert my_math.delta_of_sum_all_and_fibonacci(1) == 1 - 1
> assert my_math.delta_of_sum_all_and_fibonacci(5) == 15 - 5
E assert 14 == (15 - 5)
E + where 14 = <function delta_of_sum_all_and_fibonacci at 0x103ac0840>(5)
E + where <function delta_of_sum_all_and_fibonacci at 0x103ac0840> = my_math.delta_of_sum_all_and_fibonacci
my_math_2_test.py:45: AssertionError
========================================================== 1 failed, 1 skipped in 0.21 seconds ==========================================================
Der Test schlägt jetzt fehl. Im Testfall von "delta_of_sum_all_and_fibonacci (5)" gibt "fibonacci (5)", das ursprünglich 5 erhalten sollte, jetzt 1 zurück, sodass der Test als "14 == (15 - 5)" fehlschlägt. Ich werde.
Nachdem wir bestätigt haben, dass der Patch wie erwartet funktioniert, werden wir den Test verbessern, um den korrekten Wert zurückzugeben.
def test_patch(self):
with patch('my_math.fibonacci') as mock_fib:
mock_fib.return_value = 1
assert my_math.delta_of_sum_all_and_fibonacci(1) == 1 - 1
mock_fib.return_value = 5
assert my_math.delta_of_sum_all_and_fibonacci(5) == 15 - 5
mock_fib.return_value = 55
assert my_math.delta_of_sum_all_and_fibonacci(10) == 55 - 55
mock_fib.return_value = 6765
assert my_math.delta_of_sum_all_and_fibonacci(20) \
== -1 * (210 - 6765)
mock_fib.return_value = 832040
assert my_math.delta_of_sum_all_and_fibonacci(30) \
== -1 * (465 - 832040)
mock_fib.return_value = 9227465
assert my_math.delta_of_sum_all_and_fibonacci(35) \
== -1 * (630 - 9227465)
Versuchen Sie es nochmal.
$ pytest -v my_math_2_test.py::TestDeltaOfSumAllAndFibonacci
================================================================== test session starts ==================================================================
collected 2 items
my_math_2_test.py::TestDeltaOfSumAllAndFibonacci::test_slow SKIPPED
my_math_2_test.py::TestDeltaOfSumAllAndFibonacci::test_patch PASSED
========================================================== 1 passed, 1 skipped in 0.05 seconds ==========================================================
Der Test war erfolgreich. Die Ausführungszeit beträgt 0,05 (Sek.), Was erheblich schneller als zuvor ist. Wenn alle ausgeführt würden, würde es natürlich genauso lange dauern wie die Ausführung des ersten "Fibonacci (x)", aber trotzdem waren es 20 Sekunden -> 10 Sekunden, was einer Reduzierung um 50% entspricht.
Im vorherigen Abschnitt durch Verspotten einer langsamen Funktion. Es ist uns gelungen, den Test zu optimieren. Übrigens ist "surplus_time_by (k)" eine Funktion, die die Unixtime der aktuellen Zeit (UTC) abruft und den Rest zurückgibt. Da sich die aktuelle Zeit jedoch bei jeder Ausführung ändert, setzen Sie einen festen Wert wie zuvor. Ich kann den Test, den ich geschrieben habe, nicht machen.
Der folgende Test ist beispielsweise erfolgreich, wenn der Wert von "time.time ()" ein Vielfaches von 5 ist, ansonsten jedoch nicht.
assert my_math.surplus_time_by(5) == 5
In diesen Fällen sollten Sie "time.time ()" patchen. time ist eine Standard-Python-Bibliothek. Vorausgesetzt, sie ist bereits gut getestet, ist sie Teil der Berechnung des Restbetrags.
Um die in "my_math" verwendete "time.time ()" zu patchen, schreiben Sie:
class TestSurplusTimeBy:
def test(self):
with patch('my_math.time') as mock_time:
mock_time.time.return_value = 1000
assert my_math.surplus_time_by(3) == 1
assert my_math.surplus_time_by(5) == 0
mock_time.time.return_value = 1001
assert my_math.surplus_time_by(3) == 2
assert my_math.surplus_time_by(5) == 1
Es ist auch möglich, bis zu "time.time" zu "patchen", wie unten gezeigt.
def test2(self):
with patch('my_math.time.time') as mock_time:
mock_time.return_value = 1000
assert my_math.surplus_time_by(3) == 1
assert my_math.surplus_time_by(5) == 0
mock_time.return_value = 1001
assert my_math.surplus_time_by(3) == 2
assert my_math.surplus_time_by(5) == 1
Der Unterschied oben ist, wie viel Spott gemacht wird. Wenn Sie sich die Ausführungsergebnisse unten ansehen, können Sie sehen, dass das Zeitmodul selbst als Mock-Objekt neu geschrieben wurde, wenn die Zeit gepatcht ist. Wenn Sie beispielsweise etwas anderes als "time ()" under "time" verwenden, müssen Sie "time.time" explizit angeben oder alle von Ihnen verwendeten Methoden patchen.
# time.Patch-Zeit
>>> with patch('my_math.time.time') as m:
... print(my_math.time)
... print(my_math.time.time)
...
<module 'time' (built-in)>
<MagicMock name='time' id='4329989904'>
#Patch-Zeit
>>> with patch('my_math.time') as m:
... print(my_math.time)
... print(my_math.time.time)
...
<MagicMock name='time' id='4330034680'>
<MagicMock name='time.time' id='4330019136'>
In diesem Beispiel wird das Modul "time" in "my_math" importiert. Schreiben Sie es also als "my_math.time". Wenn daher ein anderes Modul "my_module" angezeigt wird, das "my_math" verwendet, müssen Sie "my_module.my_math.time" schreiben.
Wenn Sie die Instanzmethode einer Klasse patchen, müssen Sie bis zur Instanz angeben.
my_class.py
# Python 3.5.2
class MyClass:
def __init__(self, prefix='My'):
self._prefix = prefix
def my_method(self, x):
return '{} {}'.format(self._prefix, x)
my_main.py
# Python 3.5.2
from my_class import MyClass
def main(name):
c = MyClass()
return c.my_method(name)
if __name__ == '__main__':
print(main('Python'))
Nachdem Sie die obigen Schritte ausgeführt haben, sehen Sie "My Python". Wenn Sie nun "MyClass.my_method" patchen möchten, können Sie schreiben:
my_main_test.py
# python 3.5.2
from unittest.mock import patch
import my_main
class TestFunction:
def test(self):
assert my_main.function('Python') == 'My Python'
def test_patch_return_value(self):
with patch('my_class.MyClass.my_method') as mock:
mock.return_value = 'Hi! Perl'
assert my_main.function('Python') == 'Hi! Perl'
def test_patch_side_effect(self):
with patch('my_class.MyClass.my_method') as mock:
mock.side_effect = lambda x: 'OLA! {}'.format(x)
assert my_main.function('Python') == 'OLA! Python'
Patches sind auch nützlich, wenn Sie Module testen, auf die extern zugegriffen wird. Mit dem Modul "Anfragen" habe ich eine Methode erstellt, die den Statuscode zurückgibt, wenn die angegebene URL GET lautet. Testen Sie dies.
# Python 3.5.2
import requests
def get_status_code(url):
r = requests.get(url)
return r.status_code
Ich werde einen Test schreiben.
# python 3.5.2
import pytest
import my_http
class TestGetStatusCode:
@pytest.fixture
def url(self):
return 'http://example.com'
def test(self, url):
assert my_http.get_status_code(url) == 200
Dieser Test besteht problemlos. Aber was ist, wenn Sie offline sind? Ich werde die detaillierte Stapelverfolgung weglassen, aber der Test wird fehlschlagen.
...
> raise ConnectionError(e, request=request)
E requests.exceptions.ConnectionError: HTTPConnectionPool(host='example.com', port=80): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x1039a4cf8>: Failed to establish a new connection: [Errno 8] nodename nor servname provided, or not known',))
../../../.pyenv/versions/3.5.2/lib/python3.5/site-packages/requests/adapters.py:504: ConnectionError =============================================================== 1 failed in 1.06 seconds ================================================================
Um diesen Test offline zu bestehen, patchen Sie request.get. Da der Rückgabewert ein Objekt ist, muss in return_value explizit "MagicMock ()" angegeben werden.
# python 3.5.2
from unittest.mock import MagicMock, patch
import pytest
import my_http
class TestGetStatusCode:
@pytest.fixture
def url(self):
return 'http://example.com'
@pytest.mark.skip
def test_online(self, url):
assert my_http.get_status_code(url) == 200
def test_offline(self, url):
with patch('my_http.requests') as mock_requests:
mock_response = MagicMock(status_code=200)
mock_requests.get.return_value = mock_response
assert my_http.get_status_code(url) == 200
Der obige Test wird auch offline bestanden. Wenn Sie einen MagicMock-Statuscode von 400, 500 usw. angeben, können Sie einen Fehlerfalltest hinzufügen, während die URL unverändert bleibt. Es kann verwendet werden, um den DB-Zugriff in einer Umgebung ohne Datenbank- oder Token-Erfassung von einem externen Dienst zu verspotten.
Hier sind einige Tipps zum Patchen.
In der Python3-Serie ist "ExitStack ()" praktisch.
>>> with ExitStack() as stack:
... x = stack.enter_context(patch('my_math.fibonacci'))
... y = stack.enter_context(patch('my_math.sum_all'))
... x.return_value = 100
... y.return_value = 200
... z = my_math.delta_of_sum_all_and_fibonacci(99999)
... print(z)
...
100
Die Rückgabewerte beider Funktionen wurden neu geschrieben, sodass "my_math.delta_of_sum_all_and_fibonacci (99999)" die Differenz zwischen 200 und 100 zurückgibt.
Wenn Sie ein Modell verwenden, möchten Sie möglicherweise eine Ausnahme zurückgeben.
Verwenden Sie in diesem Fall mock.side_effect
.
>>> with patch('my_math.fibonacci') as m:
... m.side_effect = ValueError
... my_math.fibonacci(1)
...
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
File "/Users/sho/.pyenv/versions/3.5.2/lib/python3.5/unittest/mock.py", line 917, in __call__
return _mock_self._mock_call(*args, **kwargs)
File "/Users/sho/.pyenv/versions/3.5.2/lib/python3.5/unittest/mock.py", line 973, in _mock_call
raise effect
ValueError
Da das Aufrufobjekt im Scheinobjekt gespeichert ist, ist es zweckmäßig, es zu überprüfen.
Methoden, die mit assert_
beginnen, lösen eine Ausnahme aus, wenn die Bedingung nicht erfüllt ist, sodass Sie assert
nicht schreiben müssen.
Die Argumente, wenn der Mock ausgeführt wird, werden in einem Taple in mock.call_args
gespeichert.
Weitere Informationen finden Sie in der offiziellen Dokumentation.
Es werden nur typische vorgestellt.
--mock.called
Ob es ausgeführt wurde (bool)
--mock.call_count
Anzahl der Ausführungen
mock.assert_called_with ()
ausgeführt wurde
def test_called(self):
with patch('my_class.MyClass.my_method') as mock:
my_main.function('Python')
assert mock.called
assert mock.call_count == 1
mock.assert_called_with('Python')
Es ist bis zum Ende des Obon-Urlaubs unentschlossen!
Recommended Posts