Skip to content

Commit

Permalink
Add tests for zone state values (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
manzanotti authored Aug 27, 2020
1 parent bcb1d5f commit 3870574
Show file tree
Hide file tree
Showing 3 changed files with 328 additions and 0 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ print(hub.device_by_id["2-2"].data)
await session.close()
```

### Unit tests

Please see the README.md file in the tests folder for more details on unit tests protocol.

### QA/CI via CircleCI
QA includes comparing JSON from **cURL** with output from this app using **diff**, for example:
```bash
Expand Down
81 changes: 81 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
### Unit tests

Unit tests are written using the standard Python unittest module.

Tests are written using the Arrange/Act/Assert pattern, where the test data is first set up,
then the actual call to the code is made, and finally the assertion is tested.

For example:

```
def test_when_bIsActive_is_true_then_state_bIsActive_true(self):
"Check that the bIsActive is correctly set to true"
self.raw_json["bIsActive"] = 1
genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub)
self.assertTrue(genius_zone.data["_state"]["bIsActive"])
```
Here, the first line arranges the test data, setting the value in the raw JSON that we want to test the parsing of.

The intention is to limit this to setting up any data that has a direct impact on what the unit test is specifically testing. You'll see at the top of our test files that we set up the raw json with all the data needed to not fail to be parsed. In this example, we only need to set the bIsActive field on the JSON for this test, as that is the value that we are testing the parsing of.

The second line is acting, i.e. this is calling the code to test. In this example, this creates an instance of the GeniusZone class, which automatically parses the raw JSON passed in to the contructor.

The third line is asserting whether the tested property contains the expected value.

```
def test_when_iType_OnOffTimer_fSP_not_zero_setpoint_state_setpoint_set_true(self):
"""Check that the setpoint is set to true when iType is OnOffTimer
and fSP is not zero"""
self.raw_json["fSP"] = 1.0
self.raw_json["iType"] = ZONE_TYPE.OnOffTimer
genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub)
self.assertTrue(genius_zone.data["setpoint"])
```
In this example, the first two lines are setting up the data fields that are required for the test. Specifically,
it sets the iType of the zone for this specific test case, then the value of fSP that is parsed. Both these values
are required to be able to test the desired code branch.

```
def test_when_iType_should_set_setpoint_state_setpoint_set_correctly(self):
"Check that the setpoint is set for certain values of iType"
setpoint = 21.0
self.raw_json["fSP"] = setpoint
test_values = (
ZONE_TYPE.ControlSP,
ZONE_TYPE.TPI
)
for zone_type in test_values:
with self.subTest(zone_type=zone_type):
self.raw_json["iType"] = zone_type
genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub)
self.assertEqual(genius_zone.data["setpoint"], setpoint)
```
In this more complicated example, we want to test that a specific value of the raw JSON field
is successfully parsed, for a range of values of a separate field in the JSON. To do this, we are using
sub-tests, which creates a test for each value in the test_values tuple.
The arrange section here sets up a local variable for the setpoint, as this will be used to set the value on the JSON
and to assert whether that value has been successfully parsed.
The test values that will change for each sub-test are then set up, looped over, and a sub-test created. Within this sub-test, there are its own arrange, act, and assert sections.

To manually run the tests from the command line, use this command:
```
python -m unittest discover -p "*_test.py"
```

Where the code to be tested contains other classes, make use of the Mock module to mock up
these classes and any behaviours that you require for your tests.

The idea is to test as little as possible in each test. To this aim, do not use multiple
asserts in a unit test.
243 changes: 243 additions & 0 deletions tests/data_properties_v3_conversion/genius_zone_data_state_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
"""
Tests for the GeniusZone class
"""

import unittest
from unittest.mock import Mock
from geniushubclient.const import (
IMODE_TO_MODE,
ZONE_MODE,
ZONE_TYPE
)
from geniushubclient.zone import GeniusZone


class GeniusZoneDataStateTests(unittest.TestCase):
"""
Test for the GeniusZone Class, state data.
"""

_device_id = "Device Id"
_zone_name = "Zone Name"

raw_json = {
"iID": _device_id,
"strName": _zone_name,
"bIsActive": 0,
"bInHeatEnabled": 0,
"bOutRequestHeat": 0,
"fBoostSP": 0,
"fPV": 21.0,
"fPV_offset": 0.0,
"fSP": 14.0,
"iBoostTimeRemaining": 0,
"iFlagExpectedKit": 517,
"iType": ZONE_TYPE.OnOffTimer,
"iMode": ZONE_MODE.Off,
"objFootprint": {
"bIsNight": 0,
"fFootprintAwaySP": 14.0,
"iFootprintTmNightStart": 75600,
"iProfile": 1,
"lstSP": [{
"fSP": 16.0,
"iDay": 0,
"iTm": 0
}, {
"fSP": 14.0,
"iDay": 0,
"iTm": 23400
}, {
"fSP": 20.0,
"iDay": 0,
"iTm": 59700
}, {
"fSP": 14.0,
"iDay": 0,
"iTm": 75000
}, {
"fSP": 16.0,
"iDay": 0,
"iTm": 75600
}
],
"objReactive": {
"fActivityLevel": 0.0
}
},
"objTimer": [{
"fSP": 14.0,
"iDay": 0,
"iTm": -1
}],
"trigger": {
"reactive": 0,
"output": 0
},
"warmupDuration": {
"bEnable": "true",
"bEnableCalcs": "true",
"fRiseRate": 0.5,
"iLagTime": 2420,
"iRiseTime": 300,
"iTotalTime": 2720
},
"zoneReactive": {
"fActivityLevel": 0
},
"zoneSubType": 1
}

def setUp(self):
hub = Mock()
hub.api_version = 3
self.hub = hub

def test_when_bIsActive_is_false_then_state_bIsActive_false(self):
"Check that the bIsActive is correctly set to false"

self.raw_json["bIsActive"] = 0

genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub)

self.assertFalse(genius_zone.data["_state"]["bIsActive"])

def test_when_bIsActive_is_true_then_state_bIsActive_true(self):
"Check that the bIsActive is correctly set to true"

self.raw_json["bIsActive"] = 1

genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub)

self.assertTrue(genius_zone.data["_state"]["bIsActive"])

def test_when_bOutRequestHeat_is_false_then_state_bOutRequestHeat_false(self):
"Check that the bOutRequestHeat is correctly set to false"

self.raw_json["bOutRequestHeat"] = 0

genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub)

self.assertFalse(genius_zone.data["_state"]["bOutRequestHeat"])

def test_when_bOutRequestHeat_is_true_then_state_bOutRequestHeat_true(self):
"Check that the bOutRequestHeat is correctly set to true"

self.raw_json["bOutRequestHeat"] = 1

genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub)

self.assertTrue(genius_zone.data["_state"]["bOutRequestHeat"])

def test_when_iMode_set_then_state_mode_is_set_correctly(self):
"Check that the mode is set on the class"

for zone_mode, zone_mode_text in IMODE_TO_MODE.items():
with self.subTest(zone_mode=zone_mode, zone_mode_text=zone_mode_text):
self.raw_json["iMode"] = zone_mode
self.raw_json["zoneSubType"] = 1

genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub)

self.assertEqual(genius_zone.data["mode"], zone_mode_text)

def test_when_iType_should_set_temperature_state_temperature_set_correctly(self):
"Check that the temperature is set for certain values of iType"

temperature = 20.0
self.raw_json["fPV"] = temperature

test_values = (
ZONE_TYPE.ControlSP,
ZONE_TYPE.TPI,
ZONE_TYPE.Manager
)

for zone_type in test_values:
with self.subTest(zone_type=zone_type):
self.raw_json["iType"] = zone_type

genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub)

self.assertEqual(genius_zone.data["temperature"], temperature)

def test_when_iType_should_not_set_temperature_state_temperature_not_set(self):
"Check that the temperature is not set for certain values of iType"

self.raw_json["fPV"] = 20.0

test_values = (
ZONE_TYPE.OnOffTimer,
ZONE_TYPE.ControlOnOffPID,
ZONE_TYPE.Surrogate
)

for zone_type in test_values:
with self.subTest(zone_type=zone_type):
self.raw_json["iType"] = zone_type

genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub)

self.assertFalse("temperature" in genius_zone.data)

def test_when_iType_should_set_setpoint_state_setpoint_set_correctly(self):
"Check that the setpoint is set for certain values of iType"

setpoint = 21.0
self.raw_json["fSP"] = setpoint

test_values = (
ZONE_TYPE.ControlSP,
ZONE_TYPE.TPI
)

for zone_type in test_values:
with self.subTest(zone_type=zone_type):
self.raw_json["iType"] = zone_type

genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub)

self.assertEqual(genius_zone.data["setpoint"], setpoint)

def test_when_iType_should_not_set_setpoint_state_setpoint_not_set(self):
"Check that the setpoint is not set for certain values of iType"

self.raw_json["fSP"] = 21.0

test_values = (
ZONE_TYPE.Manager,
ZONE_TYPE.ControlOnOffPID,
ZONE_TYPE.Surrogate
)

for zone_type in test_values:
with self.subTest(zone_type=zone_type):
self.raw_json["iType"] = zone_type

genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub)

self.assertFalse("setpoint" in genius_zone.data)

def test_when_iType_OnOffTimer_fSP_not_zero_setpoint_state_setpoint_set_true(self):
"""Check that the setpoint is set to true when iType is OnOffTimer
and fSP is not zero"""

self.raw_json["fSP"] = 1.0
self.raw_json["iType"] = ZONE_TYPE.OnOffTimer

genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub)

self.assertTrue(genius_zone.data["setpoint"])

def test_when_iType_OnOffTimer_fSP_zero_setpoint_state_setpoint_set_false(self):
"""Check that the setpoint is set to false when iType is OnOffTimer
and fSP is zero"""

self.raw_json["fSP"] = 0.0
self.raw_json["iType"] = ZONE_TYPE.OnOffTimer

genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub)

self.assertFalse(genius_zone.data["setpoint"])

0 comments on commit 3870574

Please sign in to comment.