-
파이썬 TDD 예제: chapter 03 유료 서비스 만료일 계산기 (feat. unittest)Programing/TDD 2022. 12. 6. 00:08반응형
이 글은 아래의 스터디 도서의 내용 중,
책의 개발 순서를 따라가지 않고 자체적으로 실습한 TDD 과정입니다.
따라서 개발 내용 및 테스트 구조가 불완전 할 수 있음을 먼저 안내 드립니다.
(부족한 점이나 제가 생각해보지 못한 점에 대한 충고 주시면 정말 감사드리겠습니다.)
테스트 주도 개발 시작하기 - YES24
TDD(Test-Driven Development)는 테스트부터 시작한다. 구현을 먼저 하고 나중에 테스트하는 것이 아니라 먼저 테스트를 하고 그다음에 구현한다. 구현 코드가 없는데 어떻게 테스트할 수 있을까? 여기
www.yes24.com
파이썬으로 TDD를 진행하는 기본적인 방식에 대해서는 아래의 글을 참조해 주세요
파이썬으로 TDD 진행해보기(feat. unittest 예제)
스터디 도서의 내용을 실습하기 이전에, 파이썬으로 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