이번학기 SW 개발방법 및 도구에서 TDD(test driven development)에 대해 공부했다.

이는 test driven 즉, 통과하고자 하는 test를 지정한 후 “test를 통과하기 위해서” 개발을 진행하는 것이다.


test driven development

  1. Add a test
  • 빈 code (declare 부분이나, 이름 및 입출력 parameter만 지정된 코드)를 바탕으로, 구현하고자 하는 내용이 담긴 test code 작성

  • 예상 결과 및 오류동작 등등 모든 senario를 고려해야 함.

  1. Run all tests and see if the new test fails
  • 두번째 cycle 이후부터는 모든 코드를 다 돌려봐야 한다. 그래야 새로 만든 코드가 기존의 기능을 해치지 않았는지 확인 가능

  • 만약 3 전에 test를 통과했다면, test code가 잘못된 것

  1. Write the codes
  • 필요한 기능만 구현하기 (욕심 x)
  1. Run tests
  • 3에서 만든 production code가 2에서 실패한 test code를 통과하는지 확인
  1. Refactor code
  • 기본 동작에 문제 없는 코드를 “수정”한다.

  • 중복을 제거하여 가독성을 높히고, 부가적인 기능을 구현한다.


이를 수행하기 위한 test code들을 간단히 체험해보기 위해, 아래와 같은 실습을 진행하고자 한다.

단어를 입력하면 각 글자의 ascii code의 합을 반환하는 함수를 만들자.

def total_ascii(word):
    """
    input: word
    output: sum of ascii of letters in word
    """
    string = "".join(word)
    result = 0
    for letter in string:
        result += ord(letter)
    return result

만약 midan을 입력하면, m(109)+i(105)+d(100)+a(97)+n(110) = 521 가 나와야 한다

이를 확인하기 위해 assertunittest를 이용하자.

# using assert
midan = total_ascii('midan')
assert midan == 521, "wrong answer!"
print("test is completed!")
print(midan)

assert midan == 520, "wrong answer!"
print("test is completed!")
print(midan)

첫번째 경우에는 “test is completed! “ 그리고 521이 출력되며,

두번째 경우에는 AssertionError: wrong answer! 가 출력됨을 확인할 수 있다.

이렇게 하나의 입출력을 확인하는 test case를 실행해봤다.


이제 여러 test case가 모인 test suite를 해보자.

이때 이용할 unittest는 python에 내장되어 있는 class 이다.

이번엔 mockup을 이용해보자.


cclass ascii_string(object):
    def __init__(self):
        self.word = ""
        self.num = 0

    def set_data(self, data):
        self.word += data

    def set_number(self, num):
        self.num = num

    def total_ascii(self):
        """
        input: word
        output: sum of ascii of letters in word
        """
        string = "".join(self.word)
        result = 0
        for letter in string:
            result += ord(letter)
        return result

    def num_to_ascii(self):
        """
        input: int between 0 ~ 127
        output: letter matched with input number
        """
        if self.num > 127:
            raise Exception("Too large number")
        return chr(self.num)

위와 같은 class를 임의로 정의하고 실행하기 위해, unittest를 이용해보자.


import unittest

class test_ascii_string(unittest.TestCase):
    def test_total_ascii(self, data):
        a = ascii_string()
        a.set_data(data) #midan
        a.ascii_string()
        self.assertionEqual(521) #right
        #self.assertionEqual(520) #fail

    def test_num_to_ascii(self):
        b = ascii_string()
        b.set_data(110)
        b.num_to_ascii()
        self.assertionEqual('n')

    if __name__ == '__main__':
        unittest.main()


from unittest import TestCase
from unittest.mock import patch

class Test_ascii_string(TestCase):
    @patch("requests.post")
    def test_total_ascii(self, mock_post):
        response = mock_post.return_value
        response.status_code = 201
        response.json.return_value = {"id": 99}

        a = ascii_string()
        a.set_data('midan') #midan
        a.total_ascii()
        self.assertionEqual(521) #right

        mock_post.assert_called_once_with(
            "https://midannii.github.io/",
            data={"wrong task"},
        )