1. Python Unit Testing Framework PyUnit 에 대해서

  • 원문은 PyUnit에 있는 도큐먼트 문서임을 밝혀둠. 공부겸 1차 정리중. 일단 약간 번역작업뒤 정리함.
  • PyUnit는 Python 에 기본적으로 포함되어있는 UnitTest Framework library이다. 하지만, UnitTest작성에 대한 기본적인 개념들을 쉽게 담고 있다고 생각하여 공부중. (솔직히 C++로 UnitTest할 엄두 안나서. --; Python으로 먼저 프로토타입이 되는 부분을 작성하고 다른 언어로 포팅하는 식으로 할까 생각중)

2. 관련 사이트

3. TestCase

unit testing 의 가장 기본적인 코드 블록 단위. 셋팅과 모듈이 제대로 돌아가는지를 체크하기 위한 하나의 시나리오가 된다.

PyUnit에서는 TestCase는 unittest모듈의 TestCase 클래스로서 표현된다.testcase 클래스를 만들려면 unittest.TestCase를 상속받아서 만들면 된다.

4. 간단한 testcase 의 제작. 'failure', 'error'

가장 간단한 방법은 runTest 메소드를 오버라이딩 하는 것이다.
~cpp 
import unittest

class DefaultWidgetSizeTestCase(unittest.TestCase):
	def runTest(self):
	widget = Widget("The widget")
	assert widget.size() == (50,50), 'incorrect default size'
테스팅을 하기 위해 Python의 assert 구문을 사용한다. testcase가 실행될 때 assertion을 실행하면 AssertionError 가 일어나고, testing framework는 이 testcase를 'failure' 했다고 정의할 것이다. 'assert' 를 명시적으로 써놓지 않은 부분에서의 예외가 발생한 것들은 testing framework 에서는 'errors'로 간주한다.

testcase를 실행하는 방법은 후에 설명할 것이다. testcase 클래스를 생성하기 위해 우리는 생성자에 아무 인자 없이 호출해주면 된다.
~cpp 
testCase = DefaultWidgetSizeTestCase ()

5. 재사용하는 set-up code : 'fixtures' 만들기.

만일 testcase가 많아지면 그들의 set-up 코드들도 중복될 것이다. 매번 Widget 클래스를 테스트하기 위해 클래스들마다 widget 인스턴스를 만드는 것은 명백한 중복이다.
다행스럽게도 우리는 setUp 라는, testing framework가 테스팅을 할때 자동으로 호출해주는 메소드를 구현함으로서 해결할 수 있다.

~cpp 
import unittest

class SimpleWidgetTestCase(unittest.TestCase):
	def setUp(self):
		self.widget = Widget("The widget")

class DefaultWidgetSizeTestCase(SimpleWidgetTestCase):
	def runTest(self):
		assert self.widget.size() == (50,50), 'incorrect default size'

class WidgetResizeTestCase(SimpleWidgetTestCase):
	def runTest(self):
		self.widget.resize(100,150)
		assert self.widget.size() == (100,150), \
		'wrong size after resize'

만일 setUp 메소드가 테스트중 예외를 발생할 경우 framework는 이 테스트에 error를 가지고 있다고 생각할 것이다. 그리고 runTest 메소드가 실행되지 않을 것이다.

단순히, 우리는 tearDown 메소드를 제공할 수 있다. runTest가 실행되고 난 뒤의 일을 해결한다.
~cpp 
import unittest

class SimpleWidgetTestCase (unittest.TestCase):
	def setUp (self):
		self.widget = Widget ("The widget")
	def tearDown (self):
		self.widget.dispose ()
		self.widget = None
만일 setUp 메소드 실행이 성공되면, tearDown 메소드는 runTest가 성공하건 안하건 상관없이 호출될 것이다.

이러한 testing code를 위한 작업환경을 'fixture' 라고 한다.

6. 여러개의 test method를 포함한 TestCase classes


종종, 많은 작은 test case들이 같은 fixture를 사용하게 될 것이다. 이러한 경우, 우리는 DefaultWidgetSizeTestCase 같은 많은 작은 one-method class 안에 SimpleWidgetTestCase를 서브클래싱하게 된다. 이건 시간낭비이고,.. --a PyUnit는 더 단순한 메커니즘을 제공한다.
~cpp 
import unittest

class WidgetTestCase (unittest.TestCase):
	def setUp (self):
		self.widget = Widget ("The widget")
	def tearDown (self):
		self.widget.dispose ()
		self.widget = None
	def testDefaultSize (self):
		assert self.widget.size() == (50,50), 'incorrect default size'
	def testResize (self):
		self.widget.resize (100,150)
		assert self.wdiget.size() == (100,150), 'wrong size after resize'
여기에는 runTest 메소드가 없는대신, 두개의 다른 test 메소드를 가지고 있다. 클래스 인스턴스는 이제 각각 self.widget 을 생헝하고 각 인스턴스에 대해 따로 소멸되면서 각각의 test method를 실행한다.

인스턴스를 생성할때 우리는 그 테스트 인스턴스가 수행할 테스트 메소드를 구체적으로 명시해주어야 한다. 이 일은 constructor에 메소드 이름을 적어주면 된다.

~cpp 
defaultSizeTestCase = WidgetTestCase ("testDefaultSize")
resizeTestCase = WidgetTestCase ("testResize")

7. TestSuite : testcase들의 집합체

Test case 인스턴스들은 그들이 테스트하려는 것들에 따라 함께 그룹화된다. PyUnit는 이를 위한 'Test Suite' 메커니즘을 제공한다. Test Suite는 unittest 모듈의 TestSuite class로 표현된다.
~cpp 
widgetTestSuite = unittest.TestSuite ()
widgetTestSuite.addTest (WidgetTestCase ("testDefaultSize"))
widgetTestSuite.addTest (WidgetTestCase ("testResize"))

각각의 테스트 수행을 위해 (우리는 나중에 다시 보겠지만), 각각의 테스트 모듈을 '호출할 수 있는' test suite 객체를 제공하는 것이 좋다.
~cpp 
def suite ():
	suite = unittest.TestSuite ()
	suite.addTest (WidgetTestCase ("testDefaultSize"))
	suite.addTest (WidgetTestCase ("testResize"))
	return suite
또는
~cpp 
class WidgetTestSuite (unittest.TestSuite):
	def __init__(self):
		unittest.TestSuite.__init__(self, map(WdigetTestCase, "testDefaultSize", "testResize")))
unittest 모듈에는 makeSuite 라는 편리한 함수가 있다. 이 함수는 test case class 안의 모든 test case를 포함하는 test suite를 만들어준다. (와우!!)
~cpp 
suite = unittest.makeSuite (WidgetTestCase, 'test')
makeSuite 함수를 사용할때 testcase들은 cmp 함수를 사용하여 소트한 순서되로 실행된다.

8. test suite들의 집합. 모든 테스트들을 한번에~

종종 testcase들을 함께 묶은 suites들의 그룹을 원할때가 있다. 그렇게 함으로서 한번에 모든 시스템의 test를 수행할 수 있다. TestSuite들은 TestSuite 에 포함될 수 있기 때문에 매우 간단하다.
~cpp 
suite1 = module1.TheTestSuite ()
suite2 = module2.TheTestSuite ()
alltests = unittest.TestSuite ((suite1, suite2))

9. 어디에 테스트코드를 둘까?

testcode는 'widgettests.py' 처럼 따로 테스트코드들에 대한 모듈을 두는 것이 여러가지면에서 장점을 지닌다.
  • command line에서 test module를 단독적으로 실행할 수 있다.
  • 코드와 testcode가 쉽게 분리된다.
  • 특별한 이유없이 testcode를 test받을 code에 맞추려는 유혹을 덜 수 있다.
  • 테스트 된 코드를 refactoring 하기 더 용이해진다.
  • 테스팅 전략이 바뀌어도, source code를 고칠 필요가 없어진다.

10. Test의 실행

PyUnit test framework는 테스트를 수행하기 위해 'TestRunner' 클래스를 사용한다. 가장 일반적인 TestRunnerTextTestRunner이다.
~cpp 
runner = unittest.TextTestRunner ()
runner.run (widget.TestSuite)
기본적으로 TextTestRunner는 sys.stderr에 출력한다. TextTestrunner 같은 클래스는 Python interpreter session과 상호작용하면서 test들을 실행시켜볼 수 있는 이상적인 방법이다.

11. Test 조건들에 대해서

test 코드는 각각의 test조건에 맞춰 문제발생시 fail 등을 발생시킨다.

11.1. assert

~cpp 
def runTest(self):
	self.assert_(self.widget.size() == (100,100), "size is wrong")

11.2. failif

~cpp 
def runTest(self):
	self.failIf(self.widget.size() <> (100,100))

11.3. fail, failUnless

~cpp 
def runTest(self):
...
	if not hasattr(something, "blah"):
	self.fail("blah missing")
	# or just 'self.fail()'

11.4. assertEqual

~cpp 
def testSomething(self):
	self.widget.resize(100,100)
	self.assertEqual(self.widget.size, (100,100))

11.5. exception

~cpp 
def runTest(self):
	try:
		self.widget.resize(-1,-1)
	except ValueError:
		pass
	else:
		fail("expected a ValueError")


Retrieved from http://wiki.zeropage.org/wiki.php/PyUnit
last modified 2021-02-07 05:24:08