combacsa-20100612-1

Download Report

Transcript combacsa-20100612-1

Combacsa’s SPARCS Web Seminar
SOFTWARE DEVELOPMENT #3:
XUNIT FRAMEWORK & TESTING
xUnit Framework
Python : unittest
Other testing tools
Test 개념 복습
 Testing 이란
 프로그램이 잘 개발되었는지 검사하는 과정
 Test Code 란
 프로그램이 잘 개발되었는지 검사하는 코드
 Test Code 가 있는 상황에서의 Testing 이란
 Test Code 를 실행시키는 것을 뜻하겠지? ㅋ_ㅋ
Unittest
 Unit
 “단위”
 따로 떼어놓고 생각할 수 있는 한 단위의 기능
 Unittest
 프로그램 내 하나의 기능이 제대로 작동되는지
그것만을 검사하는 Test
 만일 프로그램의 모든 부분에 대한 Unittest 를
모은다면 프로그램 전체에 대한 Test 가 될지도
Unittest 실제로 하는 방법
 자유분방한 Test Code 를 작성한다
 직접 함수를 호출해서
 함수의 결과값을 if 문으로 검사해서
 우리가 원하는 값이 아니면 화면에 Error 라고
 띄우면 그만이겠지?
 이미 누군가 만든 Framework 를 갖다 쓴다
 쓰는 법만 배우면 간단하다
이름있는 Unittest 관련 도구들
 Java
 JUnit
 Python
 C/C++
 CppUnit
 CppTest
 GoogleTest
 unittest
 Ruby
 PHP
 PHPUnit
 SimpleTest for PHP
 Test::Unit
xUnit Framework
 역사
 Kent Beck
 SmallTalk 언어를 위한 SUnit
 Java 언어를 위한 JUnit
 계속해서 다른 언어를 위해 번역됨
 사실 앞에서 소개한 대부분의 도구들이
xUnit Framework 에 기반하고 있다
xUnit Framework Terminology
 Test Case
 Test 의 가장 작은 단위
 하나의 기능이 제대로 작동하는지 검사하는 코
드 일체를 뜻함
 Test Fixture
 Test 가 일어나기 전에 준비되어야 하는 상황
 Test 가 끝난 뒤에 정리되어야 하는 상황의 모음
 Test Suite
 같은 Fixture 를 공유하는 Test Case 의 모음
xUnit Framework Terminology
 setUp
 Test 가 시행되기 전의 준비 과정
 tearDown
 Test 가 시행된 이후의 정리 과정
 Assertion
 Test 하는 동작이 원하는 대로 이뤄졌는지를 검
사하여, 원하지 않는 결과가 나오면 Test 를 중지
시키는 것
xUnit Framework 작동구조
 Test Suite 실행
 Test Case 들 각각에 대하여
 setUp 실행
 Test Case 내의 모든 문장 실행
 중간에 assertion 걸리면 해당 Test Case 는 “실패”
 tearDown 실행
 결과 보고
xUnit Framework
Python : unittest
Other testing tools
Python unittest
 JUnit 의 Python 버전
 Python 기본 내장 라이브러리
 실제로 써보자.
상황 설정
 TDD 설명할 때와 마찬가지로 기괴한 예제.
 PrimeNumber 라는 클래스를 만들어보자
 test 라는 메소드를 갖고 있다
 파라메터 : 양의 정수 하나
 출력 : 문자열
 현재의 시각과
 해당 정수가 소수인지 아닌지에 대한 정보
어떻게 개발할까?
 프로그래밍 언어
 Python
 개발 방법론
 약간 느슨한 Test-Driven Development
 사용할 Testing Framework 라이브러리
 unittest
지금 당장 할 일
 PrimeNumber 클래스를 테스트할
테스트 코드를 만들어야 한다
 그 전에,테스트 코드의 기본형을 우선 만들
어야 한다
 결론 : unittest 모듈을 쓰는 가장 기초적인
뼈대가 되는 코드를 작성하자!
prime_number_test.py 초고
import unittest
 시작은 unittest 모듈의 import 부터.
prime_number_test.py 초고
import unittest
class PrimeNumberTest(unittest.TestCase):
 unittest.TestCase 를 상속받은 클래스인
Test Case Class 를 선언한다
 클래스 이름은 Test 하고자 하는 클래스의
이름 뒤에 Test 를 붙이는 게 관례적이다
 이 클래스가 하나의 Test Suite 가 될 것이다
prime_number_test.py 초고
import unittest
class PrimeNumberTest(unittest.TestCase):
def setUp(self):
pass
 하나의 Test Suite 에는 같은 Test Fixture 를
공유하는 Test 들이 있다고 했다
 Test Fixture 는 setUp과 tearDown 으로 구성
된다고 했다
 일단 아무 일도 안 하는 setUp.
prime_number_test.py 초고
import unittest
class PrimeNumberTest(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
 잊지 마고 tearDown 도 넣어주자
prime_number_test.py 초고
 이제 suite 를 하나 만들어야 한다
 PrimeNumberTest 로부터 만들면 된다
 unittest.TestLoader() 객체가 필요하다
def tearDown(self):
pass
def suite():
return unittest.TestLoader().loadTestsFromTestCase(
PrimeNumberTest)
 그 객체의 loadTestsFromTestCase 함수를
호출하면 어느새 suite 가 완성된다
prime_number_test.py 초고
 만든 Suite 를 실제로 구동하려면?
 unittest 모듈의 TextTestRunner 클래스의 객체를
만들어낸다 (verbosity : 시끄러운 정도.)
 그 객체의 run 메소드에 suite 를 넘겨주면 됨.
def suite():
return unittest.TestLoader().loadTestsFromTestCase(
PrimeNumberTest)
if __name__ == "__main__":
unittest.TextTestRunner(verbosity=2).run(suite())
prime_number_test.py 초고
import unittest
class PrimeNumberTest(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def suite():
return unittest.TestLoader().loadTestsFromTestCase(
PrimeNumberTest)
if __name__ == "__main__":
unittest.TextTestRunner(verbosity=2).run(suite())
참고
 보통의 Test Suite 코드들은 TextTestRunner
객체의 생성에 대한 부분 (맨 아래 2줄) 은 생
략되어 있고, 오직 실질적인 Test Runner 코
드에만 그 내용이 있는 경우가 많다.
prime_number_test.py
 prime_number 모듈의 이름과
 PrimeNumber 객체의 생성방법
 그리고 test 라는 이름의 method 정의
import prime_number
…
def setUp(self):
self.prime_number = prime_number.PrimeNumber()
…
def testProperPrime(self):
self.assertEqual(“It is a Prime number”,
self.prime_number.test(2))
…
prime_number_test.py 초고
 메소드 이름이 test 로 시작하는 애들
 TestCase 를 구성하는 하나의 test 가 된다
 실행 순서는 아무래도 좋다는 가정
import prime_number
…
def setUp(self):
self.prime_number = prime_number.PrimeNumber()
…
# assertEqual 메소드는 2개의 파라메터가 동일한지 검사한다.
def testProperPrime(self):
self.assertEqual(“It is a Prime number”,
self.prime_number.test(2))
…
python prime_number_test.py
combacsa@sparcs:~$ python prime_number_test.py
Traceback (most recent call last):
File "prime_number_test.py", line 2, in <module>
import prime_number
ImportError: No module named prime_number
 문제를 해결하는 가장 쉬운 방법
 당연히 prime_number 모듈을 만든다.
prime_number.py
class PrimeNumber(object):
def __init__(self):
pass
def test(self, x):
return "It is a Prime number"
 TDD recall)
 죄악은 당당히 저지르라고 있는 것임.
 Test Case 가 부족해서 만회가 안 되는 것을 탓해!
python prime_number_test.py
combacsa@sparcs:~$ python prime_number_test.py
testProperPrime (__main__.PrimeNumberTest) ... ok
-----------------------------------------------------------Ran 1 test in 0.000s
OK
 초록 막대를 보았다
prime_number_test.py
…
 ImproperPrime 에 대한 test 를 추가하자
def testImproperPrime(self):
self.assertEqual(“It is not a Prime number”,
self.prime_number.test(4))
…combacsa@sparcs:~$ python prime_number_test.py
testImproperPrime (__main__.PrimeNumberTest) ... FAIL
testProperPrime (__main__.PrimeNumberTest) ... Ok
============================================================
FAIL: testImproperPrime (__main__.PrimeNumberTest)
-----------------------------------------------------------Traceback (most recent call last):
File "prime_number_test.py", line 15, in testImproperPrime
self.assertEqual("It is not a Prime number",
self.prime_number.test(4))
AssertionError: 'It is not a Prime number' != 'It is a Prime
number'
-----------------------------------------------------------Ran 2 tests in 0.001s
FAILED (failures=1)
결과 관찰
 코드상엔 ImproperPrime 이 더 뒤인데 …
 FAIL 이 뜬다. 빨간 막대에 도착.
combacsa@sparcs:~$ python prime_number_test.py
testImproperPrime (__main__.PrimeNumberTest) ... FAIL
testProperPrime (__main__.PrimeNumberTest) ... Ok
============================================================
FAIL: testImproperPrime (__main__.PrimeNumberTest)
-----------------------------------------------------------Traceback (most recent call last):
File "prime_number_test.py", line 15, in testImproperPrime
self.assertEqual("It is not a Prime number",
self.prime_number.test(4))
AssertionError: 'It is not a Prime number' != 'It is a Prime
number'
-----------------------------------------------------------Ran 2 tests in 0.001s
FAILED (failures=1)
prime_number.py
…
def test(self, x):
for i in range(2, x):
if x % i == 0:
return “It is not a Prime number”
return “It is a Prime number”
…
combacsa@sparcs:~$ python prime_number_test.py
testImproperPrime (__main__.PrimeNumberTest) ... ok
testProperPrime (__main__.PrimeNumberTest) ... ok
--------------------------------------------------------------------Ran 2 tests in 0.000s
OK
Mock Object / Stub Code
 Mock
 하나의 객체가 작동하기 위해 다른 객체가 필요
할 때, 굳이 그 “다른 객체” 를 사용하지 말고, “다
른 객체” 가 “Test Case들 내” 에서 보이는 행동만
을 하는 “가짜 객체” 를 Test Code 내에서 사용할
때 쓰는 객체.
 단위 Test 를 진짜 “단위 코드만 있어도 작동하는”
Test 로 만들어줌.
 Stub
 실제 해당 함수가 반환 가능한 다양한 경우의 수
들 중 Test 에서 쓸 일부분만 리턴해보는 것.
Stub code “time”
…
import unittest
import time
def stub_time():
return 31536001.1
…
def setUp(self):
self.time = time.time
time.time = stub_time
def tearDown(self):
time.time = self.time
…
Stub code “time”
…
import unittest
import time
def stub_time(): # time.time() 함수는 실제 컴퓨터의 현재 시각을
return 31536001.1 # 알려주는 함수이다. 그러나 Test Code는
# 매번 실행되는 시각이 다를 것이기 때문에 time.time() 함수를 그대로
…
def setUp(self): # Test Code 에 사용할 수는 없다. 따라서
self.time = time.time # “특정한 시각” 만을 돌려주는
time.time = stub_time # stub 함수를 만들고, time.time
…
# 함수를 그거로 대체해버리는 것.
def tearDown(self):
# test 가 끝난 뒤에는 원래의
time.time = self.time # time.time 함수로 복귀시켜주자.
…
예제를 좀더 확충해 보자
 현재 시각을 출력하는 걸 넣어야지!
…
def testProperPrime(self):
self.assertEqual(
"It is 1971-01-01 09:00:01.100000 and It is a Prime
number",
self.prime_number.test(2))
def testImproperPrime(self):
self.assertEqual(
"It is 1971-01-01 09:00:01.100000 and It is not a Prime
number",
self.prime_number.test(4))
…
combacsa@sparcs:~$ python prime_number_test.py
testImproperPrime (__main__.PrimeNumberTest) ... FAIL
testProperPrime (__main__.PrimeNumberTest) ... FAIL
============================================================
FAIL: testImproperPrime (__main__.PrimeNumberTest)
-----------------------------------------------------------Traceback (most recent call last):
File "prime_number_test.py", line 20, in testImproperPrime
self.assertEqual("It is 1971-01-01 09:00:01.100000 and
It is not a Prime number", self.prime_number.test(4))
AssertionError: 'It is 1971-01-01 09:00:01.100000 and It is
not a Prime number' != 'It is not a Prime number'
============================================================
FAIL: testProperPrime (__main__.PrimeNumberTest)
-----------------------------------------------------------Traceback (most recent call last):
File "prime_number_test.py", line 17, in testProperPrime
self.assertEqual("It is 1971-01-01 09:00:01.100000 and
It is a Prime number", self.prime_number.test(2))
AssertionError: 'It is 1971-01-01 09:00:01.100000 and It is
a Prime number' != 'It is a Prime number'
-----------------------------------------------------------Ran 2 tests in 0.001s
FAILED (failures=2)
테스트를 통과시켜보자!
import datetime
import time
…
def test(self, x):
for i in range(2, x):
if x % i == 0:
return "It is " +
str(datetime.datetime.fromtimestamp(time.time())) +
" and It is not a Prime number"
return "It is " +
str(datetime.datetime.fromtimestamp(time.time())) +
" and It is a Prime number"
결과
combacsa@sparcs:~$ python prime_number_test.py
testImproperPrime (__main__.PrimeNumberTest) ... ok
testProperPrime (__main__.PrimeNumberTest) ... ok
--------------------------------------------------------------------Ran 2 tests in 0.000s
OK
특이사항에 대한 대처
…
def testWrongNum(self):
try:
self.prime_number.test(-1)
self.fail(“Negative number must not be couraged.”)
except ZeroDivisionError:
pass
 self.fail()
 테스트를 일부러 실패시킨다.
 의도
 함수에게 “잘못된 입력” 을 넣으면, “잘못된 입력”
임을 인지하고 에러를 발생시키는지 검사!
결과
combacsa@sparcs:~$ python prime_number_test.py
testImproperPrime (__main__.PrimeNumberTest) ... ok
testProperPrime (__main__.PrimeNumberTest) ... ok
testWrongNum (__main__.PrimeNumberTest) ... FAIL
============================================================
FAIL: testWrongNum (__main__.PrimeNumberTest)
-----------------------------------------------------------Traceback (most recent call last):
File "prime_number_test.py", line 25, in testWrongNum
self.fail("Negative number must not be couraged.")
AssertionError: Negative number must not be couraged.
-----------------------------------------------------------Ran 3 tests in 0.001s
FAILED (failures=1)
합당하게 대처하는 코드
…
return "It is " +
str(datetime.datetime.fromtimestamp(time.time())) + " and It
is not a Prime number"
if x < 0:
raise ZeroDivisionError
return "It is " +
str(datetime.datetime.fromtimestamp(time.time())) + " and It
is a Prime number“
…
결과
combacsa@sparcs:~$ python prime_number_test.py
testImproperPrime (__main__.PrimeNumberTest) ... ok
testProperPrime (__main__.PrimeNumberTest) ... ok
testWrongNum (__main__.PrimeNumberTest) ... ok
--------------------------------------------------------------------Ran 3 tests in 0.001s
OK
숙제
 우리의 prime_number.py 와
prime_number_test.py 는 “1” 에 대해 어떻게
처리할까? 1 은 소수가 아니다. 합당한 출력
을 하도록 고쳐 보자.
 새로운 Exception 을 Define 하자. 각각
“WrongNumberRangeError”,
“NotANumberError” 라고 하자. 이 두 Error
를 리턴하도록 PrimeNumber.test 를 고치자.
SUMMARY
Unittest 는 프로그램의 특정 기능 하나만을 Test
하는 것을 뜻한다. 대체로 켄트 백에 의해 주창
된 xUnit Framework 를 기반으로 한 Java의 JUnit
이나 Python unittest 모듈, C++ 의 Google Test 모
듈 등을 통해 구현된다.
Test Fixture, Test Case, Test Suite, Test Runner 로
구성된다.
Python 사용자들은 unittest, doctest, coverage
등의 모듈을 써보면 좋다.
xUnit Framework
Python : unittest
Other testing tools
Java 사용자들이여
 Junit
 http://www.junit.org
 어차피 Eclipse / NetBeans 깔면 기본으로 들어있음
 Eclipse
 http://www.eclipse.org
 Java 쓰는 사람이 Eclipse 안 쓰면 바보라는 소문이 …
 그러나 GUI Programming 할 때는 NetBeans 가 더 …
 EclEmma
 http://www.eclemma.org/
 Java 를 위한 Code Coverage 툴
Python doctest
 주석 자체에 test 를 집어넣어두면 그로부터
test 를 실행하는 모듈
 Pros
 Test 자체를 해당 함수에 대한 주석으로 삼을 수
있어, Test 도 함수에 대한 소개로 쓰일 수 있다
 Cons
 Unittest 모듈과 달리 setUp, tearDown 등을 따로
쓰기 어려우며 형식이 Python Interpreter 라 지저
분하다
Python coverage
 Code Coverage?
 한 프로그램에 대한 Test Code 들이 프로그램의
모든 소스코드를 Test 하는지, 아니라면 어느 줄
을 Test 하지 못하는지 검사하는 것
 Code Coverage 가 100% 가 아니라면, 작성된 Test
만으로는 프로그램이 정상 작동하는지 보증하
기 어렵다는 것을 뜻한다
Coverage 사용 예제
 테스트 케이스 하나 지우고 해보자.
#def testProperPrime(self):
#
self.assertEqual("It is 1971-01-01 09:00:01.100000
and It is a Prime number", self.prime_number.test(2))
combacsa@swingfixer:~/cov$ coverage run prime_number_test.py
testImproperPrime (__main__.PrimeNumberTest) ... ok
testWrongNum (__main__.PrimeNumberTest) ... ok
-----------------------------------------------------------Ran 2 tests in 0.001s
OK
combacsa@swingfixer:~/cov$ ls .coverage –alh
-rw-r--r-- 1 combacsa combacsa 213 2010-06-09 19:48 .coverage
Coverage 사용 예제
combacsa@swingfixer:~/cov$ coverage report -m
Name
Stmts
Exec Cover
Missing
------------------------------------------------prime_number
12
11
91%
14
prime_number_test
24
23
95%
25
------------------------------------------------TOTAL
36
34
94%
combacsa@swingfixer:~/cov$ coverage html
combacsa@swingfixer:~/cov$ ls htmlcov/
coverage_html.js jquery-1.3.2.min.js
prime_number.html
style.css
index.html
jquery.tablesorter.min.js
prime_number_test.html
Coverage 사용 예제
 테스트 케이스 되살려보면 …
def testProperPrime(self):
self.assertEqual("It is 1971-01-01 09:00:01.100000 and
It is a Prime number", self.prime_number.test(2))
combacsa@swingfixer:~/cov$ coverage report -m
Name
Stmts
Exec Cover
Missing
------------------------------------------------prime_number
12
12
100%
prime_number_test
26
25
96%
25
------------------------------------------------TOTAL
38
37
97%
xUnit 이외의 Testing 도구
 xUnit 말고도 이런 것도 있음
 Test Anything Protocol (TAP)
References
 Kent Beck, 테스트 주도 개발
 김창준, 강규영 역
 http://www.javajigi.net/pages/viewpage.actio
n?pageId=278