Now, we will write additional unit tests to improve the testing coverage. Specifically, we will write unit tests related to the hexacopter motor and the altimeter. Open the existing test_hexacopter.py
file and insert the following lines after the last line. The code file for the sample is included in the restful_python_chapter_10_03
folder:
def test_set_and_get_hexacopter_motor_speed(self): """ Ensure we can set and get the hexacopter's motor speed """ patch_args = {'motor_speed': 700} patch_response = self.fetch( '/hexacopters/1', method='PATCH', body=json.dumps(patch_args)) self.assertEqual(patch_response.code, status.HTTP_200_OK) get_response = self.fetch( '/hexacopters/1', method='GET') self.assertEqual(get_response.code, status.HTTP_200_OK) get_response_data = escape.json_decode(get_response.body) self.assertTrue('speed' in get_response_data.keys()) self.assertTrue('turned_on' in get_response_data.keys()) self.assertEqual(get_response_data['speed'], patch_args['motor_speed']) self.assertEqual(get_response_data['turned_on'], True) def test_get_altimeter_altitude(self): """ Ensure we can get the altimeter's altitude """ get_response = self.fetch( '/altimeters/1', method='GET') self.assertEqual(get_response.code, status.HTTP_200_OK) get_response_data = escape.json_decode(get_response.body) self.assertTrue('altitude' in get_response_data.keys()) self.assertGreaterEqual(get_response_data['altitude'], 0) self.assertLessEqual(get_response_data['altitude'], 3000)
The previous code added the following two methods to the TestHexacopter
class whose names start with the test_
prefix:
test_set_and_get_hexacopter_motor_speed
: This tests whether we can set and get the hexacopter's motor speed.test_get_altimeter_altitude
: This tests whether we can retrieve the altitude value from the altimeter.We just coded a few tests related to the hexacopter and the altimeter in order to improve test coverage and notice the impact on the test coverage report.
Now, run the following command within the same virtual environment we have been using:
nose2 -v --with-coverage
The following lines show the sample output. Notice that the numbers shown in the report might have small differences if our code includes additional lines or comments:
test_get_altimeter_altitude (test_hexacopter.TestHexacopter) ... I've started retrieving the altitude I've finished retrieving the altitude ok test_set_and_get_hexacopter_motor_speed (test_hexacopter.TestHexacopter) ... I've started setting the hexacopter's motor speed I've finished setting the hexacopter's motor speed I've started retrieving hexacopter's status I've finished retrieving hexacopter's status ok test_set_and_get_led_brightness_level (test_hexacopter.TestHexacopter) ... I've started setting the Blue LED's brightness level I've finished setting the Blue LED's brightness level I've started setting the White LED's brightness level I've finished setting the White LED's brightness level I've started retrieving Blue LED's status I've finished retrieving Blue LED's status I've started retrieving White LED's status I've finished retrieving White LED's status ok -------------------------------------------------------------- Ran 3 tests in 2.282s OK ----------- coverage: platform win32, python 3.5.2-final-0 --- Name Stmts Miss Cover ---------------------------------- async_api.py 129 38 71% drone.py 57 4 93% ---------------------------------- TOTAL 186 42 77%
The output provided details indicating that the test runner executed 3
tests and all of them passed. The test code coverage measurement report provided by the coverage
package increased the Cover
percentage of the async_api.py
module from 47%
in the previous run to 71%
. In addition, the percentage of the drone.py
module increased from 68%
to 93%
because we wrote tests that worked with all the components for the drone. The new additional tests we wrote executed additional code in the two modules, and therefore, there is an impact in the coverage report.
If we take a look at the missing statements, we will notice that we aren't testing scenarios where validations fail. Now, we will write additional unit tests to improve the testing coverage further. Specifically, we will write unit tests to make sure that we cannot set invalid brightness levels for the LEDs, we cannot set invalid motor speeds for the hexacopter, and we receive an HTTP 404 Not Found status code when we try to access a resource that doesn't exist. Open the existing test_hexacopter.py
file and insert the following lines after the last line. The code file for the sample is included in the restful_python_chapter_10_04
folder:
def test_set_invalid_brightness_level(self): """ Ensure we cannot set an invalid brightness level for a LED """ patch_args_led_1 = {'brightness_level': 256} patch_response_led_1 = self.fetch( '/leds/1', method='PATCH', body=json.dumps(patch_args_led_1)) self.assertEqual(patch_response_led_1.code, status.HTTP_400_BAD_REQUEST) patch_args_led_2 = {'brightness_level': -256} patch_response_led_2 = self.fetch( '/leds/2', method='PATCH', body=json.dumps(patch_args_led_2)) self.assertEqual(patch_response_led_2.code, status.HTTP_400_BAD_REQUEST) patch_response_led_3 = self.fetch( '/leds/2', method='PATCH', body=json.dumps({})) self.assertEqual(patch_response_led_3.code, status.HTTP_400_BAD_REQUEST) def test_set_brightness_level_invalid_led_id(self): """ Ensure we cannot set the brightness level for an invalid LED id """ patch_args_led_1 = {'brightness_level': 128} patch_response_led_1 = self.fetch( '/leds/100', method='PATCH', body=json.dumps(patch_args_led_1)) self.assertEqual(patch_response_led_1.code, status.HTTP_404_NOT_FOUND) def test_get_brightness_level_invalid_led_id(self): """ Ensure we cannot get the brightness level for an invalid LED id """ patch_response_led_1 = self.fetch( '/leds/100', method='GET') self.assertEqual(patch_response_led_1.code, status.HTTP_404_NOT_FOUND) def test_set_invalid_motor_speed(self): """ Ensure we cannot set an invalid motor speed for the hexacopter """ patch_args_hexacopter_1 = {'motor_speed': 89000} patch_response_hexacopter_1 = self.fetch( '/hexacopters/1', method='PATCH', body=json.dumps(patch_args_hexacopter_1)) self.assertEqual(patch_response_hexacopter_1.code, status.HTTP_400_BAD_REQUEST) patch_args_hexacopter_2 = {'motor_speed': -78600} patch_response_hexacopter_2 = self.fetch( '/hexacopters/1', method='PATCH', body=json.dumps(patch_args_hexacopter_2)) self.assertEqual(patch_response_hexacopter_2.code, status.HTTP_400_BAD_REQUEST) patch_response_hexacopter_3 = self.fetch( '/hexacopters/1', method='PATCH', body=json.dumps({})) self.assertEqual(patch_response_hexacopter_3.code, status.HTTP_400_BAD_REQUEST) def test_set_motor_speed_invalid_hexacopter_id(self): """ Ensure we cannot set the motor speed for an invalid hexacopter id """ patch_args_hexacopter_1 = {'motor_speed': 128} patch_response_hexacopter_1 = self.fetch( '/hexacopters/100', method='PATCH', body=json.dumps(patch_args_hexacopter_1)) self.assertEqual(patch_response_hexacopter_1.code, status.HTTP_404_NOT_FOUND) def test_get_motor_speed_invalid_hexacopter_id(self): """ Ensure we cannot get the motor speed for an invalid hexacopter id """ patch_response_hexacopter_1 = self.fetch( '/hexacopters/5', method='GET') self.assertEqual(patch_response_hexacopter_1.code, status.HTTP_404_NOT_FOUND) def test_get_altimeter_altitude_invalid_altimeter_id(self): """ Ensure we cannot get the altimeter's altitude for an invalid altimeter id """ get_response = self.fetch( '/altimeters/5', method='GET') self.assertEqual(get_response.code, status.HTTP_404_NOT_FOUND)
The previous code added the following seven methods to the TestHexacopter
class whose names start with the test_
prefix:
test_set_invalid_brightness_level
: This makes sure that we cannot set an invalid brightness level for an LED through an HTTP PATCH
request.test_set_brightness_level_invalid_led_id
: This makes sure that we cannot set the brightness level for an invalid LED ID through an HTTP PATCH
request.test_get_brightness_level_invalid_led_id
: This makes sure that we cannot get the brightness level for an invalid LED ID.test_set_invalid_motor_speed
: This makes sure that we cannot set an invalid motor seed for the hexacopter through an HTTP PATCH
request.test_set_motor_speed_invalid_hexacopter_id
: This makes sure that we cannot set the motor speed for an invalid hexacopter ID through an HTTP PATCH
request.test_get_motor_speed_invalid_hexacopter_id
: This makes sure that we cannot get the motor speed for an invalid hexacopter ID.test_get_altimeter_altitude_invalid_altimeter_id
: This makes sure that we cannot get the altitude value for an invalid altimeter ID.We coded many tests that will make sure that all the validations work as expected. Now, run the following command within the same virtual environment we have been using:
nose2 -v --with-coverage
The following lines show the sample output. Notice that the numbers shown in the report might have small differences if our code includes additional lines or comments:
I've finished retrieving the altitude ok test_get_altimeter_altitude_invalid_altimeter_id (test_hexacopter.TestHexacopter) ... WARNING:tornado.access:404 GET /altimeters/5 (127.0.0.1) 1.00ms ok test_get_brightness_level_invalid_led_id (test_hexacopter.TestHexacopter) ... WARNING:tornado.access:404 GET /leds/100 (127.0.0.1) 2.01ms ok test_get_motor_speed_invalid_hexacopter_id (test_hexacopter.TestHexacopter) ... WARNING:tornado.access:404 GET /hexacopters/5 (127.0.0.1) 2.01ms ok test_set_and_get_hexacopter_motor_speed (test_hexacopter.TestHexacopter) ... I've started setting the hexacopter's motor speed I've finished setting the hexacopter's motor speed I've started retrieving hexacopter's status I've finished retrieving hexacopter's status ok test_set_and_get_led_brightness_level (test_hexacopter.TestHexacopter) ... I've started setting the Blue LED's brightness level I've finished setting the Blue LED's brightness level I've started setting the White LED's brightness level I've finished setting the White LED's brightness level I've started retrieving Blue LED's status I've finished retrieving Blue LED's status I've started retrieving White LED's status I've finished retrieving White LED's status ok test_set_brightness_level_invalid_led_id (test_hexacopter.TestHexacopter) ... WARNING:tornado.access:404 PATCH /leds/100 (127.0.0.1) 1.01ms ok test_set_invalid_brightness_level (test_hexacopter.TestHexacopter) ... I've started setting the Blue LED's brightness level I've failed setting the Blue LED's brightness level WARNING:tornado.access:400 PATCH /leds/1 (127.0.0.1) 13.51ms I've started setting the White LED's brightness level I've failed setting the White LED's brightness level WARNING:tornado.access:400 PATCH /leds/2 (127.0.0.1) 10.03ms WARNING:tornado.access:400 PATCH /leds/2 (127.0.0.1) 2.01ms ok test_set_invalid_motor_speed (test_hexacopter.TestHexacopter) ... I've started setting the hexacopter's motor speed I've failed setting the hexacopter's motor speed WARNING:tornado.access:400 PATCH /hexacopters/1 (127.0.0.1) 19.27ms I've started setting the hexacopter's motor speed I've failed setting the hexacopter's motor speed WARNING:tornado.access:400 PATCH /hexacopters/1 (127.0.0.1) 9.04ms WARNING:tornado.access:400 PATCH /hexacopters/1 (127.0.0.1) 1.00ms ok test_set_motor_speed_invalid_hexacopter_id (test_hexacopter.TestHexacopter) ... WARNING:tornado.access:404 PATCH /hexacopters/100 (127.0.0.1) 1.00ms ok ---------------------------------------------------------------------- Ran 10 tests in 5.905s OK ----------- coverage: platform win32, python 3.5.2-final-0 ----------- Name Stmts Miss Cover ---------------------------------- async_api.py 129 5 96% drone.py 57 0 100% ---------------------------------- TOTAL 186 5 97%
The output provided details indicating that the test runner executed 10
tests and all of them passed. The test code coverage measurement report provided by the coverage
package increased the Cover
percentage of the async_api.py
module from 71%
in the previous run to 97%
. In addition, the percentage of the drone.py
module increased from 93%
to 100%
. If we check the coverage report, we will notice that the only statements that aren't executed are the statements included in the main method for the async_api.py module because they aren't part of the tests. Thus, we can say that we have 100%
coverage.
Now that we have a great test coverage, we can generate the requirements.txt
file that lists the application dependencies together with their versions. This way, any platform in which we decide to deploy the RESTful API will be able to easily install all the necessary dependencies listed in the file.
Run the following pip freeze
to generate the requirements.txt
file:
pip freeze > requirements.txt
The following lines show the content of a sample generated requirements.txt
file. However, bear in mind that many packages increase their version number quickly and you might see different versions in your configuration:
cov-core==1.15.0 coverage==4.2 nose2==0.6.5 six==1.10.0 tornado==4.4.1
18.190.156.93