단위 테스트의 중요성

그 동안 프로그래머(?)아니 코더로 3년 정도 일을 하면서 단위 테스트에 대해서는 거의 신경을 쓰지 않았던 것 같다. 왜냐하면 첫째로는 아무도 나에게 단위테스트를 알려준 사람이 없고 둘째로는 이것이 중요하다고 말한 사람이 없으며 마지막으로 나도 이것이 왜 중요한지 몰랐기 때문이다.

그러다가 조금씩 단위테스트를 알아갈 때 나는 이것이 단순히 단위테스트를 자동화는 툴인줄 알았었다. 이것이 전적으로 틀린 말이 아니지만 정확하지도 않은 것 같다 지금 생각해보면

단위테스트를 기반으로한 개발(TDD)가 왜 설계 방법인지를 이제야서야 조금씩 알아가고 있는 것 같다. 왜 아무도 나에게 이런 이야기를 하지 않았는지 모르겠습니다. ㅠㅠㅠ

아마도 그것은 한국의 개발 문화 때문인 것도 같습니다. 교육을 가더라도 단위테스트는 그냥 Skip해버립니다. 그러면서 대부분하는 말은 이것을 실체로는 사용하지 않아요 입니다.

현업에서도 개발 기간에 맞추어 빠르게 개발을 하다보니 단위테스트를 작성하지 않거나 아니면 return true로 만들어 버립니다.

단위 테스트의 중요성

그 동안 프로그래머(?)아니 코더로 3년 정도 일을 하면서 단위 테스트에 대해서는 거의 신경을 쓰지 않았던 것 같다. 왜냐하면 첫째로는 아무도 나에게 단위테스트를 알려준 사람이 없고 둘째로는 이것이 중요하다고 말한 사람이 없으며 마지막으로 나도 이것이 왜 중요한지 몰랐기 때문이다.

그러다가 조금씩 단위테스트를 알아갈 때 나는 이것이 단순히 단위테스트를 자동화는 툴인줄 알았었다. 이것이 전적으로 틀린 말이 아니지만 정확하지도 않은 것 같다 지금 생각해보면

단위테스트를 기반으로한 개발(TDD)가 왜 설계 방법인지를 이제야서야 조금씩 알아가고 있는 것 같다. 왜 아무도 나에게 이런 이야기를 하지 않았는지 모르겠습니다. ㅠㅠㅠ

아마도 그것은 한국의 개발 문화 때문인 것도 같습니다. 교육을 가더라도 단위테스트는 그냥 Skip해버립니다. 그러면서 대부분하는 말은 이것을 실체로는 사용하지 않아요 입니다.

현업에서도 개발 기간에 맞추어 빠르게 개발을 하다보니 단위테스트를 작성하지 않거나 아니면 return true로 만들어 버립니다.

하지만 이제부터는 그러지 않기를 바라면서 이 정리를 시작해 봅니다. 일단은 단위테스트가 어떠한 것인지 알아야 하기에 단위테스트에 대해서 알아봅니다.

본 글은 https://www.tutorialspoint.com/unittest_framework/unittest_framework_overview.htm 을 참고로 작성하였습니다.

단위테스트란?

‘unittest’는 테스트 자동화, 테스트를 위한 설정 및 종료 코드 공유, 컬렉션으로 테스트 집계, 리포팅 프레임워크에서 테스트의 독립을 지원합니다.

unittest 모듈은 일련의 테스트에 대해 이러한 특성을 쉽게 지원할 수 있는 클래스를 제공합니다.

이를 달성하기 위해 unittest는 다음과 같은 중요한 개념을 지원합니다.

  • 테스트 픽스쳐(test fixture) - 하나 이상의 테스트를 수행하는 데 필요한 준비 작업과 연관 클린업 작업을 나타냅니다. 예를 들어, 임시 또는 프록시 데이터베이스, 디렉토리 작성 또는 서버 프로세스 시작과 관련될 수 있습니다.

  • 테스트 케이스 - 이것은 테스트의 최소 단위입니다. 특정 입력 세트에 대한 특정 응답을 확인합니다. unittest는 새로운 테스트 케이스를 작성하는 데 사용할 수 있는 기본클레스인 TestCase를 제공합니다.

  • 테스트 슈트 - 이것은 테스트 케이서, 테스트 스위트 또는 둘 모두의 집합니다. 이것은 함께 실행되어야하는 테스트를 집계하는 데 사용됩니다. 테스트 스위트는 TestSuite 클래스에 의해 구현됩니다.

  • 테스트 Runner - 테스트 실행을 조정하고 결과를 사용자에게 제공하는 구성 요소입니다. 러너는 그래픽 인터페이스나 텍스트 인터페이스를 사용하거나 특수 값을 반환하여 테스트 실행 결과를 나타낼 수 있습니다.

단위 테스트 만들어 보기

  1. 프로그램에서 unittest 모듈을 가져옵니다.
  2. 테스트할 함수를 정의하십시오. 다음 예제에서 add() 함수는 테스트를 거친다.
  3. unittest.TestCase를 하위 클래스화하여 테스트 사례를 만듭니다.
  4. 테스트를 클래스 내부의 메소드로 정의합니다. 메소드 이름은 ‘test’로 시작해야 합니다.
  5. 각 테스트는 TestCase 클래스의 assert 함수를 호출합니다. 다양한 유형의 assert가 있습니다. 다음 예제에서는 assertEquals() 함수를 호출합니다.
  6. assertEquals() 함수는 add() 함수의 결과를 arg2 인수와 비교하고 비교가 실패하면 assertionError를 발생시킵니다.
  7. 마지막으로 unittest 모듈에서 main() 메서드를 호출합니다.
import unittest

def add(x,y):
    return x + y

class SimpleTest(unittest.TestCase):
    def testadd1(self):
        self.assertEquals(add(4,5),9)

if __name__ == '__main__':
    unittest.main()
  1. script를 command line로 실행합니다.
python SimpleTest.py
-----------------------------------------
Ran 1 test in 0.000s
OK

테스트 클래스 지원 메소드

  1. setUp()
    테스트 fixture를 준비하기 위해 호출된 메소드. 이것은 테스트 메소드를 호출하기 직전에 호출됩니다.
  2. tearDown()
    테스트 메소드가 호출되고 결과가 기록된 직후에 호출될 메소드. 이것은 테스트 메소드가 예외를 발생시키는 경우에도 호출됩니다.
  3. setUpClass()
    개발 클래스에서 테스트하기 전에 호출될 클래스 메소드입니다.
  4. tearDownClass()
    개별 클래스의 테스트가 실행된 이후에 실행될 메소드입니다.
  5. run(result=None)
    결과를 결과로 전달 된 테스트 결과 개체로 결과를 수집하여 테스트를 실행합니다.
  6. skipTest(reason)
    테스트 메소드에서 이것을 호출하거나 현재 테스트를 건너 뜁니다.
  7. debug()
    결과를 수집하지 않고 테스트를 실행하십시오.
  8. shortDescription()
    테스트에 대한 한 줄짜리 설명을 반환합니다.
import unittest

class simpleTest2(unittest.TestCase):
   def setUp(self):
      self.a = 10
      self.b = 20
      name = self.shortDescription()
      if name == "Add":
         self.a = 10
         self.b = 20
         print name, self.a, self.b
      if name == "sub":
         self.a = 50
         self.b = 60
         print name, self.a, self.b
   def tearDown(self):
      print '\nend of test',self.shortDescription()

   def testadd(self):
      """Add"""
      result = self.a+self.b
      self.assertTrue(result == 100)
   def testsub(self):
      """sub"""
      result = self.a-self.b
      self.assertTrue(result == -10)

if __name__ == '__main__':
   unittest.main()
  1. 결과
C:\Python27>python test2.py
Add 10 20
F
end of test Add
sub 50 60
end of test sub
.
================================================================
FAIL: testadd (__main__.simpleTest2)
Add
----------------------------------------------------------------------
Traceback (most recent call last):
   File "test2.py", line 21, in testadd
      self.assertTrue(result == 100)
AssertionError: False is not true
----------------------------------------------------------------------
Ran 2 tests in 0.015s

FAILED (failures = 1)

Class Fixture

TestCase 클래스에는 TestCase 클래스내에서 개별 테스트를 실행하기 전에 실행할 수 있는 setUpClass() 메소드가 있습니다. 마찬가지고, tearDownClass() 메소드는 클래스의 모든 테스트 후에 실행됩니다. 두 메소드는 모두 클래스 메소드립니다. 따라서 @classmethod를 Annotation해야 합니다.

import unittest

class TestFixtures(unittest.TestCase):

   @classmethod
   def setUpClass(cls):
      print 'called once before any tests in class'

   @classmethod
   def tearDownClass(cls):
      print '\ncalled once after all tests in class'

   def setUp(self):
      self.a = 10
      self.b = 20
      name = self.shortDescription()
      print '\n',name
   def tearDown(self):
      print '\nend of test',self.shortDescription()

   def test1(self):
      """One"""
      result = self.a+self.b
      self.assertTrue(True)
   def test2(self):
      """Two"""
      result = self.a-self.b
      self.assertTrue(False)

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

TestSuite Class

파이썬의 테스트 프레임워크는 테스트하는 기능에 따라 그룹화 될 수 있는 유용한 기능을 제공한다. 이 기능은 unittest 모듈의 TestSuite 클래스에서 사용할 수 있습니다. 다음 단계는 테스트 스위트를 작성하고 실행하는 단계입니다.

다음 단계는 TestSuite를 작성하고 실행하는 단계입니다.

  1. TestSuite 클래스 생성
suite = unittest.TestSuite
  1. suite에 있는 TestCase안에 테스트를 추가한다.
suite.addTest(testcase class)
  1. makeSuite() 메서드를 사용하여 클래스의 테스트를 추가 할 수도 있습니다.
suite = unittest.makeSuite(test case class)
  1. 각각의 테스트는 또한 suite안에 추가될 수 있습니다.
suite.addTest(testcaseclass("testmethod"))
  1. TestTestRunner 클래스의 객체를 만든다.
runner = unittest.TextTestRunner()
  1. run() 메서드를 호출하여 suite의 모든 테스트를 실행할 수 있다.
runner.run(suite)

TestSuite 클래스의 메소드들이다.

  • addTest() - Test suite안에 테스트를 추가한다.
  • addTests() - 여러 TestCase Class들에 테스트들을 추가한다.
  • run() - 테스트를 수행하고 테스트의 결과를 출력해 준다.
  • debug() - 결과수집 없이 테스트를 수행한다.
  • countTestCases() - 테스트 객체로부터 테스트의 숫자를 리턴한다.
import unittest
class suiteTest(unittest.TestCase):
   def setUp(self):
      self.a = 10
      self.b = 20

   def testadd(self):
      """Add"""
      result = self.a+self.b
      self.assertTrue(result == 100)
   def testsub(self):
      """sub"""
      result = self.a-self.b
      self.assertTrue(result == -10)

def suite():
   suite = unittest.TestSuite()
##   suite.addTest (simpleTest3("testadd"))
##   suite.addTest (simpleTest3("testsub"))
   suite.addTest(unittest.makeSuite(simpleTest3))
   return suite

if __name__ == '__main__':
   runner = unittest.TextTestRunner()
   test_suite = suite()
   runner.run (test_suite)

TestLoader Class

unittest 패키지에는 클래스와 모듈에서 테스트 스위트를 만드는 데 사용되는 TestLoader 클래스가 있습니다. 기본적으로 unittest.main (0 메서드가 호출되면 unittest.defaultTestLoader 인스턴스가 자동으로 만들어 지지만 명시 적 인스턴스에서는 특정 속성을 사용자 지정할 수 있습니다.

다음 코드에서 두 클래스의 테스트는 TestLoader 객체를 사용하여 List에 수집됩니다.

import unittest
testList = [Test1, Test2]
testLoad = unittest.TestLoader()

TestList = []
for testCase in testList:
   testSuite = testLoad.loadTestsFromTestCase(testCase)
   TestList.append(testSuite)

newSuite = unittest.TestSuite(TestList)
runner = unittest.TextTestRunner()
runner.run(newSuite)

아래는 TestLoader Class의 메소들이다.

  • loadTestsFromTestCase()
  • loadTestsFromModule()
  • loadTestsFromName()
  • discover()

TestResult Class

  • Errors
  • Failures
  • Skipped
  • wasSuccessful()
  • stop()
  • startTestRun()
  • stopTestRun()
  • testsRun
  • Buffer
if __name__ == '__main__':
   runner = unittest.TextTestRunner()
   test_suite = suite()
   result = runner.run (test_suite)

   print "---- START OF TEST RESULTS"
   print result

   print "result::errors"
   print result.errors

   print "result::failures"
   print result.failures

   print "result::skipped"
   print result.skipped

   print "result::successful"
   print result.wasSuccessful()

   print "result::test-run"
   print result.testsRun
   print "---- END OF TEST RESULTS"
---- START OF TEST RESULTS
<unittest.runner.TextTestResult run = 2 errors = 0 failures = 1>
result::errors
[]
result::failures
[(<__main__.suiteTest testMethod = testadd>, 'Traceback (most recent call last):\n
   File "test3.py", line 10, in testadd\n 
   self.assertTrue(result == 100)\nAssert
   ionError: False is not true\n')]
result::skipped
[]
result::successful
False
result::test-run
2
---- END OF TEST RESULTS

Assert

Python 테스팅 프레임 워크는 특정 조건을 테스트하는 파이썬의 기본 제공 assert () 함수를 사용합니다. Assert이 실패하면 AssertionError가 발생합니다. 테스트 프레임워크는 테스트를 실패로 식별합니다. 다른 예외는 오류로 처리됩니다.

Unittest 모듈에는 다음 세 새의 assert 메소드가 있다.

  • Basic Boolean Asserts
  • Comparative Asserts
  • Asserts for Collections

기본 Assert기능은 연산 결과가 참인지 거짓인지를 평가합니다. 모든 assert 메소드는 지정된 경우, 실패시 오류 메시지로 사용되는 msg 인수를 승인합니다.

  • assertEqual(arg1, arg2, msg = None)
  • assertNotEqual(arg1, arg2, msg = None)
  • assertTrue(expr, msg = None)
  • assertFalse(expr, msg = None)
  • assertIs(arg1, arg2, msg = None)
  • assertIsNot(arg1, arg2, msg = None)
  • assertIsNone(expr, msg = None)
  • assertIn(arg1, arg2, msg = None)
  • assertNotIn(arg1, arg2, msg = None)
  • assertIsInstance(obj, cls, msg = None)
  • assertNotIsInstance(obj, cls, msg=None)

아래는 위의 assertion 함수를 구현하는 코드이다.

import unittest

class SimpleTest(unittest.TestCase):
   def test1(self):
      self.assertEqual(4 + 5,9)
   def test2(self):
      self.assertNotEqual(5 * 2,10)
   def test3(self):
      self.assertTrue(4 + 5 == 9,"The result is False")
   def test4(self):
      self.assertTrue(4 + 5 == 10,"assertion fails")
   def test5(self):
      self.assertIn(3,[1,2,3])
   def test6(self):
      self.assertNotIn(3, range(5))

if __name__ == '__main__':
   unittest.main()
FAIL: test2 (__main__.SimpleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
   File "C:\Python27\SimpleTest.py", line 9, in test2
      self.assertNotEqual(5*2,10)
AssertionError: 10 == 10

FAIL: test4 (__main__.SimpleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
   File "C:\Python27\SimpleTest.py", line 13, in test4
      self.assertTrue(4+5==10,"assertion fails")
AssertionError: assertion fails

FAIL: test6 (__main__.SimpleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
   File "C:\Python27\SimpleTest.py", line 17, in test6
      self.assertNotIn(3, range(5))
AssertionError: 3 unexpectedly found in [0, 1, 2, 3, 4]

----------------------------------------------------------------------            
Ran 6 tests in 0.001s                                                             

FAILED (failures = 3)

두 번째 비교 assert입니다.

  • assertAlmostEqual (first, second, places = 7, msg = None, delta = None)
  • assertNotAlmostEqual (first, second, places, msg, delta)
  • assertGreater (first, second, msg = None)
  • assertGreaterEqual (first, second, msg = None)
  • assertLess (first, second, msg = None)
  • assertLessEqual (first, second, msg = None)
  • assertRegexpMatches (text, regexp, msg = None)
  • assertNotRegexpMatches (text, regexp, msg = None)
import unittest
import math
import re

class SimpleTest(unittest.TestCase):
   def test1(self):
      self.assertAlmostEqual(22.0/7,3.14)
   def test2(self):
      self.assertNotAlmostEqual(10.0/3,3)
   def test3(self):
      self.assertGreater(math.pi,3)
   def test4(self):
      self.assertNotRegexpMatches("Tutorials Point (I) Private Limited","Point")

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

위의 메소드를 실행시킨 결과입니다.

=====================================================FAIL: test1 (__main__.SimpleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
   File "asserttest.py", line 7, in test1
      self.assertAlmostEqual(22.0/7,3.14)
AssertionError: 3.142857142857143 != 3.14 within 7 places
================================================================
FAIL: test4 (__main__.SimpleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
   File "asserttest.py", line 13, in test4
      self.assertNotRegexpMatches("Tutorials Point (I) Private Limited","Point")
AssertionError: Regexp matched: 'Point' matches 'Point' in 'Tutorials Point (I)
Private Limited'
----------------------------------------------------------------------

Ran 4 tests in 0.001s                                                             

FAILED (failures = 2)

Collection를 위한 assert

  • assertListEqual (list1, list2, msg = None)
  • assertTupleEqual (tuple1, tuple2, msg = None)
  • assertSetEqual (set1, set2, msg = None)
  • assertDictEqual (expected, actual, msg = None)

위의 메소드를 수행한 예제

import unittest

class SimpleTest(unittest.TestCase):
   def test1(self):
      self.assertListEqual([2,3,4], [1,2,3,4,5])
   def test2(self):
      self.assertTupleEqual((1*2,2*2,3*2), (2,4,6))
   def test3(self):
      self.assertDictEqual({1:11,2:22},{3:33,2:22,1:11})

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

위의 메소드를 수행한 결과입니다.

FAIL: test1 (__main__.SimpleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
   File "asserttest.py", line 5, in test1
      self.assertListEqual([2,3,4], [1,2,3,4,5])
AssertionError: Lists differ: [2, 3, 4] != [1, 2, 3, 4, 5]

First differing element 0:
2
1

Second list contains 2 additional elements.
First extra element 3:
4

- [2, 3, 4]
+ [1, 2, 3, 4, 5]
? +++       +++

FAIL: test3 (__main__.SimpleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
   File "asserttest.py", line 9, in test3
      self.assertDictEqual({1:11,2:22},{3:33,2:22,1:11})
AssertionError: {1: 11, 2: 22} != {1: 11, 2: 22, 3: 33}
- {1: 11, 2: 22}
+ {1: 11, 2: 22, 3: 33}
?              +++++++

----------------------------------------------------------------------            
Ran 3 tests in 0.001s                                                             

FAILED (failures = 2)

하지만 이제부터는 그러지 않기를 바라면서 이 정리를 시작해 봅니다. 일단은 단위테스트가 어떠한 것인지 알아야 하기에 단위테스트에 대해서 알아봅니다.

본 글은 www.tutorialspoint.com/unittest_framework/unittest_framework.htm 을 참고로 작성하였습니다.

단위테스트란?

‘unittest’는 테스트 자동화, 테스트를 위한 설정 및 종료 코드 공유, 컬렉션으로 테스트 집계, 리포팅 프레임워크에서 테스트의 독립을 지원합니다.

unittest 모듈은 일련의 테스트에 대해 이러한 특성을 쉽게 지원할 수 있는 클래스를 제공합니다.

이를 달성하기 위해 unittest는 다음과 같은 중요한 개념을 지원합니다.

  • 테스트 픽스쳐(test fixture) - 하나 이상의 테스트를 수행하는 데 필요한 준비 작업과 연관 클린업 작업을 나타냅니다. 예를 들어, 임시 또는 프록시 데이터베이스, 디렉토리 작성 또는 서버 프로세스 시작과 관련될 수 있습니다.

  • 테스트 케이스 - 이것은 테스트의 최소 단위입니다. 특정 입력 세트에 대한 특정 응답을 확인합니다. unittest는 새로운 테스트 케이스를 작성하는 데 사용할 수 있는 기본클레스인 TestCase를 제공합니다.

  • 테스트 슈트 - 이것은 테스트 케이서, 테스트 스위트 또는 둘 모두의 집합니다. 이것은 함께 실행되어야하는 테스트를 집계하는 데 사용됩니다. 테스트 스위트는 TestSuite 클래스에 의해 구현됩니다.

  • 테스트 Runner - 테스트 실행을 조정하고 결과를 사용자에게 제공하는 구성 요소입니다. 러너는 그래픽 인터페이스나 텍스트 인터페이스를 사용하거나 특수 값을 반환하여 테스트 실행 결과를 나타낼 수 있습니다.

단위 테스트 만들어 보기

  1. 프로그램에서 unittest 모듈을 가져옵니다.
  2. 테스트할 함수를 정의하십시오. 다음 예제에서 add() 함수는 테스트를 거친다.
  3. unittest.TestCase를 하위 클래스화하여 테스트 사례를 만듭니다.
  4. 테스트를 클래스 내부의 메소드로 정의합니다. 메소드 이름은 ‘test’로 시작해야 합니다.
  5. 각 테스트는 TestCase 클래스의 assert 함수를 호출합니다. 다양한 유형의 assert가 있습니다. 다음 예제에서는 assertEquals() 함수를 호출합니다.
  6. assertEquals() 함수는 add() 함수의 결과를 arg2 인수와 비교하고 비교가 실패하면 assertionError를 발생시킵니다.
  7. 마지막으로 unittest 모듈에서 main() 메서드를 호출합니다.
import unittest

def add(x,y):
    return x + y

class SimpleTest(unittest.TestCase):
    def testadd1(self):
        self.assertEquals(add(4,5),9)

if __name__ == '__main__':
    unittest.main()
  1. script를 command line로 실행합니다.
python SimpleTest.py
-----------------------------------------
Ran 1 test in 0.000s
OK

테스트 클래스 지원 메소드

  1. setUp()
    테스트 fixture를 준비하기 위해 호출된 메소드. 이것은 테스트 메소드를 호출하기 직전에 호출됩니다.
  2. tearDown()
    테스트 메소드가 호출되고 결과가 기록된 직후에 호출될 메소드. 이것은 테스트 메소드가 예외를 발생시키는 경우에도 호출됩니다.
  3. setUpClass()
    개발 클래스에서 테스트하기 전에 호출될 클래스 메소드입니다.
  4. tearDownClass()
    개별 클래스의 테스트가 실행된 이후에 실행될 메소드입니다.
  5. run(result=None)
    결과를 결과로 전달 된 테스트 결과 개체로 결과를 수집하여 테스트를 실행합니다.
  6. skipTest(reason)
    테스트 메소드에서 이것을 호출하거나 현재 테스트를 건너 뜁니다.
  7. debug()
    결과를 수집하지 않고 테스트를 실행하십시오.
  8. shortDescription()
    테스트에 대한 한 줄짜리 설명을 반환합니다.
import unittest

class simpleTest2(unittest.TestCase):
   def setUp(self):
      self.a = 10
      self.b = 20
      name = self.shortDescription()
      if name == "Add":
         self.a = 10
         self.b = 20
         print name, self.a, self.b
      if name == "sub":
         self.a = 50
         self.b = 60
         print name, self.a, self.b
   def tearDown(self):
      print '\nend of test',self.shortDescription()

   def testadd(self):
      """Add"""
      result = self.a+self.b
      self.assertTrue(result == 100)
   def testsub(self):
      """sub"""
      result = self.a-self.b
      self.assertTrue(result == -10)

if __name__ == '__main__':
   unittest.main()
  1. 결과
C:\Python27>python test2.py
Add 10 20
F
end of test Add
sub 50 60
end of test sub
.
================================================================
FAIL: testadd (__main__.simpleTest2)
Add
----------------------------------------------------------------------
Traceback (most recent call last):
   File "test2.py", line 21, in testadd
      self.assertTrue(result == 100)
AssertionError: False is not true
----------------------------------------------------------------------
Ran 2 tests in 0.015s

FAILED (failures = 1)

Class Fixture

TestCase 클래스에는 TestCase 클래스내에서 개별 테스트를 실행하기 전에 실행할 수 있는 setUpClass() 메소드가 있습니다. 마찬가지고, tearDownClass() 메소드는 클래스의 모든 테스트 후에 실행됩니다. 두 메소드는 모두 클래스 메소드립니다. 따라서 @classmethod를 Annotation해야 합니다.

import unittest

class TestFixtures(unittest.TestCase):

   @classmethod
   def setUpClass(cls):
      print 'called once before any tests in class'

   @classmethod
   def tearDownClass(cls):
      print '\ncalled once after all tests in class'

   def setUp(self):
      self.a = 10
      self.b = 20
      name = self.shortDescription()
      print '\n',name
   def tearDown(self):
      print '\nend of test',self.shortDescription()

   def test1(self):
      """One"""
      result = self.a+self.b
      self.assertTrue(True)
   def test2(self):
      """Two"""
      result = self.a-self.b
      self.assertTrue(False)

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

'PYTHON > 강좌' 카테고리의 다른 글

01. Python 프로그램 로깅  (0) 2012.12.05
[PATTERN 2] Command 패턴  (0) 2012.11.19
[PATTERN 1] Factory 패턴 예제  (0) 2012.11.19

객체 지향의 설계의 기본

결합도는 낮게 응집도는 높게

객체 지향의 5대 설계 원칙(SOLID)

  1. SRP (단일책임의 원칙: Single Responsibility Principle)
       작성된 클래스는 하나의 기능만 가지며 클래스가 제공하는 모든 서비스는 그 하나의 책임(변화의 축: axis of change)을 수행하는 데 집중되어 있어야 한다는 원칙입니다. 이는 어떤 변화에 의해 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 함을 의미합니다. SRP원리를 적용하면 무엇보다도 책임 영역이 확실해지기 때문에 한 책임의 변경에서 다른 책임의 변경으로의 연쇄작용에서 자유로울 수 있습니다. 뿐만 아니라 책임을 적절히 분배함으로써 코드의 가독성 향상, 유지보수 용이라는 이점까지 누릴 수 있으며 객체지향 원리의 대전제 격인 OCP원리뿐 아니라 다른 원리들을 적용하는 기초가 됩니다. 이 원리는 다른 원리들에 비해서 개념이 비교적 단순하지만, 이 원리를 적용해서 직접 클래스를 설계하기가 그리 쉽지만은 않습니다. 왜냐하면, 실무의 프로세스는 매우 복잡 다양하고 변경 또한 빈번하기 때문에 경험이 많지 않거나 도메인에 대한 업무 이해가 부족하면 나도 모르게 SRP원리에서 멀어져 버리게 됩니다. 따라서 평소에 많은 연습(‘책임’이란 단어를 상기하는)과 경험이 필요한 원칙입니다.
  2. OCP (개방폐쇄의 원칙: Open Close Principle)
       버틀란트 메이어(Bertrand Meyer)박사가 1998년 객체지향 소프트웨어 설계 라는 책에서 정의한 내용으로 소프트웨어의 구성요소(컴포넌트, 클래스, 모듈, 함수)는 확장에는 열려있고, 변경에는 닫혀있어야 한다는 원리입니다. 이것은 변경을 위한 비용은 가능한 줄이고 확장을 위한 비용은 가능한 극대화 해야 한다는 의미로, 요구사항의 변경이나 추가사항이 발생하더라도, 기존 구성요소는 수정이 일어나지 말아야 하며, 기존 구성요소를 쉽게 확장해서 재사용할 수 있어야 한다는 뜻입니다. 로버트 C. 마틴은 OCP는 관리가능하고 재사용 가능한 코드를 만드는 기반이며, OCP를 가능케 하는 중요 메커니즘은 추상화와 다형성이라고 설명하고 있습니다. OCP는 객체지향의 장점을 극대화하는 아주 중요한 원리라 할 수 있습니다.
  3. LSP (리스코브 치환의 원칙: The Liskov Substitution Principle)
       이 원칙은 5가지 원칙 중에서 좀처럼 쉽게 이해 되지 않는 원칙의 하나로 LSP라는 이름에서는 도저히 원칙에 대한 내용을 도출 할 수 없는 원칙입니다. LSP를 한마디로 한다면, “서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다.”라고 할 수 있습니다. 즉, 서브 타입은 언제나 기반 타입과 호환될 수 있어야 합니다. 달리 말하면 서브 타입은 기반 타입이 약속한 규약(public 인터페이스, 물론 메소드가 던지는 예외까지 포함됩니다.)을 지켜야 합니다. 상속은 구현상속(extends 관계)이든 인터페이스 상속(implements 관계)이든 궁극적으로는 다형성을 통한 확장성 획득을 목표로 합니다. LSP원리도 역시 서브 클래스가 확장에 대한 인터페이스를 준수해야 함을 의미합니다. 다형성과 확장성을 극대화 하려면 하위 클래스를 사용하는 것보다는 상위의 클래스(인터페이스)를 사용하는 것이 더 좋습니다. 일반적으로 선언은 기반 클래스로 생성은 구체 클래스로 대입하는 방법을 사용합니다. 생성 시점에서 구체 클래스를 노출시키기 꺼려질 경우 생성 부분을 Abstract Factory 등의 패턴을 사용하여 유연성을 높일 수 있습니다. 상속을 통한 재사용은 기반 클래스와 서브 클래스 사이에 IS-A관계가 있을 경우로만 제한 되어야 합니다. 그 외의 경우에는 합성(composition)을 이용한 재사용을 해야 합니다. 상속은 다형성과 따로 생각할 수 없습니다. 그리고 다형성으로 인한 확장 효과를 얻기 위해서는 서브 클래스가 기반 클래스와 클라이언트 간의 규약(인터페이스)를 어겨서는 안 됩니다. 결국 이 구조는 다형성을 통한 확장의 원리인 OCP를 제공 하게 됩니다. 따라서 LSP는 OCP를 구성하는 구조가 됩니다. 객체지향 설계 원리는 이렇게 서로가 서로를 이용하기도 하고 포함하기도 하는 특징이 있습니다. LSP는 규약을 준수하는 상속구조를 제공 합니다. LSP를 바탕으로 OCP는 확장하는 부분에 다형성을 제공해 변화에 열려있는 프로그램을 만들 수 있도록 합니다.
  4. LSP (리스코브 치환의 원칙: The Liskov Substitution Principle)
        ISP원리는 한 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다는 원리입니다. 즉 어떤 클래스가 다른 클래스에 종속될 때에는 가능한 최소한의 인터페이스만을 사용해야 합니다. ISP를 ‘하나의 일반적인 인터페이스보다는, 여러 개의 구체적인 인터페이스가 낫다’라고 정의할 수 도 있습니다. 만약 어떤 클래스를 이용하는 클라이언트가 여러 개고 이들이 해당 클래스의 특정 부분집합만을 이용한다면, 이들을 따로 인터페이스로 빼내어 클라이언트가 기대하는 메시지만을 전달할 수 있도록 합니다. SRP가 클래스의 단일책임을 강조한다면 ISP는 인터페이스의 단일책임을 강조합니다. 하지만 ISP는 어떤 클래스 혹은 인터페이스가 여러 책임 혹은 역할을 갖는 것을 인정합니다. 이러한 경우 ISP가 사용되는데 SRP가 클래스 분리를 통해 변화에의 적응성을 획득하는 반면, ISP에서는 인터페이스 분리를 통해 같은 목표에 도달 합니다.
  5. DIP (의존성역전의 원칙: Dependency Inversion Principle)
       의존 관계의 역전 Dependency Inversion 이란 구조적 디자인에서 발생하던 하위 레벨 모듈의 변경이 상위 레벨 모듈의 변경을 요구하는 위계관계를 끊는 의미의역전입니다. 실제 사용 관계는 바뀌지 않으며, 추상을 매개로 메시지를 주고 받음으로써 관계를 최대한 느슨하게 만드는 원칙입니다.


       DIP의 키워드는 ‘IOC’, ‘훅 메소드(슈퍼클래스에서 디폴트 기능을 정의해두거나 비워뒀다가 서브클래스에서 선택적으로 오버라이드할 수 있도록 만들어둔 메소드를 훅(hook) 메소드라고 합니다. 서브클래스에서는 추상 메소드를 구현하거나, 훅 메소드를 오버라이드하는 방법을 이용해 기능의 일부를 확장합니다.)’’, ‘확장성’입니다. 이 세 가지 요소가 조합되어 복잡한 컴포넌트들의 관계를 단순화하고 컴포넌트 간의 커뮤니케이션을 효율적이게 합니다.

       이를 위해 Callee 컴포넌트(예를 들어 프레임워크)는 Caller 컴포넌트들이 등록할 수 있는 인터페이스를 제공 합니다. 따라서 자연스럽게 Callee는 Caller들의 컨테이너 역할이 됩니다.(JMS의 Topic 제공자, 스윙 컴포넌트, 배우 섭외 담당자들은 등록자들을 관리합니다). Callee 컴포넌트는 Caller 컴포넌트가 확장(구현)할, 그리고 IOC를 위한 훅 메소드 인터페이스를 정의 합니다. Caller 컴포넌트는 정의된 훅 메소드를 구현합니다. 이로써 DIP를 위한 준비가 완료되고 이 상태에서 다음과 같은 시나리오가 전개됩니다. Caller는 Callee에 자신을 등록합니다. Callee는 Caller에게 정보를 제공할 적당한 시점에 Caller의 훅 메소드를 호출합니다. 바로 이 시점은 Caller와 Callee의 호출관계가 역전되는 IOC 시점이 됩니다. DIP는 비동기적으로 커뮤니케이션이 이루어져도 될 (혹은, 이뤄져야 할) 경우, 컴포넌트 간의 커뮤니케이션이 복잡할 경우, 컴포넌트 간의 커뮤니케이션이 비효율적일 경우(빈번하게 확인해야 하는)에 사용됩니다.

       DIP는 복잡하고 지난한 컴포넌트간의 커뮤니케이션 관계를 단순화하기 위한 원칙입니다. 실 세계에서도 헐리우드 원칙에서와 같이 귀찮도록 자주 질문과 요청을 하는 동료에게도 써먹어 볼만한 원칙입니다.

Layering

(참고: http://www.nextree.co.kr)

스프링 DI의 목적

코드 의존성 제거 : 프로그램의 수정을 용이하게 함.

스프링 DI의 방식

  1. Constructor-based dependency injection
    
        package com.tutorialspoint;

        public class TextEditor {
            private SpellChecker spellChecker;
            public TextEditor(SpellChecker spellChecker) {
                System.out.println("Inside TextEditor constructor." );
                this.spellChecker = spellChecker;
            }
            public void spellCheck() {
                spellChecker.checkSpelling();
            }
        }  
    
    
        package com.tutorialspoint;

        public class SpellChecker {
            public SpellChecker(){
                System.out.println("Inside SpellChecker constructor." );
            }

            public void checkSpelling() {
                System.out.println("Inside checkSpelling." );
            } 
        }
    
    
package com.tutorialspoint;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        TextEditor te = (TextEditor)context.getBean("textEditor");
        te.spellCheck();
    }
}
    
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

   <!-- Definition for textEditor bean -->
   <bean id="textEditor" class="com.tutorialspoint.TextEditor">
      <constructor-arg ref="spellChecker"/>
   </bean>

   <!-- Definition for spellChecker bean -->
   <bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
   </bean>

</beans>
  1. Setter-based dependency injection
    
package com.tutorialspoint;

public class TextEditor {
    private SpellChecker spellChecker;

    // a setter method to inject the dependency.
    public void setSpellChecker(SpellChecker spellChecker) {
        System.out.println("Inside setSpellChecker." );
        this.spellChecker = spellChecker;
    }
    // a getter method to return spellChecker
    public SpellChecker getSpellChecker() {
        return spellChecker;
    }

    public void spellCheck() {
        spellChecker.checkSpelling();
    }
}
    
    
package com.tutorialspoint;

public class SpellChecker {
    public SpellChecker(){
        System.out.println("Inside SpellChecker constructor." );
    }

    public void checkSpelling() {
        System.out.println("Inside checkSpelling." );
    }
}
    
    
package com.tutorialspoint;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = 
             new ClassPathXmlApplicationContext("Beans.xml");

        TextEditor te = (TextEditor) context.getBean("textEditor");

        te.spellCheck();
    }
}
    
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

   <!-- Definition for textEditor bean -->
   <bean id="textEditor" class="com.tutorialspoint.TextEditor">
      <property name="spellChecker" ref="spellChecker"/>
   </bean>

   <!-- Definition for spellChecker bean -->
   <bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
   </bean>

</beans>

(참고: https://www.tutorialspoint.com)

'JAVA > Spring Framework' 카테고리의 다른 글

02. POJO의 개념  (0) 2016.09.06
01. Spring Framework을 사용해야 하는 이유  (0) 2016.09.05
글을 시작하기에 앞서...  (0) 2016.09.05

POJO의 개념

In software engineering, a plain old Java object (POJO) is an ordinary Java object, not bound by any special restriction and not requiring any class path. The term was coined by Martin Fowler, Rebecca Parsons and Josh MacKenzie in September 2000: [1]

“We wondered why people were so against using regular objects in their systems and concluded that it was because simple objects lacked a fancy name. So we gave them one, and it’s caught on very nicely.”[1]

The term “POJO” initially denoted a Java object which does not follow any of the major Java object models, conventions, or frameworks; nowadays “POJO” may be used as an acronym for “Plain Old JavaScript Object” as well, in which case the term denotes a JavaScript object of similar pedigree.[2]

The term continues the pattern of older terms for technologies that do not use fancy new features, such as POTS (Plain Old Telephone Service) in telephony, PODS (Plain Old Data Structures) that are defined in C++ but use only C language features, and Pod (Plain Old Documentation) in Perl. The equivalent to POJO on the .NET framework is Plain Old CLR Object (POCO).[3] For PHP, it is Plain Old PHP Object (POPO).[4][5]

The POJO phenomenon has most likely gained widespread acceptance because of the need for a common and easily understood term that contrasts with complicated object frameworks.[citation needed]

Ideally speaking, a POJO is a Java object not bound by any restriction other than those forced by the Java Language Specification; i.e. a POJO should not have to

  1. Extend prespecified classes, as in

    public class Foo extends javax.servlet.http.HttpServlet { …

  2. Implement prespecified interfaces, as in

    public class Bar implements javax.ejb.EntityBean { …

  3. Contain prespecified annotations, as in

    @javax.persistence.Entity public class Baz { …

However, due to technical difficulties and other reasons, many software products or frameworks described as POJO-compliant actually still require the use of prespecified annotations for features such as persistence to work properly. The idea is that if the object (actually class) was a POJO before any annotations were added, and would return to POJO status if the annotations are removed then it can still be considered a POJO. Then the basic object remains a POJO in that it has no special characteristics (such as an implemented interface) that makes it a “Specialized Java Object” (SJO or (sic) SoJO).

출처 : 위키피디아(EN)

'JAVA > Spring Framework' 카테고리의 다른 글

03. Spring DI의 개념  (0) 2016.10.27
01. Spring Framework을 사용해야 하는 이유  (0) 2016.09.05
글을 시작하기에 앞서...  (0) 2016.09.05

+ Recent posts