Skip to content

Commit

Permalink
Use type checking in tests (#59)
Browse files Browse the repository at this point in the history
This adds adds type annotations to tests and fixes all errors.

(This change also runs Mypy in CI to avoid regressions.)

Practically, add ‘-> None’ annotations to unit tests. This tells mypy to
check these tests. This is a good thing since most unit tests are
essentially a ‘public API consumer’.

This revealed a number of issues, which can also be seen by passing
--check-untyped-defs on the version of the code before this commit:

    $ mypy --check-untyped-defs result/tests.py
    [...]
    Found 39 errors in 1 file (checked 1 source file)

The first category is simple and happens many times, namely missing
annotations:

    result/tests.py: note: In function "test_ok_factories":
    result/tests.py:9:1: error: Function is missing a return type annotation
    result/tests.py:9:1: note: Use "-> None" if function does not return a value

The second category are missing annotations that inference cannot solve.
For example, the type of ‘x = Ok()’ is unknown because it's a generic
class. The fix is to annotate using ‘x: Result[None, None] = Ok()’.

The third category are mostly mypy limitations on type inference:

    result/tests.py: note: In function "test_map_or":
    result/tests.py:128:1: error: Function is missing a return type annotation
    result/tests.py:132:12: error: Cannot infer type argument 1 of "map_or" of "Err"
    result/tests.py:132:38: error: Unsupported left operand type for + ("object")
    result/tests.py:132:38: error: Returning Any from function declared to return "str"
    result/tests.py:137:12: error: Cannot infer type argument 1 of "map_or" of "Err"

These are fixed by avoiding unnecessary lambdas. For example, ‘lambda x:
str(x)’ is the same as just ‘str’, so use that. Other example: instead of
a lambda to ‘x+x’ use ‘str.upper’, then adapt the expected values
accordingly.

Finally, suppress flake8 errors in the pattern matching tests.
  • Loading branch information
wbolster authored Oct 29, 2021
1 parent c6dda2a commit 71afad2
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 51 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ jobs:
- name: Run tests (pattern matching)
run: pytest --cov-report term --cov-report xml:coverage.xml result/tests-pattern-matching.py
if: matrix.python == '3.10'
- name: Run mypy on result.py
run: mypy result/result.py
- name: Run mypy
run: mypy result/result.py result/tests.py

# Packaging
- name: Build packages
Expand Down
1 change: 1 addition & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
ignore_missing_imports = True
strict_optional = True
no_implicit_optional = True
show_error_codes = True
show_error_context = True
show_column_numbers = True
disallow_untyped_calls = True
Expand Down
16 changes: 9 additions & 7 deletions result/tests-pattern-matching.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
# -*- coding: utf-8 -*-
# Pytest test suite
from result import Result, Ok, Err
from result import Err, Ok, Result


def test_pattern_matching_on_ok_type():
def test_pattern_matching_on_ok_type() -> None:
o: Result[str, int] = Ok('yay')
match o:
case Ok(f):
f # Avoids flake8 F841 unused variable
assert True
case Err(e):
e
assert False


def test_pattern_matching_on_err_type():
e: Result[int, str] = Err('nay')
match e:
def test_pattern_matching_on_err_type() -> None:
n: Result[int, str] = Err('nay')
match n:
case Ok(f):
f
assert False
case Err(e):
e
assert True
81 changes: 39 additions & 42 deletions result/tests.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
# -*- coding: utf-8 -*-
# Pytest test suite

import pytest

from result import Ok, Err, UnwrapError, OkErr
from result import Err, Ok, OkErr, Result, UnwrapError


def test_ok_factories():
def test_ok_factories() -> None:
instance = Ok(1)
assert instance._value == 1
assert instance.is_ok() is True


def test_err_factories():
def test_err_factories() -> None:
instance = Err(2)
assert instance._value == 2
assert instance.is_err() is True


def test_eq():
def test_eq() -> None:
assert Ok(1) == Ok(1)
assert Err(1) == Err(1)
assert Ok(1) != Err(1)
Expand All @@ -29,142 +26,142 @@ def test_eq():
assert Ok("0") != Ok(0)


def test_hash():
def test_hash() -> None:
assert len({Ok(1), Err("2"), Ok(1), Err("2")}) == 2
assert len({Ok(1), Ok(2)}) == 2
assert len({Ok("a"), Err("a")}) == 2


def test_repr():
def test_repr() -> None:
assert Ok(u"£10") == eval(repr(Ok(u"£10")))
assert Ok("£10") == eval(repr(Ok("£10")))


def test_ok():
def test_ok() -> None:
res = Ok('haha')
assert res.is_ok() is True
assert res.is_err() is False
assert res.value == 'haha'


def test_err():
def test_err() -> None:
res = Err(':(')
assert res.is_ok() is False
assert res.is_err() is True
assert res.value == ':('


def test_ok_method():
def test_ok_method() -> None:
o = Ok('yay')
n = Err('nay')
assert o.ok() == 'yay'
assert n.ok() is None
assert n.ok() is None # type: ignore[func-returns-value]


def test_err_method():
def test_err_method() -> None:
o = Ok('yay')
n = Err('nay')
assert o.err() is None
assert o.err() is None # type: ignore[func-returns-value]
assert n.err() == 'nay'


def test_no_arg_ok():
top_level = Ok()
def test_no_arg_ok() -> None:
top_level: Result[None, None] = Ok()
assert top_level.is_ok() is True
assert top_level.ok() is True


def test_expect():
def test_expect() -> None:
o = Ok('yay')
n = Err('nay')
assert o.expect('failure') == 'yay'
with pytest.raises(UnwrapError):
n.expect('failure')


def test_expect_err():
def test_expect_err() -> None:
o = Ok('yay')
n = Err('nay')
assert n.expect_err('hello') == 'nay'
with pytest.raises(UnwrapError):
o.expect_err('hello')


def test_unwrap():
def test_unwrap() -> None:
o = Ok('yay')
n = Err('nay')
assert o.unwrap() == 'yay'
with pytest.raises(UnwrapError):
n.unwrap()


def test_unwrap_err():
def test_unwrap_err() -> None:
o = Ok('yay')
n = Err('nay')
assert n.unwrap_err() == 'nay'
with pytest.raises(UnwrapError):
o.unwrap_err()


def test_unwrap_or():
def test_unwrap_or() -> None:
o = Ok('yay')
n = Err('nay')
assert o.unwrap_or('some_default') == 'yay'
assert n.unwrap_or('another_default') == 'another_default'


def test_map():
def test_map() -> None:
o = Ok('yay')
n = Err('nay')
assert o.map(lambda x: x + x).ok() == 'yayyay'
assert n.map(lambda x: x + x).err() == 'nay'
assert o.map(str.upper).ok() == 'YAY'
assert n.map(str.upper).err() == 'nay'

num = Ok(3)
errnum = Err(2)
assert num.map(lambda x: str(x)).ok() == '3'
assert errnum.map(lambda x: str(x)).err() == 2
assert num.map(str).ok() == '3'
assert errnum.map(str).err() == 2


def test_map_or():
def test_map_or() -> None:
o = Ok('yay')
n = Err('nay')
assert o.map_or('hay', lambda x: x + x) == 'yayyay'
assert n.map_or('hay', lambda x: x + x) == 'hay'
assert o.map_or('hay', str.upper) == 'YAY'
assert n.map_or('hay', str.upper) == 'hay'

num = Ok(3)
errnum = Err(2)
assert num.map_or('-1', lambda x: str(x)) == '3'
assert errnum.map_or('-1', lambda x: str(x)) == '-1'
assert num.map_or('-1', str) == '3'
assert errnum.map_or('-1', str) == '-1'


def test_map_or_else():
def test_map_or_else() -> None:
o = Ok('yay')
n = Err('nay')
assert o.map_or_else(lambda: 'hay', lambda x: x + x) == 'yayyay'
assert n.map_or_else(lambda: 'hay', lambda x: x + x) == 'hay'
assert o.map_or_else(lambda: 'hay', str.upper) == 'YAY'
assert n.map_or_else(lambda: 'hay', str.upper) == 'hay'

num = Ok(3)
errnum = Err(2)
assert num.map_or_else(lambda: '-1', lambda x: str(x)) == '3'
assert errnum.map_or_else(lambda: '-1', lambda x: str(x)) == '-1'
assert num.map_or_else(lambda: '-1', str) == '3'
assert errnum.map_or_else(lambda: '-1', str) == '-1'


def test_map_err():
def test_map_err() -> None:
o = Ok('yay')
n = Err('nay')
assert o.map_err(lambda x: x + x).ok() == 'yay'
assert n.map_err(lambda x: x + x).err() == 'naynay'
assert o.map_err(str.upper).ok() == 'yay'
assert n.map_err(str.upper).err() == 'NAY'


def test_isinstance_result_type():
def test_isinstance_result_type() -> None:
o = Ok('yay')
n = Err('nay')
assert isinstance(o, OkErr)
assert isinstance(n, OkErr)
assert not isinstance(1, OkErr)


def test_error_context():
def test_error_context() -> None:
n = Err('nay')
with pytest.raises(UnwrapError) as e:
n.unwrap()
Expand Down

0 comments on commit 71afad2

Please sign in to comment.