Skip to content

Commit

Permalink
Start porting test suite to pytest.
Browse files Browse the repository at this point in the history
Porting to pytest allows access to the fixtures feature, parameterization and a whole load of
testing nicities, plus the assertions are cleaner IMO.

Hopefully this will provide enough momentum to expand coverage to even the lower modules
  • Loading branch information
Crozzers committed Sep 30, 2023
1 parent 05e7b97 commit 89642fc
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 1 deletion.
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ dist = [
dev = [
"mypy",
"flake8",
"types-pywin32 ; platform_system=='Windows'"
"types-pywin32 ; platform_system=='Windows'",
"pytest",
"pytest-mock"
]
docs = [
"jsmin",
Expand Down
Empty file added pytests/__init__.py
Empty file.
15 changes: 15 additions & 0 deletions pytests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from unittest.mock import Mock, patch
import screen_brightness_control as sbc
from .mocks import os_module_mock
import pytest

@pytest.fixture(autouse=True)
def mock_os_module(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(sbc, '_OS_MODULE', os_module_mock)
monkeypatch.setattr(sbc, '_OS_METHODS', os_module_mock.METHODS)
return os_module_mock


@pytest.fixture
def displays(mock_os_module):
return mock_os_module.list_monitors_info()
1 change: 1 addition & 0 deletions pytests/mocks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import *
38 changes: 38 additions & 0 deletions pytests/mocks/helpers_mock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from screen_brightness_control.helpers import BrightnessMethod


class MockBrightnessMethod(BrightnessMethod):
brightness = {}

@classmethod
def get_display_info(cls, display = None):
displays = [{
'name': 'Brand Display1',
'model': 'Display1',
'manufacturer': 'Brand',
'manufacturer_id': 'BRD',
'serial': 'serial1',
'edid': '00ffffffffff00edid1',
'method': cls,
'index': 0
}]

return displays

@classmethod
def get_brightness(cls, display = None):
results = []
for index, display in enumerate(cls.get_display_info()):
results.append(cls.brightness.get(display['name'], 100))
if display is not None and display == index:
break

return results

@classmethod
def set_brightness(cls, value, display = None):
for index, display in enumerate(cls.get_display_info()):
if display is None or display == index:
cls.brightness[display['name']] = value
if display is not None:
return
44 changes: 44 additions & 0 deletions pytests/mocks/os_module_mock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from .helpers_mock import MockBrightnessMethod

class Method1(MockBrightnessMethod):
@classmethod
def get_display_info(cls):
return super().get_display_info() + [{
'name': 'Brand Display2',
'model': 'Display2',
'manufacturer': 'Brand',
'manufacturer_id': 'BRD',
'serial': 'serial2',
'edid': '00ffffffffff00edid2',
'method': cls,
'index': 1
}]

class Method2(MockBrightnessMethod):
@classmethod
def get_display_info(cls, display = None):
return [{
'name': 'Brand Display3',
'model': 'Display3',
'manufacturer': 'Brand',
'manufacturer_id': 'BRD',
'serial': 'serial3',
'edid': '00ffffffffff00edid3',
'method': cls,
'index': 0
}]

def list_monitors_info(method = None, allow_duplicates = False, unsupported = False):
info = []
for m in METHODS:
info += m.get_display_info()

if method is not None:
method_names = [i.__name__.lower() for i in METHODS]
if method.lower() not in method_names:
raise ValueError('invalid method name')
info = [i for i in info if i['method'].__name__.lower() == method.lower()]

return info

METHODS = (Method1, Method2)
75 changes: 75 additions & 0 deletions pytests/test_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from typing import Any, Dict, List, Type
from unittest.mock import Mock
import pytest
import screen_brightness_control as sbc
from pytest_mock import MockerFixture

class TestGetBrightness:
@pytest.fixture
def patch_methods(
self, mocker: MockerFixture, mock_os_module
) -> Dict[Type[sbc.helpers.BrightnessMethod], Mock]:
'''
Patch all brightness methods to return the display's index
Returns:
dict of methods and their mocks
'''
def side_effect(display=None):
return [display]

displays = mock_os_module.list_monitors_info()
method_patches = {}
for display in displays:
method = display['method']
if method in method_patches:
continue
method_patches[method] = mocker.patch.object(method, 'get_brightness', Mock(side_effect=side_effect))
return method_patches

def test_returns_list_of_int(self):
'''Check return types and integer bounds'''
brightness = sbc.get_brightness()
assert isinstance(brightness, list)
assert all(isinstance(i, int) for i in brightness)
assert all(0 <= i <= 100 for i in brightness) # type: ignore

class TestDisplayKwarg:
@pytest.mark.parametrize('identifier', ['index', 'name', 'serial', 'edid'])
def test_identifiers(self, patch_methods: Dict[Any, Mock], displays, identifier: str):
'''Test referencing a display by a `DisplayIdentifier` works'''
for index, display in enumerate(displays):
spy = patch_methods[display['method']]
# have to call with global index
result = sbc.get_brightness(display=index if identifier == 'index' else display[identifier])
# filter_monitors resolves the identifier away to the index
# so just check that we call the right method with the right index
spy.assert_called_once_with(display=display['index'])
assert result == [display['index']]

spy.reset_mock()

def test_none(self, patch_methods: Dict[Any, Mock], displays):
'''Test all displays are fetched if no kwarg given'''
sbc.get_brightness(display=None)
# check all methods called for all displays
for display in displays:
patch_methods[display['method']].assert_any_call(display=display['index'])

class TestMethodKwarg:
def test_none(self, patch_methods: Dict[Any, Mock]):
'''Test all methods get called if no method kwarg given'''
sbc.get_brightness()
for method in patch_methods.values():
method.assert_called()

def test_methods(self, patch_methods: Dict[Any, Mock]):
'''Test method kwarg ensures only that method is called'''
for method, spy in patch_methods.items():
sbc.get_brightness(method=method.__name__)
spy.assert_called()
for other_method, other_spy in patch_methods.items():
if other_method is method:
continue
other_spy.assert_not_called()
spy.reset_mock()

0 comments on commit 89642fc

Please sign in to comment.