├── .github └── workflows │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── pyproject.toml ├── requirements.txt ├── requirements_dev.txt ├── setup.cfg ├── setup.py ├── src └── slapping │ ├── __init__.py │ ├── py.typed │ └── slap_that_like_button.py ├── tests ├── __init__.py ├── conftest.py └── test_slapping.py └── tox.ini /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | test: 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, windows-latest] 13 | python-version: ['3.6', '3.7', '3.8', '3.9'] 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python ${{ matrix.python-version }} 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install tox tox-gh-actions 25 | - name: Test with tox 26 | run: tox 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 James Murphy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SlapThatLikeButton-TestingStarterProject 2 | A starter project to show how to set up and use automated testing in Python 3 | 4 | ![Tests](https://github.com/mCodingLLC/SlapThatLikeButton-TestingStarterProject/actions/workflows/tests.yml/badge.svg) 5 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=42.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [tool.pytest.ini_options] 6 | addopts = "--cov=slapping" 7 | testpaths = [ 8 | "tests", 9 | ] 10 | 11 | [tool.mypy] 12 | mypy_path = "src" 13 | check_untyped_defs = true 14 | disallow_any_generics = true 15 | ignore_missing_imports = true 16 | no_implicit_optional = true 17 | show_error_codes = true 18 | strict_equality = true 19 | warn_redundant_casts = true 20 | warn_return_any = true 21 | warn_unreachable = true 22 | warn_unused_configs = true 23 | no_implicit_reexport = true -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.26.0 -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | flake8==3.9.2 2 | tox==3.24.3 3 | pytest==6.2.5 4 | pytest-cov==2.12.1 5 | mypy===0.910 -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = slapping 3 | description = slap that like button 4 | author = James Murphy 5 | license = MIT 6 | license_file = LICENSE 7 | platforms = unix, linux, osx, cygwin, win32 8 | classifiers = 9 | Programming Language :: Python :: 3 10 | Programming Language :: Python :: 3 :: Only 11 | Programming Language :: Python :: 3.6 12 | Programming Language :: Python :: 3.7 13 | Programming Language :: Python :: 3.8 14 | Programming Language :: Python :: 3.9 15 | 16 | [options] 17 | packages = 18 | slapping 19 | install_requires = 20 | requests>=2 21 | python_requires = >=3.6 22 | package_dir = 23 | =src 24 | zip_safe = no 25 | 26 | [options.extras_require] 27 | testing = 28 | pytest>=6.0 29 | pytest-cov>=2.0 30 | mypy>=0.910 31 | flake8>=3.9 32 | tox>=3.24 33 | 34 | [options.package_data] 35 | slapping = py.typed 36 | 37 | [flake8] 38 | max-line-length = 160 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | if __name__ == "__main__": 4 | setup() -------------------------------------------------------------------------------- /src/slapping/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mCodingLLC/SlapThatLikeButton-TestingStarterProject/4273bccbb20fa58b32e5c5ef031a3490d6c7a402/src/slapping/__init__.py -------------------------------------------------------------------------------- /src/slapping/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mCodingLLC/SlapThatLikeButton-TestingStarterProject/4273bccbb20fa58b32e5c5ef031a3490d6c7a402/src/slapping/py.typed -------------------------------------------------------------------------------- /src/slapping/slap_that_like_button.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | class LikeState(enum.Enum): 5 | empty = enum.auto() 6 | liked = enum.auto() 7 | disliked = enum.auto() 8 | 9 | 10 | slap_like_transitions = { 11 | LikeState.empty: LikeState.liked, 12 | LikeState.liked: LikeState.empty, 13 | LikeState.disliked: LikeState.liked, 14 | } 15 | 16 | slap_dislike_transitions = { 17 | LikeState.empty: LikeState.disliked, 18 | LikeState.liked: LikeState.disliked, 19 | LikeState.disliked: LikeState.empty, 20 | } 21 | 22 | 23 | def slap_like(s: LikeState) -> LikeState: 24 | return slap_like_transitions[s] 25 | 26 | 27 | def slap_dislike(s: LikeState) -> LikeState: 28 | return slap_dislike_transitions[s] 29 | 30 | 31 | def slap_many(s: LikeState, slaps: str) -> LikeState: 32 | for c in slaps: 33 | c = c.lower() 34 | if c == 'l': 35 | s = slap_like(s) 36 | elif c == 'd': 37 | s = slap_dislike(s) 38 | else: 39 | raise ValueError('invalid slap') 40 | return s 41 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mCodingLLC/SlapThatLikeButton-TestingStarterProject/4273bccbb20fa58b32e5c5ef031a3490d6c7a402/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | 4 | 5 | @pytest.fixture 6 | def capture_stdout(monkeypatch): 7 | buffer = {"stdout": "", "write_calls": 0} 8 | 9 | def fake_write(s): 10 | buffer["stdout"] += s 11 | buffer["write_calls"] += 1 12 | 13 | monkeypatch.setattr(sys.stdout, 'write', fake_write) 14 | return buffer 15 | 16 | 17 | @pytest.fixture(scope="session") 18 | def db_conn(): 19 | db = ... 20 | url = ... 21 | with db.connect(url) as conn: # connection will be torn down after all tests finish 22 | yield conn 23 | -------------------------------------------------------------------------------- /tests/test_slapping.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from slapping.slap_that_like_button import LikeState, slap_many 3 | 4 | 5 | def test_empty_slap(): 6 | assert slap_many(LikeState.empty, '') is LikeState.empty 7 | 8 | 9 | def test_single_slaps(): 10 | assert slap_many(LikeState.empty, 'l') is LikeState.liked 11 | assert slap_many(LikeState.empty, 'd') is LikeState.disliked 12 | 13 | 14 | @pytest.mark.parametrize("test_input,expected", [ 15 | ('ll', LikeState.empty), 16 | ('dd', LikeState.empty), 17 | ('ld', LikeState.disliked), 18 | ('dl', LikeState.liked), 19 | ('ldd', LikeState.empty), 20 | ('lldd', LikeState.empty), 21 | ('ddl', LikeState.liked), 22 | ]) 23 | def test_multi_slaps(test_input, expected): 24 | assert slap_many(LikeState.empty, test_input) is expected 25 | 26 | 27 | @pytest.mark.skip(reason="regexes not supported yet") 28 | def test_regex_slaps(): 29 | assert slap_many(LikeState.empty, '[ld]*ddl') is LikeState.liked 30 | 31 | 32 | @pytest.mark.xfail 33 | def test_divide_by_zero(): 34 | assert 1 / 0 == 1 35 | 36 | 37 | def test_invalid_slap(): 38 | with pytest.raises(ValueError): 39 | slap_many(LikeState.empty, 'x') 40 | 41 | 42 | @pytest.mark.xfail 43 | def test_db_slap(db_conn): 44 | db_conn.read_slaps() 45 | assert ... 46 | 47 | 48 | def test_print(capture_stdout): 49 | print("hello") 50 | assert capture_stdout["stdout"] == "hello\n" 51 | 52 | # def test_many_slaps(): 53 | # assert slap_many(LikeState.empty, 'll') is LikeState.empty 54 | # assert slap_many(LikeState.empty, 'dd') is LikeState.empty 55 | # assert slap_many(LikeState.empty, 'ld') is LikeState.disliked 56 | # assert slap_many(LikeState.empty, 'dl') is LikeState.liked 57 | # assert slap_many(LikeState.empty, 'ldd') is LikeState.empty 58 | # assert slap_many(LikeState.empty, 'lldd') is LikeState.empty 59 | # assert slap_many(LikeState.empty, 'ddl') is LikeState.liked 60 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 3.8.0 3 | envlist = py36, py37, py38, py39, flake8, mypy 4 | isolated_build = true 5 | 6 | [gh-actions] 7 | python = 8 | 3.6: py36, mypy, flake8 9 | 3.7: py37 10 | 3.8: py38 11 | 3.9: py39 12 | 13 | [testenv] 14 | setenv = 15 | PYTHONPATH = {toxinidir} 16 | deps = 17 | -r{toxinidir}/requirements_dev.txt 18 | commands = 19 | pytest --basetemp={envtmpdir} 20 | 21 | [testenv:flake8] 22 | basepython = python3.6 23 | deps = flake8 24 | commands = flake8 src tests 25 | 26 | [testenv:mypy] 27 | basepython = python3.6 28 | deps = 29 | -r{toxinidir}/requirements_dev.txt 30 | commands = mypy src 31 | 32 | --------------------------------------------------------------------------------