Hari ini saya akan membahas ide yang sama sekali baru untuk banyak pengguna (terutama untuk pythonist): mengintegrasikan tes ke dalam aplikasi Anda.
Jadi, mari kita mulai.
Status terkini
Saat ini, masalah interkoneksi kode sumber dan pengujian adalah Anda mengirimkan kode sumber ke pengguna perpustakaan Anda dan paling sering tidak menyertakan pengujian Anda di dalamnya sama sekali.
, , . .
, , .
: Django View, .
from django.contrib.auth.decorators import login_required
from django.http import HttpRequest, HttpResponse
@login_required
def my_view(request: HttpRequest) -> HttpRespose:
...
, :
-
, , ?
API :
# tests/test_views/test_my_view.py
from myapp.views import my_view
def test_authed_successfully(user):
"""Test case for our own logic."""
# Not authed case:
my_view.test_not_authed()
โ โ , !
from django.views.decorators.cache import never_cache
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
@require_http_methods(['GET', 'POST'])
@login_required
@never_cache
def my_view(request: HttpRequest) -> HttpRespose:
...
, API :
# tests/test_views/test_my_view.py
from myapp.views import my_view
my_view.run_tests()
:
HTTP
HTTP
Cache-Control
, , โ ยซ ยป , , HTTP- .
, API . , , Django.
( ), . !
deal
deal โ .
, , ( , Python).
, ( Python int
):
import deal
@deal.pre(lambda a, b: a >= 0 and b >= 0)
@deal.raises(ZeroDivisionError) # this function can raise if `b=0`, it is ok
def div(a: int, b: int) -> float:
return a / b
:
@deal.pre(lambda a, b: a >= 0 and b >= 0)
,
@deal.raises(ZeroDivisionError)
ZeroDivisionError
, -
. , (a: int, b: int) -> float
, : mypy
.
(, !):
div(1, 2) # ok
div(1, 0) # ok, runtime ZeroDivisionError
div(-1, 1) # not ok
# deal.PreContractError: expected a >= 0 and b >= 0 (where a=-1, b=1)
, . :
import deal
@deal.pre(lambda a, b: a >= 0 and b >= 0)
@deal.raises(ZeroDivisionError) # this function can raise if `b=0`, it is ok
def div(a: int, b: int) -> float:
if a > 50: # Custom, in real life this would be a bug in our logic:
raise Exception('Oh no! Bug happened!')
return a / b
, deal
. , , โ :
import deal
from my_lib import div
@deal.cases(div) # That's all we have to do to test deal-based functions!
def test_div(case: deal.TestCase) -> None:
case()
:
ยป pytest test_deal.py
============================= test session starts ==============================
collected 1 item
test_deal.py F [100%]
=================================== FAILURES ===================================
___________________________________ test_div ___________________________________
a = 51, b = 0
@deal.raises(ZeroDivisionError)
@deal.pre(lambda a, b: a >= 0 and b >= 0)
def div(a: int, b: int) -> float:
if a > 50:
> raise Exception('Oh no! Bug happened!')
E Exception: Oh no! Bug happened!
test_deal.py:8: Exception
============================== 1 failed in 0.35s ===============================
, ! ?
:
? hypothesis. , .
. int, def div(a: int, b: int)
. , >= 0
, @deal.pre(lambda a, b: a >= 0 and b >= 0)
.
ZeroDivisionError
,Exception
? : . - โ .ZeroDivisionError
deal.raises
. , , ( ). , Exception , .
? . โ . , . , , . , .
, . , .
, . , deal โ deal-solver, . , .
dry-python/returns
dry-python/returns โ , Python.
, . , , , .
ยซ ยป.
: Equable. . Python ==
. .equals()
, .
:
from returns.io import IO
IO(1) == 1 # type-checks, but pointless, always false
IO(1).equals(1) # does not type-check at all
# error: Argument 1 has incompatible type "int";
# expected "KindN[IO[Any], Any, Any, Any]"
other: IO[int]
IO(1).equals(other) # ok, might be true or false
:
_EqualType = TypeVar('_EqualType', bound='Equable')
class Equable(object):
@abstractmethod
def equals(self: _EqualType, other: _EqualType) -> bool:
"""Type-safe equality check for values of the same type."""
, ( ):
from returns.interfaces.equable import Equable
class Example(Equable):
def __init__(self, inner_value: int) -> None:
self._inner_value = inner_value
def equals(self, other: 'Example') -> bool:
return False # it breaks how `.equals` is supposed to be used!
, False
inner_value
. - : . , . .
, , :
:
:
a.equals(b) == b.equals(a)
:
a
b
,b
c
,a
c
, , . . .
.
, :
from abc import abstractmethod
from typing import ClassVar, Sequence, TypeVar
from typing_extensions import final
from returns.primitives.laws import (
Law,
Law1,
Law2,
Law3,
Lawful,
LawSpecDef,
law_definition,
)
_EqualType = TypeVar('_EqualType', bound='Equable')
@final
class _LawSpec(LawSpecDef): # LOOKATME: our laws def!
@law_definition
def reflexive_law(
first: _EqualType,
) -> None:
"""Value should be equal to itself."""
assert first.equals(first)
@law_definition
def symmetry_law(
first: _EqualType,
second: _EqualType,
) -> None:
"""If ``A == B`` then ``B == A``."""
assert first.equals(second) == second.equals(first)
@law_definition
def transitivity_law(
first: _EqualType,
second: _EqualType,
third: _EqualType,
) -> None:
"""If ``A == B`` and ``B == C`` then ``A == C``."""
if first.equals(second) and second.equals(third):
assert first.equals(third)
class Equable(Lawful['Equable']):
_laws: ClassVar[Sequence[Law]] = (
Law1(_LawSpec.reflexive_law),
Law2(_LawSpec.symmetry_law),
Law3(_LawSpec.transitivity_law),
)
@abstractmethod
def equals(self: _EqualType, other: _EqualType) -> bool:
"""Type-safe equality check for values of the same type."""
, ยซ ยป!
, , . . , hypothesis
, .
, :
, _laws
hypothesis
, ,
API, ! :
# test_example.py
from returns.contrib.hypothesis.laws import check_all_laws
from your_app import Example
check_all_laws(Example, use_init=True)
:
ยป pytest test_example.py
============================ test session starts ===============================
collected 3 items
test_example.py .F. [100%]
=================================== FAILURES ===================================
____________________ test_Example_equable_reflexive_law _____________________
first =
@law_definition
def reflexive_law(
first: _EqualType,
) -> None:
"""Value should be equal to itself."""
> assert first.equals(first)
E AssertionError
returns/interfaces/equable.py:32: AssertionError
========================= 1 failed, 2 passed in 0.22s ==========================
, test_Example_equable_reflexive_law
, equals
Example
False
, reflexive_law
, , (a == a) is True
.
Example
inner_value
:
class Example(Equable):
def __init__(self, inner_value: int) -> None:
self._inner_value = inner_value
def equals(self, other: 'Example') -> bool:
return self._inner_value == other._inner_value # now we are talking!
:
ยป pytest test_example.py
============================= test session starts ==============================
collected 3 items
test_example.py ... [100%]
============================== 3 passed in 1.57s ===============================
Example
. ! .
hypothesis
, ( returns.contrib.hypothesis.laws
.
, Equable
โ , dry-python/returns
, ; , .
, , Monad , .
. , , .
API .
Meski begitu, kasus penggunaan sangat bervariasi! Seperti yang telah saya tunjukkan, mereka dapat berkisar dari platform aplikasi web hingga alat arsitektur dan perpustakaan matematika (dekat).
Saya ingin melihat lebih banyak alat ini di masa mendatang! Semoga saya bisa berbicara tentang manfaat yang mungkin bagi penulis perpustakaan saat ini dan yang akan datang.