-
파이썬 TDD 예제: chapter 02 암호 검사기 (feat. unittest)Programing/TDD 2022. 11. 27. 19:41반응형
이 글은 아래의 스터디 도서의 내용 중, 제가 자바 코드를 파이썬 코드로 변환하고,
코드만 올린 글입니다.
파이썬으로 TDD를 진행하는 기본적인 방식에 대해서는 아래의 글을 참조해 주세요
암호 검사기 전제조건 (규칙)
- 검사할 규칙은 아래의 3가지
- 길이가 8글자 이상
- 0~9 사이의 숫자를 포함
- 대문자 포함
- 세 규칙을 모두 충족 = 강함
- 2개의 규칙을 충족 = 보통
- 1개 이하의 규칙을 충족 = 약함
첫 번째 테스트: 모든 규칙을 충족하는 경우
PasswordStrengthMeter_test.py
import unittest from PasswordStrengthMeter import Meter class PasswordStrengthMeterTest(unittest.TestCase): def test_meetsAllCriteria_Then_strong(self): meter = Meter() result = meter.meter("ab12!@AB") self.assertEqual("STRONG", result) result2 = meter.meter("abc1!Add") self.assertEqual("STRONG", result2) if __name__ == '__main__': unittest.main()
PasswordStrengthMeter.py
class Meter: def meter(self, password): return "STRONG"
두 번째 테스트: 길이만 8글자 미만이고 나머지 조건은 충족하는 경우
PasswordStrengthMeter_test.py
import unittest from PasswordStrengthMeter import Meter class PasswordStrengthMeterTest(unittest.TestCase): def test_meetsAllCriteria_Then_strong(self): meter = Meter() result = meter.meter("ab12!@AB") self.assertEqual("STRONG", result) result2 = meter.meter("abc1!Add") self.assertEqual("STRONG", result2) def test_meetsOtherCriteria_except_for_Length_Then_Normal(self): meter = Meter() result = meter.meter("ab12!@A") self.assertEqual("NORMAL", result) result2 = meter.meter("Ab12!c") self.assertEqual("NORMAL", result2) if __name__ == '__main__': unittest.main()
PasswordStrengthMeter.py
class Meter: def meter(self, password): if len(password) < 8: return "NORMAL" return "STRONG"
세 번째 테스트: 숫자를 포함하지 않고 모든 조건은 충족하는 경우
PasswordStrengthMeter_test.py
# import 생략 class PasswordStrengthMeterTest(unittest.TestCase): def test_meetsAllCriteria_Then_strong(self): meter = Meter() result = meter.meter("ab12!@AB") self.assertEqual("STRONG", result) result2 = meter.meter("abc1!Add") self.assertEqual("STRONG", result2) def test_meetsOtherCriteria_except_for_Length_Then_Normal(self): meter = Meter() result = meter.meter("ab12!@A") self.assertEqual("NORMAL", result) result2 = meter.meter("Ab12!c") self.assertEqual("NORMAL", result2) def test_meetsOtherCriteria_except_for_Number_Then_Normal(self): meter = Meter() result = meter.meter("ab!@ABqwer") self.assertEqual("NORMAL", result) if __name__ == '__main__': unittest.main()
PasswordStrengthMeter.py
class Meter: def meter(self, password): if len(password) < 8: return "NORMAL" containsNum = self.meetsContainingNumberCrieteria(password) if not containsNum: return "NORMAL" return "STRONG" def meetsContainingNumberCrieteria(self, password): for ch in password: if (ch >= '0') & (ch <= '9'): return True return False
코드 정리: 테스트 코드 정리
PasswordStrengthMeter_test.py
# import 생략 class PasswordStrengthMeterTest(unittest.TestCase): meter = Meter() def assertStrength(self, password, strength): result = self.meter.meter(password) self.assertEqual(strength, result) def test_meetsAllCriteria_Then_strong(self): self.assertStrength("ab12!@AB", "STRONG") self.assertStrength("abc1!Add", "STRONG") def test_meetsOtherCriteria_except_for_Length_Then_Normal(self): self.assertStrength("ab12!@A", "NORMAL") self.assertStrength("Ab12!c", "NORMAL") def test_meetsOtherCriteria_except_for_Number_Then_Normal(self): self.assertStrength("ab!@ABqwer", "NORMAL") if __name__ == '__main__': unittest.main()
네 번째 테스트: 값이 없는 경우
PasswordStrengthMeter_test.py
# import 생략 class PasswordStrengthMeterTest(unittest.TestCase): meter = Meter() def assertStrength(self, password, strength): result = self.meter.meter(password) self.assertEqual(strength, result) def test_meetsAllCriteria_Then_strong(self): self.assertStrength("ab12!@AB", "STRONG") self.assertStrength("abc1!Add", "STRONG") def test_meetsOtherCriteria_except_for_Length_Then_Normal(self): self.assertStrength("ab12!@A", "NORMAL") self.assertStrength("Ab12!c", "NORMAL") def test_meetsOtherCriteria_except_for_Number_Then_Normal(self): self.assertStrength("ab!@ABqwer", "NORMAL") def test_nullInput_tent_Invalid(self): self.assertStrength(None, "INVALID") def test_emptyInput_Then_Invalid(self): self.assertStrength("", "INVALID") if __name__ == '__main__': unittest.main()
PasswordStrengthMeter.py
class Meter: def meter(self, password): if (password == None) | (password == ""): return "INVALID" if len(password) < 8: return "NORMAL" containsNum = self.meetsContainingNumberCrieteria(password) if not containsNum: return "NORMAL" return "STRONG" def meetsContainingNumberCrieteria(self, password): for ch in password: if (ch >= '0') & (ch <= '9'): return True return False
다섯 번째 테스트: 대문자를 포함하지 않고 나머지 조건을 충족하는 경우
PasswordStrengthMeter_test.py
# import 생략 class PasswordStrengthMeterTest(unittest.TestCase): meter = Meter() def assertStrength(self, password, strength): result = self.meter.meter(password) self.assertEqual(strength, result) def test_meetsAllCriteria_Then_strong(self): self.assertStrength("ab12!@AB", "STRONG") self.assertStrength("abc1!Add", "STRONG") def test_meetsOtherCriteria_except_for_Length_Then_Normal(self): self.assertStrength("ab12!@A", "NORMAL") self.assertStrength("Ab12!c", "NORMAL") def test_meetsOtherCriteria_except_for_Number_Then_Normal(self): self.assertStrength("ab!@ABqwer", "NORMAL") def test_nullInput_tent_Invalid(self): self.assertStrength(None, "INVALID") def test_emptyInput_Then_Invalid(self): self.assertStrength("", "INVALID") def test_meetsOtherCriteria_excpet_for_Uppercase_Then_Normal(self): self.assertStrength("ab12!@df", "NORMAL") if __name__ == '__main__': unittest.main()
PasswordStrengthMeter.py
class Meter: def meter(self, password): if (password == None) | (password == ""): return "INVALID" if len(password) < 8: return "NORMAL" containsNum = self.meetsContainingNumberCriteria(password) containsUpp = self.meetsContainingUppercaseCriteria(password) if not containsNum: return "NORMAL" if not containsUpp: return "NORMAL" return "STRONG" def meetsContainingNumberCriteria(self, password): for ch in password: if (ch >= '0') & (ch <= '9'): return True return False def meetsContainingUppercaseCriteria(self, password): for ch in password: if ch.isupper(): return True return False
여섯 번째 테스트: 길이가 8글자 이상인 조건만 충족하는 경우
PasswordStrengthMeter_test.py
# import 생략 class PasswordStrengthMeterTest(unittest.TestCase): meter = Meter() # 기타 테스트 생략 def test_meetsOnlyLengthCriteria_Then_Weak(self): self.assertStrength("abdefghi", "WEAK") if __name__ == '__main__': unittest.main()
PasswordStrengthMeter.py
class Meter: def meter(self, password): if (password == None) | (password == ""): return "INVALID" lengthEnough = (len(password) >= 8) containsNum = self.meetsContainingNumberCriteria(password) containsUpp = self.meetsContainingUppercaseCriteria(password) if (lengthEnough) & (not containsNum) & (not containsUpp): return "WEAK" if not lengthEnough: return "NORMAL" if not containsNum: return "NORMAL" if not containsUpp: return "NORMAL" return "STRONG" def meetsContainingNumberCriteria(self, password): for ch in password: if (ch >= '0') & (ch <= '9'): return True return False def meetsContainingUppercaseCriteria(self, password): for ch in password: if ch.isupper(): return True return False
일곱 번째 테스트: 숫자 포함 조건만 충족하는 경우
PasswordStrengthMeter_test.py
# import 생략 class PasswordStrengthMeterTest(unittest.TestCase): meter = Meter() # 기타 테스트 생략 def test_meetsOnlyNumCriteria_Then_Weak(self): self.assertStrength("12345", "WEAK") if __name__ == '__main__': unittest.main()
PasswordStrengthMeter.py
class Meter: def meter(self, password): if (password == None) | (password == ""): return "INVALID" lengthEnough = (len(password) >= 8) containsNum = self.meetsContainingNumberCriteria(password) containsUpp = self.meetsContainingUppercaseCriteria(password) if (lengthEnough) & (not containsNum) & (not containsUpp): return "WEAK" if (not lengthEnough) & (containsNum) & (not containsUpp): return "WEAK" if not lengthEnough: return "NORMAL" if not containsNum: return "NORMAL" if not containsUpp: return "NORMAL" return "STRONG" # meetsContainingNumberCriteria 생략 # meetsContainingUppercaseCriteria 생략
여덟 번째 테스트: 대문자 포함 조건만 충족하는 경우
PasswordStrengthMeter_test.py
# import 생략 class PasswordStrengthMeterTest(unittest.TestCase): meter = Meter() # 기타 테스트 생략 def test_meetsOnlyUpperCriteria_Then_Weak(self): self.assertStrength("ABZEF", "WEAK") if __name__ == '__main__': unittest.main()
PasswordStrengthMeter.py
class Meter: def meter(self, password): if (password == None) | (password == ""): return "INVALID" lengthEnough = (len(password) >= 8) containsNum = self.meetsContainingNumberCriteria(password) containsUpp = self.meetsContainingUppercaseCriteria(password) if (lengthEnough) & (not containsNum) & (not containsUpp): return "WEAK" if (not lengthEnough) & (containsNum) & (not containsUpp): return "WEAK" if (not lengthEnough) & (not containsNum) & (containsUpp): return "WEAK" if not lengthEnough: return "NORMAL" if not containsNum: return "NORMAL" if not containsUpp: return "NORMAL" return "STRONG" # meetsContainingNumberCriteria 생략 # meetsContainingUppercaseCriteria 생략
코드 정리: meter() 메서드 리팩토링
PasswordStrengthMeter.py
class Meter: def meter(self, password): if (password == None) | (password == ""): return "INVALID" metCounts = 0 if (len(password) >= 8): metCounts += 1 if self.meetsContainingNumberCriteria(password): metCounts += 1 if self.meetsContainingUppercaseCriteria(password): metCounts += 1 if metCounts == 1: return "WEAK" elif metCounts == 2: return "NORMAL" return "STRONG" def meetsContainingNumberCriteria(self, password): for ch in password: if (ch >= '0') & (ch <= '9'): return True return False def meetsContainingUppercaseCriteria(self, password): for ch in password: if ch.isupper(): return True return False
아홉 번째 테스트: 아무 조건도 충족하지 않는 경우
PasswordStrengthMeter_test.py
# import 생략 class PasswordStrengthMeterTest(unittest.TestCase): meter = Meter() # 기타 테스트 생략 def test_meetsNoCriteria_Then_Weak(self): self.assertStrength("abc", "WEAK") if __name__ == '__main__': unittest.main()
PasswordStrengthMeter.py
class Meter: def meter(self, password): if (password == None) | (password == ""): return "INVALID" metCounts = 0 if (len(password) >= 8): metCounts += 1 if self.meetsContainingNumberCriteria(password): metCounts += 1 if self.meetsContainingUppercaseCriteria(password): metCounts += 1 if metCounts <= 1: return "WEAK" elif metCounts == 2: return "NORMAL" return "STRONG" # meetsContainingNumberCriteria 생략 # meetsContainingUppercaseCriteria 생략
코드 정리: 코드 가독성 개선
PasswordStrengthMeter.py
class Meter: def meter(self, password): if (password == None) | (password == ""): return "INVALID" metCounts = self.getMetCriteriaCounts(password) if metCounts <= 1: return "WEAK" elif metCounts == 2: return "NORMAL" return "STRONG" def getMetCriteriaCounts(self, password): metCounts = 0 if (len(password) >= 8): metCounts += 1 if self.meetsContainingNumberCriteria(password): metCounts += 1 if self.meetsContainingUppercaseCriteria(password): metCounts += 1 return metCounts def meetsContainingNumberCriteria(self, password): for ch in password: if (ch >= '0') & (ch <= '9'): return True return False def meetsContainingUppercaseCriteria(self, password): for ch in password: if ch.isupper(): return True return False
이 글에서는 코드만을 다룹니다
1) 상세한 내용을 다루지 않고 코드만 올리는 이유는 다음과 같습니다.
- 왜 다음과 같은 과정으로 코드를 개발하는지 직접 책을 읽고 저자의 생각에 공감하고 비평하는 것이 실력향상에 도움이 되기 때문에 세부 내용은 작성하지 않습니다.
- 지적재산권을 침해하지 않기 위해 세부 내용은 작성하지 않습니다.
2) 그럼 굳이 책만 읽어도 해결 되는데 이 글이 무슨 소용이라고 글을 올리는가?에 대한 이유는 다음과 같습니다.
- 이 책은 자바 기반의 책이기 때문에, 파이썬 언어만을 다뤄본 분들에겐 약간의 어려움이 느껴질 수 있습니다.
(그래도 한번 펼쳐보는걸 추천드려요! 자바에 친숙하지 않더라도 금방 파악하실 수 있게 쉽게 쓰인 코드라고 생각합니다.)
조금이나마 도움이 될까 싶어 파이썬으로 동일한 과정을 진행하는 코드를 공유하고자 합니다.
3) 매 단계에서 코드의 제시순서는 (테스트코드→기능코드) 순입니다. 이유는 다음과 같습니다.
- 스터디 도서에서는 테스트코드를 먼저 작성하고, 이후 테스트를 통과할 수 있는 기능을 구현하라고 말합니다.
- 그 취지에 맞게, 테스트 코드를 상위에 두고, 기능코드를 후순을 둡니다.
반응형'Programing > TDD' 카테고리의 다른 글
파이썬 TDD 예제: 숫자야구게임 만들기 #2 (feat. unittest) (0) 2023.01.28 파이썬 TDD 예제: 숫자야구게임 만들기 #1 (feat. unittest) (0) 2023.01.28 파이썬 TDD 예제: chapter 03 유료 서비스 만료일 계산기 (feat. unittest) (1) 2022.12.06 파이썬으로 TDD 진행해보기(feat. unittest 예제) (0) 2022.11.24 TDD(Test Driven Development) 사내스터디를 시작하며 (0) 2022.11.23 - 검사할 규칙은 아래의 3가지