Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
pycaret_example/notebook/catboost_info
__pycache__
4 changes: 4 additions & 0 deletions python_test_example/Dockerfile
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions python_test_example/Makefile
Original file line number Diff line number Diff line change
@@ -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"
Empty file.
24 changes: 24 additions & 0 deletions python_test_example/flask_app/app.py
Original file line number Diff line number Diff line change
@@ -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)
47 changes: 47 additions & 0 deletions python_test_example/flask_app/app_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import json
from unittest import TestCase, main
from unittest.mock import 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):
response = self.client.post('/predict')
self.assertEqual(response.status_code, 503)

if __name__ == '__main__':
main()
3 changes: 3 additions & 0 deletions python_test_example/flask_app/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def load_model():
print('Call load_model in model')
return 'model'
20 changes: 20 additions & 0 deletions python_test_example/flask_app/service.py
Original file line number Diff line number Diff line change
@@ -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):
print('Call predict in service')
return randint(0, 1000) / 1000.0

def check_model(self):
if self.model:
return True
return False
Empty file.
5 changes: 5 additions & 0 deletions python_test_example/i76_testcase_subclass/utils.py
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions python_test_example/i76_testcase_subclass/utils_test.py
Original file line number Diff line number Diff line change
@@ -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()
Empty file.
25 changes: 25 additions & 0 deletions python_test_example/i77_setup_and_teardown/integration2_test.py
Original file line number Diff line number Diff line change
@@ -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()
23 changes: 23 additions & 0 deletions python_test_example/i77_setup_and_teardown/integration_test.py
Original file line number Diff line number Diff line change
@@ -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()
Empty file.
6 changes: 6 additions & 0 deletions python_test_example/mock/my_class.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import requests

class MyClass:
def fetch_json(self, url: str):
response = requests.get(url)
return response.json()
43 changes: 43 additions & 0 deletions python_test_example/mock/my_class_test.py
Original file line number Diff line number Diff line change
@@ -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()