TDD with django for 5.0

2022년 와서 이 책을 볼려고 한다면 파이썬 3.7버전 혹은 3.8버전까지 사용하길 바란다. 3.9버전부터는 파이썬 기본라이브러리인 ’collection’이 바뀌는 변경점이 있기 때문이다.

2023년 말에 다시 확인해보니 해당 문제가 해결된것으로 보인다. 5.0버전에서도 문제없이 실행된다.

게다가 예전과 달리 맥북에서 사용했다면 ’brew’로 쉽게 설치했던것을 윈도우에서 설치하려니 삽질 경험 +1을 했다. 해당 내용은 다른 글에 적었다.

앞에 설명한 것처럼 1.6버전으로 작성되있는 예제 코드를 참고하여 최신버전(5.0)으로 재작성 및 트러블 슈팅해본다.

1장 테스팅 고트

django 개발 서버 가동 및 페이지 정상 접속 여부
Selenium 브라우저 자동화 툴을 이용하여 서버에 있는 웹 페이지 접속

1장의 인상은 “테스트를 먼저해! 테스트를 먼저하라고!” 다.

예제에서는 파이어폭스(겟코드라이버)를 셀레늄으로 테스트를 하는데, 크롬드라이버를 활용하여 테스트해도 상관은 없다.

간단하게 django 프로젝트와 git, 셀레늄을 활용하여 테스트를 시작할 준비를 마치며 1장은 끝난다.

1장에서는 크롬을 띄우고 로컬호스트를 띄우는 자동화 적용, TDD의 기본적인 이론, 프로젝트 시작하면서 구축이 가능하다는 것을 보여주는 것 같다.

어떤 책에서나 1장이 가장 중요하다. 언제나 생각하지만 프로젝트는 빌드업이 반 이다 반.

2장 Unittest모듈을 이용한 확장

functional_test.py 코드 내 스토리 추가
unittest 모듈 사용하기

셀레늄을 이용한 테스트는 실제 웹 브라우저를 실행해서 동작하는지 유저 관점에서 확인하는 것이다. 이를 기능 테스트(FT)라고 부른다.

특히 기능 테스트는 사람이 이해할 수 있는 스토리를 가지고 있어야한다. 주석으로 기록을 하던가 테스트 스토리 보드를 짜던가 해야할 것 같다. 특히 다른 사람이 볼 수 있을정도로 기록 하도록.

기능 테스트 == 승인 테스트 == 종단간 테스트

unittest 모듈의 문제는 AssertionError 메시지가 명시적으로 우리 기능의 무엇인지 알려주지 않는다.

  • 열려있는 크롬/파이어폭스 창을 종료시에 닫아주려면 try/finally 처리가 필요하나 테스트 마다 반복되는 코드 발생
  • 따라서 이 모든걸 제공하는 별도 솔루션을 사용해야한다.

2장에서 중요한 점은 다음과 같다.

  • 모든 테스트는 Test_로 시작하는 명칭으로 된 메소드에서 실행한다.
  • setIp/tearDown 메소드는 각 테스트 메소도의 시작전/후로 실행한다. 각 테스트에 공통된 전후처리를 할 때 사용한다.
  • assert 대신에 TestCase의 self.assert* 메소드로 결과 검증을 한다.
  • self.fail 은 강제적으로 테스트 실패를 발생시켜 에러 메시지를 출력한다.
  • if name==’main’은 다른 스크립트에 임포트되서 실행할 경우에는 실행되지 않는다. python 명령으로만 실행된다.
  • 또한 unittest.main()은 테스트 실행자를 run하는 코드다.

2장에서는 사용자의 관점에서 어떻게 애플리케이션이 동작해야하는지 기술할지 고민해야한다를 말하고 있다. 이는 기능 테스트 구조화를 위해 사용된다. 예측된 실패를 통해 구조를 설정해보자는 의미도 있다.

3장 단위 테스트를 이용한 간단한 홈페이지 테스트

단위 테스트와 기능 테스트의 정의 및 차이
Django 내 단위 테스트
뷰를 위한 단위 테스트

기능 테스트(functional test)는 사용자 관점에서 애플리케이션 외부를 테스트 하는 것이고, 단위테스트(Unit Test)는 프로그래머 관점에서 그 내부를 테스트 한다는 것이다.

기능 테스트는 제대로 된 기능성을 갖춘 애플리케이션을 만들게 도와주고, 단위 테스트는 깔끔하고 버그 없는 코드를 작성하도록 돕는다.

기능 테스트 단위 테스트
관점 사용자 관점 프로그래머 관점
목표 애플리케이션 외부 애플리케이션 내부
레이어 상위 레벨 하위 레벨
목적 제대로 된 기능성을 갖춘 애플리케이션을 구축 깔끔하고 버그없는 코드를 작성

테스트 작성 순서

  1. 기능 테스트 작성 : 사용자 관점에서 새로운 기능을 정의
  2. 기능 테스트가 실패하고 나면, 어떻게 코드를 작성해야 테스트를 통과할지 단위테스트를 이용해서 정의한다.
  3. 단위 테스트가 실패하고 나면 단위 테스트를 통과할 수 있을 정도의 최소한의 코드만 작성한다.

기능 테스트가 완전할 때까지 2,3을 반복

  1. 기능 테스트를 재실행 해서 통과하는지 확인

Django의 처리 흐름

  1. 특정 URL에 대한 HTTP 요청을 받는다.
  2. Django는 특정 규칙을 이용해서 해당 요청에 어떤 뷰 함수를 실행할지 결정한다. (URL “해석”이라고 하는 처리다)
  3. 이 뷰 기능이 요청을 처리해서 HTTP “응답”으로 반환한다.

우리가 테스트해야하는 것은 두 가지다.

  • URL의 사이트 루트(“/”)를 해석해서 특정 뷰 기능에 매칭시킬 수 있는지
  • 이 뷰 기능이 특정 HTML을 반환하게 해서 기능 테스트 통과하는지
여기서 View 함수는 필요한 데이터를 모델 (혹은 외부)에서 가져와서 적절히 각공하여 웹 페이지 결과를 만들도록 컨트롤 하는 역할을 한다.

4장 왜 테스트를 하는 것인가?

Selenitum을 이용한 사용자 반응 테스트
코드 리팩토링
메인 페이지 추가 수정

상수는 테스트하지 마라!

단위 테스트는 로직이나 흐름 제어, 설정 등을 테스트 하는것이다. html을 문자열로 테스트하는 것은 상수 테스트와 같다고 할 수 있다.

HttpResponse를 사용하는 대신에 Django의 render 함수를 사용했다. 함수는 첫번째 인수로 request를 지정하고, 두번째 인수로 렌더링할 템플릿을 지정한다. Djangosms 앱 폴더 내에 있는 templates라는 폴더를 자동으로 검색하고, 템플릿 컨텐츠를 기반으로 HttpResponse를 만들어준다.

from django.shortcuts import render

def home_page(request):
    return render(request, 'home.html')

정리 : TDD 프로세스

리팩토링 (Refactoring) : 기능(결과물)은 바꾸지 않고, 코드 자체를 개선하는 작업
  • 테스트 작성 -> 실패 (확인) -> 최소 코드 작성 : 테스트를 통과할 때까지 과정을 반복 필요에 따라서 코드를 리팩터링한다. 리팩터링 이후엔 테스트 과정을 반복한다.
  1. 기능 테스트 (Functional tests)
  2. 단위 테스트 (Unit tests)
  3. 단위 테스트-코드 주기 (Unit test-code cycle)
  4. 리팩터링 (Refactoring)

기능 테스트는 애플리케이션이 동작하는지 아닌지 판단하는 수단. 단위 테스트는 이 판단을 돕기 괴한 툴이라고 할 수 있다.

TDD 책의 기능 테스트와 단위 테스트로 구성되는 TDD 프로세스

5장 ~ 6장 사용자 입력 저장 ~ 최소 동작 사이트 구축

POST 요청을 전송하기 위한 폼(form) 연동
서버에서 POST 요청 처리
파이썬 변수를 전달해서 템플릿에 출력하기
스트라이크 세개면 리팩터
Django ORM과 첫 모델
POST를 데이터베이스에 저장하기
POST 후에 리디렉션
템플릿에 있는 아이템 렌더링
마이그레이션을 이용한 운영 데이터베이스 생성하기

객체 관계형 맵핑(Obeject-Relational Mapper, ORM) - 데이터 베이스의 레이블, 레코드, 칼럼 형태로 저장된 데이터를 추상화한 것 - 객체지향 코딩 스타일을 그대로 유지하는 게 목적 - Django는 기본 ORM을 탑재하고 있음.

RDB 객체지향 언어
테이블 클래스
컬럼 속성
레코드 인스턴스

마이그레이션(Migration)

  • 각 app의 models.py 파일에 적용된 내용 기반으로, 사용자가 테이블/칼럼을 삭제/추가/변경 가능하게 한다.
  • 데이터 베이스를 위한 버전관리 시스템
  • 프로젝트 루트에 manage.py에 관련 명령이 있음(migrate, makemigrations 등)
  1. migrate 명령어 -> DB 생성
  2. DB 지우고 python manage.py migrate --noinput 을 하면 재생성

makemigration, migrate 차이는 다른 글로 확인하여 정리하였다.