The `Right way` of testing and refactoring is so ambiguous to put in the real world, Especially, Business situation.
Let's look in deep the testing and refactoring at the perspective of a developer who works for a business situation.
3. Introduction to Test
Testing shows the presence, not the absence, of bugs.
In other words, a program can be proven incorrect by a
test, but it cannot be proven correct.
Robert C Martin
Clean Architecture: A Craftsman's Guide to Software Structure and Design
4. Introduction to Test
Let’s look at the code
Output: 3
def add(*args):
if len(list(filter(lambda n: not str(n).isdigit(), args))) > 0:
raise Exception('argument must be used number type')
return sum(args)
def main():
a = 1
b = 2
print(add(a, b))
if __name__ == '__main__':
main()
6. Introduction to Test
Let’s look at the code
Okay seems working well.
(The code throws Exception, Which is designed)
7. Introduction to Test
Let’s look at the code
When it will be happened, If we give number-like string as input to add func?
def add(*args):
if len(list(filter(lambda n: not str(n).isdigit(), args))) > 0:
raise Exception('argument must be used number type')
return sum(args)
def main():
a = ’3’
b = 1
print(add(a, b))
if __name__ == '__main__':
main()
9. Introduction to Test
The problem is
We can not expect all of user input.
Most of case,
User try to put some exceptional input (At least, The exception at the code, not Business)
And those input occurs not-expected bugs or failing.
You have to admit
Bugs are able to be happened every time, every where.
10. Introduction to Test
The purpose of test is quite simple.
Thinking all of the cases (aka. `TestCases`)
And make them to covered under those cases.
11. Introduction to Test
Back to the code
How can we prevent this exceptional bugs?
def add(*args):
if len(list(filter(lambda n: not str(n).isdigit(), args))) > 0:
raise Exception('argument must be used number type')
return sum(args)
def main():
a = ’3’
b = 1
print(add(a, b))
if __name__ == '__main__':
main()
12. Introduction to Test
Simple test cases
Right code block will cover for general scenarios input.
def add(*args):
if len(list(filter(lambda n: not str(n).isdigit(), args))) > 0:
raise Exception('argument must be used number type')
return sum(*args)
def main():
a = 2
b = 1
print(add(a, b))
if __name__ == '__main__':
main()
import unittest
from main import main, add
class TestSumFunction(unittest.TestCase):
def test_main(self):
try:
main()
except:
self.fail('should not throws any'
' error when we call main func')
def test_sum_with_numeric_args(self):
self.assertEqual(3, add(1, 2),
'should returns 3 when given 1, 2 in add func')
13. Introduction to Test
The result of test codes
Test result seems just Okay.
Okay is not okay, Test has to cover perfect.
14. Introduction to Test
Add more test scenarios.
These cases are going to cover more scenarios of user input.
def test_sum_with_numeric_like_str_args(self):
try:
add('1', '2')
except Exception as e:
self.assertEqual('argument must be used number type', str(e),
'should raises `argument must be used number type` message'
'when given `1`, `2` in add func')
return
self.fail('should raises an Exception when given `1`, `2` in add func')
def test_sum_with_combination_tuple_args(self):
try:
add((1, 2), 3)
except Exception as e:
self.assertEqual('argument must be used number type', str(e),
'should raises `argument must be used number type` message'
'when given (1, 2), 3 in add func')
return
self.fail('should raises an Exception when given (1, 2) in add func')
15. Introduction to Test
And look, We have got some new!
When numeric-like string is given, The code will worked to unexpected way.
16. Introduction to Test
Test will fix the code reasonably.
Finally, we can fix the code, By a result of unit test.
This is how to work based on Test.
def add(*args):
if len(list(filter(lambda n: not str(n).isdigit(), args))) > 0:
raise Exception('argument must be used number type')
return sum(args)
def add(*args):
if len(list(filter(lambda n: not isinstance(n, numbers.Number), args))) > 0:
raise Exception('argument must be used number type')
return sum(args)
17. Introduction to Test
There are so many types of test.
Unit test
Verify, Indivisual, Isolated
(like func, simple call)
Integration test
harmony, together with Unit test than one
more complex than Unit test
End to end test (aka. e2e test)
Robot script or higher level working script
will give an action like a human
(like Selenium test, Night watch test etc)
UI Test
User interface test
The pixel level must be equal as expected
(like Naver search button similarity
When finish to update at dev stage)
19. Introduction to Test
There are simple rule when you want to make unit test codes.
We do test, It, because
Steve Sanderson’s Blog (ASP.NET developer)
http://blog.stevensanderson.com/2009/08/24/writing-great-unit-tests-best-and-worst-practises/
And please This is basic rules
- Ignore test result after finish write up codes
- Change test spec frequently without coworkers
agreement
- Don’t test persistence layer
- Make function as pure or Use mock in testing inpure
function
20. The quality of codes
QA and Development should be working together to
ensure the quality of the system.
Robert C Martin
The Clean Coder: A Code of Conduct for Professional Programmers
21. About broken window theory
One broken window, left unrepaired for any substantial
length of time, instills in the inhabitants of the building a
sense of abandonment—
a sense that the powers that be don’t care about the building. So another window gets
broken. People start littering. Graffiti appears.
Serious structural damage begins. In a relatively short space of time, the building
becomes damaged beyond the owner’s desire to fix it, and the sense of abandonment
becomes reality.
22. The boy scout rule
“Always leave the code you’re editing
a little better than you found it”
Robert C. Martin
23. The quality of codes
Let’s think a simple scenario.
import requests
def get_user_session():
# this function look inside cache layer
# and user session, and return your session matched account key
return 'somekey'
def login(id, pw):
r = requests.get('https://nid.naver.com?id=' + id, '&pw=' + pw)
res = r.json()
if res.code == 200:
return res.account
else:
session = get_user_session()
if session is not None:
r = requests.get('https://nid.naver.com?key=' + session)
res = r.json()
if res.code == 200:
return res.account
else:
raise Exception('your account cannot be found')
def main():
id = 'id'
pw = 'pw'
login(id, pw)
if __name__ == '__main__':
main()
24. The quality of codes
Let’s think a simple scenario.
import requests
def get_user_session():
# this function look inside cache layer
# and user session, and return your session matched account key
return 'somekey'
def login(id, pw):
r = requests.get('https://nid.naver.com?id=' + id, '&pw=' + pw)
res = r.json()
if res.code == 200:
return res.account
else:
session = get_user_session()
if session is not None:
r = requests.get('https://nid.naver.com?key=' + session)
res = r.json()
if res.code == 200:
return res.account
else:
raise Exception('your account cannot be found')
def main():
id = 'id'
pw = 'pw'
login(id, pw)
if __name__ == '__main__':
main()
Don’t hard code URI.
Because URI can be changed,
even if the service is clear and official.
25. The quality of codes
Let’s think a simple scenario.
import requests
def get_user_session():
# this function look inside cache layer
# and user session, and return your session matched account key
return 'somekey'
def login(id, pw):
r = requests.get('https://nid.naver.com?id=' + id, '&pw=' + pw)
res = r.json()
if res.code == 200:
return res.account
else:
session = get_user_session()
if session is not None:
r = requests.get('https://nid.naver.com?key=' + session)
res = r.json()
if res.code == 200:
return res.account
else:
raise Exception('your account cannot be found')
def main():
id = 'id'
pw = 'pw'
login(id, pw)
if __name__ == '__main__':
main()
Code seem duplicated
Make them as new small function
Don’t be a worry when you add
a lot of function for avoiding duplication
26. The quality of codes
Let’s think a simple scenario.
import requests
def get_user_session():
# this function look inside cache layer
# and user session, and return your session matched account key
return 'somekey'
def login(id, pw):
r = requests.get('https://nid.naver.com?id=' + id, '&pw=' + pw)
res = r.json()
if res.code == 200:
return res.account
else:
session = get_user_session()
if session is not None:
r = requests.get('https://nid.naver.com?key=' + session)
res = r.json()
if res.code == 200:
return res.account
else:
raise Exception('your account cannot be found')
def main():
id = 'id'
pw = 'pw'
login(id, pw)
if __name__ == '__main__':
main()
So deep indent, And this is useless
Use guard clause this
or make them to another function
deep indent is always bad
27. The quality of codes
Let’s think a simple scenario.
import requests
def get_user_session():
# this function look inside cache layer
# and user session, and return your session matched account key
return 'somekey'
def login(id, pw):
r = requests.get('https://nid.naver.com?id=' + id, '&pw=' + pw)
res = r.json()
if res.code == 200:
return res.account
else:
session = get_user_session()
if session is not None:
r = requests.get('https://nid.naver.com?key=' + session)
res = r.json()
if res.code == 200:
return res.account
else:
raise Exception('your account cannot be found')
def main():
id = 'id'
pw = 'pw'
login(id, pw)
if __name__ == '__main__':
main()
Also deep indent
28. The quality of codes
Let’s think a simple scenario.
import requests
def get_user_session():
# this function look inside cache layer
# and user session, and return your session matched account key
return 'somekey'
def login(id, pw):
r = requests.get('https://nid.naver.com?id=' + id, '&pw=' + pw)
res = r.json()
if res.code == 200:
return res.account
else:
session = get_user_session()
if session is not None:
r = requests.get('https://nid.naver.com?key=' + session)
res = r.json()
if res.code == 200:
return res.account
else:
raise Exception('your account cannot be found')
def main():
id = 'id'
pw = 'pw'
login(id, pw)
if __name__ == '__main__':
main()
String concatenated by + operator
Use interpolation or
params argument of requests.get func
+ operator will be tough when you change
param name or value for future.
30. User scenario
How can we learn more, this kinda trick?
https://refactoring.guru/inline-class
https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it
https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
DRY rule
YAGNI rule