Testing이 대체 뭔가요?

테스트는 소프트웨어 공학에서 매우 중요한 역할을 하고 있습니다. 뭔가 나만 빼고 다 사용하는 것 같고, 다들 엄청 잘 알고 있는 것 같지만 항상 그렇 듯 정답은 없고 이것 저것 찾아서 사용하는 것이 답인 듯 합니다. 이번 포스트에서는 테스트가 대체 뭐고 테스트는 어떤 식으로 사용해야 하는지 설명해보고자 합니다! 특히, 사람들이 많이 쓰고 있는 Python에서의 테스팅은 어떤 식으로 이루어지는게 좋을지, 또, 테스트를 지원하는 라이브러리는 어떤 게 있고 어떻게 사용하는지에 대해 다루고자 합니다.

Waterfall Model

먼저 테스트가 무엇인지를 알아보기 이전에 앞서 Waterfall Model이 무엇인지 알아봅시다.
Waterfall Model은 소프트웨어 개발 프로세스중 하나로, 개발의 흐름을 폭포(waterfall 보다는 cascade가 더 맞는 말인 듯…)처럼 생긴 모델을 사용해 개발을 진행하는 방법론을 의미합니다. 1970년 윈스턴 W. 로이스의 논문에서 나왔으며, 신기하게도 로이스는 이 모델이 결함이 있다고 말을 하기도 합니다.

이 모델은 다음과 같이 순차적으로 진행이 됩니다.

  • Requirements
  • Design
  • Code
  • Test
  • Maintenance

waterfall image

위 그림의 순서를 지키며 코딩을 하면 코드의 질, 그리고 코드를 쓰는 속도가 좋아질 것을 예상할 수 있을 것 입니다. (적어도 마구잡이로 짜는 것 보다는 좋아지겠죠…!)

V Model

V Model은 앞서 설명한 Waterfall model을 확장한 형태 중 하나입니다. Waterfall Model과는 달리 아래 방향으로만 가는 것이 아니라, 윗 방향으로 가는 것을 같이하여 TestMaintenance를 더 체계화 한 것입니다. 이 V Model을 사용하면 개발할 때 각각의 단계마다 문서화가 필수적으로 요구되고 테스트 와 같은 작업이 처음부터 고려되어 코드 퀄리티부터 제품 퀄리티까지 폭넓게 보장할 수 있다는 장점이 있습니다. v model image

위의 그림을 보면 아래로 내려가고 올라오면서 같은 레벨에 있는 것들을 테스팅을 통해 확인하는 것을 볼 수 있습니다. 이렇게 V Model에서는 절처하게 단계마다 테스팅을 하면서 제품의 퀄리티를 높이는 것을 목표로 합니다.

이 V Model에서 각각의 절차들을 깊게 들여다 봅시다!

Verification

V Model에서 아래로 내려가는 부분을 의미합니다. 단순히 아래로 내려가는 것 뿐만 아니라 뒷 부분에서 설명할 Test를 설계하는 과정도 포함하여 하는 것이 V Model 수행의 핵심입니다.

Requirements Analysis

개발될 제품의 사용자가 어떤 것을 필요로 하는 지 알아보고 문서화하는 단계입니다. 사용자 입장에서 제품을 사용할 때, 어떤 것을 요구하는지 정확히 기술하여야 합니다. 일반적으로는 기능, 인터페이스, 성능, 데이터 등을 기록합니다. 이 문서는 설계 단계에서 설계자에 대한 지침으로 사용되기 때문에, 사용자의 깊은 검토가 필요합니다.

System Design

시스템 설계에서는 엔지니어가 앞서 설명한 요구사항들을 검토하고 개발할 시스템에 대해 분석하고 이해하는 단계입니다. 그리고 구현 가능성 및 필요한 기술들을 파악하고 요구사항에 문제가 발생하는 경우 사용자에게 알리고, 요구사항을 해결 또는 변경을 해야합니다. 이렇게 이 단계를 거치게 되면 Software specification이라는 문서가 생성되어야합니다. 시스템 구성, HLD(Hight Level Design), Enitity Diagram 등이 이 문서에 포함되게 됩니다.

Architecture Design

소프트웨어에서 어떤 구조를 가지고 갈 지 설계하는 단계입니다. 구현될 모듈들과 간략한 기능들을 정의하고, 모듈 간의 인터페이스, 관계, 의존성 등을 기술하고 데이터베이스의 테이블이나 아키텍쳐 다이어그램, 적용될 기술 등을 기술합니다. 또, 이 단계에서 Integration Test의 설계도 같이 이루어져야합니다.

Module Design

위의 단계에서 정의된 모든 모듈들을 세분화하여 프로그래머들이 코딩을 할 수 있도록 만드는 단계입니다. 의사 코드(pseudocode)나 흐름도(flow chart) 등을 이용해 기술하고 API(Application Programming Interface)를 정확히 명세해 입력과 출력 그리고 사이드이펙트 등을 설계합니다. 이 때, 가장 작은 단위의 테스트인 Unit Test를 같이 설계하도록 합니다.

Validation

여태까지 설계하고 코드로 만든 것들을 하나 하나 테스트하는 것을 의미합니다. V Model 그림에서 위로 올라가는 부분에 해당합니다.

Unit Test

가장 작은 기능의 단위인 모듈들에서 테스트 케이스를 여러 개 만들어 테스트 해보는 방식입니다. 여기에서 사용될 테스트 케이스는 Verification의 Module Design에서 만들어진 테스트 케이스를 사용하며 코드를 개발한 개발자가 직접 수행합니다.

Integration Test

이 단계에서도 마찬가지로 Architecture Design 단계에서 수행된 설계를 가지고 테스트를 하게 됩니다. 여러 개가 엮인 모듈이 정상적으로 상호작용하는 지를 테스트해보는 것이며 이 또한 일반적으로 개발자가 진행하게 됩니다.

System Test

실제로 구현된 시스템이 Software Specification과 잘 맞는지 비교하는 단계입니다. 문서로부터 도출하여 하나 하나 확인할 수도 있고, 여러 자동화 툴을 사용하여 테스트를 해볼 수도 있습니다. 이 단계는 개발자 또는 별도의 테스트 팀(Quality Assurance)에 의해 수행될 수 있습니다.

Acceptance Test

이 단계는 가장 중요한 단계입니다. 유저가 원하는 요구사항들이 만들어진 제품에서 충족시키고 있는지를 확인합니다. 이 단계에서는 소프트웨어의 결함을 찾기 위함이 아니라, 만들어진 시스템이 실제로 사용할 만한 준비가 되었는지를 평가해야합니다. 기능이 아닌 비기능적 특성(성능, 신뢰성, 확장성, 운영성, 보안, UX 등) 또한 제대로 동작하고 있는지 확인하는 것이 포함됩니다.

Testing with Python!

자, 그래서 이 내용들을 가지고 코드에선 어떤 식으로 적용을 해야할까요? 파이썬 코드를 직접 살펴보며 한번 적용을 시도해봅시다!

Unit Test and Integration Test

단위 테스트와 통합 테스트는 보통 python에서는 unittest나 pytest같은 모듈이 사용됩니다. 어떤 식으로 작동하게 되는지 간단하게 설명해보도록 하겠습니다.

assert Statement

assert문은 많은 파이썬 프로그래머들이 사용하지 않거나 무시되는 statement중 하나입니다! 보통 가정설정문 이라고도 불리는 이 문법은 일부러 에러를 내게 하는 raise와 비슷한 역할을 하고 있습니다.

# raise assertion error when condition is False
assert (condition)

# raise assertion error when condition is False
if not (condition):
    raise AssertionError()

위의 코드를 보면 두 문장은 같은 로직을 수행하고 있습니다. 아래 처럼 두 줄로 사용할 수 있는 코드를 한 줄로 줄여서 쉽게 사용할 수 있는 예약어라고 봐도 무방합니다.

assert 2 == 1 + 1 # nothing happened
assert 2 == 1
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# AssertionError

하지만, 여기서 중요한 것은 assertraise는 분명히 다른 의미를 가지도록 쓰는 것이 좋을 것 입니다. 이를 알아보기 위해 다음 코드를 살펴봅시다.

def get_bmi_score(weight, height):
    if weight < 0 or height < 0:
        raise ValueError('Weight or Height should be over or equal 0.')
    bmi = weight / (height ** 2)
    assert bmi > 0
    return bmi

이 코드는 BMI를 계산하는 간단한 함수인데요. raiseassert가 동시에 쓰였습니다. 둘의 차이가 보이시나요?

raise는 이 모듈을 사용하는 사용자에게 height나 weight를 올바른 값을 주도록 유도합니다. 반면에, assert의 경우는 사용자가 아닌 이 함수를 짠 프로그래머에게 이 함수 안의 논리가 잘못된 것 같다는 것을 보여주도록 되어있죠. BMI는 0보다 큰 값이 나와야하는 것이 자명하니까요.

이렇듯 파이썬에서는 프로그래머가 실수를 방지할 수 있도록 하는 장치를 만들 때, assert를 사용하여 수행할 수 있도록 문법을 만들어 놓았습니다. 이 문법은 앞으로 소개할 여러 테스트 라이브러리에서 사용이 되게 됩니다.

unittest

unittest는 파이썬의 기본 내장 모듈로 Java의 JUnit과 비슷한 형태로 Unit Test를 진행할 수 있도록 하는 라이브러리입니다. 여러 가지 개념을 제시하면서 조금 더 편하게 Unit Test를 할 수 있도록 해줍니다.

  • Test Fixture: 테스트 픽스쳐는 여러 개의 테스트를 수행할 때 필요한 준비와 그와 관련된 정리 동작에 해당합니다.
  • Test Case: 테스트 케이스는 테스트의 개별 단위입니다. 특정한 입력 모음에 대해 특정한 결과를 확인할 수 있습니다. unittest에서는 TestCase라는 베이스 클래스를 통해 테스트 케이스를 만듭니다.
  • Test Suite: 테스트 슈트는 여러 테스트 케이스를 하나로 묶는데 사용됩니다.
  • Test Runner: 테스트 러너는 테스트 실행을 조율하고 테스트 결과를 사용자에게 보여주는 역할을 하는 컴포넌트입니다.

이 포스트에서는 각각 라이브러리를 어떤 식으로 사용하는지 설명을 하지는 않겠습니다. 기회가 되면 포스팅을 하려고합니다. (메모…)

import unittest

class TestStringMethods(unittest.TestCase):
    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

    def test_issupper(self):
        self.assertTrue('FOO'.isupper())
        self.assertFalse('Foo'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        with self.assertRaises(TypeError):
            s.split(2)

if __name__ == "__main__":
    uniitest.main()

unittest에서는 위와 같은 구조로 테스트 케이스를 간단히 정의할 수 있고, CLI를 통해 테스트 결과를 간단히 확인해 볼 수 있습니다.

pytest

pytest는 unittest보다 더 간단한 방법으로 테스트를 할 수 있는 외부 라이브러리 입니다. pip install pytest를 통해 설치해야지 사용할 수 있습니다. assert 함수들을 사용하는 것보다 기본 assert문을 사용하는 것을 권장하고 있으며 unittest와 비슷한 구조를 가지고 있습니다.

# content of test_sample.py
def func(x):
    return x + 1

def test_answer():
    assert func(2) == 5

pytest는 커맨드 창에서 “pytest”를 실행하면 위와 같이 test_로 시작되는 함수, 클래스, 모듈, 패키지 만을 확인하고 실행합니다. File name convention은 이 링크에서 확인할 수 있습니다.

Conclusion

지금까지 테스팅이 뭔지에 대해 아주 간단하게 알아봤습니다. 다음 포스트에서는 앞에서 본 unittest, pytest를 어떤 식으로 사용하는지, 또, System Testing, Acceptance Testing에는 어떤 툴과 어떤 방법론이 있는지 살펴보겠습니다!

포스팅이 맘에 드셨다면 밑의 이메일 구독을 해주세요!