-
-
Notifications
You must be signed in to change notification settings - Fork 567
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a base to allow easier testing of devices #99
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
class DummyDevice: | ||
"""DummyDevice base class, you should inherit from this and call | ||
`super().__init__(args, kwargs)` to save the original state. | ||
|
||
This class provides helpers to test simple devices, for more complex | ||
ones you will want to extend the `return_values` accordingly. | ||
The basic idea is that the overloaded send() will read a wanted response | ||
based on the call from `return_values`. | ||
|
||
For changing values :func:`_set_state` will use :func:`pop()` to extract | ||
the first parameter and set the state accordingly. | ||
|
||
For a very simple device the following is enough, see :class:`TestPlug` | ||
for complete code. | ||
|
||
.. code-block:: | ||
self.return_values = { | ||
"get_prop": self._get_state, | ||
"power": lambda x: self._set_state("power", x) | ||
} | ||
|
||
""" | ||
def __init__(self, *args, **kwargs): | ||
self.start_state = self.state.copy() | ||
|
||
def send(self, command: str, parameters=None, retry_count=3): | ||
"""Overridden send() to return values from `self.return_values`.""" | ||
return self.return_values[command](parameters) | ||
|
||
def _reset_state(self): | ||
"""Revert back to the original state.""" | ||
self.state = self.start_state.copy() | ||
|
||
def _set_state(self, var, value): | ||
"""Set a state of a variable, | ||
the value is expected to be an array with length of 1.""" | ||
# print("setting %s = %s" % (var, value)) | ||
self.state[var] = value.pop(0) | ||
|
||
def _get_state(self, props): | ||
"""Return wanted properties""" | ||
return [self.state[x] for x in props if x in self.state] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
from unittest import TestCase | ||
from miio import Plug | ||
from .dummies import DummyDevice | ||
import pytest | ||
|
||
class DummyPlug(DummyDevice, Plug): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expected 2 blank lines, found 1 |
||
def __init__(self, *args, **kwargs): | ||
self.state = { | ||
'power': 'on', | ||
'temperature': 32, | ||
'current': 123, | ||
} | ||
self.return_values = { | ||
'get_prop': self._get_state, | ||
'set_power': lambda x: self._set_state("power", x), | ||
} | ||
super().__init__(args, kwargs) | ||
|
||
@pytest.fixture(scope="class") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expected 2 blank lines, found 1 |
||
def plug(request): | ||
request.cls.device = DummyPlug() | ||
# TODO add ability to test on a real device | ||
|
||
@pytest.mark.usefixtures("plug") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expected 2 blank lines, found 1 |
||
|
||
class TestPlug(TestCase): | ||
def test_on(self): | ||
self.device.off() # ensure off | ||
is_on = lambda: self.device.status().is_on | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do not assign a lambda expression, use a def |
||
start_state = is_on() | ||
assert start_state == False | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comparison to False should be 'if cond is False:' or 'if not cond:' |
||
|
||
self.device.on() | ||
assert is_on() == True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comparison to True should be 'if cond is True:' or 'if cond:' |
||
|
||
|
||
def test_off(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. too many blank lines (2) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. too many blank lines (2) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. too many blank lines (2) |
||
self.device.on() # ensure on | ||
is_on = lambda: self.device.status().is_on | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do not assign a lambda expression, use a def |
||
assert is_on() == True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comparison to True should be 'if cond is True:' or 'if cond:' |
||
|
||
self.device.off() | ||
assert is_on() == False | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comparison to False should be 'if cond is False:' or 'if not cond:' |
||
|
||
def test_status(self): | ||
self.device._reset_state() | ||
state = lambda: self.device.status() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do not assign a lambda expression, use a def |
||
print(state()) | ||
assert state().is_on == True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comparison to True should be 'if cond is True:' or 'if cond:' |
||
assert state().temperature == self.device.start_state["temperature"] | ||
assert state().load_power == self.device.start_state["current"] * 110 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. multiple spaces after operator There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. multiple spaces after operator There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. multiple spaces after operator |
||
|
||
def test_status_without_current(self): | ||
del self.device.state["current"] | ||
state = lambda: self.device.status() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do not assign a lambda expression, use a def |
||
assert state().load_power is None |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
from unittest import TestCase | ||
from miio import Yeelight | ||
from miio.yeelight import YeelightMode, YeelightStatus, YeelightException | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 'miio.yeelight.YeelightStatus' imported but unused |
||
import pytest | ||
from .dummies import DummyDevice | ||
|
||
class DummyLight(DummyDevice, Yeelight): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expected 2 blank lines, found 1 |
||
def __init__(self, *args, **kwargs): | ||
self.state = { | ||
'power': 'off', | ||
'bright': '100', | ||
'ct': '3584', | ||
'rgb': '16711680', | ||
'hue': '359', | ||
'sat': '100', | ||
'color_mode': '2', | ||
'name': 'test name', | ||
'lan_ctrl': '1', | ||
'save_state': '1' | ||
} | ||
|
||
self.return_values = { | ||
'get_prop': self._get_state, | ||
'set_power': lambda x: self._set_state("power", x), | ||
'set_bright': lambda x: self._set_state("bright", x), | ||
'set_ct_abx': lambda x: self._set_state("ct", x), | ||
'set_rgb': lambda x: self._set_state("rgb", x), | ||
'set_hsv': lambda x: self._set_state("hsv", x), | ||
'set_name': lambda x: self._set_state("name", x), | ||
'set_ps': lambda x: self.set_config(x), | ||
'toggle': self.toggle_power, | ||
'set_default': lambda x: 'ok' | ||
} | ||
|
||
super().__init__(*args, **kwargs) | ||
|
||
def set_config(self, x): | ||
key, value = x | ||
config_mapping = { | ||
'cfg_lan_ctrl': 'lan_ctrl', | ||
'cfg_save_state': 'save_state' | ||
} | ||
|
||
self._set_state(config_mapping[key], [value]) | ||
|
||
def toggle_power(self, _): | ||
if self.state["power"] == "on": | ||
self.state["power"] = "off" | ||
else: | ||
self.state["power"] = "on" | ||
|
||
|
||
@pytest.fixture(scope="class") | ||
def dummylight(request): | ||
request.cls.device = DummyLight() | ||
# TODO add ability to test on a real device | ||
|
||
@pytest.mark.usefixtures("dummylight") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expected 2 blank lines, found 1 |
||
class TestYeelight(TestCase): | ||
def test_status(self): | ||
self.device._reset_state() | ||
status = self.device.status() # type: YeelightStatus | ||
assert status.name == self.device.start_state["name"] | ||
assert status.is_on == False | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comparison to False should be 'if cond is False:' or 'if not cond:' |
||
assert status.brightness == 100 | ||
assert status.color_temp == 3584 | ||
assert status.color_mode == YeelightMode.ColorTemperature | ||
assert status.developer_mode == True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comparison to True should be 'if cond is True:' or 'if cond:' |
||
assert status.save_state_on_change == True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comparison to True should be 'if cond is True:' or 'if cond:' |
||
|
||
# following are tested in set mode tests | ||
# assert status.rgb == 16711680 | ||
# assert status.hsv == (359, 100, 100) | ||
|
||
def test_on(self): | ||
self.device.off() # make sure we are off | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. at least two spaces before inline comment |
||
assert self.device.status().is_on == False | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comparison to False should be 'if cond is False:' or 'if not cond:' |
||
self.device.on() | ||
assert self.device.status().is_on == True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comparison to True should be 'if cond is True:' or 'if cond:' |
||
|
||
def test_off(self): | ||
self.device.on() # make sure we are on | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. at least two spaces before inline comment |
||
assert self.device.status().is_on == True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comparison to True should be 'if cond is True:' or 'if cond:' |
||
self.device.off() | ||
assert self.device.status().is_on == False | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comparison to False should be 'if cond is False:' or 'if not cond:' |
||
|
||
def test_set_brightness(self): | ||
brightness = lambda: self.device.status().brightness | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do not assign a lambda expression, use a def |
||
|
||
self.device.set_brightness(50) | ||
assert brightness() == 50 | ||
self.device.set_brightness(0) | ||
assert brightness() == 0 | ||
self.device.set_brightness(100) | ||
|
||
with pytest.raises(YeelightException): | ||
self.device.set_brightness(-100) | ||
|
||
with pytest.raises(YeelightException): | ||
self.device.set_brightness(200) | ||
|
||
def test_set_color_temp(self): | ||
color_temp = lambda: self.device.status().color_temp | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do not assign a lambda expression, use a def |
||
self.device.set_color_temp(2000) | ||
assert color_temp() == 2000 | ||
self.device.set_color_temp(6500) | ||
assert color_temp() == 6500 | ||
|
||
with pytest.raises(YeelightException): | ||
self.device.set_color_temp(1000) | ||
|
||
with pytest.raises(YeelightException): | ||
self.device.set_color_temp(7000) | ||
|
||
@pytest.mark.skip("rgb is not properly implemented") | ||
def test_set_rgb(self): | ||
self.device._reset_state() | ||
assert self.device.status().rgb == 16711680 | ||
|
||
NEW_RGB = 16712222 | ||
self.set_rgb(NEW_RGB) | ||
assert self.device.status().rgb == NEW_RGB | ||
|
||
@pytest.mark.skip("hsv is not properly implemented") | ||
def test_set_hsv(self): | ||
self.reset_state() | ||
hue, sat, val = self.device.status().hsv | ||
assert hue == 359 | ||
assert sat == 100 | ||
assert val == 100 | ||
|
||
self.device.set_hsv() | ||
|
||
def test_set_developer_mode(self): | ||
dev_mode = lambda: self.device.status().developer_mode | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do not assign a lambda expression, use a def |
||
|
||
orig_mode = dev_mode() | ||
self.device.set_developer_mode(not orig_mode) | ||
new_mode = dev_mode() | ||
assert new_mode is not orig_mode | ||
self.device.set_developer_mode(not new_mode) | ||
assert new_mode is not dev_mode() | ||
|
||
def test_set_save_state_on_change(self): | ||
save_state = lambda: self.device.status().save_state_on_change | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do not assign a lambda expression, use a def |
||
orig_state = save_state() | ||
self.device.set_save_state_on_change(not orig_state) | ||
new_state = save_state() | ||
assert new_state is not orig_state | ||
self.device.set_save_state_on_change(not new_state) | ||
new_state = save_state() | ||
assert new_state is orig_state | ||
|
||
def test_set_name(self): | ||
name = lambda: self.device.status().name | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do not assign a lambda expression, use a def |
||
|
||
assert name() == "test name" | ||
self.device.set_name("new test name") | ||
assert name() == "new test name" | ||
|
||
def test_toggle(self): | ||
is_on = lambda: self.device.status().is_on | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do not assign a lambda expression, use a def |
||
|
||
orig_state = is_on() | ||
self.device.toggle() | ||
new_state = is_on() | ||
assert orig_state != new_state | ||
|
||
self.device.toggle() | ||
new_state = is_on() | ||
assert new_state == orig_state | ||
|
||
@pytest.mark.skip("cannot be tested easily") | ||
def test_set_default(self): | ||
self.fail() | ||
|
||
@pytest.mark.skip("set_scene is not implemented") | ||
def test_set_scene(self): | ||
self.fail() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expected 2 blank lines, found 1