(testing_reference)= # Wagtail 사이트 테스트하기 Wagtail은 사이트 테스트를 단순화하는 몇 가지 유틸리티를 제공합니다. ## WagtailPageTestCase **_class_ wagtail.test.utils.WagtailPageTestCase** `WagtailPageTestCase` 는 `django.test.TestCase` 를 확장하여 몇 가지 새로운 `assert` 메서드를 추가합니다. 이 메서드를 사용하려면 이 클래스를 상속해야 합니다: ```python from wagtail.test.utils import WagtailPageTestCase from myapp.models import MyPage class MyPageTests(WagtailPageTestCase): def test_can_create_a_page(self): ... ``` **assertPageIsRoutable(_page, route_path="/", msg=None_)** `page` 가 `Http404` 오류를 발생시키지 않고 라우팅될 수 있는지 확인합니다. 여러 경로를 가진 페이지 유형의 경우, `route_path` 를 사용하여 테스트할 대체 경로를 지정할 수 있습니다. 이 assertion은 페이지 유형의 커스텀 라우팅 로직에 대한 테스트 커버리지를 얻는 데 좋습니다. 예시: ```python from wagtail.test.utils import WagtailPageTestCase from myapp.models import EventListPage class EventListPageRoutabilityTests(WagtailPageTestCase): @classmethod def setUpTestData(cls): # 테스트용 페이지 생성 ... def test_default_route(self): self.assertPageIsRoutable(self.page) def test_year_archive_route(self): # 참고: 지정된 연도에 이벤트가 없을 때 이 페이지 유형이 404를 발생시키더라도 # 라우팅은 여전히 성공해야 합니다 self.assertPageIsRoutable(self.page, "archive/year/1984/") ``` **assertPageIsRenderable(_page, route_path="/", query_data=None, post_data=None, user=None, accept_404=False, accept_redirect=False, msg=None_)** `page` 가 치명적인 오류를 발생시키지 않고 렌더링될 수 있는지 확인합니다. 여러 경로를 가진 페이지 유형의 경우, `route_path` 를 사용하여 페이지의 일반 `url` 에 추가될 부분 경로를 지정할 수 있습니다. `post_data` 가 제공되면, 테스트는 요청 본문에 `post_data` 를 포함하여 `POST` 요청을 수행합니다. 그렇지 않으면 `GET` 요청이 수행됩니다. `query_data` 가 제공되면, 항상 쿼리스트링으로 변환되어 요청 URL에 추가됩니다. `user` 가 제공되면, 테스트는 해당 사용자를 활성 사용자로 설정하여 수행됩니다. 기본적으로, 페이지 URL에 대한 요청이 301, 302 또는 404 HTTP 응답으로 결과가 나오면 assertion은 실패합니다. 404 응답이 예상되는 페이지/경로를 테스트하는 경우, `accept_404=True` 를 사용하여 이를 표시할 수 있으며, 이 경우 404 응답을 받았을 때 assertion이 통과합니다. 마찬가지로, 리디렉션 응답이 예상되는 페이지/경로를 테스트하는 경우, `accept_redirect=True` 를 사용하여 이를 표시할 수 있으며, 이 경우 301 또는 302 응답을 받았을 때 assertion이 통과합니다. 이 assertion은 페이지 유형의 커스텀 렌더링 로직에 대한 테스트 커버리지를 얻는 데 좋습니다. 예시: ```python def test_default_route_rendering(self): self.assertPageIsRenderable(self.page) def test_year_archive_route_with_zero_matches(self): # 참고: 지정된 연도에 이벤트가 없을 때 404를 발생시켜야 함 self.assertPageIsRenderable(self.page, "archive/year/1984/", accept_404=True) def test_month_archive_route_with_zero_matches(self): # 참고: 지정된 월에 이벤트가 없을 때 연도별 뷰로 리디렉션해야 함 self.assertPageIsRenderable(self.page, "archive/year/1984/07/", accept_redirect=True) ``` **assertPageIsEditable(_page, post_data=None, user=None, msg=None_)** 페이지 편집 뷰가 `page` 에 대해 치명적인 오류 없이 작동하는지 확인합니다. `user` 가 제공되면, 테스트는 해당 사용자를 활성 사용자로 설정하여 수행됩니다. 그렇지 않으면, 슈퍼유저가 생성되어 테스트에 사용됩니다. 성공적인 `GET` 요청 후, 요청 본문에 필드 데이터를 포함한 `POST` 요청이 수행됩니다. `post_data` 가 제공되면, 그것이 이 목적으로 사용됩니다. 그렇지 않으면, 이 데이터는 `GET` 응답 HTML에서 추출됩니다. 이 assertion은 커스텀 필드, 패널 구성 및 커스텀 유효성 검사 로직에 대한 테스트 커버리지를 얻는 데 좋습니다. 예시: ```python def test_editability(self): self.assertPageIsEditable(self.page) def test_editability_on_post(self): self.assertPageIsEditable( self.page, post_data={ "title": "Fabulous events", "slug": "events", "show_featured": True, "show_expired": False, "action-publish": "", } ) ``` **assertPageIsPreviewable(_page, mode="", post_data=None, user=None, msg=None_)** 페이지 미리보기 뷰가 `page` 에 대해 치명적인 오류 없이 로드될 수 있는지 확인합니다. 다양한 미리보기 모드를 지원하는 페이지 유형의 경우, `mode` 를 사용하여 테스트할 미리보기 모드를 지정할 수 있습니다. `user` 가 제공되면, 테스트는 해당 사용자를 활성 사용자로 설정하여 수행됩니다. 그렇지 않으면, 슈퍼유저가 생성되어 테스트에 사용됩니다. 미리보기를 로드하기 위해, 테스트 클라이언트는 요청 본문에 모든 필수 필드 데이터를 포함하여 `POST` 요청을 수행해야 합니다. `post_data` 가 제공되면, 그것이 이 목적으로 사용됩니다. 그렇지 않으면, 이 메서드는 페이지 편집 뷰에서 이 데이터를 추출하려고 시도합니다. 이 assertion은 커스텀 미리보기 모드에 대한 테스트 커버리지를 얻거나, 커스텀 렌더링 로직이 Wagtail의 미리보기 모드와 호환되는지 확인하는 데 좋습니다. 예시: ```python def test_general_previewability(self): self.assertPageIsPreviewable(self.page) def test_archive_previewability(self): self.assertPageIsPreviewable(self.page, mode="year-archive") ``` **assertCanCreateAt(_parent_model, child_model, msg=None_)** 특정 자식 페이지 유형이 부모 페이지 유형 아래에 생성될 수 있는지 확인합니다. `parent_model` 과 `child_model` 은 테스트 중인 페이지 클래스여야 합니다. ```python def test_can_create_under_home_page(self): # HomePage 아래에 ContentPage를 생성할 수 있습니다 self.assertCanCreateAt(HomePage, ContentPage) ``` **assertCanNotCreateAt(_parent_model, child_model, msg=None_)** 특정 자식 페이지 유형이 부모 페이지 유형 아래에 생성될 수 없는지 확인합니다. `parent_model` 과 `child_model` 은 테스트 중인 페이지 클래스여야 합니다. ```python def test_cant_create_under_event_page(self): # EventPage 아래에 ContentPage를 생성할 수 없습니다 self.assertCanNotCreateAt(EventPage, ContentPage) ``` **assertCanCreate(_parent, child_model, data, msg=None_, publish=True)** 제공된 POST 데이터를 사용하여 주어진 페이지 유형의 자식이 부모 아래에 생성될 수 있는지 확인합니다. `parent` 는 페이지 인스턴스여야 하고, `child_model` 은 페이지 하위 클래스여야 합니다. `data` 는 Wagtail 관리자 페이지 생성 메서드에 POST할 딕셔너리여야 합니다. `publish` 는 생성되는 페이지가 게시되어야 하는지 여부를 지정합니다 - 기본값은 `True` 입니다. ```python from wagtail.test.utils.form_data import nested_form_data, streamfield def test_can_create_content_page(self): # HomePage 가져오기 root_page = HomePage.objects.get(pk=2) # 이 POST 데이터로 여기에 ContentPage를 만들 수 있는지 확인 self.assertCanCreate(root_page, ContentPage, nested_form_data({ 'title': 'About us', 'body': streamfield([ ('text', 'Lorem ipsum dolor sit amet'), ]) })) ``` POST 데이터 구성에 유용한 함수 세트는 [](form_data_test_helpers)를 참조하세요. **assertAllowedParentPageTypes(_child_model, parent_models, msg=None_)** `child_model` 이 생성될 수 있는 유일한 페이지 유형이 `parent_models` 인지 테스트합니다. 허용된 부모 모델 목록은 부모 모델이 `Page.subpage_types` 를 설정한 경우 `Page.parent_page_types` 에 설정된 것과 다를 수 있습니다. ```python def test_content_page_parent_pages(self): # ContentPage는 HomePage 또는 다른 ContentPage 아래에서만 생성될 수 있습니다 self.assertAllowedParentPageTypes( ContentPage, {HomePage, ContentPage}) # EventPage는 EventIndex 아래에서만 생성될 수 있습니다 self.assertAllowedParentPageTypes( EventPage, {EventIndex}) ``` **assertAllowedSubpageTypes(_parent_model, child_models, msg=None_)** `parent_model` 아래에 생성될 수 있는 유일한 페이지 유형이 `child_models` 인지 테스트합니다. 허용된 자식 모델 목록은 자식 모델이 `Page.parent_page_types` 를 설정한 경우 `Page.subpage_types` 에 설정된 것과 다를 수 있습니다. ```python def test_content_page_subpages(self): # ContentPage는 다른 ContentPage 자식만 가질 수 있습니다 self.assertAllowedSubpageTypes( ContentPage, {ContentPage}) # HomePage는 ContentPage와 EventIndex 자식을 가질 수 있습니다 self.assertAllowedSubpageTypes( HomePage, {ContentPage, EventIndex}) ``` (form_data_test_helpers)= ## 폼 데이터 헬퍼 ```{eval-rst} .. automodule:: wagtail.test.utils.form_data .. autofunction:: nested_form_data .. autofunction:: rich_text .. autofunction:: streamfield .. autofunction:: inline_formset ``` ## 테스트 내에서 Page 객체 생성하기 테스트 내에서 페이지 객체를 생성하려면, 실제로 테스트하려는 페이지를 생성하기 전에 몇 가지 단계를 거쳐야 합니다. - 페이지는 일반 Django 모델처럼 `MyPage.objects.create()` 로 직접 생성할 수 없으며, `parent.add_child(instance=child)` 를 사용하여 부모 페이지의 자식으로 추가해야 합니다. - 페이지 트리를 시작하려면 `Page.get_first_root_node()` 로 생성할 수 있는 루트 페이지가 필요합니다. - 또한 올바른 `hostname` 과 `root_page` 로 설정된 `Site` 가 필요합니다. ```python from wagtail.models import Page, Site from wagtail.rich_text import RichText from wagtail.test.utils import WagtailPageTestCase from home.models import HomePage, MyPage class MyPageTest(WagtailPageTestCase): @classmethod def setUpTestData(cls): root = Page.get_first_root_node() Site.objects.create( hostname="testserver", root_page=root, is_default_site=True, site_name="testserver", ) home = HomePage(title="Home") root.add_child(instance=home) cls.page = MyPage( title="My Page", slug="mypage", ) home.add_child(instance=cls.page) def test_get(self): response = self.client.get(self.page.url) self.assertEqual(response.status_code, 200) ``` ### 페이지 콘텐츠 작업하기 페이지의 콘텐츠를 테스트하고 싶을 것입니다. `StreamField` 를 포함하는 경우, 블록의 이름과 콘텐츠를 포함하는 튜플 목록으로 콘텐츠를 설정해야 합니다. `RichTextBlock` 의 경우, 콘텐츠는 `RichText` 의 인스턴스여야 합니다. ```python ... from wagtail.rich_text import RichText class MyPageTest(WagtailPageTestCase): @classmethod def setUpTestData(cls): ... # 여기서 페이지 인스턴스 생성 cls.page.body.extend( [ ("heading", "Just a CharField Heading"), ("paragraph", RichText("
First paragraph
")), ("paragraph", RichText("Second paragraph
")), ] ) cls.page.save() def test_page_content(self): response = self.client.get(self.page.url) self.assertEqual(response.status_code, 200) self.assertContains(response, "Just a CharField Heading") self.assertContains(response, "First paragraph
") self.assertContains(response, "Second paragraph
") ``` ## 픽스처 ### `dumpdata` 사용하기 테스트를 위한 [픽스처](inv:django#howto/initial-data) 생성은 개발 환경에서 콘텐츠를 생성한 다음 Django의 [`dumpdata`](inv:django#dumpdata) 명령을 사용하는 것이 가장 좋습니다. 기본적으로 `dumpdata` 는 `content_type` 을 기본 키로 표현한다는 점에 유의하세요. 이는 모델을 추가/제거할 때 일관성 문제를 일으킬 수 있습니다. 콘텐츠 유형은 픽스처와 별도로 채워지기 때문입니다. 이를 방지하려면 `--natural-foreign` 스위치를 사용하세요. 이는 콘텐츠 유형을 기본 키 대신 `["app", "model"]` 로 표현합니다. ### 수동 수정 덤프된 픽스처를 수동으로 수정하거나 모두 직접 작성할 수도 있습니다. 주의해야 할 몇 가지 사항이 있습니다. #### 커스텀 페이지 모델 픽스처에서 커스텀 페이지 모델을 생성할 때, `wagtailcore.page` 항목과 커스텀 페이지 모델 항목을 모두 추가해야 합니다. `website` 모듈에 `Homepage(Page)` 클래스를 정의했다고 가정해 봅시다. 다음과 같이 픽스처에서 이러한 홈페이지를 생성할 수 있습니다: ```json [ { "model": "wagtailcore.page", "pk": 3, "fields": { "title": "My Customer's Homepage", "content_type": ["website", "homepage"], "depth": 2 } }, { "model": "website.homepage", "pk": 3, "fields": {} } ] ``` #### Treebeard 필드 `path` / `numchild` / `depth` 필드를 채우는 것은 `get_parent()` 와 같은 트리 연산이 올바르게 작동하기 위해 필요합니다. `url_path` 는 채워지지 않으면 일부 드문 경우에 오류를 일으킬 수 있는 또 다른 필드입니다. [Treebeard 문서](inv:treebeard:std:doc#mp_tree)는 이것이 어떻게 작동하는지 이해하는 데 도움이 될 수 있습니다.