파이썬에서 유닛테스트를 할 경우 하나의 파일을 작성하고 실행하는 경우보다 여러개의 파일을 작성하고 총 결과를 얻는 것이 필요한 경우가 많다. 이런 경우 기존의 모듈들에서 적당한 unittest 파일들을 테스트하는 사례를 참고하여 자신에 맞게 구성해서 사용하면 좋을 것이다.
만약 docutils 의 테스트 실행이 좋다면 docutils 모듈을 설치한 후 test 폴더의 package_unittest 와 그 와 관련된 파일들을 자신에 맞게 고쳐서 자신의 테스트 폴더에서 실행하면 된다.
먼저 docutils 모듈을 설치한 후 test 폴더를 살펴 보자
C:\_ik\download\python\docutil\docutils-0.5\test>
├─data
├─functional
│ ├─expected
│ │ └─ui
│ │ ├─default
│ │ └─small-black
│ ├─input
│ │ └─data
│ ├─output
│ │ └─ui
│ │ ├─default
│ │ └─small-black
│ └─tests
├─test_parsers
│ └─test_rst
│ ├─includes
│ └─test_directives
│ └─includes
│ ├─more
│ └─sibling
├─test_readers
│ ├─test_pep
│ └─test_python
├─test_transforms
└─test_writers
위 폴더 중 test_ 로 시작하는 폴더명은 모두 unittest 를 위한 폴더들이다.
또한 test_ 로 시작하는 파일명도 unittest 를 실행하는 테스트 파일이다.
한번 docutils 모듈을 설치한후 모듈 테스트를 감행해 보자! ( python 2.x 에서 테스트 )
C:\_ik\download\python\docutil\docutils-0.5\test>python alltest.py
모든 디렉토리의 test_ 파일이 실행될 것이다.
이 좋은 테스틑 모듈을 바로 가져와 적용하고 싶으나 내가 만든 테스트 파일은 'test_' 로 시작하는 대신 파일명과 폴더명에 '+' 기호가 들어가기 때문에 바로 위 테스트 스크립트를 적용할 수 없다.
unitest_files
│ test_pytok+.py
│ test_pytok2.py
│ test_pytok3.py
│
├─a+
│ │ a_pytok2+.py
│ │ a_pytok3+.py
│ │
│ └─b+
│ b_pytok2+.py
│ b_pytok3+.py
│
└─c+
c_pytok2+.py
c_pytok3+.py
다행히도 약간의 수정만 가하면 쉽게 적용시킬수 있다.
위
C:\_ik\download\python\docutil\docutils-0.5\test 폴더의 package_unittest.py , alltests.py
를 2to3 를 적용해 python 3.x 에 맞게 변경하고 불필요한 코드를 제거하고 변경해보자. ( 현재 테스트 스크립트가 3.x에서 작성 되었기 때문 )
그리고 다음과 같이 불필요한 부분을 주석처리하고 코드에 약간의 수정을 가하였다.
package_unittest.py
#! /usr/bin/env python
# $Id: package_unittest.py 4564 2006-05-21 20:44:42Z fwiemann $
# Author: Garth Kidd <garth@deadlybloodyserious.com>
# Copyright: This module has been placed in the public domain.
"""
This module extends unittest.py with `loadTestModules()`, by loading multiple
test modules from a directory. Optionally, test packages are also loaded,
recursively.
"""
import sys
import os
import getopt
import types
import unittest
import re
# So that individual test modules can share a bit of state,
# `package_unittest` acts as an intermediary for the following
# variables:
debug = 0
verbosity = 2
USAGE = """\
Usage: test_whatever [options]
Options:
-h, --help Show this message
-v, --verbose Verbose output
-q, --quiet Minimal output
-d, --debug Debug mode
"""
def usageExit(msg=None):
"""Print usage and exit."""
if msg:
print(msg)
print(USAGE)
sys.exit(2)
def parseArgs(argv=sys.argv):
"""Parse command line arguments and set TestFramework state.
State is to be acquired by test_* modules by a grotty hack:
``from TestFramework import *``. For this stylistic
transgression, I expect to be first up against the wall
when the revolution comes. --Garth"""
global verbosity, debug
try:
options, args = getopt.getopt(argv[1:], 'hHvqd',
['help', 'verbose', 'quiet', 'debug'])
for opt, value in options:
if opt in ('-h', '-H', '--help'):
usageExit()
if opt in ('-q', '--quiet'):
verbosity = 0
if opt in ('-v', '--verbose'):
verbosity = 2
if opt in ('-d', '--debug'):
debug =1
if len(args) != 0:
usageExit("No command-line arguments supported yet.")
except getopt.error as msg:
usageExit(msg)
def loadTestModules(path, name='', packages=None):
"""
Return a test suite composed of all the tests from modules in a directory.
Search for modules in directory `path`, beginning with `name`. If
`packages` is true, search subdirectories (also beginning with `name`)
recursively. Subdirectories must be Python packages; they must contain an
'__init__.py' module.
"""
testLoader = unittest.defaultTestLoader
testSuite = unittest.TestSuite()
testModules = []
path = os.path.abspath(path) # current working dir if `path` empty
paths = [path]
while paths:
p = paths.pop(0)
files = os.listdir(p)
for filename in files:
#if filename.startswith(name):
if re.search( name , filename ) :
fullpath = os.path.join(p, filename)
if filename.endswith('.py'):
fullpath = fullpath[len(path)+1:]
testModules.append(path2mod(fullpath))
elif packages and os.path.isdir(fullpath) and \
os.path.isfile(os.path.join(fullpath, '__init__.py')):
paths.append(fullpath)
# Import modules and add their tests to the suite.
sys.path.insert(0, path)
for mod in testModules:
if debug:
print("importing %s" % mod, file=sys.stderr)
try:
module = import_module(mod)
except ImportError:
print("ERROR: Can't import %s, skipping its tests:" % mod, file=sys.stderr)
sys.excepthook(*sys.exc_info())
else:
# if there's a suite defined, incorporate its contents
try:
suite = getattr(module, 'suite')
except AttributeError:
# Look for individual tests
moduleTests = testLoader.loadTestsFromModule(module)
# unittest.TestSuite.addTests() doesn't work as advertised,
# as it can't load tests from another TestSuite, so we have
# to cheat:
testSuite.addTest(moduleTests)
continue
if type(suite) == types.FunctionType:
testSuite.addTest(suite())
elif type(suite) == types.InstanceType \
and isinstance(suite, unittest.TestSuite):
testSuite.addTest(suite)
else:
raise AssertionError("don't understand suite (%s)" % mod)
sys.path.pop(0)
return testSuite
def path2mod(path):
"""Convert a file path to a dotted module name."""
return path[:-3].replace(os.sep, '.')
def import_module(name):
"""Import a dotted-path module name, and return the final component."""
mod = __import__(name)
components = name.split('.')
for comp in components[1:]:
mod = getattr(mod, comp)
return mod
def main(suite=None):
"""
Shared `main` for any individual test_* file.
suite -- TestSuite to run. If not specified, look for any globally defined
tests and run them.
"""
parseArgs()
if suite is None:
# Load any globally defined tests.
suite = unittest.defaultTestLoader.loadTestsFromModule(
__import__('__main__'))
if debug:
print("Debug: Suite=%s" % suite, file=sys.stderr)
testRunner = unittest.TextTestRunner(verbosity=verbosity)
# run suites (if we were called from test_all) or suite...
if type(suite) == type([]):
for s in suite:
testRunner.run(s)
else:
testRunner.run(suite)
alltest.py
#!/bin/sh
''''exec python -u "$0" "$@" #'''
# $Id: alltests.py 4629 2006-06-22 19:03:06Z goodger $
# Author: David Goodger <goodger@python.org>
# Copyright: This module has been placed in the public domain.
__doc__ = \
"""
All modules named 'test_*.py' in the current directory, and recursively in
subdirectories (packages) called 'test_*', are loaded and test suites within
are run.
"""
import time
# Start point for actual elapsed time, including imports
# and setup outside of unittest.
start = time.time()
import sys
import os
# import DocutilsTestSupport # must be imported before docutils
# import docutils
class Tee:
"""Write to a file and a stream (default: stdout) simultaneously."""
def __init__(self, filename, stream=sys.__stdout__):
self.file = open(filename, 'w')
self.stream = stream
def write(self, string):
self.stream.write(string)
self.file.write(string)
def flush(self):
self.stream.flush()
self.file.flush()
def pformat(suite):
step = 4
suitestr = repr(suite).replace('=[<', '=[\n<').replace(', ', ',\n')
indent = 0
output = []
for line in suitestr.splitlines():
output.append(' ' * indent + line)
if line[-1:] == '[':
indent += step
else:
if line [-5:] == ']>]>,':
indent -= step * 2
elif line[-3:] == ']>,':
indent -= step
return '\n'.join(output)
def suite():
path, script = os.path.split(sys.argv[0])
suite = package_unittest.loadTestModules('.','\+', packages=1)
sys.stdout.flush()
return suite
# must redirect stderr *before* first import of unittest
sys.stdout = sys.stderr = Tee('alltests.out')
import package_unittest
if __name__ == '__main__':
suite = suite()
# print ('Testing Docutils %s [%s] with Python %s on %s at %s'
# % (docutils.__version__, docutils.__version_details__,
# sys.version.split()[0],
# time.strftime('%Y-%m-%d'), time.strftime('%H:%M:%S')))
# print 'Working directory: %s' % os.getcwd()
# print 'Docutils package: %s' % os.path.dirname(docutils.__file__)
sys.stdout.flush()
package_unittest.main(suite)
#if package_unittest.verbosity > 1:
# print >>sys.stderr, pformat(suite) # check the test suite
finish = time.time()
print('Elapsed time: %.3f seconds' % (finish - start))
위 두 파일을 my_unitest_files 폴더에 복사하고 하위폴더에 __init__.py 를 생성하여 모듈로 만든다.
그러면 다음과 같다.
my_unitest_files
│ alltests.py
│ package_unittest.py
│ test_pytok+.py
│ test_pytok2.py
│ test_pytok3.py
│
├─a+
│ │ a_pytok2+.py
│ │ a_pytok3+.py
│ │ __init__.py
│ │
│ └─b+
│ b_pytok2+.py
│ b_pytok3+.py
│ __init__.py
│
└─c+
c_pytok2+.py
c_pytok3+.py
__init__.py
그리고 실행해 보자. ( python 3.x 에서 테스트 )
python alltest.py
다음과 같이 잘 실행된다면 성공 -_-
my_unitest_files > python alltest.py
runTest (test_pytok+.TestSequenceFunctions) ... ok
test_delcomm2 (a+.a_pytok2+.TestSequenceFunctions) ... FAIL
test_delcomm3 (a+.a_pytok2+.TestSequenceFunctions) ... ok
test_0 (a+.a_pytok3+.테스트테스트테스트) ... ok
test_1 (a+.a_pytok3+.테스트테스트테스트) ... ok
test_delcomm2 (c+.c_pytok2+.TestSequenceFunctions2) ... ok
test_delcomm3 (c+.c_pytok2+.TestSequenceFunctions2) ... ok
test_0 (c+.c_pytok3+.테스트테스트테스트) ... ok
test_1 (c+.c_pytok3+.테스트테스트테스트) ... ok
test_delcomm2 (a+.b+.b_pytok2+.TestSequenceFunctions) ... FAIL
test_delcomm3 (a+.b+.b_pytok2+.TestSequenceFunctions) ... ok
test_0 (a+.b+.b_pytok3+.테스트테스트테스트) ... ok
test_1 (a+.b+.b_pytok3+.테스트테스트테스트) ... ok
======================================================================
FAIL: test_delcomm2 (a+.a_pytok2+.TestSequenceFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\MYSQL\smasta\py\pyProj\test\a+\a_pytok2+.py", line 24, in test_delcom
m2
self.assertEqual('0','1')
AssertionError: '0' != '1'
======================================================================
FAIL: test_delcomm2 (a+.b+.b_pytok2+.TestSequenceFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\MYSQL\smasta\py\pyProj\test\a+\b+\b_pytok2+.py", line 24, in test_del
comm2
self.assertEqual('0','1')
AssertionError: '0' != '1'
----------------------------------------------------------------------
Ran 13 tests in 0.015s
FAILED (failures=2)
Elapsed time: 0.109 seconds
위와 같이 a+ , b+ , c+ 모든 폴더의 테스트파일이 실행된 것을 볼 수 있다.