From b51378cf871df1de4e82e68ebe0ba8d23ba199ea Mon Sep 17 00:00:00 2001 From: suaaa7 Date: Sun, 26 Jul 2020 21:35:42 +0900 Subject: [PATCH 01/10] Update .gitignore --- .gitignore | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 1c01023..8ddd045 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,8 @@ project/plugins/project/ .worksheet .idea -# pycaret_example +# pycaret_example & python_test_example *.csv pycaret_example/notebook/.ipynb_checkpoints -pycaret_example/notebook/catboost_info \ No newline at end of file +pycaret_example/notebook/catboost_info +__pycache__ From 4de5c739ee1473ad48e8ba518802c8da137e146b Mon Sep 17 00:00:00 2001 From: suaaa7 Date: Thu, 30 Jul 2020 23:28:21 +0900 Subject: [PATCH 02/10] Add i76_testcase_subclass/ --- .../i76_testcase_subclass/__init__.py | 0 .../i76_testcase_subclass/utils.py | 5 ++++ .../i76_testcase_subclass/utils_test.py | 26 +++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 python_test_example/i76_testcase_subclass/__init__.py create mode 100644 python_test_example/i76_testcase_subclass/utils.py create mode 100644 python_test_example/i76_testcase_subclass/utils_test.py diff --git a/python_test_example/i76_testcase_subclass/__init__.py b/python_test_example/i76_testcase_subclass/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python_test_example/i76_testcase_subclass/utils.py b/python_test_example/i76_testcase_subclass/utils.py new file mode 100644 index 0000000..0f8d014 --- /dev/null +++ b/python_test_example/i76_testcase_subclass/utils.py @@ -0,0 +1,5 @@ +def concat(str1: str, str2: str) -> str: + if not isinstance(str1, str) and isinstance(str2, str): + raise TypeError + + return str1 + str2 diff --git a/python_test_example/i76_testcase_subclass/utils_test.py b/python_test_example/i76_testcase_subclass/utils_test.py new file mode 100644 index 0000000..efdbd66 --- /dev/null +++ b/python_test_example/i76_testcase_subclass/utils_test.py @@ -0,0 +1,26 @@ +from unittest import TestCase, main + +from i76_testcase_subclass.utils import concat + +class UtilsTestCase(TestCase): + def test_good_for_concat(self): + test_cases = [ + (('a', 'b'), 'ab'), + (('test', 'case'), 'testcase'), + ] + for value, expected in test_cases: + with self.subTest(value): + self.assertEqual(expected, concat(value[0], value[1])) + + def test_bad_for_concat(self): + test_cases = [ + (('a', 2), TypeError), + ((1, 'b'), TypeError), + ] + for value, exception in test_cases: + with self.subTest(value): + with self.assertRaises(exception): + concat(value[0], value[1]) + +if __name__ == '__main__': + main() From be9ed0811ce4571c824e2e0abcdbe67cf6666676 Mon Sep 17 00:00:00 2001 From: suaaa7 Date: Thu, 30 Jul 2020 23:28:58 +0900 Subject: [PATCH 03/10] Add i77_setup_and_teardown --- .../i77_setup_and_teardown/__init__.py | 0 .../integration2_test.py | 25 +++++++++++++++++++ .../integration_test.py | 23 +++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 python_test_example/i77_setup_and_teardown/__init__.py create mode 100644 python_test_example/i77_setup_and_teardown/integration2_test.py create mode 100644 python_test_example/i77_setup_and_teardown/integration_test.py diff --git a/python_test_example/i77_setup_and_teardown/__init__.py b/python_test_example/i77_setup_and_teardown/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python_test_example/i77_setup_and_teardown/integration2_test.py b/python_test_example/i77_setup_and_teardown/integration2_test.py new file mode 100644 index 0000000..b424f70 --- /dev/null +++ b/python_test_example/i77_setup_and_teardown/integration2_test.py @@ -0,0 +1,25 @@ +from unittest import TestCase, main + +class Integration2Test(TestCase): + @classmethod + def setUpClass(cls): + print('* Class setup') + + @classmethod + def tearDownClass(cls): + print('* Class clean-up') + + def setUp(self): + print('** Test setup') + + def tearDown(self): + print('** Test clean-up') + + def test_1(self): + print('** Test 1') + + def test_2(self): + print('** Test 2') + +if __name__ == '__main__': + main() diff --git a/python_test_example/i77_setup_and_teardown/integration_test.py b/python_test_example/i77_setup_and_teardown/integration_test.py new file mode 100644 index 0000000..14efc17 --- /dev/null +++ b/python_test_example/i77_setup_and_teardown/integration_test.py @@ -0,0 +1,23 @@ +from unittest import TestCase, main + +def setUpModule(): + print('* Module setup') + +def tearDownModule(): + print('* Module clean-up') + +class IntegrationTest(TestCase): + def setUp(self): + print('** Test setup') + + def tearDown(self): + print('** Test clean-up') + + def test_1(self): + print('** Test 1') + + def test_2(self): + print('** Test 2') + +if __name__ == '__main__': + main() From 0947bae0469777627c8dedc4ec2d0027ef9a00b1 Mon Sep 17 00:00:00 2001 From: suaaa7 Date: Sat, 1 Aug 2020 20:10:51 +0900 Subject: [PATCH 04/10] Add flask_app --- python_test_example/flask_app/__init__.py | 0 python_test_example/flask_app/app.py | 22 +++++++++++++++ python_test_example/flask_app/app_test.py | 34 +++++++++++++++++++++++ python_test_example/flask_app/service.py | 11 ++++++++ 4 files changed, 67 insertions(+) create mode 100644 python_test_example/flask_app/__init__.py create mode 100644 python_test_example/flask_app/app.py create mode 100644 python_test_example/flask_app/app_test.py create mode 100644 python_test_example/flask_app/service.py diff --git a/python_test_example/flask_app/__init__.py b/python_test_example/flask_app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python_test_example/flask_app/app.py b/python_test_example/flask_app/app.py new file mode 100644 index 0000000..8b4de6c --- /dev/null +++ b/python_test_example/flask_app/app.py @@ -0,0 +1,22 @@ +import json + +from flask import Flask, jsonify + +from .service import Service + +app = Flask(__name__) + +print('Instantiate Service in app') +service = Service() + +@app.route('/', methods=['GET']) +def index(): + message = {'Message': 'ok'} + return jsonify(message) + +@app.route('/predict', methods=['POST']) +def predict(): + print('Call predict in app') + target = 'A' + message = {'result': service.predict(target)} + return jsonify(message) diff --git a/python_test_example/flask_app/app_test.py b/python_test_example/flask_app/app_test.py new file mode 100644 index 0000000..4213c8f --- /dev/null +++ b/python_test_example/flask_app/app_test.py @@ -0,0 +1,34 @@ +import json +from unittest import TestCase, main + +from flask_app.app import app + +print('In app_test') + +class AppTestCase(TestCase): + def setUp(self): + print('Call setUp in AppTestCase') + self.client = app.test_client() + + def test_index(self): + response = self.client.get('/') + self.assertEqual(response.status_code, 200) + + def test_index_content(self): + response = self.client.get('/') + self.assertEqual(response.content_type, 'application/json') + + def test_predict(self): + response = self.client.get('/predict') + self.assertEqual(response.status_code, 200) + + def test_predict_content(self): + response = self.client.get('/predict') + self.assertEqual(response.content_type, 'application/json') + + def test_predict_data(self): + response = self.client.get('/predict') + self.assertIsInstance(json.loads(response.data)['result'], float) + +if __name__ == '__main__': + main() diff --git a/python_test_example/flask_app/service.py b/python_test_example/flask_app/service.py new file mode 100644 index 0000000..2f419ff --- /dev/null +++ b/python_test_example/flask_app/service.py @@ -0,0 +1,11 @@ +from random import randint + +print('In service') + +class Service: + def __init__(self): + print('Init Service') + + def predict(self, target: str): + print('Call predict in service') + return randint(0, 1000) / 1000.0 From 9e8da81fc4f04f24ee1b667cd76c0862e51d988b Mon Sep 17 00:00:00 2001 From: suaaa7 Date: Sun, 2 Aug 2020 18:24:03 +0900 Subject: [PATCH 05/10] Fixed bug --- python_test_example/flask_app/app_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python_test_example/flask_app/app_test.py b/python_test_example/flask_app/app_test.py index 4213c8f..98a0762 100644 --- a/python_test_example/flask_app/app_test.py +++ b/python_test_example/flask_app/app_test.py @@ -19,15 +19,15 @@ def test_index_content(self): self.assertEqual(response.content_type, 'application/json') def test_predict(self): - response = self.client.get('/predict') + response = self.client.post('/predict') self.assertEqual(response.status_code, 200) def test_predict_content(self): - response = self.client.get('/predict') + response = self.client.post('/predict') self.assertEqual(response.content_type, 'application/json') def test_predict_data(self): - response = self.client.get('/predict') + response = self.client.post('/predict') self.assertIsInstance(json.loads(response.data)['result'], float) if __name__ == '__main__': From 96026eb2fe250e74a25c296f3bc4e2b4aea3d852 Mon Sep 17 00:00:00 2001 From: suaaa7 Date: Sun, 2 Aug 2020 19:57:29 +0900 Subject: [PATCH 06/10] Add patch to flask_app/app_test --- python_test_example/flask_app/app.py | 12 +++++++----- python_test_example/flask_app/app_test.py | 12 ++++++++++-- python_test_example/flask_app/model.py | 3 +++ python_test_example/flask_app/service.py | 9 +++++++++ 4 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 python_test_example/flask_app/model.py diff --git a/python_test_example/flask_app/app.py b/python_test_example/flask_app/app.py index 8b4de6c..a7200ad 100644 --- a/python_test_example/flask_app/app.py +++ b/python_test_example/flask_app/app.py @@ -1,6 +1,6 @@ import json -from flask import Flask, jsonify +from flask import Flask, jsonify, make_response from .service import Service @@ -11,12 +11,14 @@ @app.route('/', methods=['GET']) def index(): - message = {'Message': 'ok'} - return jsonify(message) + message = {'message': 'OK'} + return make_response(jsonify(message), 200) @app.route('/predict', methods=['POST']) def predict(): print('Call predict in app') target = 'A' - message = {'result': service.predict(target)} - return jsonify(message) + if not service.check_model: + return make_response(jsonify({'message': 'Service Unavailable'}), 503) + result = {'result': service.predict(target)} + return make_response(jsonify(result), 200) diff --git a/python_test_example/flask_app/app_test.py b/python_test_example/flask_app/app_test.py index 98a0762..5d96345 100644 --- a/python_test_example/flask_app/app_test.py +++ b/python_test_example/flask_app/app_test.py @@ -1,15 +1,23 @@ import json from unittest import TestCase, main - -from flask_app.app import app +from unittest.mock import Mock, patch print('In app_test') class AppTestCase(TestCase): def setUp(self): print('Call setUp in AppTestCase') + + self.load_model_patcher = patch('flask_app.model.load_model') + self.load_model_m = self.load_model_patcher.start() + self.load_model_m.return_value = 'test' + + from flask_app.app import app self.client = app.test_client() + def tearDown(self): + self.load_model_patcher.stop() + def test_index(self): response = self.client.get('/') self.assertEqual(response.status_code, 200) diff --git a/python_test_example/flask_app/model.py b/python_test_example/flask_app/model.py new file mode 100644 index 0000000..ef296ea --- /dev/null +++ b/python_test_example/flask_app/model.py @@ -0,0 +1,3 @@ +def load_model(): + print('Call load_model in model') + return 'model' diff --git a/python_test_example/flask_app/service.py b/python_test_example/flask_app/service.py index 2f419ff..a14990e 100644 --- a/python_test_example/flask_app/service.py +++ b/python_test_example/flask_app/service.py @@ -1,11 +1,20 @@ from random import randint +from .model import load_model + print('In service') class Service: def __init__(self): print('Init Service') + self.model = load_model() + print(f'self.model: {self.model}') def predict(self, target: str): print('Call predict in service') return randint(0, 1000) / 1000.0 + + def check_model(self): + if self.model: + return True + return False From cc439d70df049e36c24e5fd11abc09d08571d599 Mon Sep 17 00:00:00 2001 From: suaaa7 Date: Sun, 2 Aug 2020 20:24:18 +0900 Subject: [PATCH 07/10] Add test_predict_503 to AppTestCase --- python_test_example/flask_app/app.py | 2 +- python_test_example/flask_app/app_test.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/python_test_example/flask_app/app.py b/python_test_example/flask_app/app.py index a7200ad..de21bc0 100644 --- a/python_test_example/flask_app/app.py +++ b/python_test_example/flask_app/app.py @@ -18,7 +18,7 @@ def index(): def predict(): print('Call predict in app') target = 'A' - if not service.check_model: + if not service.check_model(): return make_response(jsonify({'message': 'Service Unavailable'}), 503) result = {'result': service.predict(target)} return make_response(jsonify(result), 200) diff --git a/python_test_example/flask_app/app_test.py b/python_test_example/flask_app/app_test.py index 5d96345..9141c75 100644 --- a/python_test_example/flask_app/app_test.py +++ b/python_test_example/flask_app/app_test.py @@ -1,6 +1,6 @@ import json from unittest import TestCase, main -from unittest.mock import Mock, patch +from unittest.mock import patch print('In app_test') @@ -38,5 +38,10 @@ def test_predict_data(self): response = self.client.post('/predict') self.assertIsInstance(json.loads(response.data)['result'], float) + @patch('flask_app.service.Service.check_model', return_value=False) + def test_predict_503(self, mock): + response = self.client.post('/predict') + self.assertEqual(response.status_code, 503) + if __name__ == '__main__': main() From e7192f3289e3e74bb2a80c52cc918c09af53ac1f Mon Sep 17 00:00:00 2001 From: suaaa7 Date: Sun, 2 Aug 2020 20:25:13 +0900 Subject: [PATCH 08/10] Add mock --- python_test_example/mock/__init__.py | 0 python_test_example/mock/my_class.py | 6 ++++ python_test_example/mock/my_class_test.py | 43 +++++++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 python_test_example/mock/__init__.py create mode 100644 python_test_example/mock/my_class.py create mode 100644 python_test_example/mock/my_class_test.py diff --git a/python_test_example/mock/__init__.py b/python_test_example/mock/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python_test_example/mock/my_class.py b/python_test_example/mock/my_class.py new file mode 100644 index 0000000..22ebb9f --- /dev/null +++ b/python_test_example/mock/my_class.py @@ -0,0 +1,6 @@ +import requests + +class MyClass: + def fetch_json(self, url: str): + response = requests.get(url) + return response.json() diff --git a/python_test_example/mock/my_class_test.py b/python_test_example/mock/my_class_test.py new file mode 100644 index 0000000..ea6ffbc --- /dev/null +++ b/python_test_example/mock/my_class_test.py @@ -0,0 +1,43 @@ +from unittest import TestCase, main, mock + +from mock.my_class import MyClass + +class MyClassTestCase(TestCase): + def mocked_requests_get(*args, **kwargs): + class MockResponse: + def __init__(self, json_data, status_code): + self.json_data = json_data + self.status_code = status_code + + def json(self): + return self.json_data + + if args[0] == 'http://example.com/test.json': + return MockResponse({'key1': 'value1'}, 200) + elif args[0] == 'http://example.com/another_test.json': + return MockResponse({'key2': 'value2'}, 200) + + return MockResponse(None, 404) + + @mock.patch('requests.get', side_effect=mocked_requests_get) + def test_fetch_json(self, mock_get): + my_class = MyClass() + + json_data = my_class.fetch_json('http://example.com/test.json') + self.assertEqual(json_data, {'key1': 'value1'}) + json_data = my_class.fetch_json('http://example.com/another_test.json') + self.assertEqual(json_data, {'key2': 'value2'}) + json_data = my_class.fetch_json('http://no_example.com/test.json') + self.assertIsNone(json_data) + + self.assertIn( + mock.call('http://example.com/test.json'), mock_get.call_args_list + ) + self.assertIn( + mock.call('http://example.com/another_test.json'), mock_get.call_args_list + ) + + self.assertEqual(len(mock_get.call_args_list), 3) + +if __name__ == '__main__': + main() From 311ea9f1f50071041e86c34b99f8323456a72d9c Mon Sep 17 00:00:00 2001 From: suaaa7 Date: Sun, 2 Aug 2020 20:25:40 +0900 Subject: [PATCH 09/10] Add Dockerfile --- python_test_example/Dockerfile | 4 ++++ python_test_example/Makefile | 15 +++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 python_test_example/Dockerfile create mode 100644 python_test_example/Makefile diff --git a/python_test_example/Dockerfile b/python_test_example/Dockerfile new file mode 100644 index 0000000..9d0711b --- /dev/null +++ b/python_test_example/Dockerfile @@ -0,0 +1,4 @@ +FROM python:3.7-slim + +RUN pip install --no-cache-dir --upgrade pip setuptools wheel \ + && pip install --no-cache-dir requests flask diff --git a/python_test_example/Makefile b/python_test_example/Makefile new file mode 100644 index 0000000..9f0661e --- /dev/null +++ b/python_test_example/Makefile @@ -0,0 +1,15 @@ +image = unittest +tag = 0.1.0 +curdir := `pwd` + +.PHONY: dbuild +dbuild: + docker build -t $(image):$(tag) . + +.PHONY: drun +drun: + docker run --rm -it -v $(curdir):/opt -w /opt $(image):$(tag) bash + +.PHONY: all_test +all_test: + python3 -m unittest discover --verbose --pattern "*_test.py" From 08da75026adc2e15514966575c98963739cafe9a Mon Sep 17 00:00:00 2001 From: suaaa7 Date: Wed, 5 Aug 2020 22:35:01 +0900 Subject: [PATCH 10/10] Add type hints --- python_test_example/flask_app/app_test.py | 4 ++-- python_test_example/flask_app/service.py | 4 ++-- python_test_example/mock/my_class.py | 2 +- python_test_example/mock/my_class_test.py | 11 ++++++----- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/python_test_example/flask_app/app_test.py b/python_test_example/flask_app/app_test.py index 9141c75..0c231e4 100644 --- a/python_test_example/flask_app/app_test.py +++ b/python_test_example/flask_app/app_test.py @@ -1,6 +1,6 @@ import json from unittest import TestCase, main -from unittest.mock import patch +from unittest.mock import MagicMock, patch print('In app_test') @@ -39,7 +39,7 @@ def test_predict_data(self): self.assertIsInstance(json.loads(response.data)['result'], float) @patch('flask_app.service.Service.check_model', return_value=False) - def test_predict_503(self, mock): + def test_predict_503(self, mock: MagicMock): response = self.client.post('/predict') self.assertEqual(response.status_code, 503) diff --git a/python_test_example/flask_app/service.py b/python_test_example/flask_app/service.py index a14990e..5f4791f 100644 --- a/python_test_example/flask_app/service.py +++ b/python_test_example/flask_app/service.py @@ -10,11 +10,11 @@ def __init__(self): self.model = load_model() print(f'self.model: {self.model}') - def predict(self, target: str): + def predict(self, target: str) -> float: print('Call predict in service') return randint(0, 1000) / 1000.0 - def check_model(self): + def check_model(self) -> bool: if self.model: return True return False diff --git a/python_test_example/mock/my_class.py b/python_test_example/mock/my_class.py index 22ebb9f..487eca1 100644 --- a/python_test_example/mock/my_class.py +++ b/python_test_example/mock/my_class.py @@ -1,6 +1,6 @@ import requests class MyClass: - def fetch_json(self, url: str): + def fetch_json(self, url: str) -> dict: response = requests.get(url) return response.json() diff --git a/python_test_example/mock/my_class_test.py b/python_test_example/mock/my_class_test.py index ea6ffbc..d99af84 100644 --- a/python_test_example/mock/my_class_test.py +++ b/python_test_example/mock/my_class_test.py @@ -1,4 +1,5 @@ -from unittest import TestCase, main, mock +from unittest import TestCase, main +from unittest.mock import MagicMock, call, patch from mock.my_class import MyClass @@ -19,8 +20,8 @@ def json(self): return MockResponse(None, 404) - @mock.patch('requests.get', side_effect=mocked_requests_get) - def test_fetch_json(self, mock_get): + @patch('requests.get', side_effect=mocked_requests_get) + def test_fetch_json(self, mock_get: MagicMock): my_class = MyClass() json_data = my_class.fetch_json('http://example.com/test.json') @@ -31,10 +32,10 @@ def test_fetch_json(self, mock_get): self.assertIsNone(json_data) self.assertIn( - mock.call('http://example.com/test.json'), mock_get.call_args_list + call('http://example.com/test.json'), mock_get.call_args_list ) self.assertIn( - mock.call('http://example.com/another_test.json'), mock_get.call_args_list + call('http://example.com/another_test.json'), mock_get.call_args_list ) self.assertEqual(len(mock_get.call_args_list), 3)