Skip to content

Commit 6ca96ea

Browse files
committed
1026 TDD 21장
1 parent cb8a8bf commit 6ca96ea

File tree

4 files changed

+395
-27
lines changed

4 files changed

+395
-27
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/TDD/.idea/
2+

TDD/ch11~17.md TDD/Ch11~17.md

File renamed without changes.

TDD/Ch18~24.md

+328-1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ TestCaseTest("testRunning").run() # 성공!
106106

107107

108108

109+
---
110+
111+
112+
109113
# 19장 테이블 차리기
110114

111115
#
@@ -232,4 +236,327 @@ TestCaseTest("testSetUp").run()
232236

233237
1부에서도 언급했던, 단단한 기반(초록 막대!)에 한 발을 두면서 새로운 곳으로 발을 옮기는 것과 비슷하다. 한번에 여러 곳을 고치면, 흐름을 따라가기도 어렵고, 최적의 리팩토링을 고민할 시간을 없앤다.
234238

235-
... 실제 코드를 짜면서 내가 그걸 할 수 있을지는 모르겠지만.
239+
... 실제 코드를 짜면서 내가 그걸 할 수 있을지는 모르겠지만.
240+
241+
242+
243+
---
244+
245+
246+
247+
# 20장 뒷정리하기
248+
249+
#
250+
251+
> ~~○ 테스트 메서드 호출하기~~
252+
>
253+
> ~~○ 먼저 setUp 호출하기~~
254+
>
255+
> **○ 나중에 tearDown 호출하기**
256+
>
257+
> ○ 테스트 메서드가 실패하더라도 tearDown 호출하기
258+
>
259+
> ○ 여러 개의 테스트 실행하기
260+
>
261+
> ○ 수집된 결과를 출력하기
262+
>
263+
> **○ WasRun에 로그 문자열 남기기**
264+
265+
#
266+
267+
`setUp()` 에서 만약 외부자원을 할당받는다면, 테스트가 독립적이기 위해서 해당 자원을 반환해주는 `tearDown()` 이 있어야 할 것이다.
268+
269+
간단히 setUp처럼 플래그를 도입하여 할 수도 있지만, 문제가 있다. **플래그는 순서를 표시할 수 없다** 는 것이다. `setUp()` 은 테스트메서드 실행 전에 호출되어야하고, `tearDown()` 은 테스트메서드 호출 후에 실행되어야 한다. 이를 표시하기위해 플래그대신 로그(log)를 도입해보기로 한다.
270+
271+
이는 위의 todo-list에서 **'WasRun에 로그 문자열 남기기'**에 해당한다.
272+
273+
#
274+
275+
```python
276+
class TestCase():
277+
278+
def __init__(self, name):
279+
self.name = name
280+
281+
def setUp(self):
282+
'''
283+
하위 클래스 WasRun에서 오버라이드할 추상 메서드
284+
'''
285+
pass
286+
287+
def tearDown(self):
288+
'''
289+
하위 클래스 WasRun에서 오버라이드할 추상 메서드
290+
'''
291+
pass
292+
293+
def run(self):
294+
'''
295+
메서드 동적 호출
296+
getattr(object, name, default)는 object 내에서 주어진 string(name)과 동일한 method를 반환해준다.
297+
따라서 테스트케이스의 이름을 전달했을 때, 해당 테스트케이스가 호출되었는지를 기록할 수 있다.
298+
'''
299+
self.setUp()
300+
method = getattr(self, self.name) # 상수를 변수(method)로 변화시켜 일반화하는 리팩토링의 예
301+
method()
302+
self.tearDown()
303+
304+
305+
class WasRun(TestCase):
306+
'''
307+
메서드가 실행되었는지를 알려주는 테스트클래스
308+
'''
309+
310+
def __init__(self, name):
311+
TestCase.__init__(self, name)
312+
313+
def setUp(self):
314+
'''
315+
setUp 여부 기록
316+
여러 테스트를 실행할 때, 테스트 커플링을 피하기 위해(환경을 독립시키기 위해) 사용하는 일종의 세팅 메서드
317+
'''
318+
# self.wasSetUp = 1 # 플래그는 순서를 기록할 수 없다. 따라서 테스트 코드 호출전에 setUp과 tearDown의 순서를 보장할 수 없다.
319+
self.log = "setUp" # 플래그대신 로그를 사용하여 코드의 흐름을 확실히 한다.
320+
321+
def testMethod(self):
322+
'''
323+
테스트메서드 호출여부 기록
324+
메서드가 호출되었는지를 기억(flag)하는 메서드
325+
'''
326+
self.log += " testMethod"
327+
328+
def tearDown(self):
329+
'''
330+
tearDown 여부 기록
331+
테스트를 위해 setUp에서 할당받은 외부 자원을, 테스트가 종료되면 반환하는 메서드
332+
'''
333+
self.log += " tearDown"
334+
335+
336+
337+
class TestCaseTest(TestCase):
338+
'''
339+
테스트케이스를 수행하는 메인 클래스
340+
'''
341+
342+
def testTemplateMethod(self): # testSetUp의 진화! (testRunning 메서드는 이제 필요없음.)
343+
'''
344+
setUp과 tearDown의 호출 순서 체크 테스트코드
345+
'''
346+
test = WasRun("testMethod") # 이제 test-SetUp과 Running 두 군데서 사용하지 않으니, setUp 메서드도 삭제하고 리팩토링을 되돌린다.
347+
test.run()
348+
assert("setUp testMethod tearDown" == test.log) # 플래그 대신에 로그를 검
349+
350+
# main
351+
352+
TestCaseTest("testTemplateMethod").run()
353+
```
354+
355+
#
356+
357+
tearDown이 어떤 외부 자원을 할당받는지는 몰라서 stub으로 구현하긴 했지만, 그건 setUp도 마찬가지니까... 일단 테스트를 통과하는것(초록막대)까지는 확인했다.
358+
359+
이 장에서 크게 다룰 것은 없다.
360+
361+
1. '플래그'에서 '로그'로 전략을 수정했다는 것, 그 이유는 순서를 남기기 위해서라는 것.
362+
2. TestCaseTest 클래스에서 테스트 메서드를 `testTemplateMethod()` 로 일원화하였으므로, setUp 메서드의 분리도 삭제하고 리팩토링을 되돌린 점.
363+
364+
정도가 정리할 만한 것인듯 하다.
365+
366+
367+
368+
---
369+
370+
371+
372+
# 21장 셈하기
373+
374+
#
375+
376+
> ~~○ 테스트 메서드 호출하기~~
377+
>
378+
> ~~○ 먼저 setUp 호출하기~~
379+
>
380+
> ~~○ 나중에 tearDown 호출하기~~
381+
>
382+
> ○ 테스트 메서드가 실패하더라도 tearDown 호출하기
383+
>
384+
> ○ 여러 개의 테스트 실행하기
385+
>
386+
> **○ 수집된 결과를 출력하기**
387+
>
388+
> ~~○ WasRun에 로그 문자열 남기기~~
389+
390+
#
391+
392+
지금까지 구현한 코드에서는, 테스트메서드가 실패하면 예외가 발생하여 `tearDown()` 이 제대로 수행되지 않는다. 예외가 발생하건 말건 모든 테스트케이스와 setUp과 tearDown은 다 수행되어야한다.
393+
394+
#
395+
396+
여러 테스트를 실행하고, 그 결과를 다음과 같이 표현할 수 있다면 좋지 않을까?
397+
398+
#
399+
400+
> 5개 테스트가 실행됨
401+
>
402+
> 2개 실패
403+
>
404+
> TestCaseTest.testFooBar-ZeroDivide Exception
405+
>
406+
> MoneyTest.testNegation-AssertionError
407+
408+
#
409+
410+
위와 같이 뜬다면 최소한 어떤 에러들이 테스트케이스에서 발생했고, 무엇을 잡아야 하는지 파악할 수 있게 된다.
411+
412+
일단 시작은 사소하게, 테스트 하나의 실행결과를 기록하는 TestResult 객체를 반환하게 해본다.물론 차후에 테스트 여러개도 처리할 수 있게 리팩토링 할 것이다.
413+
414+
#
415+
416+
```python
417+
class TestCase():
418+
419+
def __init__(self, name):
420+
self.name = name
421+
422+
def setUp(self):
423+
'''
424+
하위 클래스 WasRun에서 오버라이드할 추상 메서드
425+
'''
426+
pass
427+
428+
def tearDown(self):
429+
'''
430+
하위 클래스 WasRun에서 오버라이드할 추상 메서드
431+
'''
432+
pass
433+
434+
def run(self):
435+
'''
436+
메서드 동적 호출
437+
'''
438+
result = TestResult() # 실행결과 객체 할당
439+
result.testStarted() # 실행 카운트 기록
440+
self.setUp()
441+
method = getattr(self, self.name)
442+
method()
443+
self.tearDown()
444+
return result
445+
446+
447+
class WasRun(TestCase):
448+
'''
449+
메서드가 실행되었는지를 알려주는 테스트클래스
450+
'''
451+
452+
def __init__(self, name):
453+
TestCase.__init__(self, name)
454+
455+
def setUp(self):
456+
'''
457+
setUp 여부 기록
458+
여러 테스트를 실행할 때, 테스트 커플링을 피하기 위해(환경을 독립시키기 위해) 사용하는 일종의 세팅 메서드
459+
'''
460+
self.log = "setUp"
461+
462+
def testMethod(self):
463+
'''
464+
테스트메서드 호출여부 기록
465+
메서드가 호출되었는지를 기억(flag)하는 메서드
466+
'''
467+
self.log += " testMethod"
468+
469+
def testBrokenMethod(self):
470+
'''
471+
실패하는 테스트의 stub
472+
'''
473+
raise Exception # 이번 장에서는 아직 예외 핸들링을 하지 않았다!
474+
475+
def tearDown(self):
476+
'''
477+
tearDown 여부 기록
478+
테스트를 위해 setUp에서 할당받은 외부 자원을, 테스트가 종료되면 반환하는 메드서
479+
'''
480+
self.log += " tearDown"
481+
482+
483+
484+
class TestCaseTest(TestCase):
485+
'''
486+
테스트케이스를 수행하는 메인 클래스
487+
'''
488+
489+
def testTemplateMethod(self):
490+
'''
491+
setUp과 tearDown의 호출 순서 체크 테스트코드
492+
'''
493+
test = WasRun("testMethod")
494+
test.run()
495+
assert("setUp testMethod tearDown" == test.log)
496+
497+
def testFailedResult(self):
498+
'''
499+
실패하는 테스트 수가 제대로 나오는지 체크하는 테스트코드
500+
'''
501+
test = WasRun("testBrokenMethod")
502+
result = test.run()
503+
assert("1 run, 1 failed" == result.summary())
504+
505+
def testResult(self):
506+
'''
507+
실행결과가 제대로 나오는지 체크하는 테스트코드
508+
'''
509+
test = WasRun("testMethod")
510+
result = test.run()
511+
assert("1 run, 0 failed" == result.summary())
512+
513+
class TestResult:
514+
'''
515+
테스트메서드(들)의 실행결과를 기록하는 객체
516+
'''
517+
518+
def __init__(self):
519+
self.runCount = 0 # 실행된 테스트의 수 0으로 초기화
520+
521+
def testStarted(self):
522+
self.runCount = self.runCount + 1
523+
524+
def summary(self):
525+
'''
526+
실행결과 반환 메서드
527+
'''
528+
return f"{self.runCount} run, 0 failed"
529+
530+
# main
531+
532+
TestCaseTest("testTemplateMethod").run()
533+
534+
```
535+
536+
#
537+
538+
일단 일단 상수들을 변수로 만들어 실제 구현을 몇 하긴 했지만, stub으로 구현한 부분도 많다. 예외가 발생할 경우의 Exception 핸들링이 되어있지 않다. `testBrokenMethod()`에서 실제로 예외가 발생하면 테스트케이스가 모두 실행되지 못하고 종료된다. 따라서 실행 결과도 제대로 얻을 수 없을 것이다.
539+
540+
이 문제는 다음 장에서 해결해 보기로 한다.
541+
542+
#
543+
544+
테스트 하나를 성공시켰는데 그 다음 테스트에서 문제가 생기면, 두 단계 물러서는 것도 고려하라고 한다. TDD는 아주 방어적인 프로그래밍인 것 같다. 절대 두 칸을 건너뛰라고 하지 않는다. 오히려, 다음 칸에 문제가 있어보인다면 일단 뒤로 한칸 가서, 돌아가보라고 조언한다.
545+
546+
#
547+
548+
> ~~○ 테스트 메서드 호출하기~~
549+
>
550+
> ~~○ 먼저 setUp 호출하기~~
551+
>
552+
> ~~○ 나중에 tearDown 호출하기~~
553+
>
554+
> ○ 테스트 메서드가 실패하더라도 tearDown 호출하기
555+
>
556+
> ○ 여러 개의 테스트 실행하기
557+
>
558+
> ~~○ 수집된 결과를 출력하기~~
559+
>
560+
> ~~○ WasRun에 로그 문자열 남기기~~
561+
>
562+
> ○ 실패한 테스트 보고하기

0 commit comments

Comments
 (0)