-
파이썬 TDD 예제: chapter 03 유료 서비스 만료일 계산기 (feat. unittest)Programing/TDD 2022. 12. 6. 00:08반응형
이 글은 아래의 스터디 도서의 내용 중,
책의 개발 순서를 따라가지 않고 자체적으로 실습한 TDD 과정입니다.
따라서 개발 내용 및 테스트 구조가 불완전 할 수 있음을 먼저 안내 드립니다.
(부족한 점이나 제가 생각해보지 못한 점에 대한 충고 주시면 정말 감사드리겠습니다.)
파이썬으로 TDD를 진행하는 기본적인 방식에 대해서는 아래의 글을 참조해 주세요
유료 서비스 만료일 계산기 전제조건 (규칙)
- 서비스를 사용하려면 매달 1만원을 선불로 납부한다. 납부일 기준으로 한 달 뒤가 서비스 만료일이 된다.
- 2개월 이상 요금을 납부할 수 있다.
- 10만원을 납부하면 서비스를 1년 제공한다
[(추가적으로 생각해본) 고려할 요건, 테스트] 1) 한번에 납부하는 요금 - 딱 1달 치(1만원) - 2~9개월 치(2~9만원) - 딱 1년 치(10만원) - 13~19개월치(11~19만원) - 그 이상 2) 특수조건 - 2월 29일 (2020년에 존재) - 2월 29일이 낀 경우 - 2월 29일에 시작 - 2월 29일에 끝남 3) N번째 납부하는 요금
첫번째 테스트 : 1만원 넣으면 한달 뒤 만료 (+ 리팩토링)
ExpiryDateCalculator_test.py
import unittest from datetime import datetime from ExpiryDateCalculator import Calculator class ExpiryDateCalculatorTest(unittest.TestCase): cal = Calculator() def test_pay_10000_then_1_month(self): pay_amount = 10000 pay_date = datetime.strptime("221205", "%y%m%d") estimate_expiriy_date = self.cal.calculateExpiryDate(pay_date, pay_amount) real_expiry_date = datetime.strptime("230105", "%y%m%d") self.assertEqual(estimate_expiriy_date, real_expiry_date) pay_date = datetime.strptime("220131", "%y%m%d") estimate_expiriy_date = self.cal.calculateExpiryDate(pay_date, pay_amount) real_expiry_date = datetime.strptime("220228", "%y%m%d") self.assertEqual(estimate_expiriy_date, real_expiry_date) pay_date = datetime.strptime("230531", "%y%m%d") estimate_expiriy_date = self.cal.calculateExpiryDate(pay_date, pay_amount) real_expiry_date = datetime.strptime("230630", "%y%m%d") self.assertEqual(estimate_expiriy_date, real_expiry_date)
ExpiryDateCalculator.py
from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta class Calculator: def calculateExpiryDate(self, pay_date, pay_amount): return pay_date + relativedelta(months=1)
두번째 테스트 : 2~9만원 내는 경우 + 2월 29일이 낀 경우
ExpiryDateCalculator_test.py
import unittest from datetime import datetime from ExpiryDateCalculator import Calculator class ExpiryDateCalculatorTest(unittest.TestCase): cal = Calculator() def assertExpiryDate(self, pay_date, pay_amount, real_expiry_date): pay_date = datetime.strptime(pay_date, "%y%m%d") real_expiry_date = datetime.strptime(real_expiry_date, "%y%m%d") estimate_expiry_date = self.cal.calculateExpiryDate(pay_date, pay_amount) self.assertEqual(estimate_expiry_date, real_expiry_date) def test_pay_10000_then_1_month(self): self.assertExpiryDate("221205", 10000, "230105") self.assertExpiryDate("220131", 10000, "220228") self.assertExpiryDate("230531", 10000, "230630") def test_pay_10000_then_1_month_but_0229_existed(self): self.assertExpiryDate("200131", 10000, "200229") self.assertExpiryDate("200130", 10000, "200229") self.assertExpiryDate("240130", 10000, "240229") def test_pay_20000to90000_then_2to9_month(self): self.assertExpiryDate("220131", 20000, "220331") self.assertExpiryDate("230531", 40000, "230930") def test_pay_20000to90000_then_2to9_month_but_2029_existed(self): self.assertExpiryDate("200112", 60000, "200712") self.assertExpiryDate("231031", 40000, "240229") self.assertExpiryDate("230530", 90000, "240229")
ExpiryDateCalculator.py
from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta class Calculator: def calculateExpiryDate(self, pay_date, pay_amount): month_amount = pay_amount/10000 return pay_date + relativedelta(months=month_amount)
[(추가적으로 생각해본) 고려할 요건, 테스트] 1) 처음으로 납부하는 사람이 한번에 납부하는 경우 - 딱 1달 치(1만원) -> 해결 - 2~9개월 치(2~9만원) -> 이때도 특수조건(2월29일)해보기, 이때 되면 다른때는 추가로 테스트 안해봐도 됨 -> 해결 - 딱 1년 치(10만원) - 13~19개월치(11~19만원) - 그 이상 2) 특수조건 - 2월 29일 (2020년, 2024년에 존재) - 2월 29일이 낀 경우 - 2월 29일에 시작(다시 생각해보니 테스트 할 필요 없음 -> 그래도 일단 한번 해보긴 함) - 2월 29일에 끝남 3) N번째 납부하는 사람의 경우 4) 예외처리 - 납부금액이 1만원 단위가 아닌 경우 ex: 15000 - 납부금액이 0원, 마이너스로 입력된 경우
세번째 테스트 : 예외처리 - 0원이나 음수를 지불하려고 하는 경우 + 10000원 단위가 아닌 금액을 지불하려고 하는 경우
ExpiryDateCalculator_test.py
import unittest from datetime import datetime from ExpiryDateCalculator import Calculator class ExpiryDateCalculatorTest(unittest.TestCase): cal = Calculator() def assertExpiryDate(self, pay_date, pay_amount, real_expiry_date): pay_date = datetime.strptime(pay_date, "%y%m%d") if real_expiry_date != "INVALID": real_expiry_date = datetime.strptime(real_expiry_date, "%y%m%d") estimate_expiry_date = self.cal.calculateExpiryDate(pay_date, pay_amount) self.assertEqual(estimate_expiry_date, real_expiry_date) def test_pay_10000_then_1_month(self): self.assertExpiryDate("221205", 10000, "230105") self.assertExpiryDate("220131", 10000, "220228") self.assertExpiryDate("230531", 10000, "230630") def test_pay_10000_then_1_month_but_0229_existed(self): self.assertExpiryDate("200131", 10000, "200229") self.assertExpiryDate("200130", 10000, "200229") self.assertExpiryDate("240130", 10000, "240229") def test_pay_20000to90000_then_2to9_month(self): self.assertExpiryDate("220131", 20000, "220331") self.assertExpiryDate("230531", 40000, "230930") def test_pay_20000to90000_then_2to9_month_but_2029_existed(self): self.assertExpiryDate("200112", 60000, "200712") self.assertExpiryDate("231031", 40000, "240229") self.assertExpiryDate("230530", 90000, "240229") def test_pay_0_or_minus_then_Invalid(self): self.assertExpiryDate("200112", -10000, "INVALID") self.assertExpiryDate("231031", 0, "INVALID") def test_pay_that_not_divided_by_10000_then_INVALID(self): self.assertExpiryDate("231031", 15000, "INVALID") self.assertExpiryDate("221231", 27000, "INVALID")
ExpiryDateCalculator.py
from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta class Calculator: def calculateExpiryDate(self, pay_date, pay_amount): if pay_amount <= 0: return "INVALID" if (pay_amount % 10000) > 0: return "INVALID" month_amount = pay_amount/10000 return pay_date + relativedelta(months=month_amount)
[(추가적으로 생각해본) 고려할 요건, 테스트] 1) 처음으로 납부하는 사람이 한번에 납부하는 경우 - 딱 1달 치(1만원) -> 완료 - 2~9개월 치(2~9만원) -> 이때도 특수조건(2월29일)해보기, 이때 되면 다른때는 추가로 테스트 안해봐도 됨 -> 완료 - 딱 1년 치(10만원) - 13~19개월치(11~19만원) - 그 이상 2) 특수조건 - 2월 29일 (2020년, 2024년에 존재) - 2월 29일이 낀 경우 -> 완료 - 2월 29일에 끝남 -> 완료 3) N번째 납부하는 사람의 경우 4) 예외처리 - 납부금액이 1만원 단위가 아닌 경우 ex: 15000 -> 완료 - 납부금액이 0원, 마이너스로 입력된 경우 -> 완료
네번째 테스트 : 10만원 납부하면 10개월이 아닌 1년 뒤 만료
ExpiryDateCalculator_test.py
import unittest from datetime import datetime from ExpiryDateCalculator import Calculator class ExpiryDateCalculatorTest(unittest.TestCase): cal = Calculator() def assertExpiryDate(self, pay_date, pay_amount, real_expiry_date): pay_date = datetime.strptime(pay_date, "%y%m%d") if real_expiry_date != "INVALID": real_expiry_date = datetime.strptime(real_expiry_date, "%y%m%d") estimate_expiry_date = self.cal.calculateExpiryDate(pay_date, pay_amount) self.assertEqual(estimate_expiry_date, real_expiry_date) # 앞선 다른 테스트 생략 def test_pay_100000_then_1_year(self): self.assertExpiryDate("230101", 100000, "240101") self.assertExpiryDate("240215", 100000, "250215")
ExpiryDateCalculator.py
from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta class Calculator: def calculateExpiryDate(self, pay_date, pay_amount): if pay_amount <= 0: return "INVALID" if (pay_amount % 10000) > 0: return "INVALID" month_amount = pay_amount/10000 if (month_amount / 10) >= 1: month_amount += 2 return pay_date + relativedelta(months=month_amount)
다섯번째 테스트 : 11~19만원, 20만원 이상 납부하는 경우
ExpiryDateCalculator_test.py
import unittest from datetime import datetime from ExpiryDateCalculator import Calculator class ExpiryDateCalculatorTest(unittest.TestCase): cal = Calculator() def assertExpiryDate(self, pay_date, pay_amount, real_expiry_date): pay_date = datetime.strptime(pay_date, "%y%m%d") if real_expiry_date != "INVALID": real_expiry_date = datetime.strptime(real_expiry_date, "%y%m%d") estimate_expiry_date = self.cal.calculateExpiryDate(pay_date, pay_amount) self.assertEqual(estimate_expiry_date, real_expiry_date) # 앞선 다른 테스트 생략 def test_pay_110000to190000_then_1_year_and_2to9_month(self): self.assertExpiryDate("230101", 130000, "240401") self.assertExpiryDate("200331", 180000, "211130") def test_pay_more_than_200000(self): self.assertExpiryDate("220101", 200000, "240101") self.assertExpiryDate("220101", 250000, "240601") self.assertExpiryDate("140130", 610000, "200229")
ExpiryDateCalculator.py
from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta class Calculator: def calculateExpiryDate(self, pay_date, pay_amount): if pay_amount <= 0: return "INVALID" if (pay_amount % 10000) > 0: return "INVALID" month_amount = pay_amount/10000 bonus_amount = int(month_amount / 10)*2 month_amount += bonus_amount return pay_date + relativedelta(months=month_amount)
여섯번째 테스트 : 서비스만료 이전에 추가로 납부하는 경우
ExpiryDateCalculator_test.py
import unittest from datetime import datetime from ExpiryDateCalculator import Calculator class ExpiryDateCalculatorTest(unittest.TestCase): cal = Calculator() def assertExpiryDate(self, pay_date, pay_amount, real_expiry_date, last_expiry_date=None): pay_date = datetime.strptime(pay_date, "%y%m%d") if real_expiry_date != "INVALID": real_expiry_date = datetime.strptime(real_expiry_date, "%y%m%d") estimate_expiry_date = self.cal.calculateExpiryDate(pay_date, pay_amount, last_expiry_date) self.assertEqual(estimate_expiry_date, real_expiry_date) # 앞선 테스트 생략 def test_pay_before_expiry_date(self): self.assertExpiryDate("220115", 10000, "220217", "220117") # 220117에 만료되는 사람이 220115에 1만원 납부 self.assertExpiryDate("231007", 110000, "250101", "231201") # 231201에 만료되는 사람이 231007에 11만원 납부
ExpiryDateCalculator.py
from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta class Calculator: def calculateExpiryDate(self, pay_date, pay_amount, last_expiry_date=None): if pay_amount <= 0: return "INVALID" if (pay_amount % 10000) > 0: return "INVALID" month_amount = pay_amount/10000 bonus_amount = int(month_amount / 10)*2 month_amount += bonus_amount if last_expiry_date != None: pay_date = datetime.strptime(last_expiry_date, "%y%m%d") return pay_date + relativedelta(months=month_amount)
[(추가적으로 생각해본) 고려할 요건, 테스트] 1) 처음으로 납부하는 사람이 한번에 납부하는 경우 - 딱 1달 치(1만원) -> 완료 - 2~9개월 치(2~9만원) -> 이때도 특수조건(2월29일)해보기, 이때 되면 다른때는 추가로 테스트 안해봐도 됨 -> 완료 - 딱 1년 치(10만원) -> 완료 - 13~19개월치(11~19만원) -> 완료 - 그 이상 -> 완료 2) 특수조건 - 2월 29일 (2020년, 2024년에 존재) - 2월 29일이 낀 경우 -> 완료 - 2월 29일에 끝남 -> 완료 3) N번째 납부하는 사람의 경우 - 이전에 납부했던 금액에 대한 만료 당월에 추가로 납부하는 경우 -> 완료 - 이전에 납부했던 금액에 대한 만료 당월이 아닌데 또 납부하는 경우 -> 완료 - 근데 만료일(년월일 중 일)이 시작일자랑 다른 경우 (31일에 납부 시작했던 사람이 이번 납부 만료일은 30일) 4) 예외처리 - 납부금액이 1만원 단위가 아닌 경우 ex: 15000 -> 완료 - 납부금액이 0원, 마이너스로 입력된 경우 -> 완료
일곱번째 테스트 : N번째 납부하는 사람의 시작일자와 현재의 서비스 만료일이 다른 경우
ExpiryDateCalculator_test.py
import unittest from datetime import datetime from ExpiryDateCalculator import Calculator class ExpiryDateCalculatorTest(unittest.TestCase): cal = Calculator() def assertExpiryDate(self, pay_date, pay_amount, real_expiry_date, last_expiry_date=None, first_pay_date=None): pay_date = datetime.strptime(pay_date, "%y%m%d") if real_expiry_date != "INVALID": real_expiry_date = datetime.strptime(real_expiry_date, "%y%m%d") if last_expiry_date: last_expiry_date = datetime.strptime(last_expiry_date, "%y%m%d") if first_pay_date: first_pay_date = datetime.strptime(first_pay_date, "%y%m%d") estimate_expiry_date = self.cal.calculateExpiryDate(pay_date, pay_amount, last_expiry_date, first_pay_date) self.assertEqual(estimate_expiry_date, real_expiry_date) def test_pay_10000_then_1_month(self): self.assertExpiryDate("221205", 10000, "230105") self.assertExpiryDate("220131", 10000, "220228") self.assertExpiryDate("230531", 10000, "230630") def test_pay_10000_then_1_month_but_0229_existed(self): self.assertExpiryDate("200131", 10000, "200229") self.assertExpiryDate("200130", 10000, "200229") self.assertExpiryDate("240130", 10000, "240229") def test_pay_20000to90000_then_2to9_month(self): self.assertExpiryDate("220131", 20000, "220331") self.assertExpiryDate("230531", 40000, "230930") def test_pay_20000to90000_then_2to9_month_but_2029_existed(self): self.assertExpiryDate("200112", 60000, "200712") self.assertExpiryDate("231031", 40000, "240229") self.assertExpiryDate("230530", 90000, "240229") def test_pay_0_or_minus_then_Invalid(self): self.assertExpiryDate("200112", -10000, "INVALID") self.assertExpiryDate("231031", 0, "INVALID") def test_pay_that_not_divided_by_10000_then_INVALID(self): self.assertExpiryDate("231031", 15000, "INVALID") self.assertExpiryDate("221231", 27000, "INVALID") def test_pay_100000_then_1_year(self): self.assertExpiryDate("230101", 100000, "240101") self.assertExpiryDate("240215", 100000, "250215") def test_pay_110000to190000_then_1_year_and_2to9_month(self): self.assertExpiryDate("230101", 130000, "240401") self.assertExpiryDate("200331", 180000, "211130") def test_pay_more_than_200000(self): self.assertExpiryDate("220101", 200000, "240101") self.assertExpiryDate("220101", 250000, "240601") self.assertExpiryDate("140130", 610000, "200229") def test_pay_before_expiry_date(self): self.assertExpiryDate("220115", 10000, "220217", last_expiry_date="220117", first_pay_date="210617") self.assertExpiryDate("231007", 110000, "250101", last_expiry_date="231201", first_pay_date="200401") def test_pay_before_expiry_date_but_first_pay_date_is_different(self): self.assertExpiryDate("220115", 10000, "220331", last_expiry_date="220228", first_pay_date="190331") self.assertExpiryDate("190615", 100000, "210228", last_expiry_date="200229", first_pay_date="190430")
ExpiryDateCalculator.py
from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta class Calculator: def calculateExpiryDate(self, pay_date, pay_amount, last_expiry_date=None, first_pay_date=None): if pay_amount <= 0: return "INVALID" if (pay_amount % 10000) > 0: return "INVALID" month_amount = pay_amount/10000 bonus_amount = int(month_amount / 10)*2 month_amount += bonus_amount if (last_expiry_date != None) & (first_pay_date != None): relative_years = relativedelta(last_expiry_date, first_pay_date).years relative_months = relativedelta(last_expiry_date, first_pay_date).months return first_pay_date + relativedelta(years=relative_years) + relativedelta(months=month_amount+relative_months) return pay_date + relativedelta(months=month_amount)
이 글에서는 코드만을 다룹니다
1) 상세한 내용을 다루지 않고 코드만 올리는 이유는 다음과 같습니다.
- 왜 다음과 같은 과정으로 코드를 개발하는지 직접 책을 읽고 저자의 생각에 공감하고 비평하는 것이 실력향상에 도움이 되기 때문에 세부 내용은 작성하지 않습니다.
- 지적재산권을 침해하지 않기 위해 세부 내용은 작성하지 않습니다.
2) 그럼 굳이 책만 읽어도 해결 되는데 이 글이 무슨 소용이라고 글을 올리는가?에 대한 이유는 다음과 같습니다.
- 이 책은 자바 기반의 책이기 때문에, 파이썬 언어만을 다뤄본 분들에겐 약간의 어려움이 느껴질 수 있습니다.
(그래도 한번 펼쳐보는걸 추천드려요! 자바에 친숙하지 않더라도 금방 파악하실 수 있게 쉽게 쓰인 코드라고 생각합니다.)
조금이나마 도움이 될까 싶어 파이썬으로 동일한 과정을 진행하는 코드를 공유하고자 합니다.
3) 매 단계에서 코드의 제시순서는 (테스트코드→기능코드) 순입니다. 이유는 다음과 같습니다.
- 스터디 도서에서는 테스트코드를 먼저 작성하고, 이후 테스트를 통과할 수 있는 기능을 구현하라고 말합니다.
- 그 취지에 맞게, 테스트 코드를 상위에 두고, 기능코드를 후순을 둡니다.
반응형'Programing > TDD' 카테고리의 다른 글
파이썬 TDD 예제: 숫자야구게임 만들기 #2 (feat. unittest) (0) 2023.01.28 파이썬 TDD 예제: 숫자야구게임 만들기 #1 (feat. unittest) (0) 2023.01.28 파이썬 TDD 예제: chapter 02 암호 검사기 (feat. unittest) (0) 2022.11.27 파이썬으로 TDD 진행해보기(feat. unittest 예제) (0) 2022.11.24 TDD(Test Driven Development) 사내스터디를 시작하며 (0) 2022.11.23