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__ 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" 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..de21bc0 --- /dev/null +++ b/python_test_example/flask_app/app.py @@ -0,0 +1,24 @@ +import json + +from flask import Flask, jsonify, make_response + +from .service import Service + +app = Flask(__name__) + +print('Instantiate Service in app') +service = Service() + +@app.route('/', methods=['GET']) +def index(): + message = {'message': 'OK'} + return make_response(jsonify(message), 200) + +@app.route('/predict', methods=['POST']) +def predict(): + print('Call predict in app') + target = 'A' + 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 new file mode 100644 index 0000000..0c231e4 --- /dev/null +++ b/python_test_example/flask_app/app_test.py @@ -0,0 +1,47 @@ +import json +from unittest import TestCase, main +from unittest.mock import MagicMock, 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) + + def test_index_content(self): + response = self.client.get('/') + self.assertEqual(response.content_type, 'application/json') + + def test_predict(self): + response = self.client.post('/predict') + self.assertEqual(response.status_code, 200) + + def test_predict_content(self): + response = self.client.post('/predict') + self.assertEqual(response.content_type, 'application/json') + + 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: MagicMock): + response = self.client.post('/predict') + self.assertEqual(response.status_code, 503) + +if __name__ == '__main__': + main() 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 new file mode 100644 index 0000000..5f4791f --- /dev/null +++ b/python_test_example/flask_app/service.py @@ -0,0 +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) -> float: + print('Call predict in service') + return randint(0, 1000) / 1000.0 + + def check_model(self) -> bool: + if self.model: + return True + return False 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() 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() 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..487eca1 --- /dev/null +++ b/python_test_example/mock/my_class.py @@ -0,0 +1,6 @@ +import requests + +class MyClass: + 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 new file mode 100644 index 0000000..d99af84 --- /dev/null +++ b/python_test_example/mock/my_class_test.py @@ -0,0 +1,44 @@ +from unittest import TestCase, main +from unittest.mock import MagicMock, call, patch + +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) + + @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') + 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( + call('http://example.com/test.json'), mock_get.call_args_list + ) + self.assertIn( + 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()