diff --git a/code/newsletter/N11/tests/conftest.py b/code/newsletter/N11/tests/conftest.py new file mode 100644 index 0000000..ec884cb --- /dev/null +++ b/code/newsletter/N11/tests/conftest.py @@ -0,0 +1,79 @@ +import dataclasses + +import pytest + + +@dataclasses.dataclass +class Connection: + def execute(self, sql: str): + print(f"execute {sql}...") + return True + + def close(self): + print(f"\ndisconnect {repr(self)} object...") + + +@dataclasses.dataclass +class Database: + dialect: str = "sqlite" + + def connect(self): + return Connection() + + def close(self): + print(f"\ndisconnect {repr(self)} object...") + + +@pytest.fixture() +def sqlite(): + db = Database() + conn = db.connect() + try: + yield conn + finally: + conn.close() + db.close() + + +@pytest.fixture() +def mysql(): + db = Database(dialect="mysql") + conn = db.connect() + try: + yield conn + finally: + conn.close() + db.close() + + +@pytest.fixture(params=["sqlite", "mysql"]) +def database(request): + db = Database(dialect=request.param) + conn = db.connect() + try: + yield conn + finally: + conn.close() + db.close() + + +@pytest.fixture() +def gender(): + return ["male", "female"] + + +@pytest.fixture() +def cpu(): + return "cpu" + + +@pytest.fixture() +def device(cpu): + return f"the device with {cpu}" + + +@pytest.fixture(scope="class") +def logger(): + print("\nstart recording...") + yield + print("\nend recording...") diff --git a/code/newsletter/N11/tests/pytest_setup_teardown.py b/code/newsletter/N11/tests/pytest_setup_teardown.py new file mode 100644 index 0000000..ccc65cf --- /dev/null +++ b/code/newsletter/N11/tests/pytest_setup_teardown.py @@ -0,0 +1,37 @@ +import pytest + + +def setup_module(): + print("[module level]: setup") + + +def teardown_module(): + print("[module level]: teardown") + + +class TestFoo: + def setup(self, method) -> None: + print("\t\t[function level]: setup") + + def teardown(self, method) -> None: + print("\t\t[function level]: teardown") + + @classmethod + def setup_class(cls): + print("\t[class level]: setup") + + @classmethod + def teardown_class(cls): + print("\t[class level]: teardown") + + def test_case1(self): + print("\t\t\t--> test case 1 here") + assert True + + def test_case2(self): + print("\t\t\t--> test case 2 here") + assert True + + +if __name__ == '__main__': + pytest.main([__file__, "-s", "--no-header"]) diff --git a/code/newsletter/N11/tests/test_accumulate.py b/code/newsletter/N11/tests/test_accumulate.py new file mode 100644 index 0000000..07e6e93 --- /dev/null +++ b/code/newsletter/N11/tests/test_accumulate.py @@ -0,0 +1,27 @@ +from typing import Tuple, TypeVar + +import pytest + +Number = TypeVar("Number", int, float) + + +def accumulate(*numbers: Tuple[Number]) -> Number: + result = 0 + for n in numbers: + if not isinstance(n, (int, float)): + raise ValueError(f"{n} isn't a valid number value") + + result += n + + return result + + +def test_accumulate(): + numbers = [1, 2, 3, 4] + expected = 10 + + assert accumulate(*numbers) == expected + + +if __name__== '__main__': + pytest.main() diff --git a/code/newsletter/N11/tests/test_accumulate_with_other_mark.py b/code/newsletter/N11/tests/test_accumulate_with_other_mark.py new file mode 100644 index 0000000..81abd5b --- /dev/null +++ b/code/newsletter/N11/tests/test_accumulate_with_other_mark.py @@ -0,0 +1,52 @@ +import sys +from typing import Tuple, TypeVar + +import pytest + +Number = TypeVar("Number", int, float) +VERSION = "0.0.2" + + +def accumulate(*numbers: Tuple[Number]) -> Number: + result = 0 + for n in numbers: + if not isinstance(n, (int, float)): + raise ValueError(f"{n} isn't a valid number value") + + result += n + + return result + + +@pytest.mark.xfail() +@pytest.mark.parametrize("numbers, expected", argvalues=[([10, 11, -11, -12], 1)]) +def test_accumulate_with_failed(numbers, expected): + assert accumulate(*numbers) == expected + + +@pytest.mark.parametrize( + "numbers, expected", + argvalues=[ + pytest.param( + [10, 11, -11, -12], 1, marks=pytest.mark.xfail(reason="expected failed") + ) + ], +) +def test_accumulate_with_failed_mark(numbers, expected): + assert accumulate(*numbers) == expected + + +@pytest.mark.skip(reason="the case is WIP") +def test_wip_with_skip_mark(): + ... + + +def test_wip_with_skip_function(): + if VERSION <= '0.0.2': + pytest.skip("the API is still on WIP") + assert False + + +@pytest.mark.skipif(sys.platform != "linux", reason="only for linux") +def test_wip_function_only_for_linux(): + assert True diff --git a/code/newsletter/N11/tests/test_accumulate_with_parametrize_mark.py b/code/newsletter/N11/tests/test_accumulate_with_parametrize_mark.py new file mode 100644 index 0000000..c2b63d2 --- /dev/null +++ b/code/newsletter/N11/tests/test_accumulate_with_parametrize_mark.py @@ -0,0 +1,56 @@ +from typing import Tuple, TypeVar + +import pytest + +Number = TypeVar("Number", int, float) + + +def accumulate(*numbers: Tuple[Number]) -> Number: + result = 0 + for n in numbers: + if not isinstance(n, (int, float)): + raise ValueError(f"{n} isn't a valid number value") + + result += n + + return result + + +def test_accumulate_without_mark(): + numbers = [ + [1, 2, 3, 4], + [5, 6, 7, 8], + [-1, 0, 1, 0], + [10, 11, -11, -12], + ] + expected = (10, 26, 0, 1) + + for number, expect in zip(numbers, expected): + assert accumulate(*number) == expect + + +@pytest.mark.parametrize( + argnames="numbers,expected", + argvalues=[ + ([1, 2, 3, 4], 10), + ([5, 6, 7, 8], 26), + ([-1, 0, 1, 0], 0), + ([10, 11, -11, -12], 1), + ], + ids=list("abcd"), +) +def test_accumulate_with_parametrize_mark(numbers, expected): + assert accumulate(*numbers) == expected + + +@pytest.mark.parametrize(argnames="even_number", argvalues=[2, 4]) +@pytest.mark.parametrize(argnames="odd_number", argvalues=[1, 3]) +def test_accumulate_with_multiple_parametrize_mark(even_number, odd_number): + expected = { + "1+2": 3, + "1+4": 5, + "3+2": 5, + "3+4": 7, + } + expr = f"{odd_number}+{even_number}" + assert accumulate(odd_number, even_number) == expected[expr] diff --git a/code/newsletter/N11/tests/test_with_fixture.py b/code/newsletter/N11/tests/test_with_fixture.py new file mode 100644 index 0000000..c2d1ede --- /dev/null +++ b/code/newsletter/N11/tests/test_with_fixture.py @@ -0,0 +1,68 @@ +def test_sql_with_mock_sqlite_database(sqlite): + ok = sqlite.execute("SELECT 1;") + assert ok + + +def test_sql_with_mock_mysql_database(mysql): + ok = mysql.execute("SELECT * FROM mock_table;") + assert ok + + +def test_gender_fixture(gender): + print(f"Gender fixture value: {gender}") + assert ["male", "female"] == gender + + +class TestClassScopeFixture: + def test_foo(self, logger): + assert True + + def test_bar(self, logger): + assert True + + +class TestClassScopeFixture2: + def test_foo2(self, logger): + assert True + + def test_bar2(self, logger): + assert True + + +def test_with_mock_database(database): + sql = "SELECT * FROM mock_table;" + ok = database.execute(sql) + assert ok + + +def test_with_request_fixture(request): + from pprint import pformat + + props = [prop for prop in dir(request) if not prop.startswith("_")] + + print(f"request properties:\n{pformat(props)}") + assert True + + +def test_with_capsys_fixture(capsys): + def greet(name: str = ""): + msg = name or "World" + print(f"Hello, {msg}") + + greet() + captured = capsys.readouterr() + assert captured.out == "Hello, World\n" + + greet("100gle") + captured = capsys.readouterr() + assert captured.out == "Hello, 100gle\n" + + +def test_with_tmp_path_fixture(tmp_path): + import pathlib + + p = tmp_path.joinpath("foo") + print(f"\n{p}") + + assert isinstance(p, pathlib.Path) + assert p.stem == "foo" diff --git a/code/newsletter/N11/tests/unittest_setup_teardown.py b/code/newsletter/N11/tests/unittest_setup_teardown.py new file mode 100644 index 0000000..0fa3b50 --- /dev/null +++ b/code/newsletter/N11/tests/unittest_setup_teardown.py @@ -0,0 +1,37 @@ +import unittest + + +def setUpModule(): + print("[module level]: setup") + + +def tearDownModule(): + print("[module level]: teardown") + + +class TestFoo(unittest.TestCase): + def setUp(self) -> None: + print("\t\t[function level]: setup") + + def tearDown(self) -> None: + print("\t\t[function level]: teardown") + + @classmethod + def setUpClass(cls): + print("\t[class level]: setup") + + @classmethod + def tearDownClass(cls): + print("\t[class level]: teardown") + + def test_case1(self): + print("\t\t\t--> test case 1 here") + self.assertEqual(1, 1) + + def test_case2(self): + print("\t\t\t--> test case 2 here") + self.assertEqual(1, 1) + + +if __name__ == '__main__': + unittest.main()