<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Leon's Tech blog]]></title><description><![CDATA[Learn by doing]]></description><link>https://leonkim.dev</link><generator>GatsbyJS</generator><lastBuildDate>Wed, 25 Mar 2020 04:22:28 GMT</lastBuildDate><item><title><![CDATA[개발자는 왜 유닛 테스트를 작성 해야 하는가?]]></title><description><![CDATA[무엇을 개발해야 하는지 이해하는데 도움이 된다 (Understand what to build) 개발자가 무엇을 개발해야 하는지 정할 때 여러사람들(도메인 전문가, QA…]]></description><link>https://leonkim.dev/tdd/why_write_unit_test/</link><guid isPermaLink="false">https://leonkim.dev/tdd/why_write_unit_test/</guid><pubDate>Wed, 25 Mar 2020 13:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;무엇을-개발해야-하는지-이해하는데-도움이-된다-understand-what-to-build&quot;&gt;&lt;a href=&quot;#%EB%AC%B4%EC%97%87%EC%9D%84-%EA%B0%9C%EB%B0%9C%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94%EC%A7%80-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94%EB%8D%B0-%EB%8F%84%EC%9B%80%EC%9D%B4-%EB%90%9C%EB%8B%A4-understand-what-to-build&quot; aria-label=&quot;무엇을 개발해야 하는지 이해하는데 도움이 된다 understand what to build permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;무엇을 개발해야 하는지 이해하는데 도움이 된다 (Understand what to build)&lt;/h2&gt;
&lt;p&gt;개발자가 무엇을 개발해야 하는지 정할 때 여러사람들(도메인 전문가, QA, 디자이너, 아키텍트, 사용자 등)과 협업을 한다.&lt;/p&gt;
&lt;p&gt;단위 테스트를 작성하면 비즈니스 규칙(도메인 지식)을 좀더 명확히 할 수 있다.&lt;/p&gt;
&lt;p&gt;코드로 명시적으로 표현하기 때문에 2가지 이점이 생긴다.&lt;/p&gt;
&lt;p&gt;첫번째는 관계자들과 명확한 의사소통을 할 수 있다.&lt;/p&gt;
&lt;p&gt;두번째는 명확히 하는 과정에서 고려하지 못한 사항도 알게 된다.&lt;/p&gt;
&lt;h2 id=&quot;문서화되는-단위-기능-document-the-units&quot;&gt;&lt;a href=&quot;#%EB%AC%B8%EC%84%9C%ED%99%94%EB%90%98%EB%8A%94-%EB%8B%A8%EC%9C%84-%EA%B8%B0%EB%8A%A5-document-the-units&quot; aria-label=&quot;문서화되는 단위 기능 document the units permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;문서화되는 단위 기능 (Document the units)&lt;/h2&gt;
&lt;p&gt;개발자 사이에 의사소통도 명확해진다. 단위 테스트 자체가 해당 단위의 API 사용법이 예제가 되기 때문이다.&lt;/p&gt;
&lt;p&gt;이로 인해 2가지 이점이 생긴다.&lt;/p&gt;
&lt;p&gt;첫째 해당 기능의 개발자의 개발 의도를 이해할 수 있게 된다.&lt;/p&gt;
&lt;p&gt;둘째 타 개발자들도 이 예제를 통해 단위 기능의 사용법을 알게 된다.&lt;/p&gt;
&lt;h2 id=&quot;단위-설계-design-the-units&quot;&gt;&lt;a href=&quot;#%EB%8B%A8%EC%9C%84-%EC%84%A4%EA%B3%84-design-the-units&quot; aria-label=&quot;단위 설계 design the units permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;단위 설계 (Design the units)&lt;/h2&gt;
&lt;p&gt;유닛 테스트는 테스트 가능한 단위로 분리하게 된다.&lt;/p&gt;
&lt;p&gt;결과적으로 분리된 단위 만큼 낮은 결합도와 높은 응집력(Loose coupling &amp;#x26; High Cohesion) 을 가진 설계구조로 가게 된다.&lt;/p&gt;
&lt;p&gt;또한 유닛 테스트는 개발자가 인터페이스(함수 이름, 함수 시그니쳐)와 구현을 분리하여 설계를 고민하게 한다.&lt;/p&gt;
&lt;p&gt;유닛 테스트를 작성하면서 자연스럽게 해당 단위 기능의 인터페이스를 호출하는 입장에서 작성하게 되기 때문이다.&lt;/p&gt;
&lt;h2 id=&quot;회귀-보호regression-protection&quot;&gt;&lt;a href=&quot;#%ED%9A%8C%EA%B7%80-%EB%B3%B4%ED%98%B8regression-protection&quot; aria-label=&quot;회귀 보호regression protection permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;회귀 보호(Regression protection)&lt;/h2&gt;
&lt;p&gt;회귀 테스트(Regression test)란 회귀 버그를 찾는 소프트웨어 테스트 방식이다. 그리고 회귀 버그는 이전에 제대로 작동하던 소프트웨어 기능에 문제가 생기는 것을 가리킨다.&lt;/p&gt;
&lt;p&gt;즉 개발하면서 추가/변경된 사항이 영향을 미쳐서 원래 기능에 없던 버그가 생기는 것을 말한다.&lt;/p&gt;
&lt;p&gt;기존 단위 테스트 들을 추가/변경 개발후에 테스트 함으로 회귀 버그가 생기는지 인지할 수 있다.&lt;/p&gt;
&lt;p&gt;즉 변경의 영향을 단위 테스트로 알아낼 수 있고 변경후 다른 기능들이 문제가 없음을 보증할수 있는 수단이 된다.&lt;/p&gt;
&lt;p&gt;회귀 보호는 기존 시스템이 복잡할 수록 더 필요하며, 기존 시스템의 품질을 유지하는데 유용하다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[10만명의 유저가 될때까지 백엔드 인프라 확장하기]]></title><description><![CDATA[이 글은 원문 (https://alexpareto.com/scalability/systems/2020/02/03/scaling-100k.html) 원 저자인 Alex Pareto…]]></description><link>https://leonkim.dev/systems/scaling-100k/</link><guid isPermaLink="false">https://leonkim.dev/systems/scaling-100k/</guid><pubDate>Mon, 10 Feb 2020 12:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;이 글은 원문 &lt;code class=&quot;language-text&quot;&gt;Scaling to 100k Users&lt;/code&gt;(&lt;a href=&quot;https://alexpareto.com/scalability/systems/2020/02/03/scaling-100k.html&quot;&gt;https://alexpareto.com/scalability/systems/2020/02/03/scaling-100k.html&lt;/a&gt;) 원 저자인 Alex Pareto 의 동의 하에 번역하였음을 알려드립니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;많은 스타트업이 있습니다. 수많은 신규 사용자가 매일 계정에 쉴세없이 등록하는 것처럼 느껴지고 엔지니어링 팀은 인프라가 지속적으로 유지하기 위해 분투합니다.&lt;/p&gt;
&lt;p&gt;행복한 고민이지만 0 에서 수십만 명의 사용자를 받는 웹앱을 구현하는 정보가 부족할 수 있습니다.&lt;/p&gt;
&lt;p&gt;일반적으로 솔루션은 갑자기 발생한 집중된 트래픽이나 병목현상을 찾아내는 것에서(종종 두가지 모두) 얻게 됩니다.&lt;/p&gt;
&lt;p&gt;그렇긴 하지만 저는 확장성이 높은 사이드 프로젝트를 수행하면서 주요 패턴 중 상당수가 비교적 정형회되어 있다는 것을 알게 되었습니다.&lt;/p&gt;
&lt;p&gt;이 글은 그 공식의 기본을 글로 만들기 위한 시도입니다. 이제 우리는 새로운 사진 공유 웹 사이트인 Graminsta를 1명에서 10만명의 사용자로 만들 것입니다.&lt;/p&gt;
&lt;h2 id=&quot;1-사용자-1개-머신&quot;&gt;&lt;a href=&quot;#1-%EC%82%AC%EC%9A%A9%EC%9E%90-1%EA%B0%9C-%EB%A8%B8%EC%8B%A0&quot; aria-label=&quot;1 사용자 1개 머신 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;1 사용자: 1개 머신&lt;/h2&gt;
&lt;p&gt;거의 모든 에플리케이션(즉 웹 사이트 또는 모바일 응용 프로그램)은 API, 데이터베이스, 클라이언트(일반적으로 응용 프로그램 또는 웹 사이트)의 세 가지 주요 구성 요소가 있습니다.&lt;/p&gt;
&lt;p&gt;최신 에플리케이션 개발에서 클라이언트를 API 와 완전히 별도의 개체로 생각하면 응용 프로그램 확장에 대한 추론이 훨씬 쉬워진다는 것&lt;sup id=&quot;fnref-2&quot;&gt;&lt;a href=&quot;#fn-2&quot; class=&quot;footnote-ref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;을 알았습니다.&lt;/p&gt;
&lt;p&gt;처음으로 에플리케이션을 빌드 할 때 이 3가지가 모두 하나의 서버에서 실행되는 것이 좋습니다. 어떤면에서 이것은 개발 환경과 비슷합니다. 한 엔지니어가 데이터베이스, API 및 클라이언트를 모두 같은 컴퓨터에서 실행합니다.&lt;/p&gt;
&lt;p&gt;이론적으로는 아래와 같이 단일 DigitalOcean Droplet 또는 AWS EC2 인스턴스의 클라우드에 이를 배포 할 수 있습니다:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexpareto.com/assets/Scaling%20to%20100k%20Users/Screen_Shot_2020-01-21_at_8.11.27_AM.png&quot; alt=&quot;1명의 사용자&quot;&gt;&lt;/p&gt;
&lt;p&gt;따라서 Graminsta를 한 사람 이상이 사용할 것으로 예상되는 경우 거의 항상 데이터베이스 계층을 분리하는 것이 좋습니다.&lt;/p&gt;
&lt;h2 id=&quot;10-명의-사용자--데이터베이스-계층-분리&quot;&gt;&lt;a href=&quot;#10-%EB%AA%85%EC%9D%98-%EC%82%AC%EC%9A%A9%EC%9E%90--%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EA%B3%84%EC%B8%B5-%EB%B6%84%EB%A6%AC&quot; aria-label=&quot;10 명의 사용자  데이터베이스 계층 분리 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;10 명의 사용자 : 데이터베이스 계층 분리&lt;/h2&gt;
&lt;p&gt;데이터베이스를 Amazon RDS 또는 Digital Ocean의 Managed Database와 같은 관리형 서비스로 분리하면 오랫동안 우리에게 도움이 될 것입니다. &lt;/p&gt;
&lt;p&gt;단일머신 또는 EC2 인스턴스에서 자체 호스팅보다 약간 비싸지만 이러한 서비스를 사용하면 즉시 사용 가능한 많은 추가 기능을 즉시 사용할 수 있습니다. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;멀티 리전 중복성, 읽기 전용 복제본, 자동 백업 등&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Graminsta 시스템의 모습은 다음과 같습니다:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexpareto.com/assets/Scaling%20to%20100k%20Users/Screen_Shot_2020-01-21_at_8.13.17_AM.png&quot; alt=&quot;10명의 사용자&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;100-명의-사용자--클라이언트-분리&quot;&gt;&lt;a href=&quot;#100-%EB%AA%85%EC%9D%98-%EC%82%AC%EC%9A%A9%EC%9E%90--%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EB%B6%84%EB%A6%AC&quot; aria-label=&quot;100 명의 사용자  클라이언트 분리 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;100 명의 사용자 : 클라이언트 분리&lt;/h2&gt;
&lt;p&gt;운 좋게도 처음 몇 명의 사용자는 Graminsta를 좋아합니다.
이제 트래픽이 더욱 안정적으로 시작되었으므로 이제 고객을 분리해야합니다.
한 가지 주목할 점은 엔티티를 분리하는 것이 확장 가능한 응용 프로그램을 빌드하는 데 있어 핵심적인 요소라는 것입니다.
시스템의 한 부분이 더 많은 트래픽을 얻으면 자체 트래픽 패턴을 기반으로 서비스 확장을 처리 할 수 있도록 분할 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;이것이 제가 클라이언트를 API와 분리 된 것으로 생각하는 이유입니다.
웹, 모바일 웹, iOS, Android, 데스크톱 앱, 타사 서비스 등 여러 플랫폼을 구축하는 데 있어 추론하기가 매우 쉽습니다&lt;sup id=&quot;fnref-2&quot;&gt;&lt;a href=&quot;#fn-2&quot; class=&quot;footnote-ref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.
모두 동일한 API를 사용하는 클라이언트 일 뿐입니다.&lt;/p&gt;
&lt;p&gt;같은 맥락에서 사용자로부터 얻는 가장 큰 피드백은 Graminsta를 휴대 전화에서 원한다는 것입니다.
따라서 모바일 앱을 사용하는 동안 모바일 앱을 시작하겠습니다.&lt;/p&gt;
&lt;p&gt;시스템의 모습은 다음과 같습니다:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexpareto.com/assets/Scaling%20to%20100k%20Users/Screen_Shot_2020-02-03_at_9.56.51_PM.png&quot; alt=&quot;100명의 사용자&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;1000-명의-사용자--로드-밸런서-추가&quot;&gt;&lt;a href=&quot;#1000-%EB%AA%85%EC%9D%98-%EC%82%AC%EC%9A%A9%EC%9E%90--%EB%A1%9C%EB%93%9C-%EB%B0%B8%EB%9F%B0%EC%84%9C-%EC%B6%94%EA%B0%80&quot; aria-label=&quot;1000 명의 사용자  로드 밸런서 추가 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;1,000 명의 사용자 : 로드 밸런서 추가&lt;/h2&gt;
&lt;p&gt;Graminsta의 상황이 호전되고 있습니다.
사용자가 사진을 어디서든지 업로드하고 있습니다.
더 많은 가입을 시작하고 있습니다.
외로운 API 인스턴스가 모든 트래픽을 유지하는 데 문제가 있습니다.
더 많은 컴퓨팅 파워가 필요합니다!&lt;/p&gt;
&lt;p&gt;로드 밸런서는 매우 강력합니다.
핵심 아이디어는 API 앞에로드 밸런서를 배치하고 트래픽을 해당 서비스의 인스턴스로 라우팅한다는 것입니다.
이를 통해 수평적 확장이 가능합니다 (동일한 코드를 실행하는 서버를 더 추가하여 처리 할 수있는 요청량 증가).&lt;/p&gt;
&lt;p&gt;우리는 웹 클라이언트와 API 앞에 별도의 로드 밸런서를 배치 할 것입니다.
즉, API 및 웹 클라이언트 코드를 실행하는 인스턴스가 여러 개 있을 수 있습니다.
로드 밸런서는 트래픽이 가장 적은 인스턴스로 요청을 라우팅합니다.&lt;/p&gt;
&lt;p&gt;이것으로부터 우리가 얻는 것은 이중화입니다.
한 인스턴스가 다운되면 (오버로드 또는 충돌이 발생하더라도) 전체 시스템이 중단되는 대신 들어오는 요청에 응답 할 수있는 다른 인스턴스가 있습니다.&lt;/p&gt;
&lt;p&gt;로드 밸런서는 또한 자동 확장을 가능하게 합니다.
모든 사용자가 온라인 상태인 슈퍼볼 기간 동안 인스턴스 수를 늘리고 모든 사용자가 잠들 때 인스턴스 수를 줄이도록 로드 밸런서를 설정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;로드 밸런서를 사용하면 API 계층이 실질적으로 무한대로 확장 될 수 있으므로 더 많은 요청을 받을 때 인스턴스를 계속 추가 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexpareto.com/assets/Scaling%20to%20100k%20Users/Screen_Shot_2020-01-21_at_8.25.50_AM.png&quot; alt=&quot;1000명의 사용자&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;참고 사항 : 지금까지 우리가 보유한 것은 Heroku 또는 AWS의 Elastic Beanstalk과 같은 PaaS 회사가 기본적으로 제공하는 것과 매우 유사합니다(인기가 높은 이유).
Heroku는 데이터베이스를 별도의 호스트에 배치하고 자동 확장으로 로드 밸런서를 관리하며 API와 별도로 웹 클라이언트를 호스팅 할 수 있습니다.
이것은 프로젝트 또는 초기 단계의 스타트업에 Heroku와 같은 서비스를 사용하는 좋은 이유입니다. 필요한 모든 기본 사항이 기본적으로 제공됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;10000-사용자--cdn&quot;&gt;&lt;a href=&quot;#10000-%EC%82%AC%EC%9A%A9%EC%9E%90--cdn&quot; aria-label=&quot;10000 사용자  cdn permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;10,000 사용자 : CDN&lt;/h2&gt;
&lt;p&gt;처음부터 이렇게 했어야 했는데, 그러나 Graminsta에서 빠르게 이동하고 있습니다. 이 모든 이미지를 제공하고 업로드하면 서버에 너무 많은 부하가 걸리기 시작합니다.&lt;/p&gt;
&lt;p&gt;이 시점에서 이미지, 비디오 등을 생각하는(AWS의 S3 또는 Digital Ocean’s Spaces) 정적 콘텐츠를 호스팅하기 위해 클라우드 스토리지 서비스를 사용해야 합니다.
일반적으로 API는 이미지 및 이미지 업로드와 같은 작업을 처리하지 않아야 합니다.&lt;/p&gt;
&lt;p&gt;우리가 클라우드 스토리지 서비스에서 얻는 또 다른 것은 CDN입니다(AWS에서는 이것이 Cloudfront라는 추가 기능이지만 많은 클라우드 스토리지 서비스가 즉시 제공합니다).
CDN은 전 세계의 다른 데이터 센터에 이미지를 자동으로 캐시합니다.&lt;/p&gt;
&lt;p&gt;주요 데이터 센터가 오하이오에서 호스팅 될 수 있지만 누군가 일본에서 이미지를 요청하면 클라우드 제공 업체가 사본을 만들어 일본의 데이터 센터에 저장합니다.
다음으로 일본에서 이미지를 요청한 사람은 이미지를 훨씬 더 빨리받습니다.
이는 전 세계에 적제 및 전송하는 데 오랜 시간이 걸리는 이미지 또는 비디오와 같은 더 큰 파일 크기를 제공해야 할 때 중요합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexpareto.com/assets/Scaling%20to%20100k%20Users/Screen_Shot_2020-01-21_at_8.30.06_AM.png&quot; alt=&quot;10,000명의 사용자&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;100000-명의-사용자--데이터-계층-확장&quot;&gt;&lt;a href=&quot;#100000-%EB%AA%85%EC%9D%98-%EC%82%AC%EC%9A%A9%EC%9E%90--%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B3%84%EC%B8%B5-%ED%99%95%EC%9E%A5&quot; aria-label=&quot;100000 명의 사용자  데이터 계층 확장 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;100,000 명의 사용자 : 데이터 계층 확장&lt;/h2&gt;
&lt;p&gt;CDN은 우리에게 많은 도움을 주었습니다.
Graminsta에서 많은 일들이 일어나고 있습니다.
YouTube 유명인사인 &lt;code class=&quot;language-text&quot;&gt;Mavid Mobrick&lt;/code&gt;이 방금 가입하여 이야기를 게시했습니다.
환경에 10개의 API 인스턴스를 추가하는 로드 밸런서 덕분에 API CPU 및 메모리 사용량이 전반적으로 낮습니다.
그러나 요청에 대해 많은 시간 초과가 발생하기 시작했습니다. 왜 모든 요청이 오래 걸리는걸까요?&lt;/p&gt;
&lt;p&gt;좀 더 자세히 조사해보면 데이터베이스 CPU가 80~90% 로 차 있습니다. 우리는 최대 한도를 초과했습니다.&lt;/p&gt;
&lt;p&gt;데이터 계층의 스케일링은 아마도 방정식의 가장 까다로운 부분 일 것입니다.
상태 비 저장 요청을 처리하는 API 서버의 경우 인스턴스를 더 추가 할 수 있지만 대부분의 데이터베이스 시스템에서는 동일하지 않습니다.
이 경우 널리 사용되는 관계형 데이터베이스 시스템 (PostgreSQL, MySQL 등)을 살펴 보겠습니다.&lt;/p&gt;
&lt;h3 id=&quot;캐싱&quot;&gt;&lt;a href=&quot;#%EC%BA%90%EC%8B%B1&quot; aria-label=&quot;캐싱 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;캐싱&lt;/h3&gt;
&lt;p&gt;데이터베이스를 최대한 활용하는 가장 쉬운 방법 중 하나는 시스템에 새로운 구성 요소 인 캐시 계층을 도입하는 것입니다.
캐시를 구현하는 가장 일반적인 방법은 Redis 또는 Memcached와 같은 메모리 내 키 값 저장소를 사용하는 것입니다.
대부분의 클라우드에는 AWS의 Elasticache 및 Google Cloud의 Memorystore와 같은 관리 서비스 버전이 있습니다.&lt;/p&gt;
&lt;p&gt;서비스가 동일한 정보에 대해 데이터베이스를 반복적으로 많이 호출 할 때 캐시가 유용합니다.
기본적으로 데이터베이스를 한 번 누르고 캐시에 정보를 저장하며 데이터베이스를 다시 만질 필요가 없습니다.&lt;/p&gt;
&lt;p&gt;예를 들어 Graminsta에서 누군가가 Mavid Mobrick의 프로필 페이지를 방문 할 때마다 API 계층은 데이터베이스에서 Mavid Mobrick의 프로필 정보를 요청합니다.
이것은 계속해서 또 다시 일어나고 있습니다. Mavid Mobrick의 프로필 정보는 모든 요청에 ​​따라 변경되지 않으므로 해당 정보는 캐시에 적합합니다.&lt;/p&gt;
&lt;p&gt;Redis의 데이터베이스 결과를 키 &lt;code class=&quot;language-text&quot;&gt;user : id&lt;/code&gt; 아래에 만료 시간 30 초로 캐시합니다.
이제 누군가 Mavid Mobrick의 프로필을 방문하면 Redis를 먼저 확인하고 Redis에서 데이터를 제공합니다.
Mavid Mobrick이 사이트에서 가장 인기가 있지만 프로파일을 요청하면 데이터베이스에 거의 부하가 걸리지 않습니다.&lt;/p&gt;
&lt;p&gt;대부분의 캐시 서비스의 또 다른 장점은 데이터베이스보다 쉽게 ​​확장 할 수 있다는 것입니다.
Redis에는 로드 밸런서&lt;sup id=&quot;fnref-1&quot;&gt;&lt;a href=&quot;#fn-1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;와 유사한 방식으로 Redis 클러스터 모드가 내장되어있어 Redis 캐시를 여러 머신 (수천 대의 경우 수천 대)에 분산시킬 수 있습니다.&lt;/p&gt;
&lt;p&gt;거의 모든 고도로 확장 된 응용 프로그램은 캐싱을 충분히 활용하므로 빠른 API를 만드는 데 절대적으로 필요한 부분입니다.
더 나은 쿼리와 성능이 좋은 코드는 모두 방정식의 일부이지만 캐시가 없으면 수백만 명의 사용자로 확장하기에 충분하지 않습니다.&lt;/p&gt;
&lt;h3 id=&quot;읽기-전용-복제본&quot;&gt;&lt;a href=&quot;#%EC%9D%BD%EA%B8%B0-%EC%A0%84%EC%9A%A9-%EB%B3%B5%EC%A0%9C%EB%B3%B8&quot; aria-label=&quot;읽기 전용 복제본 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;읽기 전용 복제본&lt;/h3&gt;
&lt;p&gt;우리 데이터베이스가 꽤 많은 타격을 받기 시작한 지금 우리가 할 수있는 또 다른 일은 데이터베이스 관리 시스템을 사용하여 읽기 복제본을 추가하는 것입니다.
위의 관리 서비스를 사용하면 한 번의 클릭으로 수행 할 수 있습니다. 읽기 전용 복제본은 마스터 DB를 최신 상태로 유지하며 SELECT 문에 사용될 수 있습니다.&lt;/p&gt;
&lt;p&gt;시스템은 다음과 같습니다:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexpareto.com/assets/Scaling%20to%20100k%20Users/Screen_Shot_2020-01-21_at_8.35.01_AM.png&quot; alt=&quot;100,000명의 사용자&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;그-이후에&quot;&gt;&lt;a href=&quot;#%EA%B7%B8-%EC%9D%B4%ED%9B%84%EC%97%90&quot; aria-label=&quot;그 이후에 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;그 이후에&lt;/h2&gt;
&lt;p&gt;앱이 계속 확장됨에 따라 독립적으로 확장 할 수있는 서비스를 분할하는 데 집중하려고 합니다.
예를 들어, 웹 소켓을 사용하기 시작하면 웹 소켓 처리 코드를 꺼내는 것이 좋습니다.
우리는 HTTP 로드 수에 관계없이 개방 또는 폐쇄 된 웹 소켓 연결 수에 따라 확장 및 축소 할 수 있는 자체 로드밸런서 뒤에 새로운 안정성을 제공 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;또한 데이터 계층의 한계에 부딪 치려고 계속 노력할 것입니다.
데이터베이스를 분할하고 샤딩하기 시작하려고 할 때입니다.
이 두 가지 모두 더 많은 오버 헤드가 필요하지만 효과적으로 데이터 계층을 무한대로 확장 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;New Relic 또는 Datadog 와 같은 서비스를 사용하여 모니터링을 설치했는지 확인하려고 합니다.
이를 통해 요청이 느리고 개선이 필요한 부분을 이해할 수 있습니다.
규모를 확장 할 때 종종 이전 섹션의 일부 아이디어를 활용하여 병목 현상을 찾아 수정하는 데 집중하려고 합니다.&lt;/p&gt;
&lt;p&gt;이 시점에서 우리는 팀에 도움을 줄 다른 사람들도 있습니다!&lt;/p&gt;
&lt;h4 id=&quot;참고-문헌-&quot;&gt;&lt;a href=&quot;#%EC%B0%B8%EA%B3%A0-%EB%AC%B8%ED%97%8C-&quot; aria-label=&quot;참고 문헌  permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;참고 문헌 :&lt;/h4&gt;
&lt;p&gt;이 게시물은 High Scalability에 대해 &lt;a href=&quot;http://highscalability.com/blog/2016/1/11/a-beginners-guide-to-scaling-to-11-million-users-on-amazons.html&quot;&gt;제가 가장 좋아하는 게시물 중 하나&lt;/a&gt;에서 영감을 받았습니다.
나는 위의 게시물에 초기 단계의 내용을 조금 더 풍부하게 하고 클라우드에 구애받지 않기를 원했습니다.
이런 것들에 관심이 있다면 꼭 확인하십시오.&lt;/p&gt;
&lt;h4 id=&quot;각주&quot;&gt;&lt;a href=&quot;#%EA%B0%81%EC%A3%BC&quot; aria-label=&quot;각주 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;각주&lt;/h4&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;여러 인스턴스에 로드를 분산시킬 수 있다는 점에서 비슷하지만 Redis Cluster의 기본 구현은 로드 밸런서와 크게 다릅니다.&lt;/p&gt;
&lt;a href=&quot;#fnref-1&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-2&quot;&gt;
&lt;p&gt;&lt;img src=&quot;https://www.freecodecamp.org/news/what-does-it-mean-when-code-is-easy-to-reason-about-4e6f63eb386f/&quot; alt=&quot;easy to reason about&quot;&gt; 프로그래밍의 관용적 표현&lt;/p&gt;
&lt;a href=&quot;#fnref-2&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Linux 에서 사용자의 그룹확인 및 특정 그룹에 추가]]></title><description><![CDATA[현재 로그인한 사용자가 속한 그룹 확인 특정 사용자 그룹 확인(예 : tomcat) leonkim 사용자를 wheel 그룹에 추가할 때 대부분 sudo권한이 있는 user가 입력 가능함. leonkim 사용자를 wheel 그룹에 뺄때]]></description><link>https://leonkim.dev/linux/user-group/</link><guid isPermaLink="false">https://leonkim.dev/linux/user-group/</guid><pubDate>Wed, 15 Jan 2020 12:00:00 GMT</pubDate><content:encoded>&lt;h3 id=&quot;현재-로그인한-사용자가-속한-그룹-확인&quot;&gt;&lt;a href=&quot;#%ED%98%84%EC%9E%AC-%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%95%9C-%EC%82%AC%EC%9A%A9%EC%9E%90%EA%B0%80-%EC%86%8D%ED%95%9C-%EA%B7%B8%EB%A3%B9-%ED%99%95%EC%9D%B8&quot; aria-label=&quot;현재 로그인한 사용자가 속한 그룹 확인 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;현재 로그인한 사용자가 속한 그룹 확인&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shellscript&quot;&gt;&lt;pre class=&quot;language-shellscript&quot;&gt;&lt;code class=&quot;language-shellscript&quot;&gt;$ groups
ubuntu adm dialout cdrom floppy sudo audio dip video plugdev lxd netdev&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;특정-사용자-그룹-확인예--tomcat&quot;&gt;&lt;a href=&quot;#%ED%8A%B9%EC%A0%95-%EC%82%AC%EC%9A%A9%EC%9E%90-%EA%B7%B8%EB%A3%B9-%ED%99%95%EC%9D%B8%EC%98%88--tomcat&quot; aria-label=&quot;특정 사용자 그룹 확인예  tomcat permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;특정 사용자 그룹 확인(예 : tomcat)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shellscript&quot;&gt;&lt;pre class=&quot;language-shellscript&quot;&gt;&lt;code class=&quot;language-shellscript&quot;&gt;$ groups tomcat
tomcat : tomcat sudo&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;leonkim-사용자를-wheel-그룹에-추가할-때&quot;&gt;&lt;a href=&quot;#leonkim-%EC%82%AC%EC%9A%A9%EC%9E%90%EB%A5%BC-wheel-%EA%B7%B8%EB%A3%B9%EC%97%90-%EC%B6%94%EA%B0%80%ED%95%A0-%EB%95%8C&quot; aria-label=&quot;leonkim 사용자를 wheel 그룹에 추가할 때 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;leonkim 사용자를 wheel 그룹에 추가할 때&lt;/h3&gt;
&lt;p&gt;대부분 sudo권한이 있는 user가 입력 가능함.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shellscript&quot;&gt;&lt;pre class=&quot;language-shellscript&quot;&gt;&lt;code class=&quot;language-shellscript&quot;&gt;$ sudo gpasswd -a leonkim wheel
Adding user leonkim to group wheel&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;leonkim-사용자를-wheel-그룹에-뺄때&quot;&gt;&lt;a href=&quot;#leonkim-%EC%82%AC%EC%9A%A9%EC%9E%90%EB%A5%BC-wheel-%EA%B7%B8%EB%A3%B9%EC%97%90-%EB%BA%84%EB%95%8C&quot; aria-label=&quot;leonkim 사용자를 wheel 그룹에 뺄때 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;leonkim 사용자를 wheel 그룹에 뺄때&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shellscript&quot;&gt;&lt;pre class=&quot;language-shellscript&quot;&gt;&lt;code class=&quot;language-shellscript&quot;&gt;$ sudo gpasswd -d leonkim wheel
Removing user leonkim from group wheel&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[AWS EC2 디스크 용량 늘리기]]></title><description><![CDATA[가끔 운영중인 EC2의 디스크가 부족해 디스크 용량을 늘려야 할 경우가 생긴다. 디스크 용량을 늘리는 방법은 생각보다 손쉬웠는데 EC2 를 stop 시키지 않아도 되었다. 그 방법을 정리해 보았다. AWS 콘솔 설정 AWS console…]]></description><link>https://leonkim.dev/aws/ce2-increase-disk-space/</link><guid isPermaLink="false">https://leonkim.dev/aws/ce2-increase-disk-space/</guid><pubDate>Mon, 13 Jan 2020 15:30:15 GMT</pubDate><content:encoded>&lt;p&gt;가끔 운영중인 EC2의 디스크가 부족해 디스크 용량을 늘려야 할 경우가 생긴다.&lt;/p&gt;
&lt;p&gt;디스크 용량을 늘리는 방법은 생각보다 손쉬웠는데 EC2 를 stop 시키지 않아도 되었다.&lt;/p&gt;
&lt;p&gt;그 방법을 정리해 보았다.&lt;/p&gt;
&lt;h2 id=&quot;aws-콘솔-설정&quot;&gt;&lt;a href=&quot;#aws-%EC%BD%98%EC%86%94-%EC%84%A4%EC%A0%95&quot; aria-label=&quot;aws 콘솔 설정 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;AWS 콘솔 설정&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;AWS console에 접속 &gt; EC2 서비스 이동&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 590px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 25.09025270758123%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAAA10lEQVQY03VQi26DMBDj/z+x0qaKFQh5E0LJ2z3YVglNs+ScLmf5nHRqeaKUjJQrhHa4fdxxfzAwaanO6KcRA1cYpcYkFggjqVpIu6HUhlYrYm7nHVMO3eo9OBd4jCNuXwKfA8cwKzI0kNpAGw1jLXHBxBi44HDrij0EMqwUpiCXSoEyYkroQKg0OETSF/jYkCoQMhALLnDOwW87/kNrDd1xBNqmtUKK4WQIO2opb9HBA2yeMfU90uYvs5M/wd6GQgjavp3D4xnfwqvhc99h6WuiMX8Mf/sXL5qD+JEe7zIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;전체 TDD 프로세스&quot;
        title=&quot;전체 TDD 프로세스&quot;
        src=&quot;/static/04490123e03c1d3f03f94d79098370ab/b9e4f/ce2-increase-disk-space-01.png&quot;
        srcset=&quot;/static/04490123e03c1d3f03f94d79098370ab/cf440/ce2-increase-disk-space-01.png 148w,
/static/04490123e03c1d3f03f94d79098370ab/d2d38/ce2-increase-disk-space-01.png 295w,
/static/04490123e03c1d3f03f94d79098370ab/b9e4f/ce2-increase-disk-space-01.png 590w,
/static/04490123e03c1d3f03f94d79098370ab/f9b6a/ce2-increase-disk-space-01.png 885w,
/static/04490123e03c1d3f03f94d79098370ab/2d849/ce2-increase-disk-space-01.png 1180w,
/static/04490123e03c1d3f03f94d79098370ab/acc00/ce2-increase-disk-space-01.png 1662w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;EC2 인스턴스를 선택후 &lt;code class=&quot;language-text&quot;&gt;detail&lt;/code&gt; 탭의 &lt;code class=&quot;language-text&quot;&gt;Root device&lt;/code&gt; 를 선택. EBS_ID 링크로 EBS 콘솔 화면으로 이동&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 590px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 36.01236476043277%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAABpklEQVQoz1WR3WoaURSFfagmFmKaODP+TGIb7Ywz40xGTQht04baUij9uWmh9EF6m5tCY0R74Vso4oWiERNjRPBC0OLXc6ZU2gWLdeDss/ba+4RWqxWSEovFgsFgwPVwyPXNLd/OLzj/XuZnrUa1WqUmtFKpcHF5STnQSqCSP8pl6vU6If7BcrlkOp0yn89ptVoYjzPYtoVhGJimSTZrEolE2Apvsr1xj+3NDSJ/NRwmnU4Tko8nkwnj8TjQ0WjEbDaj3W4Tj8fRYjESiQQxoRlhXHRs3qY0vuQdPrkGX4s5PntZznbvUxDNQ3JMaTAUY3Y6HXq9XnBuNpuBiaIo6LqO7/scHR9T8A85NdOcCbrqDvm4QjGpYe1skbPtP4bju7t1ym63S//qikajsTaUCW1RbFkWjw4OsL1DXpRKgWbMLA8UNaBtO//vUOKX2KNEv99H01RhGMV1c3ieJ+jiOA6mGL2Q90nt75GU65B10ai4Ewmflt7z5OW7NZ+9+sDp64+cPH+DqmrCNCY+Q3yMSOI4uUBTqYck9T12o4poqAZ1imBS3+c33OBuPijvZX4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;전체 TDD 프로세스&quot;
        title=&quot;전체 TDD 프로세스&quot;
        src=&quot;/static/96a21cb852e3ec568974691ef2cbb659/b9e4f/ce2-increase-disk-space-02.png&quot;
        srcset=&quot;/static/96a21cb852e3ec568974691ef2cbb659/cf440/ce2-increase-disk-space-02.png 148w,
/static/96a21cb852e3ec568974691ef2cbb659/d2d38/ce2-increase-disk-space-02.png 295w,
/static/96a21cb852e3ec568974691ef2cbb659/b9e4f/ce2-increase-disk-space-02.png 590w,
/static/96a21cb852e3ec568974691ef2cbb659/374ce/ce2-increase-disk-space-02.png 647w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;해당되는 EBS 선택된 상태로 ‘Modify Volume’ 을 선택&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 590px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 15.038945476333133%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsSAAALEgHS3X78AAAAs0lEQVQI1z2KvQ6CQBCEeS4a7XwL8blsjDb+FTZKYngAe+yMQeAOvCPGwmD07jjG9TBu9svM7I63OJRYbyJE0zFWyyU457C2gVJvaK0IDWM0tFIOQ1n9vOuQPusaVSUhhYD3er0xn00w6Pfg+z624R63+wOskLjkBVJWkhdIUk6Zg12Fyxm/4pSWuDCJc0a/jOGc5PBAs92FGAYjIkAcH78nmMaise0f+6XtPEnnnXbQus4HkMfY/y9gDzYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;전체 TDD 프로세스&quot;
        title=&quot;전체 TDD 프로세스&quot;
        src=&quot;/static/d4f243d5ee8e13d94953387a68d569ad/b9e4f/ce2-increase-disk-space-03.png&quot;
        srcset=&quot;/static/d4f243d5ee8e13d94953387a68d569ad/cf440/ce2-increase-disk-space-03.png 148w,
/static/d4f243d5ee8e13d94953387a68d569ad/d2d38/ce2-increase-disk-space-03.png 295w,
/static/d4f243d5ee8e13d94953387a68d569ad/b9e4f/ce2-increase-disk-space-03.png 590w,
/static/d4f243d5ee8e13d94953387a68d569ad/f9b6a/ce2-increase-disk-space-03.png 885w,
/static/d4f243d5ee8e13d94953387a68d569ad/2d849/ce2-increase-disk-space-03.png 1180w,
/static/d4f243d5ee8e13d94953387a68d569ad/dc55e/ce2-increase-disk-space-03.png 1669w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;원하는 디스크 사이즈 입력하고 &lt;code class=&quot;language-text&quot;&gt;Modify&lt;/code&gt; 버튼 클릭&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 590px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 59.29078014184397%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsSAAALEgHS3X78AAABlElEQVQoz5VTa2uDQBD0//+lfgyBtpSWJgRiSo2Q+jw9jafx7XT3qsZAKO3CcI/dnZ3buzM+LQt708Th8KFhWUeEQkBEEYQYEf2OiBAEITzfh2ER4Xa7xX6/x26302NMAUmSzJBSQsbxzyjjW4z7gkT4TFiWJdj6vkfXdXqs6xpVVc2Y9ruu1+M9DMOgMRNmWUayA2Tn86yKlTIGSvirGayAK3AfWH5BBVKlkFCBJFOQVIDXXJhjlsaKlvMbhYqSUkrORQjlfiEPfCjfhfIcqDBA3TT6FFNrGBNJQ76JdCZkuzQtzs8rNOsH4P0Jw+saeFmhentElhdI0wTnsSUxnYaNiflC7hIWRaGVZpRQqIyj6bY6rSBJU+3n3jJhSus8z7Xitm3vE/L8crmgpL7W4zHYOHGz2cC2bXieB8dxcDqd4LguvYgGYRheCZmAJ1NPlg3ueW98RqyK1c++nmMZ1xfAao1moeS/VtJJ8xrUwxBiVGm4ng9J1aOY3p28D/ZNiGVC3y3WX852BI5OBNM80Pc9aN83eX2cJ5HKkjYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;전체 TDD 프로세스&quot;
        title=&quot;전체 TDD 프로세스&quot;
        src=&quot;/static/bf25119c123e669372cc07de8d845697/b9e4f/ce2-increase-disk-space-04.png&quot;
        srcset=&quot;/static/bf25119c123e669372cc07de8d845697/cf440/ce2-increase-disk-space-04.png 148w,
/static/bf25119c123e669372cc07de8d845697/d2d38/ce2-increase-disk-space-04.png 295w,
/static/bf25119c123e669372cc07de8d845697/b9e4f/ce2-increase-disk-space-04.png 590w,
/static/bf25119c123e669372cc07de8d845697/caaab/ce2-increase-disk-space-04.png 705w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;물리적 디스크 용량은 늘어나지만 여기가 끝이 아님.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;파티션-설정&quot;&gt;&lt;a href=&quot;#%ED%8C%8C%ED%8B%B0%EC%85%98-%EC%84%A4%EC%A0%95&quot; aria-label=&quot;파티션 설정 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;파티션 설정&lt;/h2&gt;
&lt;p&gt;ssh 로 들어가서 df로 확인해보면 하드디스크 용량은 아직 늘지 않았다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shellscript&quot;&gt;&lt;pre class=&quot;language-shellscript&quot;&gt;&lt;code class=&quot;language-shellscript&quot;&gt;$ df -hT
Filesystem     Type      Size  Used Avail Use% Mounted on
udev           devtmpfs  1.9G     0  1.9G   0% /dev
tmpfs          tmpfs     390M  772K  389M   1% /run
/dev/nvme0n1p1 ext4       15G   13G  1.8G  89% /
tmpfs          tmpfs     2.0G     0  2.0G   0% /dev/shm
tmpfs          tmpfs     5.0M     0  5.0M   0% /run/lock
tmpfs          tmpfs     2.0G     0  2.0G   0% /sys/fs/cgroup
/dev/loop0     squashfs   90M   90M     0 100% /snap/core/7713
/dev/loop1     squashfs   18M   18M     0 100% /snap/amazon-ssm-agent/1480
/dev/loop2     squashfs   90M   90M     0 100% /snap/core/8268
tmpfs          tmpfs     390M     0  390M   0% /run/user/1000&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;파티션 설정이 되어 있지 않아 늘어난 볼륨이 반영 되어 있지 않기 때문이다. &lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shellscript&quot;&gt;&lt;pre class=&quot;language-shellscript&quot;&gt;&lt;code class=&quot;language-shellscript&quot;&gt;$ lsblk
NAME        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
loop0         7:0    0   89M  1 loop /snap/core/7713
loop1         7:1    0   18M  1 loop /snap/amazon-ssm-agent/1480
loop2         7:2    0 89.1M  1 loop /snap/core/8268
nvme0n1     259:0    0   50G  0 disk
└─nvme0n1p1 259:1    0   15G  0 part /&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위의 명령어로 확인해보면 50G 로 늘어난 것은 보이지만 실제 &lt;code class=&quot;language-text&quot;&gt;/&lt;/code&gt;(루트)에는 15G 밖에 할당되어 있지 않음&lt;/p&gt;
&lt;p&gt;파티션 설정을 해보자.&lt;/p&gt;
&lt;h2 id=&quot;linux-파티션-크기-조정&quot;&gt;&lt;a href=&quot;#linux-%ED%8C%8C%ED%8B%B0%EC%85%98-%ED%81%AC%EA%B8%B0-%EC%A1%B0%EC%A0%95&quot; aria-label=&quot;linux 파티션 크기 조정 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Linux 파티션 크기 조정&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shellscript&quot;&gt;&lt;pre class=&quot;language-shellscript&quot;&gt;&lt;code class=&quot;language-shellscript&quot;&gt;$ sudo growpart /dev/nvme0n1 1
CHANGED: partition=1 start=2048 old: size=31455199 end=31457247 new: size=104855519,end=104857567&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;파티션이 적용되어 있는지 확인해보면&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shellscript&quot;&gt;&lt;pre class=&quot;language-shellscript&quot;&gt;&lt;code class=&quot;language-shellscript&quot;&gt;$ lsblk
NAME        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
loop0         7:0    0   89M  1 loop /snap/core/7713
loop1         7:1    0   18M  1 loop /snap/amazon-ssm-agent/1480
loop2         7:2    0 89.1M  1 loop /snap/core/8268
nvme0n1     259:0    0   50G  0 disk
└─nvme0n1p1 259:1    0   50G  0 part /&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;늘어난 만큼 파티션 크기가 반영되었다.&lt;/p&gt;
&lt;p&gt;그러나 리눅스는 파일 시스템 확장까지 해야 실제 하드디스크를 이용할 수 있다.&lt;/p&gt;
&lt;h2 id=&quot;ex4-파일-시스템-확장&quot;&gt;&lt;a href=&quot;#ex4-%ED%8C%8C%EC%9D%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%ED%99%95%EC%9E%A5&quot; aria-label=&quot;ex4 파일 시스템 확장 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ex4 파일 시스템 확장&lt;/h2&gt;
&lt;p&gt;파일시스템의 종류에 따라 파일시스템 변경방식이 다르다.&lt;/p&gt;
&lt;p&gt;현재 ex4 파일시스템을 &lt;code class=&quot;language-text&quot;&gt;/&lt;/code&gt; 에 쓰고 있으므로 다음 명령어로 확장해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shellscript&quot;&gt;&lt;pre class=&quot;language-shellscript&quot;&gt;&lt;code class=&quot;language-shellscript&quot;&gt;$ sudo resize2fs /dev/nvme0n1p1
resize2fs 1.44.1 (24-Mar-2018)
Filesystem at /dev/nvme0n1p1 is mounted on /; on-line resizing required
old_desc_blocks = 2, new_desc_blocks = 7
The filesystem on /dev/nvme0n1p1 is now 13106939 (4k) blocks long.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;실제로 파일 시스템이 확장 되었는지 확인해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shellscript&quot;&gt;&lt;pre class=&quot;language-shellscript&quot;&gt;&lt;code class=&quot;language-shellscript&quot;&gt;$ df -hT
Filesystem     Type      Size  Used Avail Use% Mounted on
udev           devtmpfs  1.9G     0  1.9G   0% /dev
tmpfs          tmpfs     390M  772K  389M   1% /run
/dev/nvme0n1p1 ext4       49G   13G   36G  27% /
tmpfs          tmpfs     2.0G     0  2.0G   0% /dev/shm
tmpfs          tmpfs     5.0M     0  5.0M   0% /run/lock
tmpfs          tmpfs     2.0G     0  2.0G   0% /sys/fs/cgroup
/dev/loop0     squashfs   90M   90M     0 100% /snap/core/7713
/dev/loop1     squashfs   18M   18M     0 100% /snap/amazon-ssm-agent/1480
/dev/loop2     squashfs   90M   90M     0 100% /snap/core/8268
tmpfs          tmpfs     390M     0  390M   0% /run/user/1000&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;루트가 확장되어 50G 모두 반영되었다.&lt;/p&gt;
&lt;h2 id=&quot;참고&quot;&gt;&lt;a href=&quot;#%EC%B0%B8%EA%B3%A0&quot; aria-label=&quot;참고 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/recognize-expanded-volume-linux.html&quot;&gt;https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/recognize-expanded-volume-linux.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[파이썬을 이용한 클린 코드를 위한 테스트 주도 개발 - 5장 사용자 입력 저장하기]]></title><description><![CDATA[Intro 다시 한번 강조! TDD의 핵심은 한 번에 한가지만 하는 것 기능 테스트에 최소한의 기능만 구현 이번장에서 보여줄 것은? 사용자가 입력한 작업 아이템을 서버로 보내고 이를 저장한 후 다시 사용자에게 보여주는 시스템 TDD…]]></description><link>https://leonkim.dev/tdd/ch05/</link><guid isPermaLink="false">https://leonkim.dev/tdd/ch05/</guid><pubDate>Mon, 23 Dec 2019 14:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;intro&quot;&gt;&lt;a href=&quot;#intro&quot; aria-label=&quot;intro permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Intro&lt;/h2&gt;
&lt;p&gt;다시 한번 강조! TDD의 핵심은 &lt;strong&gt;한 번에 한가지만 하는 것&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;기능 테스트에 최소한의 기능만 구현&lt;/p&gt;
&lt;p&gt;이번장에서 보여줄 것은?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;사용자가 입력한 작업 아이템을 서버로 보내고 이를 저장한 후 다시 사용자에게 보여주는 시스템&lt;/li&gt;
&lt;li&gt;TDD 가 어떻게 반복적인 개발 스타일을 지원하는지 =&gt; 가장 빠른 방법은 아니나 결과적으로 개발 속도를 높여줌.&lt;/li&gt;
&lt;li&gt;장고 모델, POST 요청처리, 장고 템플릿 테그 같은새로운 개념 소개&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;post-요청을-전송하기-위한-폼form-연동예제--05-01&quot;&gt;&lt;a href=&quot;#post-%EC%9A%94%EC%B2%AD%EC%9D%84-%EC%A0%84%EC%86%A1%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%9C-%ED%8F%BCform-%EC%97%B0%EB%8F%99%EC%98%88%EC%A0%9C--05-01&quot; aria-label=&quot;post 요청을 전송하기 위한 폼form 연동예제  05 01 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;POST 요청을 전송하기 위한 폼(Form) 연동(예제 : &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-01&quot;&gt;05-01&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;4장 마지막에서 하지못했던 사용자 입력 기능을 구현해보도록 한다. &lt;/p&gt;
&lt;p&gt;바로 아래 기능 테스트 부분이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;        &lt;span class=&quot;token comment&quot;&gt;# 그녀는 바로 작업을 추가하기로 한다.&lt;/span&gt;
        inputbox &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find_element_by_id&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;id_new_item&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            inputbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get_attribute&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;placeholder&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&apos;작업 아이템 입력&apos;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;# &quot;공작깃털 사기&quot; 라고 텍스트 상자에 입력한다.&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# (에디스의 취미는 날치 잡이용 그물을 만드는 것이다)&lt;/span&gt;
        inputbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send_keys&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;공작깃털 사기&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;# 엔터키를 치면 페이지가 갱신되고 작업 목록에&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# &quot;1: 공작깃털 사기&quot; 아이템이 추가된다&lt;/span&gt;
        inputbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send_keys&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Keys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ENTER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        table &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find_element_by_id&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;id_list_table&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        rows &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; table&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find_elements_by_tag_name&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;tr&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertTrue&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1: 공작깃털 사기&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; row &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; rows&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&apos;신규 작업이 테이블에 표시되지 않는다&apos;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;브라우저가 POST 요청을 보내기 위해서는&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;input&amp;gt;&lt;/code&gt; 속성에 name= 속성을 지정&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 테그로 감싸야 함&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 테그에 method=“POST” 지정해서 전송 방식 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 내용을 반영한 내용이 아래에 링크되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-01/superlists/lists/templates/home.html&quot;&gt;lists/templates/home.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이제 반영이 되었으니 기능 테스트를 돌려본다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python functional_test.py
E
======================================================================
ERROR: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;functional_test.py&amp;quot;, line 39, in test_can_start_a_list_and_retrieve_it_later
    table = self.browser.find_element_by_id(&amp;#39;id_list_table&amp;#39;)
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py&amp;quot;, line 360, in find_element_by_id
    return self.find_element(by=By.ID, value=id_)
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py&amp;quot;, line 978, in find_element
    &amp;#39;value&amp;#39;: value})[&amp;#39;value&amp;#39;]
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py&amp;quot;, line 321, in execute
    self.error_handler.check_response(response)
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/remote/errorhandler.py&amp;quot;, line 242, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {&amp;quot;method&amp;quot;:&amp;quot;css selector&amp;quot;,&amp;quot;selector&amp;quot;:&amp;quot;[id=&amp;quot;id_list_table&amp;quot;]&amp;quot;}
  (Session info: chrome=78.0.3904.108)

----------------------------------------------------------------------
Ran 1 test in 7.253s

FAILED (errors=1)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이전에 보지 못했던 몇가지 특이한 에러(그리고 현상)들이 나온다.&lt;/p&gt;
&lt;p&gt;이 에러들을 파악해보려면 몇가지 디버깅 방법이 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;print 문을 이용해 현재 페이지 텍스트 확인하기&lt;/li&gt;
&lt;li&gt;에러 메시지를 개선해서 더 자세한 정보를 출력하기&lt;/li&gt;
&lt;li&gt;수동으로 사이트를 열어보기&lt;/li&gt;
&lt;li&gt;time.sleep을 이용해 실행중에 있는 테스트를 잠시 정지시키기&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여기서 마지막 sleep을 사용해보도록 한다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-01/functional_test.py&quot;&gt;functional_test.py - sleep 추가&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;기능 테스트를 다시 실행하면 브라우저가 10초 정도 멈추게 되는데 다음과 같은 화면이 나온다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 590px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 46.66088464874241%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABmklEQVQoz42Q20oCURSG96tEoRRkIEknSUrLTj5jDxB0FXRUKCu8EGsmD6Gj4+iMxxlDGGe0m7+1d2JlEF18/OvAXuvfiwX8PiwFlhHa3kVgLYgFnx/eBR9mZr2E5594MeeZx+KSHywc3Uf04BAHsRj2j2IIR/cQCkewshHEKrEyZjr+QTCI9c0QtiI7YLJ8gmTyGNnsKTTtAo3GDVqtOIzGNcWfGNMYV7/QiXr9Eqxn3VBwhmbzXPD2FofjJDEY3Als+5byO7juV204vMf76EHoSOiD6POc6fojLOuJBkno9Z5hmk9EBt1uZhLzPu9x5bnj5OnxKw0pjPUrZopSgapqaLe7VBzRxndqcEYCXnPdIQ1xx/oXLphlWahUylAURaBpGnRdR61m0CkatKhNNx3QOYZoNQfkvDvBNDkmOp0O+v0+LXTAbNtGOp1GIpFAKpWCJEmQZQmZjEz1Z+TzeaKAl5csCqTValUs/Q6vGYYBPotxB/xRLpdDsVgULkulEsrlMjmtk8s6/aAiHnE+3dcmOUdVVeGSD/wAiEk/hPepqkYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;TF 결과&quot;
        title=&quot;TF 결과&quot;
        src=&quot;/static/67d3bb8cccd2c9c46b8fd6e45d607a44/b9e4f/ch05-01.png&quot;
        srcset=&quot;/static/67d3bb8cccd2c9c46b8fd6e45d607a44/cf440/ch05-01.png 148w,
/static/67d3bb8cccd2c9c46b8fd6e45d607a44/d2d38/ch05-01.png 295w,
/static/67d3bb8cccd2c9c46b8fd6e45d607a44/b9e4f/ch05-01.png 590w,
/static/67d3bb8cccd2c9c46b8fd6e45d607a44/f9b6a/ch05-01.png 885w,
/static/67d3bb8cccd2c9c46b8fd6e45d607a44/be91b/ch05-01.png 1153w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;장고는 CSRF 보호를 기본으로 지원한다. CSRF에 대해서는 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%82%AC%EC%9D%B4%ED%8A%B8_%EA%B0%84_%EC%9A%94%EC%B2%AD_%EC%9C%84%EC%A1%B0&quot;&gt;CSRF 란?&lt;/a&gt; 을 참고하자.&lt;/p&gt;
&lt;p&gt;CSRF 보호를 위해 장고는 각 폼이 생성하는 POST 요청에 토큰을 부여한다.&lt;/p&gt;
&lt;p&gt;현재 방금 form에 그 처리가 되어 있지 않아 이 에러가 &lt;code class=&quot;language-text&quot;&gt;CSRF validation fail&lt;/code&gt; 이 발생한 것이다.&lt;/p&gt;
&lt;p&gt;장고는 이 기능도 간편하게 제공한다. CSRF 전용 &lt;strong&gt;템플릿 테그&lt;/strong&gt;를 추가한다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-01/superlists/lists/templates/home.html&quot;&gt;lists/templates/home.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;장고 내부에서는 이 테그를 CSRF 토근을 포함하는 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;input type=&amp;quot;hidden&amp;quot;&amp;gt;&lt;/code&gt; 요소로 변경해서 랜더링한다.&lt;/p&gt;
&lt;p&gt;다시 기능 테스트를 돌려보면 브라우저의 CSRF 에러는 나오지 않는다.&lt;/p&gt;
&lt;p&gt;하지만 테스트 결과는 Fail 이 된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python functional_test.py
F
======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;functional_test.py&amp;quot;, line 47, in test_can_start_a_list_and_retrieve_it_later
    &amp;#39;신규 작업이 테이블에 표시되지 않는다&amp;#39;
AssertionError: False is not true : 신규 작업이 테이블에 표시되지 않는다

----------------------------------------------------------------------
Ran 1 test in 16.030s

FAILED (failures=1)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 브라우저 동작을 확인했으니 time.sleep은 필요없으므로 제거하자.&lt;/p&gt;
&lt;h2 id=&quot;서버에서-post-요청-처리예제--05-02&quot;&gt;&lt;a href=&quot;#%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C-post-%EC%9A%94%EC%B2%AD-%EC%B2%98%EB%A6%AC%EC%98%88%EC%A0%9C--05-02&quot; aria-label=&quot;서버에서 post 요청 처리예제  05 02 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;서버에서 POST 요청 처리(예제 : &lt;a href=&quot;./05-02&quot;&gt;05-02&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;폼에 action= 속성 내용이 없는 상태에서 submit 을 하면?&lt;/p&gt;
&lt;p&gt;method=POST 가 지정되어 있으므로 현재 동일 URL에 POST 방식으로 요청함&lt;/p&gt;
&lt;p&gt;이것을 검증할 단위 테스트가 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-02/superlists/lists/tests.py&quot;&gt;lists/tests.py&lt;/a&gt; - 단위 테스트 추가&lt;/p&gt;
&lt;p&gt;HttpRequest 내 속성 정리&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;.method : HTTP 요청 메서드(string 타입)&lt;/li&gt;
&lt;li&gt;.POST : POST 메서드인 요청 http body 내용(dict 타입)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;새로 단위 테스트를 추가했으므로 의도적인 실패가 일어나는지 확인하자&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;python manage.py test
Creating test database for alias &amp;#39;default&amp;#39;...
System check identified no issues (0 silenced).
FF.
======================================================================
FAIL: test_home_page_can_save_a_POST_request (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;/Users/pilhwankim/Github/books/test_driven_development_with_python/ch05/05-02/superlists/lists/tests.py&amp;quot;, line 28, in test_home_page_can_save_a_POST_request
    self.assertIn(&amp;#39;신규 작업 아이템&amp;#39;, response.content.decode())
AssertionError: &amp;#39;신규 작업 아이템&amp;#39; not found in &amp;#39;&amp;lt;html&amp;gt;\n    &amp;lt;head&amp;gt;\n        &amp;lt;title&amp;gt;To-Do lists&amp;lt;/title&amp;gt;\n    &amp;lt;/head&amp;gt;\n    &amp;lt;body&amp;gt;\n        &amp;lt;h1&amp;gt;Your To-Do list&amp;lt;/h1&amp;gt;\n        &amp;lt;form method=&amp;quot;POST&amp;quot;&amp;gt;\n            &amp;lt;input name=&amp;quot;item_text&amp;quot; id=&amp;quot;id_new_item&amp;quot; placeholder=&amp;quot;작업 아이템 입력&amp;quot;&amp;gt;\n            &amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;csrfmiddlewaretoken&amp;quot; value=&amp;quot;67JC67qNcs6bsJlmhjMGrbMSvevTJ7qYVFfbZlGQ9WPLwK82e9r0jFfpSD9XJAUt&amp;quot;&amp;gt;\n        &amp;lt;/form&amp;gt;\n\n        &amp;lt;table id=&amp;quot;id_list_table&amp;quot;&amp;gt;\n        &amp;lt;/table&amp;gt;\n    &amp;lt;/body&amp;gt;\n&amp;lt;/html&amp;gt;&amp;#39;

======================================================================
FAIL: test_home_page_returns_correct_html (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;/Users/pilhwankim/Github/books/test_driven_development_with_python/ch05/05-02/superlists/lists/tests.py&amp;quot;, line 18, in test_home_page_returns_correct_html
    self.assertEqual(response.content.decode(), expected_html)
AssertionError: &amp;#39;&amp;lt;htm[227 chars]     &amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;csrfmiddleware[171 chars]tml&amp;gt;&amp;#39; != &amp;#39;&amp;lt;htm[227 chars]     \n        &amp;lt;/form&amp;gt;\n\n        &amp;lt;table id=&amp;quot;i[50 chars]tml&amp;gt;&amp;#39;

----------------------------------------------------------------------
Ran 3 tests in 0.006s

FAILED (failures=2)
Destroying test database for alias &amp;#39;default&amp;#39;...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;예상치 못한 상황이 발생했다. 보면 3개중 2개의 단위 테스트가 에러가 난다.&lt;/p&gt;
&lt;p&gt;테스트 로그를 확인해보면 원인은 5장 맨처음 csrf tag를 넣음으로 템플릿 html 문자열과 응답 문자열(csrf input 이 포함된) 결과가 차이가 나서 생기는 실패이다.&lt;/p&gt;
&lt;p&gt;이 차이는 어떻게 극복해야 하나?&lt;/p&gt;
&lt;p&gt;스텍오버플로우에 관련 질문을 찾아보니 우회하는 방법이 있었다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://stackoverflow.com/questions/34629261/django-render-to-string-ignores-csrf-token/39859042#39859042&quot;&gt;https://stackoverflow.com/questions/34629261/django-render-to-string-ignores-csrf-token/39859042#39859042&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;요약하면 &lt;code class=&quot;language-text&quot;&gt;csrfmiddlewaretoken&lt;/code&gt; 히든 필드를 테스트 코드에서 파이썬 정규표현식으로 제거하는 함수를 만드는 것이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;remove_csrf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;html_code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    csrf_regex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;&amp;amp;lt;input[^&amp;amp;gt;]+csrfmiddlewaretoken[^&amp;amp;gt;]+&amp;amp;gt;&apos;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; re&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sub&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;csrf_regex&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; html_code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;lists/tests.py remove_csrf 함수를 다음과 같이 추가한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_home_page_returns_correct_html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;#...생략...&lt;/span&gt;
        expected_html &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; render_to_string&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;home.html&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;remove_csrf&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;decode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; remove_csrf&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expected_html&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;테스트를 다시 돌려보면&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python manage.py test
Creating test database for alias &amp;#39;default&amp;#39;...
System check identified no issues (0 silenced).
F..
======================================================================
FAIL: test_home_page_can_save_a_POST_request (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;/Users/pilhwankim/Github/books/test_driven_development_with_python/ch05/05-02/superlists/lists/tests.py&amp;quot;, line 36, in test_home_page_can_save_a_POST_request
    self.assertIn(&amp;#39;신규 작업 아이템&amp;#39;, response.content.decode())
AssertionError: &amp;#39;신규 작업 아이템&amp;#39; not found in &amp;#39;&amp;lt;html&amp;gt;\n    &amp;lt;head&amp;gt;\n        &amp;lt;title&amp;gt;To-Do lists&amp;lt;/title&amp;gt;\n    &amp;lt;/head&amp;gt;\n    &amp;lt;body&amp;gt;\n        &amp;lt;h1&amp;gt;Your To-Do list&amp;lt;/h1&amp;gt;\n        &amp;lt;form method=&amp;quot;POST&amp;quot;&amp;gt;\n            &amp;lt;input name=&amp;quot;item_text&amp;quot; id=&amp;quot;id_new_item&amp;quot; placeholder=&amp;quot;작업 아이템 입력&amp;quot;&amp;gt;\n            &amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;csrfmiddlewaretoken&amp;quot; value=&amp;quot;FO3gHv4cwdk1hJyTEmkpr5e3KGenddLiKNXz7K31FIcgFDOUi5tL47OXeohJW0Xs&amp;quot;&amp;gt;\n        &amp;lt;/form&amp;gt;\n\n        &amp;lt;table id=&amp;quot;id_list_table&amp;quot;&amp;gt;\n        &amp;lt;/table&amp;gt;\n    &amp;lt;/body&amp;gt;\n&amp;lt;/html&amp;gt;&amp;#39;

----------------------------------------------------------------------
Ran 3 tests in 0.006s

FAILED (failures=1)
Destroying test database for alias &amp;#39;default&amp;#39;..&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 1가지 테스트 실패만 남았는데 이번에 추가한 테스트 코드이다.&lt;/p&gt;
&lt;p&gt;이 것을 해결하려면 POST 요청을 처리하는 코드를 작성해야 한다.&lt;/p&gt;
&lt;p&gt;제일 간단한 방식으로 반환값을 이용해서 해결해보자.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-02/superlists/lists/views.py&quot;&gt;lists/views.py&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;home_page&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; HttpResponse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;POST&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;item_text&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;테스트는 모두 통과한다. 그러나 우리가 최종 원하는 결과는 아니다.&lt;/p&gt;
&lt;p&gt;우리가 원하는 결과는 템플릿에 있는 새 TODO 리스트에 ‘신규 작업 아이템’ 이 추가되는 것이다.&lt;/p&gt;
&lt;h2 id=&quot;파이썬-변수를-전달해서-템플릿에-출력하기예제--05-03&quot;&gt;&lt;a href=&quot;#%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%B3%80%EC%88%98%EB%A5%BC-%EC%A0%84%EB%8B%AC%ED%95%B4%EC%84%9C-%ED%85%9C%ED%94%8C%EB%A6%BF%EC%97%90-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0%EC%98%88%EC%A0%9C--05-03&quot; aria-label=&quot;파이썬 변수를 전달해서 템플릿에 출력하기예제  05 03 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;파이썬 변수를 전달해서 템플릿에 출력하기(예제 : &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-03&quot;&gt;05-03&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;어떻게 ‘신규 작업 아이템’을 추가해야 할까?&lt;/p&gt;
&lt;p&gt;장고는 템플릿 구문을 이용하면 파이썬 뷰 코드에 있는 변수를 그대로 추가 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-03/superlists/lists/templates/home.html&quot;&gt;lists/templates/home.html&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;table&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;id_list_table&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;tr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;td&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{{ new_item_text }}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;td&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;tr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;table&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;뷰가 제대로 된 값을 new&lt;em&gt;item&lt;/em&gt;text 에 전달하는지 어떻게 테스트 하는가?&lt;/p&gt;
&lt;p&gt;render&lt;em&gt;to&lt;/em&gt;string 함수를 이용한다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-03/superlists/lists/tests.py&quot;&gt;lists/tests.py&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertIn&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;신규 작업 아이템&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;decode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        expected_html &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; render_to_string&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&apos;home.html&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;new_item_text&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;신규 작업 아이템&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;remove_csrf&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;decode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; remove_csrf&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expected_html&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;render&lt;em&gt;to&lt;/em&gt;string 두번째 인수에 변수명과 값을 매칭한 dict 를 넣는다.&lt;/p&gt;
&lt;p&gt;이는 ‘new&lt;em&gt;item&lt;/em&gt;text’ 라는 변수를 home.html 템플릿의 {{new&lt;em&gt;item&lt;/em&gt;text}} 를 값으로 치환시킨다.&lt;/p&gt;
&lt;p&gt;다시 테스트를 돌려보면 실제 뷰 처리가 없어 실패한다. 다시 뷰 처리를 해보자.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-03/superlists/lists/views.py&quot;&gt;lists/views.py&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;home_page&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; render&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;home.html&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;new_item_text&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;POST&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;item_text&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다시 테스트를 돌려보자! 예상하지 못한 테스트 실패(에러) 가 나온다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python manage.py test
Creating test database for alias &amp;#39;default&amp;#39;...
System check identified no issues (0 silenced).
FE.
======================================================================
ERROR: test_home_page_returns_correct_html (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/django/utils/datastructures.py&amp;quot;, line 78, in __getitem__
    list_ = super().__getitem__(key)
KeyError: &amp;#39;item_text&amp;#39;

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File &amp;quot;/Users/pilhwankim/Github/books/test_driven_development_with_python/ch05/05-03/superlists/lists/tests.py&amp;quot;, line 25, in test_home_page_returns_correct_html
    response = home_page(request)
  File &amp;quot;/Users/pilhwankim/Github/books/test_driven_development_with_python/ch05/05-03/superlists/lists/views.py&amp;quot;, line 9, in home_page
    &amp;#39;new_item_text&amp;#39;: request.POST[&amp;#39;item_text&amp;#39;]
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/django/utils/datastructures.py&amp;quot;, line 80, in __getitem__
    raise MultiValueDictKeyError(key)
django.utils.datastructures.MultiValueDictKeyError: &amp;#39;item_text&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;공교롭게도 우리가 POST관련 내용을 변경하고 있을 때 &lt;strong&gt;다른 테스트&lt;/strong&gt;에서 실패가 발생하였다. 이 결과로 방금의 POST 요청 처리 코드는 잘 못되었음을 발견 할 수 있다.&lt;/p&gt;
&lt;p&gt;이것이 우리가 테스트를 작성해야 하는 이유이다. 테스트가 에플리케이션을 망가뜨릴수 있는 상황에서 우리를 구해주었다.&lt;/p&gt;
&lt;p&gt;이 에러 케이스도 반영해서 다음과 같이 변경하자.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-03/superlists/lists/views.py&quot;&gt;lists/views.py&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; render&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;home.html&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;new_item_text&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;POST&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;item_text&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 단위 테스트는 통과한다. 기능 테스트는 어떤지 알아보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python functional_test.py
F
======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;functional_test.py&amp;quot;, line 45, in test_can_start_a_list_and_retrieve_it_later
    &amp;#39;신규 작업이 테이블에 표시되지 않는다&amp;#39;
AssertionError: False is not true : 신규 작업이 테이블에 표시되지 않는다

----------------------------------------------------------------------
Ran 1 test in 12.803s

FAILED (failures=1)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;TF 디버깅 방법 “에러 메시지를 개선” 하는 방법을 사용하자.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-03/functional_test.py&quot;&gt;functional_test.py&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertIn&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;1: 공작깃털 사기&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; row &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; rows&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
            &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&apos;신규 작업이 테이블에 표시되지 않는다 -- 해당 텍스트:\n&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;table&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다음과 같이 유용한 메시지를 출력한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;AssertionError: False is not true : 신규 작업이 테이블에 표시되지 않는다 -- 해당 텍스트:
공작깃털 사기&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다시 테스트의 문제 해결로 돌아와보자. FT 가 실패하는 원인은 “1:“로 시작하는 첫번째 아이템을 원한다는 것이다. 약간의 편법을 동원하자.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-03/superlists/lists/templates/home.html&quot;&gt;lists/templates/home.html&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;table&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;id_list_table&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;tr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;td&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;1: {{ new_item_text }}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;td&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;tr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;table&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;finish the test!&lt;/strong&gt; 결과를 볼수 있다.&lt;/p&gt;
&lt;p&gt;그러나.. 모두 알다시피 이런 꼼수는 2번째 아이템 등록에서는 동작하지 않는다. 2번째 아이템 등록 테스트를 추가해보자.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-03/functional_test.py&quot;&gt;functional_test.py&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;        &lt;span class=&quot;token comment&quot;&gt;# 그녀는 바로 작업을 추가하기로 한다.&lt;/span&gt;
        inputbox &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find_element_by_id&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;id_new_item&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            inputbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get_attribute&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;placeholder&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
            &lt;span class=&quot;token string&quot;&gt;&apos;작업 아이템 입력&apos;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;    

        &lt;span class=&quot;token comment&quot;&gt;# &quot;공작깃털 사기&quot; 라고 텍스트 상자에 입력한다.&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# (에디스의 취미는 날치 잡이용 그물을 만드는 것이다)&lt;/span&gt;
        inputbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send_keys&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;공작깃털 사기&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;# 엔터키를 치면 페이지가 갱신되고 작업 목록에&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# &quot;1: 공작깃털 사기&quot; 아이템이 추가된다&lt;/span&gt;
        inputbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send_keys&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Keys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ENTER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertIn&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;1: 공작깃털 사기&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; row &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; rows&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
            &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&apos;신규 작업이 테이블에 표시되지 않는다 -- 해당 텍스트:\n&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;table&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; time
        time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sleep&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;# (에디스는 매우 체계적인 사람이다)&lt;/span&gt;
        inputbox &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find_element_by_id&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;id_new_item&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        inputbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send_keys&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;공작깃털을 이용해서 그물 만들기&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        inputbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send_keys&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Keys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ENTER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; time
        time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sleep&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# 페에제는 다시 갱신되고, 두 개 아이템이 목록에 보인다.&lt;/span&gt;
        table &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find_element_by_id&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;id_list_table&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        rows &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; table&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find_elements_by_tag_name&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;tr&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertIn&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;1: 공작깃털 사기&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; row &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; rows&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
            &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&apos;신규 작업이 테이블에 표시되지 않는다 -- 해당 텍스트:\n&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;table&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertIn&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&apos;2: 공작깃털을 이용해서 그물 만들기&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
            &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; row &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; rows&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
            &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&apos;신규 작업이 테이블에 표시되지 않는다 -- 해당 텍스트:\n&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;table&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;예상된 실패를 보여준다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python functional_test.py
F
======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;functional_test.py&amp;quot;, line 53, in test_can_start_a_list_and_retrieve_it_later
    f&amp;#39;신규 작업이 테이블에 표시되지 않는다 -- 해당 텍스트:\n{table.text}&amp;#39;
AssertionError: &amp;#39;1: 공작깃털 사기&amp;#39; not found in [&amp;#39;1: 공작깃털을 이용해서 그물 만들기&amp;#39;] : 신규 작업이 테이블에 표시되지 않는다 -- 해당 텍스트:
1: 공작깃털을 이용해서 그물 만들기

----------------------------------------------------------------------
Ran 1 test in 6.966s

FAILED (failures=1)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;단위-테스트-주기---레드그린리팩터-그리고-삼각법&quot;&gt;&lt;a href=&quot;#%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A3%BC%EA%B8%B0---%EB%A0%88%EB%93%9C%EA%B7%B8%EB%A6%B0%EB%A6%AC%ED%8C%A9%ED%84%B0-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%82%BC%EA%B0%81%EB%B2%95&quot; aria-label=&quot;단위 테스트 주기   레드그린리팩터 그리고 삼각법 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;단위 테스트 주기 - 레드/그린/리팩터 그리고 삼각법&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;레드 : 실패할 단위 테스트를 작성함으로써 작업을 시작한다.&lt;/li&gt;
&lt;li&gt;그린 : 이 테스트를 통과할 최소 코드를 작성한다. 편법도 상관없다.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;리펙터링 : 이해할 수 있는 코드로 바꾼다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;삼각법(Triangulation) : 방금 우리가 보여준 방식. 두번째 아이템도 추가해 본다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;스트라이크-3개면-리팩터예제--05-04&quot;&gt;&lt;a href=&quot;#%EC%8A%A4%ED%8A%B8%EB%9D%BC%EC%9D%B4%ED%81%AC-3%EA%B0%9C%EB%A9%B4-%EB%A6%AC%ED%8C%A9%ED%84%B0%EC%98%88%EC%A0%9C--05-04&quot; aria-label=&quot;스트라이크 3개면 리팩터예제  05 04 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;스트라이크 3개면 리팩터(예제 : &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-04&quot;&gt;05-04&lt;/a&gt;)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Three strikes and Refactor : 한번 정도는 복사-붙여넣기를 해줄 수 있지만, 같은 코드가 3번 이상 등장하게 되면 중복을 제거 해야 한다는 이론&lt;/li&gt;
&lt;li&gt;DRY(Don’t Repeat Yourself)&lt;/li&gt;
&lt;li&gt;리펙토링 전에는 커밋을 하고 진행할 것(일단은 돌아가는 코드 베이스로 시작하는 의미)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;FT를 리팩토링 해보자. 저번 FT 코드의 증가로 중복된 코드 내용이 생겼다.&lt;/p&gt;
&lt;p&gt;이 부분을 &lt;code class=&quot;language-text&quot;&gt;check_for_row_in_list_table&lt;/code&gt; 함수로 추출하는 리펙토링 한다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-04/functional_test.py&quot;&gt;functional_test.py&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;check_for_row_in_list_table&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; row_text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        table &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find_element_by_id&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;id_list_table&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        rows &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; table&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find_elements_by_tag_name&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;tr&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertIn&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;row_text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; row &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; rows&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_can_start_a_list_and_retrieve_it_later&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;생략&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;# &quot;공작깃털 사기&quot; 라고 텍스트 상자에 입력한다.&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# (에디스의 취미는 날치 잡이용 그물을 만드는 것이다)&lt;/span&gt;
        inputbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send_keys&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;공작깃털 사기&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;# 엔터키를 치면 페이지가 갱신되고 작업 목록에&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# &quot;1: 공작깃털 사기&quot; 아이템이 추가된다&lt;/span&gt;
        inputbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send_keys&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Keys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ENTER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        
        &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; time
        time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sleep&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;check_for_row_in_list_table&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;1: 공작깃털 사기&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;# (에디스는 매우 체계적인 사람이다)&lt;/span&gt;
        inputbox &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find_element_by_id&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;id_new_item&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        inputbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send_keys&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;공작깃털을 이용해서 그물 만들기&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        inputbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send_keys&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Keys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ENTER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; time
        time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sleep&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        
        &lt;span class=&quot;token comment&quot;&gt;# 페이지는 다시 갱신되고, 두 개 아이템이 목록에 보인다.&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;check_for_row_in_list_table&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;1: 공작깃털 사기&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;check_for_row_in_list_table&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;2: 공작깃털을 이용해서 그물 만들기&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;FT 를 실행해보면 결과가 바뀌지 않은 것을 확인 할수 있다.
작은 변경이지만 테스트 코드의 가독성이 좋아졌다.&lt;/p&gt;
&lt;h2 id=&quot;django-orm과-첫-모델예제--05-05&quot;&gt;&lt;a href=&quot;#django-orm%EA%B3%BC-%EC%B2%AB-%EB%AA%A8%EB%8D%B8%EC%98%88%EC%A0%9C--05-05&quot; aria-label=&quot;django orm과 첫 모델예제  05 05 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Django ORM과 첫 모델(예제 : &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-05&quot;&gt;05-05&lt;/a&gt;)&lt;/h2&gt;
&lt;h3 id=&quot;객체-관계형-맵핑object-relational-mapper-orm&quot;&gt;&lt;a href=&quot;#%EA%B0%9D%EC%B2%B4-%EA%B4%80%EA%B3%84%ED%98%95-%EB%A7%B5%ED%95%91object-relational-mapper-orm&quot; aria-label=&quot;객체 관계형 맵핑object relational mapper orm permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;객체 관계형 맵핑(Object-Relational Mapper, ORM)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;데이터 베이스의 테이블, 레코드, 칼럼 형태로 저장된 데이터를 추상화한 것&lt;/li&gt;
&lt;li&gt;객체지향 코딩 스타일을 그대로 유지하는 게 목적&lt;/li&gt;
&lt;li&gt;Django는 기본 ORM을 탑재하고 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;RDB&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;객체지향 언어&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;테이블&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;클래스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;칼럼&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;속성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;레코드&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;인스턴스&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;유닛 테스트에 새로운 클래스를 추가해 보자.&lt;/p&gt;
&lt;h3 id=&quot;liststestspy&quot;&gt;&lt;a href=&quot;#liststestspy&quot; aria-label=&quot;liststestspy permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-05/superlists/lists/tests.py&quot;&gt;lists/tests.py&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; lists&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;models &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Item

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ItemModelTest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;TestCase&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_saving_and_retrieving_items&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        first_item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Item&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        first_item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;첫 번째 아이템&apos;&lt;/span&gt;
        first_item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;save&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        second_item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Item&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        second_item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;두 번째 아이템&apos;&lt;/span&gt;
        second_item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;save&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        saved_items &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;saved_items&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        first_saved_item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; saved_items&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        second_saved_item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; saved_items&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;first_saved_item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;첫 번째 아이템&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;second_saved_item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;두 번째 아이템&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;위의-테스트-코드-orm-내용-정리&quot;&gt;&lt;a href=&quot;#%EC%9C%84%EC%9D%98-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-orm-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC&quot; aria-label=&quot;위의 테스트 코드 orm 내용 정리 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;위의 테스트 코드 ORM 내용 정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;save() : 비교적 쉽게 DB에 레코드 생성 가능&lt;/li&gt;
&lt;li&gt;objects.all() : 테이블에 있는 모든 레코드 조회&lt;/li&gt;
&lt;li&gt;QuerySet : 리스트 형태 객체. 개별 객체 조회가능&lt;/li&gt;
&lt;li&gt;count() : 조회한 객체의 갯수&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;단위 테스트를 이제 실행해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python manage.py test
[...]
  File &amp;quot;/superlists/lists/tests.py&amp;quot;, line 9, in &amp;lt;module&amp;gt;
    from lists.models import Item
ImportError: cannot import name &amp;#39;Item&amp;#39; from &amp;#39;lists.models&amp;#39; (/superlists/lists/models.py)
[...]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;del&gt;지겹지만&lt;/del&gt; 의도적인 실패(에러)가 발생했다.&lt;/p&gt;
&lt;p&gt;임포트 문제를 해결해 나가보자.&lt;/p&gt;
&lt;h3 id=&quot;listsmodelspy&quot;&gt;&lt;a href=&quot;#listsmodelspy&quot; aria-label=&quot;listsmodelspy permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-05/superlists/lists/models.py&quot;&gt;lists/models.py&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; django&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;db &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; models


&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pass&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다시 테스트를 실행해보면?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python manage.py test
[...]
Traceback (most recent call last):
  File &amp;quot;/Users/pilhwankim/Github/books/test_driven_development_with_python/ch05/05-05/superlists/lists/tests.py&amp;quot;, line 46, in test_saving_and_retrieving_items
    first_item.save()
AttributeError: &amp;#39;Item&amp;#39; object has no attribute &amp;#39;save&amp;#39;
[...]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Item 클래스에는 save 메소드가 없다. 이걸 어떻게 해야하나?&lt;/p&gt;
&lt;p&gt;저자는 이미 알고 있는데 뜸을 들이고 있다. &lt;/p&gt;
&lt;p&gt;해결책은 Django model을 상속받아 사용하려는 의도이다.&lt;/p&gt;
&lt;h3 id=&quot;listsmodelspy-1&quot;&gt;&lt;a href=&quot;#listsmodelspy-1&quot; aria-label=&quot;listsmodelspy 1 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-05/superlists/lists/models.py&quot;&gt;lists/models.py&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; django&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;db &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; models


&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;models&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Model&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pass&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다시 또! 테스트를 실행해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python manage.py test
[...]
    return Database.Cursor.execute(self, query, params)
django.db.utils.OperationalError: no such table: lists_item
[...]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;no such table 이라고 나온다. 이유는 간단하다. ORM을 사용하려면 그 전에 필요한 과정이 있기 때문이다.&lt;/p&gt;
&lt;p&gt;우리는 지금까지 DB 구성을 한 적이 없다. 이제 ORM은 당연하게도 DB 가 필요한데도 말이다.&lt;/p&gt;
&lt;p&gt;친절하게도 Django ORM 은 &lt;strong&gt;마이그레이션(Migration)&lt;/strong&gt; 기능을 넣어두었다.&lt;/p&gt;
&lt;h3 id=&quot;마이그레이션migration&quot;&gt;&lt;a href=&quot;#%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98migration&quot; aria-label=&quot;마이그레이션migration permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;마이그레이션(Migration)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;각 app의 models.py 파일에 적용된 내용 기반으로, 사용자가 테이블/칼럼을 삭제/추가/변경 가능하게 한다.&lt;/li&gt;
&lt;li&gt;데이터베이스를 위한 버전관리 시스템&lt;/li&gt;
&lt;li&gt;프로젝트 루트에 manage.py 의 관련 명령이 있음(migrate, makemigrations 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;자 이제 다음 명령으로 DB셋업을 시작해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt; python manage.py makemigrations
Migrations for &amp;#39;lists&amp;#39;:
  lists/migrations/0001_initial.py
    - Create model Item&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 명령을 실행해보고 다음 위치 &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-05/superlists/lists/migrations&quot;&gt;lists/migrations&lt;/a&gt; 디렉토리 가 생성되고 그 하위에 파일이 생성된다. &lt;/p&gt;
&lt;p&gt;뭔가 변하였으므로 다시 테스트를 실행해 본다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python manage.py test
[...]
Traceback (most recent call last):
  File &amp;quot;/Users/pilhwankim/Github/books/test_driven_development_with_python/ch05/05-05/superlists/lists/tests.py&amp;quot;, line 58, in test_saving_and_retrieving_items
    self.assertEqual(first_saved_item.text, &amp;#39;첫 번째 아이템&amp;#39;)
AttributeError: &amp;#39;Item&amp;#39; object has no attribute &amp;#39;text&amp;#39;
[...]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;테스트 결과가 뭔가 달라졌다. 아예 DB가 없다는 내용이었는데 attribute 가 없다는 식으로 변했다.&lt;/p&gt;
&lt;h3 id=&quot;django-orm-추가-내용-정리&quot;&gt;&lt;a href=&quot;#django-orm-%EC%B6%94%EA%B0%80-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC&quot; aria-label=&quot;django orm 추가 내용 정리 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Django ORM 추가 내용 정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;models.Model&lt;/strong&gt; 를 상속한 클래스 - DB의 Table&lt;/li&gt;
&lt;li&gt;클래스가 생성될 때 PK(Primary key) 역할의 ID 속성은 기본 생성됨&lt;/li&gt;
&lt;li&gt;다른 칼럼들은 직접 정의 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위의 테스트 결과를 해결하려면 정리 내용대로 text 칼럼을 추가해야 한다.&lt;/p&gt;
&lt;h3 id=&quot;listsmodelspy-2&quot;&gt;&lt;a href=&quot;#listsmodelspy-2&quot; aria-label=&quot;listsmodelspy 2 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-05/superlists/lists/models.py&quot;&gt;lists/models.py&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; django&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;db &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; models


&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;models&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Model&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; models&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TextField&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 될거리 믿고 다시 테스트를 실행해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;[...]
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/django/db/backends/sqlite3/base.py&amp;quot;, line 383, in execute
    return Database.Cursor.execute(self, query, params)
django.db.utils.OperationalError: no such column: lists_item.text&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;모델에 칼럼을 추가했지만 그것으로 끝나지 않는다. ORM은 마이그레이션 과정을 원한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python manage.py makemigrations
You are trying to add a non-nullable field &amp;#39;text&amp;#39; to item without a default; we can&amp;#39;t do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py
Select an option: 2&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다음과 같은 물음이 나오는데 초기값이 필요하다고 한다. 초기값을 추가해주자.&lt;/p&gt;
&lt;h3 id=&quot;listsmodelspy-3&quot;&gt;&lt;a href=&quot;#listsmodelspy-3&quot; aria-label=&quot;listsmodelspy 3 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-05/superlists/lists/models.py&quot;&gt;lists/models.py&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; django&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;db &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; models


&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;models&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Model&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; models&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TextField&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;default&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;또 다시 시도해보자 마이그래이션이 성공한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python manage.py makemigrations
Migrations for &amp;#39;lists&amp;#39;:
  lists/migrations/0002_item_text.py
    - Add field text to item&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다시 테스트로 돌아와서 실행시켜보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt; python manage.py test
Creating test database for alias &amp;#39;default&amp;#39;...
System check identified no issues (0 silenced).
....
----------------------------------------------------------------------
Ran 4 tests in 0.010s

OK
Destroying test database for alias &amp;#39;default&amp;#39;...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이번 테스트도 마무리 되었다!&lt;/p&gt;
&lt;h2 id=&quot;post를-데이터베이스에-저장하기예제--05-06&quot;&gt;&lt;a href=&quot;#post%EB%A5%BC-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4%EC%97%90-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0%EC%98%88%EC%A0%9C--05-06&quot; aria-label=&quot;post를 데이터베이스에 저장하기예제  05 06 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;POST를 데이터베이스에 저장하기(예제 : &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-06&quot;&gt;05-06&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;지금까지는 POST 요청이 테스트 결과에 맞춰 단순히 응답에 반환되도록 짜여저 있다.&lt;/p&gt;
&lt;p&gt;이제 이것을 데이터베이스에 저장하도록 수정하는 작업을 해보자.&lt;/p&gt;
&lt;p&gt;먼저 단위 테스트 부터 변경한다.&lt;/p&gt;
&lt;h3 id=&quot;liststestspy-1&quot;&gt;&lt;a href=&quot;#liststestspy-1&quot; aria-label=&quot;liststestspy 1 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-06/superlists/lists/tests.py&quot;&gt;lists/tests.py&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_home_page_can_save_a_POST_request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; home_page&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        new_item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;first&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;new_item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;신규 작업 아이템&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertIn&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;신규 작업 아이템&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;decode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;POST 요청 후에 ORM으로 DB조회 결과를 확인하는 테스트 코드가 추가되었다.&lt;/p&gt;
&lt;p&gt;어디선가 코드 냄새가 난다. 왜? 단위 테스트 길이가 너무 길기 때문에.&lt;/p&gt;
&lt;h4 id=&quot;단위-테스트가-길다는-것은&quot;&gt;&lt;a href=&quot;#%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8%EA%B0%80-%EA%B8%B8%EB%8B%A4%EB%8A%94-%EA%B2%83%EC%9D%80&quot; aria-label=&quot;단위 테스트가 길다는 것은 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;단위 테스트가 길다는 것은?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;테스트 자체가 복잡하다&lt;/li&gt;
&lt;li&gt;테스트를 몇 개로 나눌수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;언제나 그랬듯 변경이 있으면 테스트를 실행한다. 여기서는 의도된 실패나 나와야 한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;======================================================================
FAIL: test_home_page_can_save_a_POST_request (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;/superlists/lists/tests.py&amp;quot;, line 34, in test_home_page_can_save_a_POST_request
    self.assertEqual(Item.objects.count(), 1)
AssertionError: 0 != 1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;의도대로 진행되었다. 이제는 장고 뷰를 코딩해야 한다.&lt;/p&gt;
&lt;h3 id=&quot;listsviewspy&quot;&gt;&lt;a href=&quot;#listsviewspy&quot; aria-label=&quot;listsviewspy permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-06/superlists/lists/views.py&quot;&gt;lists/views.py&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;models &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Item


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;home_page&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Item&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;POST&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;item_text&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;save&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; render&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;home.html&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;new_item_text&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;POST&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;item_text&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 코드의 문제점은? 모든 요청에 대해서 빈 아이템을 저장하고 있는것&lt;/p&gt;
&lt;p&gt;이 문제점은 잠시 메모하고 일단 테스트를 실행해 보자. 실패하던 테스트는 성공한다.&lt;/p&gt;
&lt;h4 id=&quot;저자의-깨알-hint--문제를-발견하면&quot;&gt;&lt;a href=&quot;#%EC%A0%80%EC%9E%90%EC%9D%98-%EA%B9%A8%EC%95%8C-hint--%EB%AC%B8%EC%A0%9C%EB%A5%BC-%EB%B0%9C%EA%B2%AC%ED%95%98%EB%A9%B4&quot; aria-label=&quot;저자의 깨알 hint  문제를 발견하면 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;저자의 깨알 Hint : 문제를 발견하면?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;문제가 작으면? - 하던 것을 멈추고 다시 한다.&lt;/li&gt;
&lt;li&gt;문제가 크면? - 일단 메모하고 뒤로 미룬다.&lt;/li&gt;
&lt;li&gt;저자는 생각해보면 참 원칙주의자이다. 책 전체에서 진짜로 한번에 한가지 일(문제해결)만 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;메모해둔 문제점 리스트를 다시 정리하면…&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;모든 요청에 대한 비어있는 요청은 저장하지 않는다.&lt;/li&gt;
&lt;li&gt;코드 냄새: POST 테스트가 너무 긴가?&lt;/li&gt;
&lt;li&gt;테이블에 아이템 여러 개 표시하기&lt;/li&gt;
&lt;li&gt;하나 이상의 목록 지원하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;=&gt; 목록도 엄청 디테일하게 짜여져 있다. 의도적이라는 생각이 든다. TDD 기본이 10~15분 내에 해결 가능한 수준으로 잘게 쪼개서 문제 해결 사이클을 작게 유지하는게 핵심이기 때문이다.&lt;/p&gt;
&lt;p&gt;저 작업중 1번째 것을 진행한다. 우선 검증할 단위 테스트를 추가한다.&lt;/p&gt;
&lt;h3 id=&quot;liststestspy-2&quot;&gt;&lt;a href=&quot;#liststestspy-2&quot; aria-label=&quot;liststestspy 2 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-06/superlists/lists/tests.py&quot;&gt;lists/tests.py&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_home_page_only_saves_items_when_necessary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; HttpRequest&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        home_page&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;테스트를 돌려보면 의도적인 실패가 발생한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;Traceback (most recent call last):
  File &amp;quot;/superlists/lists/tests.py&amp;quot;, line 48, in test_home_page_only_saves_items_when_necessary
    self.assertEqual(Item.objects.all().count(), 0)
AssertionError: 1 != 0&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;비어있는 요청과 분리하는 코딩을 해보자.&lt;/p&gt;
&lt;h3 id=&quot;listsviewspy-1&quot;&gt;&lt;a href=&quot;#listsviewspy-1&quot; aria-label=&quot;listsviewspy 1 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-06/superlists/lists/views.py&quot;&gt;lists/views.py&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;home_page&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;POST&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        new_item_text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;POST&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;item_text&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        Item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;new_item_text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        new_item_text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;
        
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; render&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;home.html&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;new_item_text&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; new_item_text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;완료되었으면 테스트가 통과하는지 확인한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;Ran 5 tests in 0.020s

OK&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;post-후에-리디렉션예제--05-07&quot;&gt;&lt;a href=&quot;#post-%ED%9B%84%EC%97%90-%EB%A6%AC%EB%94%94%EB%A0%89%EC%85%98%EC%98%88%EC%A0%9C--05-07&quot; aria-label=&quot;post 후에 리디렉션예제  05 07 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;POST 후에 리디렉션(예제 : &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-07&quot;&gt;05-07&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;POST 후에는 항상 리디렉션하라(&lt;a href=&quot;https://en.wikipedia.org/wiki/Post/Redirect/Get&quot;&gt;https://en.wikipedia.org/wiki/Post/Redirect/Get&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;POST 요청의 응답은 HTML렌더링이 아니라 GET 리디렉션 응답으로 변경하는게 목적이다.&lt;/p&gt;
&lt;p&gt;그 목적에 맞게 단위 테스트를 변경한다.&lt;/p&gt;
&lt;h3 id=&quot;liststestspy-3&quot;&gt;&lt;a href=&quot;#liststestspy-3&quot; aria-label=&quot;liststestspy 3 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-07/superlists/lists/tests.py&quot;&gt;lists/tests.py&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_home_page_can_save_a_POST_request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; HttpRequest&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;POST&apos;&lt;/span&gt;
        request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;POST&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;item_text&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;신규 작업 아이템&apos;&lt;/span&gt;

        response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; home_page&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        new_item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;first&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;new_item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;신규 작업 아이템&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status_code&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;302&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;location&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;HTML 본문을 확인하는 assertion을 제거하고 대신 HTTP status가 302(리디렉션 코드), 리디렉션 URL 이 맞는지 확인하는 코드이다.&lt;/p&gt;
&lt;p&gt;변경했으니 테스트를 돌려본다. status code가 아직 200이어서 &lt;strong&gt;200 != 302&lt;/strong&gt; 에러를 일으킨다.&lt;/p&gt;
&lt;p&gt;뷰를 그에 맞게 수정한다.&lt;/p&gt;
&lt;h3 id=&quot;listsviewspy-2&quot;&gt;&lt;a href=&quot;#listsviewspy-2&quot; aria-label=&quot;listsviewspy 2 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-07/superlists/lists/views.py&quot;&gt;lists/views.py&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;home_page&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;POST&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        Item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;POST&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;item_text&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; redirect&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; render&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;home.html&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;테스트 결과는 성공한다.&lt;/p&gt;
&lt;h3 id=&quot;hint--좋은-단위-테스트란&quot;&gt;&lt;a href=&quot;#hint--%EC%A2%8B%EC%9D%80-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%9E%80&quot; aria-label=&quot;hint  좋은 단위 테스트란 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;HINT : 좋은 단위 테스트란?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;한 가지만 테스트를 하는 것&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;왜 그래야 하나요?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;버그 추적이 용이해짐&lt;/li&gt;
&lt;li&gt;assertion 이 많아지면 무엇이 틀렸는지 파악하기 힘듦(assertion 이 적을수록 유리)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위의 내용을 기준으로 `&lt;code class=&quot;language-text&quot;&gt;&lt;/code&gt;test&lt;em&gt;home&lt;/em&gt;page&lt;em&gt;can&lt;/em&gt;save&lt;em&gt;a&lt;/em&gt;POST_request`를 2개로 나눠보자.&lt;/p&gt;
&lt;h3 id=&quot;liststestspy-4&quot;&gt;&lt;a href=&quot;#liststestspy-4&quot; aria-label=&quot;liststestspy 4 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-07/superlists/lists/tests.py&quot;&gt;lists/tests.py&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_home_page_can_save_a_POST_request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; HttpRequest&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;POST&apos;&lt;/span&gt;
        request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;POST&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;item_text&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;신규 작업 아이템&apos;&lt;/span&gt;

        response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; home_page&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status_code&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;302&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;location&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_home_page_redirects_after_POST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; HttpRequest&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;POST&apos;&lt;/span&gt;
        request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;POST&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;item_text&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;신규 작업 아이템&apos;&lt;/span&gt;

        response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; home_page&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        new_item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;first&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;new_item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;신규 작업 아이템&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;테스트를 돌려보면 테스트가 6개로 늘었고 결과는 OK이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python manage.py test
Creating test database for alias &amp;#39;default&amp;#39;...
System check identified no issues (0 silenced).
......
----------------------------------------------------------------------
Ran 6 tests in 0.016s

OK
Destroying test database for alias &amp;#39;default&amp;#39;...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;템플릿에-있는-아이템-렌더링예제--05-08&quot;&gt;&lt;a href=&quot;#%ED%85%9C%ED%94%8C%EB%A6%BF%EC%97%90-%EC%9E%88%EB%8A%94-%EC%95%84%EC%9D%B4%ED%85%9C-%EB%A0%8C%EB%8D%94%EB%A7%81%EC%98%88%EC%A0%9C--05-08&quot; aria-label=&quot;템플릿에 있는 아이템 렌더링예제  05 08 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;템플릿에 있는 아이템 렌더링(예제 : &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-08&quot;&gt;05-08&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;문제점 리스트는 현재 다음과 같은 상태이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; &lt;del&gt;모든 요청에 대한 비어있는 요청은 저장하지 않는다.&lt;/del&gt;&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; &lt;del&gt;코드 냄새: POST 테스트가 너무 긴가?&lt;/del&gt;&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 테이블에 아이템 여러 개 표시하기&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 하나 이상의 목록 지원하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이제 &lt;code class=&quot;language-text&quot;&gt;테이블에 아이템 여러 개 표시하기&lt;/code&gt; 작업을 해 보자.&lt;/p&gt;
&lt;p&gt;일단 여러 아이템을 출력 하는 단위 테스트를 만들어 보자.&lt;/p&gt;
&lt;h3 id=&quot;liststestspy-5&quot;&gt;&lt;a href=&quot;#liststestspy-5&quot; aria-label=&quot;liststestspy 5 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-08/superlists/lists/tests.py&quot;&gt;lists/tests.py&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HomePageTest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;TestCase&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_home_page_displays_all_list_items&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        Item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;itemey 1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        Item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;itemey 2&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; HttpRequest&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; home_page&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertIn&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;itemey 1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;decode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertIn&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;itemey 2&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;decode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;테스트를 돌려보면 예상되는 실패가 나타났다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;    self.assertIn(&amp;#39;itemy 1&amp;#39;, response.content.decode())
AssertionError: &amp;#39;itemy 1&amp;#39; not found in &amp;#39;&amp;lt;html&amp;gt;\n    &amp;lt;head&amp;gt;\n        &amp;lt;title&amp;gt;To-Do lists&amp;lt;/title&amp;gt;\n    &amp;lt;/head&amp;gt;\n    &amp;lt;body&amp;gt;\n        &amp;lt;h1&amp;gt;Your To-Do list&amp;lt;/h1&amp;gt;\n        &amp;lt;form method=&amp;quot;POST&amp;quot;&amp;gt;\n            &amp;lt;input name=&amp;quot;item_text&amp;quot; id=&amp;quot;id_new_item&amp;quot; placeholder=&amp;quot;작업 아이템 입력&amp;quot;&amp;gt;\n            &amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;csrfmiddlewaretoken&amp;quot; value=&amp;quot;QeSZ9iAYixbuEvIboVutb0LnrSaJqN2RHuzEzBFetxuzXg7jsKn4alwUfL0JqOUb&amp;quot;&amp;gt;\n        &amp;lt;/form&amp;gt;\n\n        &amp;lt;table id=&amp;quot;id_list_table&amp;quot;&amp;gt;\n            &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;1: &amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;\n        &amp;lt;/table&amp;gt;\n    &amp;lt;/body&amp;gt;\n&amp;lt;/html&amp;gt;&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Django 템플릿은 리스트 반복 처리를 위한 테그를 제공&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;{% for .. in .. %}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;liststemplateshomehtml&quot;&gt;&lt;a href=&quot;#liststemplateshomehtml&quot; aria-label=&quot;liststemplateshomehtml permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-08/superlists/lists/templates/home.html&quot;&gt;lists/templates/home.html&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;[...]
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;table&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;id_list_table&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
            {% for item in items %}
                &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;tr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;td&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{{forloop.counter}}: {{ item.text }}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;td&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;tr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
            {% endfor %}
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;table&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
[...]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다시 테스트를 해도 통과되진 않는다. 템플릿에 items 를 건네주어야 한다.&lt;/p&gt;
&lt;h3 id=&quot;listsviewspy-3&quot;&gt;&lt;a href=&quot;#listsviewspy-3&quot; aria-label=&quot;listsviewspy 3 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-08/superlists/lists/views.py&quot;&gt;lists/views.py&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;home_page&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    items &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; render&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;home.html&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;items&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; items&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;여기까지 하면 단위 테스트는 통과한다.&lt;/p&gt;
&lt;p&gt;기능 테스트도 실행해 보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python functional_test.py
F
======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;functional_test.py&amp;quot;, line 25, in test_can_start_a_list_and_retrieve_it_later
    self.assertIn(&amp;#39;To-Do&amp;#39;, self.browser.title)
AssertionError: &amp;#39;To-Do&amp;#39; not found in &amp;#39;OperationalError at /&amp;#39;

----------------------------------------------------------------------
Ran 1 test in 2.690s

FAILED (failures=1)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;불행히도 실패한다. 왜 그런걸까? 일단 브러우저로 수동 접속을 해보면 다음과 같은 에러가 뜬다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 590px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75.32861476238625%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsSAAALEgHS3X78AAACUElEQVQ4y5WSCU/bQBCF/f9/R1WpUksrqFISShsSQoEckIoctG7inJD4tmMncWy/vt2YiBZaCUtP453Z+faN10qr9RbV6is0Gq/RbL6Bqu7DcU7g+adw3TI872VSPO8Uo1Ee/X4Og0EOllVEHF8CqFONF0sBrkmuQNMOcXd3BH3+WTrcAq8yNR7F+qP493sdSprWJOz29oAxh273Hd0eEF6AYXyBrh/DNL/K6LoncqxtLDGW5OG+X5YSawLrbD7mt8vJ0cfjT5hMDqWm0wKVl3CRF7AwPMNiUZFaLr9htTrHen2R6VyMLKzWEEXnLJ6h1/tA+HuoP/cY9zAefZRR0/YxnxfookhYieAy0vRS9gLVTDUoYXhFuzXMZhXc35cJPCIgj+GwyFEv6KLJ5msZhVar71w3sdm0COwgSdoyxnFLSlHVHtrtLlqtDm5u2vyGP9DpdOnG4EELOvIpbyfH8WTOtt1MDg+2uTdAECyhaJpGSJeuVLrrseBxg8lL0Ol6xosxCHGeyLbtXbQsk7AFJwihxHEM8SRJwjE2LFpyYxRFHG8lc/970jTd9QspDw1iEUVrecpkMsFwMOANT3lyQPCS3y2gQulE5Hzfl4cJoOhN060ITDIg2Cg2AKblYm5YcL0AEevr9UbWhJZL4TwifMVLSOV+0fsgAl3iXBZtbp5jExNkDRA6I3hGH6E7Zt1jo0s5WXSfzQkRGLAYEugTaDMZYuHNMZv+gq2PCJ/yvzMQBibH1Pmu051JNwsqeKI/RhZXb1m8Of4aUZb/92U8r99eAmk0WbCsbwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;브라우저 결과&quot;
        title=&quot;브라우저 결과&quot;
        src=&quot;/static/943701573579a2f15126d3f2719359fc/b9e4f/ch05-02.png&quot;
        srcset=&quot;/static/943701573579a2f15126d3f2719359fc/cf440/ch05-02.png 148w,
/static/943701573579a2f15126d3f2719359fc/d2d38/ch05-02.png 295w,
/static/943701573579a2f15126d3f2719359fc/b9e4f/ch05-02.png 590w,
/static/943701573579a2f15126d3f2719359fc/f9b6a/ch05-02.png 885w,
/static/943701573579a2f15126d3f2719359fc/0a791/ch05-02.png 989w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;마이그레이션을-이용한-운영-데이터베이스-생성하기예제--05-08&quot;&gt;&lt;a href=&quot;#%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9A%B4%EC%98%81-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0%EC%98%88%EC%A0%9C--05-08&quot; aria-label=&quot;마이그레이션을 이용한 운영 데이터베이스 생성하기예제  05 08 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;마이그레이션을 이용한 운영 데이터베이스 생성하기(예제 : &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-08&quot;&gt;05-08&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;코드상 기능은 분명 완성되었는데 왜 이런 에러가 표시되는걸까?&lt;/p&gt;
&lt;p&gt;에러를 보고 구글링해보면 실제 데이터베이스가 생성되지 않아 생기는 문제임을 알 수 있다.&lt;/p&gt;
&lt;p&gt;진짜 데이터베이스를 구축해 보자. Django 는 기본으로 localhost 의 SQLite 를 기본 DB로 설정하고 있다.&lt;/p&gt;
&lt;h3 id=&quot;superlistssettingspy&quot;&gt;&lt;a href=&quot;#superlistssettingspy&quot; aria-label=&quot;superlistssettingspy permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch05/05-08/superlists/superlists/settings.py&quot;&gt;superlists/settings.py&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
DATABASES &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;default&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;ENGINE&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;django.db.backends.sqlite3&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;NAME&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BASE_DIR&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;db.sqlite3&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이전 makemigrate 로 마이그레이션 히스토리를 작성했다면, 이번에는 그 히스토리를 토대로 진짜 DB에 적용하는 과정이 필요하다. &lt;strong&gt;migrate&lt;/strong&gt; 명령이 그 역할을 한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, lists, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying lists.0001_initial... OK
  Applying lists.0002_item_text... OK
  Applying sessions.0001_initial... OK&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 명령을 실행하면 django 루트 디렉토리에 &lt;code class=&quot;language-text&quot;&gt;db.sqlite3&lt;/code&gt; 파일이 생성된 것을 확인 가능하다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ ls
db.sqlite3 lists      manage.py  superlists&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Sqlite DB 가 생성된 것이다.&lt;/p&gt;
&lt;p&gt;이제 django 서버를 다시 기동하고 FT 를 돌려보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;python functional_test.py
F
======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;functional_test.py&amp;quot;, line 64, in test_can_start_a_list_and_retrieve_it_later
    self.fail(&amp;#39;Finish the test!&amp;#39;)
AssertionError: Finish the test!

----------------------------------------------------------------------
Ran 1 test in 6.858s

FAILED (failures=1)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;성공할 것이다. 그러나 약간의 문제가 남아있다. &lt;/p&gt;
&lt;p&gt;FT를 매번 실행하면 할 수록 같은 목록이 반복되어 늘어난다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 287px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 86.75958188153311%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsSAAALEgHS3X78AAAB8ElEQVQ4y62UN29CQRCE32+no6KigAoJIdFQUdBSYgsBIuecs8h5rW+ls58xtrHkk1a3XJibmd2H1W63JRgMSjablWq1KplMRsbjsfj9fkmlUtLr9SQQCEg0GpXFYiGhUEhisZi8vr7Io2HNZjPxer3idDrF5XLpvFqtxOfzicfjkXA4rDN7kUhEc4fDIW63W/b7vYJcLhe5Xq8aljwxbrfbj+v2fQv04/GocTqdNMhZP5/POptLj4Dv16ztdiv1el29ajQaUqvVpNvtvgPygAF7FF8YGu1m2HM7i2dCAWFBEQaDgTSbTa0w1Z7P58qa9Ufg33mqgJPJRMFooeFwKPl8XkajkebscRDmxk9TWdbNbJRZm81GWbRaLfUS/3K5nKTT6XdfE4mExONx7dXlcqn7hUJBSqWSJJNJPYMiBdztdsqm0+koKKwqlYr0+319CAvW67VQPNMN/OYefUhuukIBWQQQMF6CFeym06myvffwt37VtgEQICTDDCn4CFuYI5HPsFwuq+RisajtxXn28J7P8pNkFu2SyY1sZqTzmSIP9hQLEPaQfTgcPhobEAB4lQP8QXDJPPAnyfQgrxmGMKKCgJtikdObtBYE8JZgD985B44yxBNA2CTIkYyPHCQMID5zERD7Weyg/RRQ/nEg+Q2UCgHnM7DHNQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;이거 뭥미?&quot;
        title=&quot;이거 뭥미?&quot;
        src=&quot;/static/0777900afabe86b93c946d0df1483e05/d92cc/ch05-03.png&quot;
        srcset=&quot;/static/0777900afabe86b93c946d0df1483e05/cf440/ch05-03.png 148w,
/static/0777900afabe86b93c946d0df1483e05/d92cc/ch05-03.png 287w&quot;
        sizes=&quot;(max-width: 287px) 100vw, 287px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이전 FT 에서 입력한 내용이 계속 DB에 저장되기 때문이다. 대첵은 다음에 세우도록 한다.&lt;/p&gt;
&lt;p&gt;일단은 임시로 DB 를 지운후에 재생성한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ rm db.sqlite3
$ python manage.py migrate --noinput&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다시 DB 가 잘 리셋된다. 일단 이번 장을 여기서 마치자.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[파이썬을 이용한 클린 코드를 위한 테스트 주도 개발 - 4장 왜 테스트를 하는 것인가(그리고 리펙토링)?]]></title><description><![CDATA[프로그래밍은 우물에서 물을 퍼 올리는 것과 같다 켄트 벡(Kent Back, TDD) says…]]></description><link>https://leonkim.dev/tdd/ch04/</link><guid isPermaLink="false">https://leonkim.dev/tdd/ch04/</guid><pubDate>Mon, 23 Dec 2019 13:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;프로그래밍은-우물에서-물을-퍼-올리는-것과-같다&quot;&gt;&lt;a href=&quot;#%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%80-%EC%9A%B0%EB%AC%BC%EC%97%90%EC%84%9C-%EB%AC%BC%EC%9D%84-%ED%8D%BC-%EC%98%AC%EB%A6%AC%EB%8A%94-%EA%B2%83%EA%B3%BC-%EA%B0%99%EB%8B%A4&quot; aria-label=&quot;프로그래밍은 우물에서 물을 퍼 올리는 것과 같다 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;프로그래밍은 우물에서 물을 퍼 올리는 것과 같다&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;켄트 벡(Kent Back, TDD) says&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;우물가에 있는 물 뜨는 두레박 비유&lt;/li&gt;
&lt;li&gt;처음 몇번은 두레박으로 물을 퍼올리는건 쉬움&lt;/li&gt;
&lt;li&gt;시간이 지나면서 곧 지치기 시작함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;도르레&lt;/strong&gt; 를 이용하면 직접 퍼올리는 것보도 효율적&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TDD는 &lt;strong&gt;도르레&lt;/strong&gt; 와 같이 작업 효율을 올려줌, 작업이 뒤로 미끄러져 가는것도 막아줌&lt;/li&gt;
&lt;li&gt;TDD는 &lt;strong&gt;훈련&lt;/strong&gt; 이다. 자연스럽게 익혀지는 것이 아니다. 성과가 즉시 나는 것이 아닌 오랜 기간을 거쳐야 한다.&lt;/li&gt;
&lt;li&gt;필자가 보여주고자 하는 것은 철저한 TDD. 무술의 카타&lt;a href=&quot;https://en.wikipedia.org/wiki/Kata&quot;&gt;kata&lt;/a&gt;와 같음&lt;/li&gt;
&lt;li&gt;결론은 개발 내공증진에 분명 도움되니 무술처럼 연습해서 익혀라!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;셀레늄을-이용한-사용자-반응-테스트예제--04-01&quot;&gt;&lt;a href=&quot;#%EC%85%80%EB%A0%88%EB%8A%84%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-%EB%B0%98%EC%9D%91-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%98%88%EC%A0%9C--04-01&quot; aria-label=&quot;셀레늄을 이용한 사용자 반응 테스트예제  04 01 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;셀레늄을 이용한 사용자 반응 테스트(예제 : &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch04/04-01&quot;&gt;04-01&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;이전 장 마지막에 이어서 작업한다.&lt;/p&gt;
&lt;p&gt;바로 아래 주석 처리되어 있는 내용의 테스트 코드를 작성한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;        &lt;span class=&quot;token comment&quot;&gt;# 그녀는 바로 작업을 추가하기로 한다.&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;# &quot;공작깃털 사기&quot; 라고 텍스트 상자에 입력한다.&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# (에디스의 취미는 날치 잡이용 그물을 만드는 것이다)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;# 엔터키를 치면 페이지가 갱신되고 작업 목록에&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# &quot;1: 공작깃털 사기&quot; 아이템이 추가된다&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch04/04-01/functional_test.py&quot;&gt;functional_test.py - 추가 작성한 테스트 코드&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;셀레늄 메소드 설명&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;find&lt;em&gt;element(s)&lt;/em&gt;by_id - tag id로 요소를 찾음. ‘s’는 복수개의 요소를 list로 반환&lt;/li&gt;
&lt;li&gt;find&lt;em&gt;element&lt;/em&gt;by&lt;em&gt;tag&lt;/em&gt;name - tag 이름으로 요소를 찾음. ‘s’는 복수개의 요소를 list로 반환&lt;/li&gt;
&lt;li&gt;send_keys : 입력 요소를 타이핑함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;작성한 FT를 실행해보면 의도된 실패가 발생한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python functional_test.py

======================================================================
ERROR: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;functional_test.py&amp;quot;, line 21, in test_can_start_a_list_and_retrieve_it_later
    header_text = self.browser.find_element_by_tag_name(&amp;#39;h1&amp;#39;).text
  File &amp;quot;/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py&amp;quot;, line 530, in find_element_by_tag_name
    return self.find_element(by=By.TAG_NAME, value=name)
  File &amp;quot;/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py&amp;quot;, line 978, in find_element
    &amp;#39;value&amp;#39;: value})[&amp;#39;value&amp;#39;]
  File &amp;quot;/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py&amp;quot;, line 321, in execute
    self.error_handler.check_response(response)
  File &amp;quot;/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/remote/errorhandler.py&amp;quot;, line 242, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {&amp;quot;method&amp;quot;:&amp;quot;css selector&amp;quot;,&amp;quot;selector&amp;quot;:&amp;quot;h1&amp;quot;}
  (Session info: chrome=78.0.3904.108)

----------------------------------------------------------------------
Ran 1 test in 7.356s

FAILED (errors=1)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;상수는-테스트하지-마라는-규칙과-탈출구로-사용할-템플릿-예제--04-02&quot;&gt;&lt;a href=&quot;#%EC%83%81%EC%88%98%EB%8A%94-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EC%A7%80-%EB%A7%88%EB%9D%BC%EB%8A%94-%EA%B7%9C%EC%B9%99%EA%B3%BC-%ED%83%88%EC%B6%9C%EA%B5%AC%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%A0-%ED%85%9C%ED%94%8C%EB%A6%BF-%EC%98%88%EC%A0%9C--04-02&quot; aria-label=&quot;상수는 테스트하지 마라는 규칙과 탈출구로 사용할 템플릿 예제  04 02 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;“상수는 테스트하지 마라”는 규칙과 탈출구로 사용할 템플릿 (예제 : &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch04/04-02&quot;&gt;04-02&lt;/a&gt;)&lt;/h2&gt;
&lt;h3 id=&quot;단위-테스트-시의-규칙--상수는-테스트하지-마라&quot;&gt;&lt;a href=&quot;#%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%8B%9C%EC%9D%98-%EA%B7%9C%EC%B9%99--%EC%83%81%EC%88%98%EB%8A%94-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EC%A7%80-%EB%A7%88%EB%9D%BC&quot; aria-label=&quot;단위 테스트 시의 규칙  상수는 테스트하지 마라 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;단위 테스트 시의 규칙 : &lt;strong&gt;상수는 테스트하지 마라&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;아래의 코드를&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;wibble &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;아래 테스트로 굳이 만들 필요는 없다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; myprogram &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; wibble
&lt;span class=&quot;token keyword&quot;&gt;assert&lt;/span&gt; wibble &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;단위 테스트는 로직이나 흐름제어, 설정 등을 테스트하기 위한 것이다. 현재 테스트로 구현된 HTML 테스트는 문자열 비교를 하고 있는데 이것은 상수 테스트와 유사하다.&lt;/p&gt;
&lt;h3 id=&quot;템플릿을-사용하기-위한-리펙터링&quot;&gt;&lt;a href=&quot;#%ED%85%9C%ED%94%8C%EB%A6%BF%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%9C-%EB%A6%AC%ED%8E%99%ED%84%B0%EB%A7%81&quot; aria-label=&quot;템플릿을 사용하기 위한 리펙터링 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;템플릿을 사용하기 위한 리펙터링&lt;/h3&gt;
&lt;h4 id=&quot;리펙터링이란&quot;&gt;&lt;a href=&quot;#%EB%A6%AC%ED%8E%99%ED%84%B0%EB%A7%81%EC%9D%B4%EB%9E%80&quot; aria-label=&quot;리펙터링이란 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;리펙터링이란?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;기능(결과물)은 바꾸지 않고&lt;/strong&gt; 코드 자체를 개선하는 작업&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;기능(결과물)은 바꾸지 않고&lt;/strong&gt; 가 핵심이다. 즉 기능 변경, 추가와 병행하면 안된다는 뜻이다. 반드시 문제가 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;지금부터 할 일은 HTML을 상수 문자열로 응답하고 테스트 했던 것을 Django 템플릿 기능으로 리펙터링 하는 과정이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;먼저 단위 테스트를 실행하여 통과하는지 확인한다. 통과가 되어야 이 결과 기준으로 리펙토링을 시작할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python manage.py test

Creating test database for alias &amp;#39;default&amp;#39;...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.020s

OK
Destroying test database for alias &amp;#39;default&amp;#39;...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;HTML 문자열을 별도의 파일(템플릿 파일)에 저장&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch04/04-02/superlists/lists/templates/home.html&quot;&gt;lists/templates/home.html&lt;/a&gt;&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;뷰 함수를 새로 만든 템플릿 파일에 지정함&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch04/04-02/superlists/lists/views.py&quot;&gt;lists/views.py&lt;/a&gt;&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;테스트 동작 여부 확인&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python manage.py test

Creating test database for alias &amp;#39;default&amp;#39;...
System check identified no issues (0 silenced).
E.
======================================================================
ERROR: test_home_page_returns_correct_html (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;/superlists/lists/tests.py&amp;quot;, line 15, in test_home_page_returns_correct_html
    response = home_page(request)
  File &amp;quot;/superlists/lists/views.py&amp;quot;, line 6, in home_page
    return render(request, &amp;#39;home.html&amp;#39;)
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/django/shortcuts.py&amp;quot;, line 36, in render
    content = loader.render_to_string(template_name, context, request, using=using)
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/django/template/loader.py&amp;quot;, line 61, in render_to_string
    template = get_template(template_name, using=using)
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/django/template/loader.py&amp;quot;, line 19, in get_template
    raise TemplateDoesNotExist(template_name, chain=chain)
django.template.exceptions.TemplateDoesNotExist: home.html

----------------------------------------------------------------------
Ran 2 tests in 0.011s

FAILED (errors=1)
Destroying test database for alias &amp;#39;default&amp;#39;...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;왜 통과되지 않는지 확인해 보자면&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;test&lt;em&gt;home&lt;/em&gt;page&lt;em&gt;returns&lt;/em&gt;correct_html 테스트가 ERROR 가 난다.&lt;/li&gt;
&lt;li&gt;TemplateDoesNotExist 인 것을 보면 템플릿을 찾을 수 없다고 한다.&lt;/li&gt;
&lt;li&gt;lists/views.py 6번째 라인에 문제가 있다. 찾아보면 render 함수 호출 부분이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;문제의 원인은 Django 내 settings.py 의 INSTALLED_APPS 에 lists 앱을 사용한다는 등록이 되지 않았기 때문이다. 자세한 내용은(&lt;a href=&quot;https://docs.djangoproject.com/en/2.2/ref/settings/#installed-apps&quot;&gt;https://docs.djangoproject.com/en/2.2/ref/settings/#installed-apps&lt;/a&gt;)에 언급되어 있다.&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;INSTALLED_APPS 항목 추가&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch04/04-02/superlists/superlists/settings.py&quot;&gt;superlists/settings.py&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Application definition&lt;/span&gt;

INSTALLED_APPS &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;django.contrib.admin&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;django.contrib.auth&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;django.contrib.contenttypes&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;django.contrib.sessions&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;django.contrib.messages&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;django.contrib.staticfiles&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;lists&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 추가&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;단위 테스트 재 확인&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python manage.py test

Creating test database for alias &amp;#39;default&amp;#39;...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.035s

OK&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;코드 리펙토링 과정을 마쳤다. 한 가지가 남았다. 테스트 코드가 아직 HTML 문자열 상수로 남아있는데 이것도 고쳐주는 것이 좋다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch04/04-02/superlists/lists/tests.py&quot;&gt;lists/tests.py&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;생략&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; django&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;template&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loader &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; render_to_string

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;생략&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_home_page_returns_correct_html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; HttpRequest&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; home_page&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        expected_html &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; render_to_string&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;home.html&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;decode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; expected_html&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;response.content.decode() 의 decode() 함수는 response.content 바이트 데이터를 유니코드 문자열로 변환한다.&lt;/p&gt;
&lt;p&gt;이것으로 바이트와 바이트를 비교 했던 것들을 문자열과 문자열로 비교하는 것으로 테스트가 바뀌었다.&lt;/p&gt;
&lt;h3 id=&quot;리펙터링에-관하여&quot;&gt;&lt;a href=&quot;#%EB%A6%AC%ED%8E%99%ED%84%B0%EB%A7%81%EC%97%90-%EA%B4%80%ED%95%98%EC%97%AC&quot; aria-label=&quot;리펙터링에 관하여 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;리펙터링에 관하여&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;중요!&lt;/strong&gt; - 리펙터링 시에는 앱 코드와 테스트 코드를 한 번에 수정하는 것이 아니라 하나씩 수정해야 한다.&lt;/li&gt;
&lt;li&gt;보통은 간단한 변경이라고 생각해서 단계를 건너뛰는 경우가 많다. 그게 쌓이다 보면 꼬이게 되고 돌아돌아 작업해야 하는 경우가 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://media.giphy.com/media/1rf4hhXCoTQNa/giphy.gif&quot; alt=&quot;리펙터링 켓(refactoring cat) - 이와 같은 행동을 하게 된다.&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;메인-페이지-추가-수정-예제--04-03&quot;&gt;&lt;a href=&quot;#%EB%A9%94%EC%9D%B8-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%B6%94%EA%B0%80-%EC%88%98%EC%A0%95-%EC%98%88%EC%A0%9C--04-03&quot; aria-label=&quot;메인 페이지 추가 수정 예제  04 03 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;메인 페이지 추가 수정 (예제 : &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch04/04-03&quot;&gt;04-03&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Reminder&lt;/strong&gt; - 4장 처음에 추가했던 기능 테스트가 아직은 실패하고 있는 상태&lt;/p&gt;
&lt;p&gt;다시 기능 테스트를 돌려 보면 추가가 필요한게 있다. home.html의 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch04/04-03/superlists/lists/templates/home.html&quot;&gt;lists/templates/home.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;추가후에 기능 테스트를 다시 돌리면&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python functional_test.py
E
======================================================================
ERROR: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;functional_test.py&amp;quot;, line 25, in test_can_start_a_list_and_retrieve_it_later
    inputbox = self.browser.find_element_by_id(&amp;#39;id_new_item&amp;#39;)
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py&amp;quot;, line 360, in find_element_by_id
    return self.find_element(by=By.ID, value=id_)
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py&amp;quot;, line 978, in find_element
    &amp;#39;value&amp;#39;: value})[&amp;#39;value&amp;#39;]
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py&amp;quot;, line 321, in execute
    self.error_handler.check_response(response)
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/remote/errorhandler.py&amp;quot;, line 242, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {&amp;quot;method&amp;quot;:&amp;quot;css selector&amp;quot;,&amp;quot;selector&amp;quot;:&amp;quot;[id=&amp;quot;id_new_item&amp;quot;]&amp;quot;}
  (Session info: chrome=78.0.3904.108)


----------------------------------------------------------------------
Ran 1 test in 5.369s

FAILED (errors=1)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;id 가 “id&lt;em&gt;new&lt;/em&gt;item” 이라는 HTML 엘리먼트가 없다는 실패 결과를 내어준다.&lt;/p&gt;
&lt;p&gt;이제 아래의 테스트 코드 주석을 만족시킬 구현 코드르 작성해야 한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;        &lt;span class=&quot;token comment&quot;&gt;# 그녀는 바로 작업을 추가하기로 한다.&lt;/span&gt;
        inputbox &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find_element_by_id&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;id_new_item&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            inputbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get_attribute&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;placeholder&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
            &lt;span class=&quot;token string&quot;&gt;&apos;작업 아이템 입력&apos;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;    

        &lt;span class=&quot;token comment&quot;&gt;# &quot;공작깃털 사기&quot; 라고 텍스트 상자에 입력한다.&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# (에디스의 취미는 날치 잡이용 그물을 만드는 것이다)&lt;/span&gt;
        inputbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send_keys&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;공작깃털 사기&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch04/04-03/superlists/lists/templates/home.html&quot;&gt;lists/templates/home.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;다시 기능 테스트를 실행해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python functional_test.py
E
======================================================================
ERROR: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;functional_test.py&amp;quot;, line 39, in test_can_start_a_list_and_retrieve_it_later
    table = self.browser.find_element_by_id(&amp;#39;id_list_table&amp;#39;)
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py&amp;quot;, line 360, in find_element_by_id
    return self.find_element(by=By.ID, value=id_)
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py&amp;quot;, line 978, in find_element
    &amp;#39;value&amp;#39;: value})[&amp;#39;value&amp;#39;]
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py&amp;quot;, line 321, in execute
    self.error_handler.check_response(response)
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/remote/errorhandler.py&amp;quot;, line 242, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {&amp;quot;method&amp;quot;:&amp;quot;css selector&amp;quot;,&amp;quot;selector&amp;quot;:&amp;quot;[id=&amp;quot;id_list_table&amp;quot;]&amp;quot;}
  (Session info: chrome=78.0.3904.108)


----------------------------------------------------------------------
Ran 1 test in 5.612s

FAILED (errors=1)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이번에는 id가 “id&lt;em&gt;list&lt;/em&gt;table” 인 엘리먼트가 없다는 실패가 뜬다.&lt;/p&gt;
&lt;p&gt;이제 아래의 테스트 코드 주석을 만족시킬 구현 코드르 작성해야 한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;        &lt;span class=&quot;token comment&quot;&gt;# 엔터키를 치면 페이지가 갱신되고 작업 목록에&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# &quot;1: 공작깃털 사기&quot; 아이템이 추가된다&lt;/span&gt;
        inputbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send_keys&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Keys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ENTER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        table &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find_element_by_id&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;id_list_table&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        rows &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; table&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find_elements_by_tag_name&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;tr&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertTrue&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1: 공작깃털 사기&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; row &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; rows&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;또 다시 기능 테스트를 실행해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python functional_test.py
F
======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;functional_test.py&amp;quot;, line 41, in test_can_start_a_list_and_retrieve_it_later
    self.assertTrue(any(row.text == &amp;#39;1: 공작깃털 사기&amp;#39; for row in rows))
AssertionError: False is not true

----------------------------------------------------------------------
Ran 1 test in 4.534s

FAILED (failures=1)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;실패 원인이 분명치 않은데 “functional_test.py”, line 41 을 따라가 보면 기능 테스트 함수에 자세한 실패 메시지가 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch04/04-03/functional_test.py&quot;&gt;functional_test.py - 추가 작성한 메시지&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;python functional_test.py
F
======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;functional_test.py&amp;quot;, line 44, in test_can_start_a_list_and_retrieve_it_later
    &amp;#39;신규 작업이 테이블에 표시되지 않는다&amp;#39;
AssertionError: False is not true : 신규 작업이 테이블에 표시되지 않는다

----------------------------------------------------------------------
Ran 1 test in 5.785s

FAILED (failures=1)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 실패 메시지가 명확하게 표시된다. 남은 기능 테스트의 문제의 해결은 5장에서 다룬다.&lt;/p&gt;
&lt;h2 id=&quot;정리--tdd-프로세스&quot;&gt;&lt;a href=&quot;#%EC%A0%95%EB%A6%AC--tdd-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4&quot; aria-label=&quot;정리  tdd 프로세스 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;정리 : TDD 프로세스&lt;/h2&gt;
&lt;h3 id=&quot;전체-tdd-프로세스&quot;&gt;&lt;a href=&quot;#%EC%A0%84%EC%B2%B4-tdd-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4&quot; aria-label=&quot;전체 tdd 프로세스 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;전체 TDD 프로세스&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 590px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.57627118644068%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABkElEQVQoz3VSaU/CQBD1/38zRDR+MSQYjX4hBAOixERB7rNyFAql3DddLKUttM9tScuROMmku82bN+/N7IVhGDiOnaZC36rQVBVrWbZSVRV6V6yzTHOz2UAUCYi4tGp0XXe+F/aB603wI0yQqrSQb/Qwmi2cJpOFiPF8X3xob4DvjSGt5f3NOCLcahoCWR7ehICndBeeaBPZaositqgIY3jeC7h7y6HWnWBFRBBCEK8IuA1m8BwtozOcnio0CUPMwCK8j3HwJjtIMg2QxQyRLItL3zdc/gQegp8Yj4bo9gd4jKRxHcrD5YviK1M6t7xDjOEQznH4KPEIxMvgOn3HXLHeRophocgStbjeW6Q10QKLcr3p4CxCeykGtbfTFGcp5lDsYS/nU6zEBcXo1DJxCMx/5rL2MzQOCs83bcd6o+CVqg6kWPgTVQTTdfBCx8Kr9BXM5nOn6YllE3CeZhBJhjuYgztUwE24hKuXDApMGapCnw1Vai7nWIxDeB42SKEq8mwbRa5rZb7G41eS/sWbhH8rTaY/cdQzIwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;전체 TDD 프로세스&quot;
        title=&quot;전체 TDD 프로세스&quot;
        src=&quot;/static/e3ac40eb9586065337d1fe30bda7c36b/b9e4f/ch04-01.png&quot;
        srcset=&quot;/static/e3ac40eb9586065337d1fe30bda7c36b/cf440/ch04-01.png 148w,
/static/e3ac40eb9586065337d1fe30bda7c36b/d2d38/ch04-01.png 295w,
/static/e3ac40eb9586065337d1fe30bda7c36b/b9e4f/ch04-01.png 590w,
/static/e3ac40eb9586065337d1fe30bda7c36b/f9b6a/ch04-01.png 885w,
/static/e3ac40eb9586065337d1fe30bda7c36b/2d849/ch04-01.png 1180w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;2장부터 4장까지 죽 위와 같은 흐름으로 해왔다.&lt;/p&gt;
&lt;p&gt;어떤 테스트라도 저 프로세스에 맞게 작성하였고 1번 테스트에 1번씩 저 프로세스를 돌게 했었다.&lt;/p&gt;
&lt;h3 id=&quot;double-loop-tdd&quot;&gt;&lt;a href=&quot;#double-loop-tdd&quot; aria-label=&quot;double loop tdd permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Double-Loop TDD&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 590px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 82.20024721878863%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsSAAALEgHS3X78AAADbUlEQVQ4y12T91MqVxTH/fPzY9pkjD5f3iRWRLpIkKKIgEsvyxrBgrBU6V3KJ3eX0XnJmfnOuRzO/e4p37u1Xq/5tNWS5XzKerlgMpkwHo+ZTaesFu9Mhdd+a/HJZEqr2YT1Sr+mcXxgSwtUW13ij3WC2Uci9xXShVcWi4WePB5PkJ9UpoLo47JmrU4P5Vnl/6YTZp/rfLn6h1+st3wNPGAIZJlNRqiNN8zhe36y3HIeK1KpNRj2BdGLyleXJPJDuKMKg8GApehqtVptCHOvLfbvyuzfPnIUq3IalOm0WxRLFU5vFUEYYt8Tp/D0wqDXJSYX+Nno5YdDNwfOAI1mi06no49EJyzX3/CnCoTyJW5yL0SyD58taC17QnFKpdJ/WmuKlhP5oj5fzWazmT7fLW0kWnA1G7PUMWL9PmP5PmepxcUs1VdRWb/P4v1dYK5DW6CYqHALHdrSdMLhaIxPruBVGvjydXy6r+HPV/Fmy7jiRUzBHPaIIvJU8V9148UdzXtyFYL3Kr1eT1fEVqc/xJSoYE5WsKZULMkypqSKMdXgSCrzu0dm25Vk16dwEqtgTNYx5zoYMx1Okw2MuT7OXIO3dltve6s9nOENRWilzJRjZrpZG3dXVo6lV6yZJj4pQSpwhltKY04LgngZa7pGMHRNImDGHn3gQhC2Pwhbgym3UQmeL1B8f7AqOFAiNrYvcxjc1xT9exQ82yiXO+xZ3Ox4FRwuBxn7jyiuX7mx7QppKfSFdPSW28Mp3rCoMG2hmXXSkx2E/RYOw8+YUjVcwTCS+wCDy8s3vywqFFKKlXFeunCb9vjzMoojraKqKvP5nK3eYIRRemY/kOfwRuEgIHMUKnCWULHKXQyZLr+5MpzlhxjiKid3L9iVHqeJOl+9OWzKAJuYe7Va3byUbl8EMg0cSl+gK9DjXLsQr3J+J3MlxbB7r7iOxjnxxzCIxdjltlikWJyo1CbOF9maLptPQm3I1mxLoKkvwpETL0dSyUgeutI3KqG/GCb2cToM7IoZGu5ER8EHTqMlQdjBmakyGo2+IxQVal+y5d50aBUcx2p4gzfIISvJ0Dly2IbF7eUoqm4+nN3kWoWEnNn694RDob0KlnRdr9QiFmEVZ02bh6EiO38n+XKZZted4jjyhEmXzSZXg1mcnamy/o41+xcXhWsM0XwYFAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;기능 테스트와 단위 테스트로 구성된 TDD 프로세스&quot;
        title=&quot;기능 테스트와 단위 테스트로 구성된 TDD 프로세스&quot;
        src=&quot;/static/64908ad72eb776dc5a66313d6f464bbc/b9e4f/ch04-02.png&quot;
        srcset=&quot;/static/64908ad72eb776dc5a66313d6f464bbc/cf440/ch04-02.png 148w,
/static/64908ad72eb776dc5a66313d6f464bbc/d2d38/ch04-02.png 295w,
/static/64908ad72eb776dc5a66313d6f464bbc/b9e4f/ch04-02.png 590w,
/static/64908ad72eb776dc5a66313d6f464bbc/e4e31/ch04-02.png 809w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이 흐름도는 단위 테스트와 기능 테스트가 같이 존재할 때 어떻게 TDD를 진행하는지 나타나는 예시다.&lt;/p&gt;
&lt;p&gt;마치 이중 for 문을 돌 때처럼&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;inner for 문을 단위 테스트 프로세스&lt;/li&gt;
&lt;li&gt;outer for 문을 기능 테스트 프로세스&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;로 이해하면 편할 듯 하다.&lt;/p&gt;
&lt;p&gt;즉 1개의 기능 테스트에 n개의 단위 테스트가 존재하며,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1개의 기능 테스트 작성 후 실패&lt;/li&gt;
&lt;li&gt;해당 기능테스트에 속한 단위 테스트를 하나하나 프로세스를 거침&lt;/li&gt;
&lt;li&gt;1개 기능 테스트 성공&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;의 flow 를 거친다고 이해하면 편할 듯 하다.&lt;/p&gt;
&lt;p&gt;그 동안 기능 테스트와 단위 테스트를 실제로 어떻게 적용하는지 감이 없었는데 이렇게 예제로 접하니깐 뭔가 적용할 만한거 같다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[파이썬을 이용한 클린 코드를 위한 테스트 주도 개발 - 3장 단위 테스트를 이용한 간단한 홈페이지 테스트]]></title><description><![CDATA[Intro 본격적인 To-Do 웹 에플리케이션 개발을 위해 단위 테스트를 만들어본다. 첫 Django 애플리케이션과 첫 단위 테스트(예제 : 03-01) Django 는 1개 프로젝트에 n개 app…]]></description><link>https://leonkim.dev/tdd/ch03/</link><guid isPermaLink="false">https://leonkim.dev/tdd/ch03/</guid><pubDate>Mon, 23 Dec 2019 12:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;intro&quot;&gt;&lt;a href=&quot;#intro&quot; aria-label=&quot;intro permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Intro&lt;/h2&gt;
&lt;p&gt;본격적인 To-Do 웹 에플리케이션 개발을 위해 단위 테스트를 만들어본다.&lt;/p&gt;
&lt;h2 id=&quot;첫-django-애플리케이션과-첫-단위-테스트예제--03-01&quot;&gt;&lt;a href=&quot;#%EC%B2%AB-django-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EA%B3%BC-%EC%B2%AB-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%98%88%EC%A0%9C--03-01&quot; aria-label=&quot;첫 django 애플리케이션과 첫 단위 테스트예제  03 01 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;첫 Django 애플리케이션과 첫 단위 테스트(예제 : &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch03/03-01&quot;&gt;03-01&lt;/a&gt;)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Django 는 1개 프로젝트에 n개 app으록 구성되어 있다.&lt;/li&gt;
&lt;li&gt;이것은 다른 프로젝트에서도 동일한 앱을 사용가능하도록 app 단위로 재사용을 가능하게 하기 위한 것이다.&lt;/li&gt;
&lt;li&gt;앱은 코드를 구조화하기위한 좋은 수단이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;작업-목록-앱을-만들어-보자&quot;&gt;&lt;a href=&quot;#%EC%9E%91%EC%97%85-%EB%AA%A9%EB%A1%9D-%EC%95%B1%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4-%EB%B3%B4%EC%9E%90&quot; aria-label=&quot;작업 목록 앱을 만들어 보자 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;작업 목록 앱을 만들어 보자.&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python manage.py startapp lists&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;실행하면 superlists/superlists 와 동일 위치에 spuerlists/lists 라는 폴더가 생성된다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 157px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 238.85350318471333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAwCAYAAAARtFotAAAACXBIWXMAAAsSAAALEgHS3X78AAAF5klEQVRIx5VXeW/bdgzNl9iANbYsybIlW7IO37Z832eS5miONk3XZuhaDFh3AN0+/RtJ20m8NZb3B/GTIpniI/kemQMrqCFh+tAyeSiGh0TKo9OFagXQ7QLUtA+VnuvZPLS16TvsoFIuoVwoIhc46N+M0HnVR//1CIM3YwzvpmLhsomkqiFlGGTJnXbgeT4sMwPLyaIyaaC+bKM8rCPfLCHfKqM0qMl1MplCKpWGEWEHjpOjl/nLKRhrS2oGdFUX0xJ8JlfO1s/5+jk7sG0HTi6HarmGYreK2eeXmP58gvCoDV1LIm2aSKW3f7TL6UEu5z6+QD+0AxdZz0GGUpDUDRgUfZIcGwRZ7tfvPudUHDJk07Tg2Cv4lp0Vh2YmI2eu5MPMrq75o7ucikNdTyKbtVGt1OCWffTfTHHyxyWWv15gfH+ExS9n6L2eSDo4LTrl9d9p2I6Qk61T8hPKQwS2n0PWdSSHlp15SIlpWRE5dD1ylIBb76I0Pad8adIiXjWPUr8m7aQpGlyC7eTdLajfhkwONSWOwvAYjevPFFEKQVhE+2KA4bs5ytSHfi2P0fsFejfj6LbZVJnhqocvBJZFBdAUHYkXCaQJIheJKxxVkCcRKsj3l6id35MzE1ViTPt8gMZJF5VxKPCrdHI7GcmoCNkh5dBvjVCaXVGeHAzeztC5Il5fDlFftNA87WHy8VhSsWHNXpC1+KH8IPZdDPHvY4iRMWzlB0pHXHug3s4quxShGo9RdBdovf2NWiWD7vVYouMoue+4GBwdc3xXdGvI1NgkTU6pDq85ojzZ6JKj1nkf4XFHlIc/4IeFlcP0HlwWtZHGNYW3GsFLHCak0nKvrFRnxe2IKm8gV45u0PnwF5zARv92Jj3IxrQb3M1FJ/meK84qtDtC4rJJwpBx80I1ph3nzKsGCBpFaexCu4zKiFrHtVc9uduhDivnIxuUyWFaVIYfmhlLhGAjYavr/wG5/f4rvIpLqnKK2aeV0LbOejj6ciHFEaj7FsXYGL9MxWHKMXyG6JYDoeMGaoTabNomhN+e0B8NccB5ZGMes2RlcrZ8IFocmHrxuDR28/YLbC8rUMcflpgS3fpvJlLd6ccTkTIjaURTjxVbZx3UVv0mRhRUSQd1hsmDim2PySdjNJPJIvDzsHMOCvUiimEJxUYJ4biB5rSFSruCoJKPhLvlMB8U4BY8DM6HWLxbYnw1weBsiNGrkZytWXv/McqQudJcRWkNI41EXEU8pjycKg19VdUeaLpzjLJDjrJara2kjJw2m020Wm2EYQPdbg+9Hu08/QF8P4BGuX4un1tz2aUWYsfFYgnD0QjD4VAcDgYD1Oshmq0WGo0mmAyRg152GqYVnexcTWhiGkFVFBLZWAKxQ0Xe4+eR1CsvrtC9/ypjlFnRO+lj+eMR8rWCNPbgdIDOsvufQfVNyDrlJOsV4JQbsEgQSs2yOApIqgqkOtxK9WGIKqn31pAydslXxiEm+CtlWY9MjlRSQfdP17potYmtBfb+bzhEvaP3x5jfLXD8gahHUE8/naE1b2NyM0XvZX8PPaSo0mmL6JWhhJvS4Lxp2SQIjpdDOGogR2sIM8gvBY8j49k+pELY+QrtNz2CnJQieEVfXhDY692QIT+VsG/uNo9VvkTr3Z/I+TZmt3PMbxdCQ640V1e22bS5H/V4nUuqKnRFWfejIf3H/ZYhPWQd3EdcH9c5cuaFPRTHZ+QkJS3DkH1a4TiffM3zZS+1ebrOhVefqAg2FndLgXry0wkm1xM6X4rjXePzv9TjDZYiZUVJKKoIhpEmoaAisW3oGZlD3q39II/5YkksyWI2m6Naq4lAsMJMJlPUa3VRnF0c3vo/hWF3ul3wNTtg2Wq12zg+psYmpen1euh0OpH79RaXWeOYgqwqcVIVhs0nmxJPiHHlo+bKQZY2hdL8Eu2732E7psj99GaGNlGN5X9yPUVj3BQKskBEDynKYbZQpVVuCMsyRWl4fnDrdI97MluKpDh8XevXd9KO7R/Cmrcy8CP9dAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;새로 생긴 장고 앱&quot;
        title=&quot;새로 생긴 장고 앱&quot;
        src=&quot;/static/b1f9c4a4ee769dc5b6aaa3c3f23881e8/76e19/ch03-01.png&quot;
        srcset=&quot;/static/b1f9c4a4ee769dc5b6aaa3c3f23881e8/cf440/ch03-01.png 148w,
/static/b1f9c4a4ee769dc5b6aaa3c3f23881e8/76e19/ch03-01.png 157w&quot;
        sizes=&quot;(max-width: 157px) 100vw, 157px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;단위-테스트는-무엇이고-기능-테스트와-어떤-차이가-있을까&quot;&gt;&lt;a href=&quot;#%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EA%B8%B0%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%99%80-%EC%96%B4%EB%96%A4-%EC%B0%A8%EC%9D%B4%EA%B0%80-%EC%9E%88%EC%9D%84%EA%B9%8C&quot; aria-label=&quot;단위 테스트는 무엇이고 기능 테스트와 어떤 차이가 있을까 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;단위 테스트는 무엇이고, 기능 테스트와 어떤 차이가 있을까?&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;종류&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;단위 테스트&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;기능 테스트&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;관점&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;프로그래머 관점&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;사용자 관점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;목표&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;애플리케이션 내부&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;애플리케이션 외부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;레이어&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;하위 레벨&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;상위 레벨&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;테스트-작업-순서&quot;&gt;&lt;a href=&quot;#%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%91%EC%97%85-%EC%88%9C%EC%84%9C&quot; aria-label=&quot;테스트 작업 순서 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;테스트 작업 순서&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;기능 테스트 작성 : 사용자 관점의 새로운 기능성 정의&lt;/li&gt;
&lt;li&gt;기능 테스트 실패 - 하위단의 어떤 기능을 작성해야 통과할지 고민/설계
2-1. 단위 테스트 작성 : 기능테스트에서의 고민은 테스트로 작성
2-2. 단위 테스트 실패 : 테스트를 통과할 정도로 최소한의 코드 작성. 과정 2-1/2-2를 하나의 기능 테스트 완성될 때까지 반복&lt;/li&gt;
&lt;li&gt;기능 테스트 재실행하여 통과하는지 확인. 실패시 과정 2로 돌아가서 다시 작성. 통과시 한가지 기능 완성&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;즉 상위단(기능테스트) 를 먼저 작성후에 그 하위단(단위 테스트)을 잘게 쪼게서 테스트 함을 알수 있다.
번거로워 보일 수 있으나, 실질적인 설계와 이후에 검증할수 있는 자동화된 툴까지 만들어지기 때문에 합리적인 프로세스인거 같다.&lt;/p&gt;
&lt;h2 id=&quot;django에서의-단위-테스트예제--03-02&quot;&gt;&lt;a href=&quot;#django%EC%97%90%EC%84%9C%EC%9D%98-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%98%88%EC%A0%9C--03-02&quot; aria-label=&quot;django에서의 단위 테스트예제  03 02 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Django에서의 단위 테스트(예제 : &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch03/03-02&quot;&gt;03-02&lt;/a&gt;)&lt;/h2&gt;
&lt;h3 id=&quot;tdd-주기&quot;&gt;&lt;a href=&quot;#tdd-%EC%A3%BC%EA%B8%B0&quot; aria-label=&quot;tdd 주기 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TDD 주기&lt;/h3&gt;
&lt;p&gt;선 실패 -&gt; 테스트를 통과할 코드 작성 -&gt; 후 통과 -&gt; 새 테스트 코드 작성 -&gt; 선 실패..(계속 반복)&lt;/p&gt;
&lt;h3 id=&quot;djangotesttestcase-클래스&quot;&gt;&lt;a href=&quot;#djangotesttestcase-%ED%81%B4%EB%9E%98%EC%8A%A4&quot; aria-label=&quot;djangotesttestcase 클래스 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;django.test.TestCase 클래스&lt;/h3&gt;
&lt;p&gt;Django는 기본 TestCase class를 확장한 django.test.TestCase 클래스를 기본 단위 테스트로 사용하도록 권하고 있다.
django 프로젝트에 맞는 여러 확장 기능들이 있다.
그 중에 manage.py 에서 test 커맨드로 전체 testcase를 실행하는 기능이 포함되어 있다.
고의적인 실패 테스트를 작성하여 이를 확인해 보자.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch03/03-02/superlists/lists/tests.py&quot;&gt;lists/tests.py&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python manage.py test

Creating test database for alias &amp;#39;default&amp;#39;...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_bad_maths (lists.tests.SmokeTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;workspace/superlists/lists/tests.py&amp;quot;, line 7, in test_bad_maths
    self.assertEqual(1 + 1, 3)
AssertionError: 2 != 3

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)
Destroying test database for alias &amp;#39;default&amp;#39;...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;django의-mvc-url-뷰-함수예제--03-03&quot;&gt;&lt;a href=&quot;#django%EC%9D%98-mvc-url-%EB%B7%B0-%ED%95%A8%EC%88%98%EC%98%88%EC%A0%9C--03-03&quot; aria-label=&quot;django의 mvc url 뷰 함수예제  03 03 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Django의 MVC, URL, 뷰 함수(예제 : &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch03/03-03&quot;&gt;03-03&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;Django는 &lt;strong&gt;대체로&lt;/strong&gt; MVC(Model-View-Cotroller) 패턴을 따름&lt;/p&gt;
&lt;p&gt;이 MVC 패턴을 차용하여 Django는&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MVC패턴&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;Django&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model(Data)&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;Model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;View&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;Template&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Controller&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;View&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;라고 표현하여 MTV(model-template-view)패턴이라고 명명함&lt;/p&gt;
&lt;h3 id=&quot;http-요청-django-의-처리-흐름&quot;&gt;&lt;a href=&quot;#http-%EC%9A%94%EC%B2%AD-django-%EC%9D%98-%EC%B2%98%EB%A6%AC-%ED%9D%90%EB%A6%84&quot; aria-label=&quot;http 요청 django 의 처리 흐름 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;HTTP 요청 Django 의 처리 흐름&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 575px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 37.73913043478261%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABeklEQVQoz4WSQWvUQBTH87U8iIK33jx7UkREoRcP9aCIx4InPYgHC4J+AGsp9SZFEQ+CLNqaSdtNdhPdzO5mM0lMYpqZn5NdtSjivuHHvBnmD/957znGGBYw37XWnNx1aPRP7Ill4Zykyx+bBoQQHPUP6QufffmB9eE1HoQ3WR9c5026jaNqQ2ppraAsSz7t7REEAb7vU1c1mU6Ivw/nJI1EZYpYjpDhhHfhK66IM9z1L7HqrfB88hgnzA1BZhjmkKUJwvMIBgHegccknnI7uMDF/VNWeJZVscJMJVRVNXesrY2o7s8J60PyVuGIxOB2zECOviKlZDSyDsaSXOU8je9xP1zjYXSLR+Edslz9v4blsaHjWC9qWBQFruvStu0/BUVeMB6PKdQ34iJia/qEnekzNicbeGVveVO6Lv/GriiK6H3scfTZ5/2X11x2T3Pj4DxXxTlbww2cX+NiFslfI/MnXTRNQ11XGPurQqfszjZ5q16ym75gUAl+AFgMUyMrB5DQAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Django의 처리 흐름&quot;
        title=&quot;Django의 처리 흐름&quot;
        src=&quot;/static/71e5ee84bc966084fa594667da817cdc/2cbf8/ch03-02.png&quot;
        srcset=&quot;/static/71e5ee84bc966084fa594667da817cdc/cf440/ch03-02.png 148w,
/static/71e5ee84bc966084fa594667da817cdc/d2d38/ch03-02.png 295w,
/static/71e5ee84bc966084fa594667da817cdc/2cbf8/ch03-02.png 575w&quot;
        sizes=&quot;(max-width: 575px) 100vw, 575px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;저기 중에 http client - view 사이에서 일어나는 일을 좀더 자세히 설명하면 이렇다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;특정 URL에 대한 HTTP 요청 받음&lt;/li&gt;
&lt;li&gt;특정 규칙을 이용해서 해당 요청에 어떤 뷰 함수를 실행할 지 결정&lt;/li&gt;
&lt;li&gt;뷰 기능 요청을 처리하고 HTTP 응답을 반환&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;따라서 우리가 테스트 할 2가지&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;URL의 루트(”/“)를 해석해서 특정 뷰 기능에 매칭시킬 수 있는가?&lt;/li&gt;
&lt;li&gt;이 뷰 기능이 특정 HTML을 반환하게 해서 기능 테스트를 통과하는가?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch03/03-03/superlists/lists/tests.py&quot;&gt;첫 번째 테스트 코드&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python manage.py test
System check identified no issues (0 silenced).
E
======================================================================
ERROR: lists.tests (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: lists.tests
Traceback (most recent call last):
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/3.7.1/lib/python3.7/unittest/loader.py&amp;quot;, line 434, in _find_test_path
    module = self._get_module_from_name(name)
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/3.7.1/lib/python3.7/unittest/loader.py&amp;quot;, line 375, in _get_module_from_name
    __import__(name)
  File &amp;quot;/Users/pilhwankim/Github/books/test_driven_development_with_python/ch03_Testing_a_Simple_Home_Page_with_Unit_Tests/03-03/superlists/lists/tests.py&amp;quot;, line 3, in &amp;lt;module&amp;gt;
    from lists.views import home_page
ImportError: cannot import name &amp;#39;home_page&amp;#39; from &amp;#39;lists.views&amp;#39; (/Users/pilhwankim/Github/books/test_driven_development_with_python/ch03_Testing_a_Simple_Home_Page_with_Unit_Tests/03-03/superlists/lists/views.py)


----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;예상하던 대로 에러가 발생한다.
대략 에러의 내용은 veiws 의 home_page 라는 존재하지 않는 것을 임포트 하려고 했기 때문에 발생했다.
예측된 실패이고 TDD 관점으로는 거쳐가야 할 첫 과정이다.&lt;/p&gt;
&lt;h2 id=&quot;마침내-실질적인-애플리케이션을-작성한다예제--03-04&quot;&gt;&lt;a href=&quot;#%EB%A7%88%EC%B9%A8%EB%82%B4-%EC%8B%A4%EC%A7%88%EC%A0%81%EC%9D%B8-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%9D%84-%EC%9E%91%EC%84%B1%ED%95%9C%EB%8B%A4%EC%98%88%EC%A0%9C--03-04&quot; aria-label=&quot;마침내 실질적인 애플리케이션을 작성한다예제  03 04 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;마침내 실질적인 애플리케이션을 작성한다(예제 : &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch03/03-04&quot;&gt;03-04&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;실패 테스트를 해결해 보자. 한번에 하나씩! 일단 import 에러를 해결한다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch03/03-04/superlists/lists/views.py&quot;&gt;view 구현 코드&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;너무 단순하고 허무한 코드이기 한데, 저자의 의도는 이런거 같다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;한 번에 한 가지의 당면한 문제를 해결해라!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;다음과 같이 구현하고 다시 테스트를 돌려보면 다른 에러가 발생한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;python manage.py test
Creating test database for alias &amp;#39;default&amp;#39;...
System check identified no issues (0 silenced).
E
======================================================================
ERROR: test_root_url_resolves_to_home_page_view (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;/Users/pilhwankim/Github/books/test_driven_development_with_python/ch03_Testing_a_Simple_Home_Page_with_Unit_Tests/03-04/superlists/lists/tests.py&amp;quot;, line 8, in test_root_url_resolves_to_home_page_view
    found = resolve(&amp;#39;/&amp;#39;)
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/django/urls/base.py&amp;quot;, line 24, in resolve
    return get_resolver(urlconf).resolve(path)
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/django/urls/resolvers.py&amp;quot;, line 567, in resolve
    raise Resolver404({&amp;#39;tried&amp;#39;: tried, &amp;#39;path&amp;#39;: new_path})
django.urls.exceptions.Resolver404: {&amp;#39;tried&amp;#39;: [[&amp;lt;URLResolver &amp;lt;URLPattern list&amp;gt; (admin:admin) &amp;#39;admin/&amp;#39;&amp;gt;]], &amp;#39;path&amp;#39;: &amp;#39;&amp;#39;}

----------------------------------------------------------------------
Ran 1 test in 0.004s

FAILED (errors=1)
Destroying test database for alias &amp;#39;default&amp;#39;...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;url과-뷰-함수-맵핑예제--03-05&quot;&gt;&lt;a href=&quot;#url%EA%B3%BC-%EB%B7%B0-%ED%95%A8%EC%88%98-%EB%A7%B5%ED%95%91%EC%98%88%EC%A0%9C--03-05&quot; aria-label=&quot;url과 뷰 함수 맵핑예제  03 05 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;URL과 뷰 함수 맵핑(예제 : &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch03/03-05&quot;&gt;03-05&lt;/a&gt;)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Django 는 urls.py 에서 URL과 view 를 매핑할지 정의&lt;/li&gt;
&lt;li&gt;superlists(project root)/superlists/urls.py - 전체 사이트 대상으로 하는 메인 urls.py&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;참고 사항) 2판 기준 책이 django 1.7 기준이다. 굳이 예전버전을 쓸 필요는 없으므로 최신의 2.2 버전으로 최대한 예제를 따라 진행한다.
크게 바뀐 내용은 3가지 정도 요약됨&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;django.conf.urls.url 로 매핑하던 내용이 django.urls.path 로 표준적인 설정이 바뀜(이전것도 사용가능하나. django 에서는 path를 기본으로 함)&lt;/li&gt;
&lt;li&gt;따라서 url pattern 입력이 정규식 표현에서 path convert 방식으로 바뀜&lt;/li&gt;
&lt;li&gt;문자열로 view 경로 설정 방식 제외. 직접 view 를 import 함&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;책의 내용과 비슷하게 아래와 같이 urls.py 를 변경해 진행해보면&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; django&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;contrib &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; admin
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; django&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;urls &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; path
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; lists &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; views &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; home_views

urlpatterns &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# path(&apos;admin/&apos;, admin.site.urls),&lt;/span&gt;
    path&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; home_views&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;home_page&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;home&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python manage.py test
Creating test database for alias &amp;#39;default&amp;#39;...
Destroying test database for alias &amp;#39;default&amp;#39;...
Traceback (most recent call last):
(...생략...)
  File &amp;quot;/test_driven_development_with_python/ch03_Testing_a_Simple_Home_Page_with_Unit_Tests/03-05/superlists/superlists/urls.py&amp;quot;, line 22, in &amp;lt;module&amp;gt;
    path(&amp;#39;/&amp;#39;, lists.views.home_page, name=&amp;#39;home&amp;#39;),
  File &amp;quot;~/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/django/urls/conf.py&amp;quot;, line 73, in _path
    raise TypeError(&amp;#39;view must be a callable or a list/tuple in the case of include().&amp;#39;)
TypeError: view must be a callable or a list/tuple in the case of include().&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;urls.py 에 view와 url 매핑은 했으나,아직 lists.views.home_page 가 None 값으로 아직 미구현 되어서 나타나는 에러이다.&lt;/p&gt;
&lt;p&gt;다시 lists/views.py 로 돌아가서 실제 view 를 함수로 구현해보자.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch03/03-05/superlists/lists/views.py&quot;&gt;view 구현 코드&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;테스트를 실행해보면&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;python manage.py test
Creating test database for alias &amp;#39;default&amp;#39;...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Destroying test database for alias &amp;#39;default&amp;#39;...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;첫 테스트가 성공했다! 사이트 루트(”/”) 요청을 django view 까지 연결하는 것이 성공했다는 의미이다.&lt;/p&gt;
&lt;h2 id=&quot;뷰를-위한-단위-테스트예제--03-06&quot;&gt;&lt;a href=&quot;#%EB%B7%B0%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%98%88%EC%A0%9C--03-06&quot; aria-label=&quot;뷰를 위한 단위 테스트예제  03 06 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;뷰를 위한 단위 테스트(예제 : &lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch03/03-06&quot;&gt;03-06&lt;/a&gt;)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;이제 사이트 루트 요청에 실제 HTML 응답값을 반환하는 내용도 추가해야 함&lt;/li&gt;
&lt;li&gt;위의 내용을 검증하는 테스트 코드 추가가 먼저&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch03/03-06/superlists/lists/tests.py&quot;&gt;tests 구현 코드&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;추가된 테스트는 요약하면 response body 텍스트가&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;To-Do lists&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;내용으로 오는지 검증하는 내용이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;python manage.py test
======================================================================
ERROR: test_home_page_returns_correct_html (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;/Users/pilhwankim/Github/books/test_driven_development_with_python/ch03_Testing_a_Simple_Home_Page_with_Unit_Tests/03-06/superlists/lists/tests.py&amp;quot;, line 15, in test_home_page_returns_correct_html
    response = home_page(request)
TypeError: home_page() takes 0 positional arguments but 1 was given

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (errors=1)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;의도적으로 테스트를 돌려보면 실패한다. 이제 구현해야 한다.&lt;/p&gt;
&lt;h3 id=&quot;단위-테스트-코드-주기&quot;&gt;&lt;a href=&quot;#%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%A3%BC%EA%B8%B0&quot; aria-label=&quot;단위 테스트 코드 주기 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;단위 테스트-코드 주기&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;터미널에서 단위 테스트를 실행하여 어떻게 실패하는지 확인한다.&lt;/li&gt;
&lt;li&gt;편집기 상에서 현재 실패 테스트를 수정하기 위한 &lt;strong&gt;최소한의 코드&lt;/strong&gt;를 변경한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;코드 품질을 높이고자 한다면 코드 변경은 최소화 한다. 즉 이 케이스에서는&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;home_page() takes 0 positional arguments but 1 was given&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;라고 했기 때문에 home_page 함수에 argument를 1개 추가하는 &lt;strong&gt;최소한의 코드&lt;/strong&gt;를 변경하고 다시 테스트 해야한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;코드 변경(request 추가)&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;home_page&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pass&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;테스트&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;======================================================================
ERROR: test_home_page_returns_correct_html (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;/superlists/lists/tests.py&amp;quot;, line 16, in test_home_page_returns_correct_html
    self.assertTrue(response.content.startswith(b&amp;#39;&amp;lt;html&amp;gt;&amp;#39;))
AttributeError: &amp;#39;NoneType&amp;#39; object has no attribute &amp;#39;content&amp;#39;

----------------------------------------------------------------------&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;home_page 에 리턴값 없이 None 으로 오기 때문이다. 그래서 코드 변경은?&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;코드 변경(return 에 django.http.HttpResponse 사용)&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; django&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;http &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; HttpResponse

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;home_page&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; HttpResponse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;테스트&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;======================================================================
FAIL: test_home_page_returns_correct_html (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;/superlists/lists/tests.py&amp;quot;, line 16, in test_home_page_returns_correct_html
    self.assertTrue(response.content.startswith(b&amp;#39;&amp;lt;html&amp;gt;&amp;#39;))
AssertionError: False is not true

----------------------------------------------------------------------
Ran 2 tests in 0.007s&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;리턴값 객체는 맞게 왔으나 contents 가 없기 때문이다. contents를 채울 코드 변경은?&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;코드 변경(HttpResponse 에 html content 텍스트 넣기)&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; django&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;http &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; HttpResponse


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;home_page&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; HttpResponse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&amp;lt;html&gt;&amp;lt;title&gt;To-Do lists&amp;lt;/title&gt;&amp;lt;/html&gt;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;테스트&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;python manage.py test
Creating test database for alias &amp;#39;default&amp;#39;...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.002s

OK
Destroying test database for alias &amp;#39;default&amp;#39;...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;드디어 하나의 단위 테스트가 완결되었다!&lt;/p&gt;
&lt;p&gt;이전에 개발했었던 기능 테스트(functional_test.py)를 다시 실행해보자!&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;# shell을 2개 띄우고 한쪽에는 django 서버를 띄움 
$ python manage.py runserver

# 다른 한쪽에서 기능 테스트 실행
$ python functional_test.py
F
======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;functional_test.py&amp;quot;, line 21, in test_can_start_a_list_and_retrieve_it_later
    self.fail(&amp;#39;Finish the test!&amp;#39;)
AssertionError: Finish the test!

----------------------------------------------------------------------
Ran 1 test in 2.256s

FAILED (failures=1)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;unittest 결과는 실패로 나오지만 self.fail()가 실행되어 asserionError 가 난 것이므로 이전 test 는 통과한 것이다.
첫번째 기능 테스트도 성공이다!&lt;/p&gt;
&lt;h2 id=&quot;느낀점&quot;&gt;&lt;a href=&quot;#%EB%8A%90%EB%82%80%EC%A0%90&quot; aria-label=&quot;느낀점 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;느낀점&lt;/h2&gt;
&lt;p&gt;이 장에서 저자가 독자들이 깨닫기 원하는 핵심적인 내용은 &lt;strong&gt;단위 테스트-코드 주기&lt;/strong&gt; 와  &lt;strong&gt;최소한의 코드 변경&lt;/strong&gt; 이 아닌가 싶다.
사실 기능이나 코드 양으로 따지면 되게 적은 양인데 저렇게 까지 자주 테스트 해야하나? 라는 생각도 든다.
하지만 여기서 이렇게 세밀한 스텝을 언급하는 이유는 아마도 TDD가 어떤 것인지 실제 경험해 보기 위함이라는 생각이 든다.
한번에 한 가지씩! 한번에 아주 작은 스텝을 밟아나가기! 실제 TDD를 개발하는 방법이 이렇다는걸 잘 깨닫게 된 계기가 된 것 같다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[파이썬을 이용한 클린 코드를 위한 테스트 주도 개발 - 2장 unittest 모듈을 이용한 기능 테스트 확장]]></title><description><![CDATA[앞으로 나아갈 방향 To-Do 웹 에플리케이션 개발 왜 선택했는가? 최소한의 실현 가능한(MinimumViableProduct) 예제로 적합 어떤 형태로든 확장이 가능하다.(마감일, 알람, 공유 기능 등..) 웹 프로그래밍 전반적인 내용과 TDD…]]></description><link>https://leonkim.dev/tdd/ch02/</link><guid isPermaLink="false">https://leonkim.dev/tdd/ch02/</guid><pubDate>Mon, 23 Dec 2019 11:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;앞으로-나아갈-방향&quot;&gt;&lt;a href=&quot;#%EC%95%9E%EC%9C%BC%EB%A1%9C-%EB%82%98%EC%95%84%EA%B0%88-%EB%B0%A9%ED%96%A5&quot; aria-label=&quot;앞으로 나아갈 방향 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;앞으로 나아갈 방향&lt;/h2&gt;
&lt;p&gt;To-Do 웹 에플리케이션 개발&lt;/p&gt;
&lt;p&gt;왜 선택했는가?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;최소한의 실현 가능한(MinimumViableProduct) 예제로 적합&lt;/li&gt;
&lt;li&gt;어떤 형태로든 확장이 가능하다.(마감일, 알람, 공유 기능 등..)&lt;/li&gt;
&lt;li&gt;웹 프로그래밍 전반적인 내용과 TDD 를 배울 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;기능-테스트&quot;&gt;&lt;a href=&quot;#%EA%B8%B0%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8&quot; aria-label=&quot;기능 테스트 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;기능 테스트&lt;/h2&gt;
&lt;h3 id=&quot;기능-테스트란-무엇인가&quot;&gt;&lt;a href=&quot;#%EA%B8%B0%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80&quot; aria-label=&quot;기능 테스트란 무엇인가 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;기능 테스트란 무엇인가?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;사용자 관점의 테스트&lt;/li&gt;
&lt;li&gt;특정 기능을 사용자가 어떻게 사용하며 이때 애플리케이션이 어떻게 반응하는지 확인하는 방식&lt;/li&gt;
&lt;li&gt;Functional Test == Acceptance Test == End to End Test&lt;/li&gt;
&lt;li&gt;사람이 이해할수 있는 스토리를 가지고 있어야 함&lt;/li&gt;
&lt;li&gt;테스트 코드에 스토리를 주석으로 기록하여 먼저 작성 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;맨 마지막의 스토리를 주석으로 먼저 작성하는 방식은 좀 신선하다.&lt;/p&gt;
&lt;h3 id=&quot;스토리-주석이-추가된-기능-테스트-코드-보기&quot;&gt;&lt;a href=&quot;#%EC%8A%A4%ED%86%A0%EB%A6%AC-%EC%A3%BC%EC%84%9D%EC%9D%B4-%EC%B6%94%EA%B0%80%EB%90%9C-%EA%B8%B0%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EB%B3%B4%EA%B8%B0&quot; aria-label=&quot;스토리 주석이 추가된 기능 테스트 코드 보기 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch02/02-01/functional_test.py&quot;&gt;스토리 주석이 추가된 기능 테스트 코드 보기&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;실제 실행시켜 보면&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;# 먼저 파이썬 서버를 실행
$ cd superlists
$ python manage.py runserver

# FT(Functional Test) 실행
$ python functional_test.py
Traceback (most recent call last):
  File &amp;quot;functional_test.py&amp;quot;, line 10, in &amp;lt;module&amp;gt;
    assert &amp;#39;To-Do&amp;#39; in browser.title
AssertionError&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;아직 브라우저 타이틀을 변경한 적이 없어서 실패한다.&lt;/p&gt;
&lt;h2 id=&quot;unittest로-바꾸기&quot;&gt;&lt;a href=&quot;#unittest%EB%A1%9C-%EB%B0%94%EA%BE%B8%EA%B8%B0&quot; aria-label=&quot;unittest로 바꾸기 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;unittest로 바꾸기&lt;/h2&gt;
&lt;h3 id=&quot;pure-python-테스트-코드의-문제점&quot;&gt;&lt;a href=&quot;#pure-python-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C%EC%9D%98-%EB%AC%B8%EC%A0%9C%EC%A0%90&quot; aria-label=&quot;pure python 테스트 코드의 문제점 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;pure python 테스트 코드의 문제점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;assert 조건이 맞지 않을시 발생하는 &lt;strong&gt;AssertionError&lt;/strong&gt; 메시지가 명시적으로 우리 기능의 무엇이 문제인지 알려주지 않음(단순 Exception 메시지)&lt;/li&gt;
&lt;li&gt;열려있는 크롬 브라우저 창을 테스트 종료시에 닫아주려면 try/finally 처리 필요하나 테스트마다 반복되는 코드가 발생함&lt;/li&gt;
&lt;li&gt;고로 이 모든걸 제공하는 unittest가 필요하다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;적용된-코드-보기&quot;&gt;&lt;a href=&quot;#%EC%A0%81%EC%9A%A9%EB%90%9C-%EC%BD%94%EB%93%9C-%EB%B3%B4%EA%B8%B0&quot; aria-label=&quot;적용된 코드 보기 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch02/02-02/functional_test.py&quot;&gt;적용된 코드 보기&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;모든 테스트는 &lt;strong&gt;test_&lt;/strong&gt; 로 시작하는 명칭으로 된 메서드만 실행한다. 현재 코드는 test&lt;em&gt;can&lt;/em&gt;start&lt;em&gt;a&lt;/em&gt;list&lt;em&gt;and&lt;/em&gt;retrieve&lt;em&gt;it&lt;/em&gt;later 가 테스트 메서드다&lt;/li&gt;
&lt;li&gt;setUp/tearDown 메서드는 각 테스트 메서드의 시작전/후로 실행한다. 각 테스트에 공통된 전후처리를 할때 사용한다&lt;/li&gt;
&lt;li&gt;assert 대신에 TestCase 의 self.assert* 메서드로 결과 검증을 한다.&lt;/li&gt;
&lt;li&gt;self.fail 은 강제적으로 테스트 실패를 발생시켜 에러 메시지를 출력한다.&lt;/li&gt;
&lt;li&gt;if &lt;strong&gt;name&lt;/strong&gt; == ’&lt;strong&gt;main&lt;/strong&gt;’: 은 다른 스크립트에 임포트되서 실행할 경우에는 실행되지 않는다. python 명령으로 실행될 시 실행된다. unittest.main() 은 테스트 실행자를 run 하는 코드이다. 또한 자동으로 파일내 test코드들을 찾아서 실행시켜 준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;실행시-결과&quot;&gt;&lt;a href=&quot;#%EC%8B%A4%ED%96%89%EC%8B%9C-%EA%B2%B0%EA%B3%BC&quot; aria-label=&quot;실행시 결과 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;실행시 결과&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python functional_test.py

F
======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File &amp;quot;functional_test.py&amp;quot;, line 19, in test_can_start_a_list_and_retrieve_it_later
    assert &amp;#39;To-Do&amp;#39; in self.browser.title
AssertionError

----------------------------------------------------------------------
Ran 1 test in 4.884s

FAILED (failures=1)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[파이썬을 이용한 클린 코드를 위한 테스트 주도 개발 - 1장 기능 테스트를 이용한 Django 설치]]></title><description><![CDATA[Testing Goat…]]></description><link>https://leonkim.dev/tdd/ch01/</link><guid isPermaLink="false">https://leonkim.dev/tdd/ch01/</guid><pubDate>Mon, 23 Dec 2019 10:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;testing-goat&quot;&gt;&lt;a href=&quot;#testing-goat&quot; aria-label=&quot;testing goat permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Testing Goat&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;염소가 소리친다. “테스트를 먼저 해! 테스트를 먼저 하라고!”&lt;/li&gt;
&lt;li&gt;사실 개발할 때는 잠시 테스트가 생각이 나지만 귀차니즘 + 시간부족에 초조함 등의 복합적인 생각이 들어 실천하지 않는 경우가 많다.&lt;/li&gt;
&lt;li&gt;먼저 테스트를 작성한 후 실행 -&gt; 예상대로 실패하는지 확인 -&gt; 실제 코드 작성 -&gt;  테스트 작성 2 -&gt; (무한 반복…)&lt;/li&gt;
&lt;li&gt;염소는 1번에 1가지만 행동 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;실전---django를-이용한-애플리케이션-개발&quot;&gt;&lt;a href=&quot;#%EC%8B%A4%EC%A0%84---django%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B0%9C%EB%B0%9C&quot; aria-label=&quot;실전   django를 이용한 애플리케이션 개발 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;실전 - Django를 이용한 애플리케이션 개발&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch01/01-01/functional_test.py&quot;&gt;첫번째 예제 보기&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;첫 단계 : Django가 제대로 설치되었는지 확인&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Django 개발 서버 가동&lt;/li&gt;
&lt;li&gt;서버에 있는 웹 페이지 로컬 PC상의 브라우저 접속(셀레늄 브라우저 자동화 툴)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;실행하면 어떻게 될까?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python3 functional_test.py
Traceback (most recent call last):
  File &amp;quot;functional_test.py&amp;quot;, line 1, in &amp;lt;module&amp;gt;
    from selenium import webdriver
ModuleNotFoundError: No module named &amp;#39;selenium&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;예상대로 안된다. 이제 이걸 되게 해야한다. selenium 파이썬 패키지가 안 깔려 있었다. 그래서 pip 로 추가했다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;pip install selenium&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다시 실행하면 다음과 같은 에러가 뜬다&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python3 functional_test.py

Traceback (most recent call last):
  File &amp;quot;functional_test.py&amp;quot;, line 3, in &amp;lt;module&amp;gt;
    browser = webdriver.Firefox()
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/firefox/webdriver.py&amp;quot;, line 164, in __init__
    self.service.start()
  File &amp;quot;/Users/pilhwankim/.pyenv/versions/tdd-with-python-env/lib/python3.7/site-packages/selenium/webdriver/common/service.py&amp;quot;, line 83, in start
    os.path.basename(self.path), self.start_error_message)
selenium.common.exceptions.WebDriverException: Message: &amp;#39;geckodriver&amp;#39; executable needs to be in PATH.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;두 가지 실행 되지 않는 문제를 예상해 볼 수 있는데&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;예제와 달리 내 PC에 크롬만 사용중이다.&lt;/li&gt;
&lt;li&gt;특정 드라이버가 PATH에 존재해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;나는 파이어 폭스를 쓰긴(설치하긴) 싫으므로 예제를 변경했다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PilhwanKim/books/blob/master/test_driven_development_with_python/ch01/01-02/functional_test.py&quot;&gt;변경한 테스트 예제&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;검색을 통해 드라이버를 인스톨하고 실행하는 법을 찾았다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://beomi.github.io/2016/12/27/Django-TDD-Study-01-Setting-DevEnviron/&quot;&gt;[DjangoTDDStudy] #01: 개발환경 세팅하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sites.google.com/a/chromium.org/chromedriver/home&quot;&gt;크롬 드라이버 다운로드&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;크롬은 실행이 잘 되었다. 다만 장고 웹서버가 띄워지지 않아서 웹 화면은 띄워지지 않았다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ python3 functional_test.py

Traceback (most recent call last):
  File &amp;quot;functional_test.py&amp;quot;, line 6, in &amp;lt;module&amp;gt;
    assert &amp;#39;Django&amp;#39; in browser.title
AssertionError&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 590px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 71.29237288135593%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAABWklEQVQ4y51Sy27CMBD0T1SVihIgcRwC5GGckFJeoaK0qsSh6qUf0Vv/X9O1QxCNDC09jLxee8eznmVzNUY6U1g/7jBbrpGqKZJJgTidgAchfC6uAnuotlhvdlhutti97lFtn1HOV5jOFsjy0jxwDVicSqRSIZvkSDNlYqkKs4+GMcJoRBj/Gcx1e3Cdbo1D7Bzibrd/NVi/78MGfdjreTh3fg5MFzXQCb16HodSOQIyxUZ6ercNZrusCZMkg+8HR6VNS6dkVoW2l3XhkAxJyDCDpF4lGcVpNC59hfUPdUFNQPMYZ4hJbTgYmi/Q6v9FGIgBhEEEEUZmaG0tt8nPEnIeQuYLqLKCLBaI5T2icU4Kgx8kdcxN/iKhVheSsqZVrc7msI6dTgd3tzd2l48KBRGJEcXUZpNvmaeVuY6DsnrDy8cXPF9cIAwGRBr9Oiau46JY7fH0/mkIvwFsfpPRgnbuhAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;크롬 화면&quot;
        title=&quot;크롬 화면&quot;
        src=&quot;/static/b6d029a12a48d97366b0e53c7a9816b9/b9e4f/ch01-01.png&quot;
        srcset=&quot;/static/b6d029a12a48d97366b0e53c7a9816b9/cf440/ch01-01.png 148w,
/static/b6d029a12a48d97366b0e53c7a9816b9/d2d38/ch01-01.png 295w,
/static/b6d029a12a48d97366b0e53c7a9816b9/b9e4f/ch01-01.png 590w,
/static/b6d029a12a48d97366b0e53c7a9816b9/f9b6a/ch01-01.png 885w,
/static/b6d029a12a48d97366b0e53c7a9816b9/177ab/ch01-01.png 944w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이제 Django 웹 서비스를 실제로 띄워 볼 차레이다. 이런 문제를 해결하는 과정 자체가 TDD라고 생각한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;먼저 검증할 수 있는 수단(TestCase)을 먼저 만든다.&lt;/li&gt;
&lt;li&gt;검증이 실패하는 것을 확인한다.&lt;/li&gt;
&lt;li&gt;해결책을 찾아 적용한다.&lt;/li&gt;
&lt;li&gt;테스트를 통해 검증해 본다. 실패하면 또 해결책을 찾아 적용한다.&lt;/li&gt;
&lt;li&gt;테스트를 돌려봐서 성공하면 TDD 한 싸이클이 완성된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ django-admin startproject superlists
# 장고 프로젝트가 생성된다.
$ cd superlists
# 장고 서버 실행 명령
$ python manage.py runserver
atching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run &amp;#39;python manage.py migrate&amp;#39; to apply them.

November 26, 2019 - 22:20:42
Django version 2.2.7, using settings &amp;#39;superlists.settings&amp;#39;
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;새로운 shell 을 실행시켜서 이전의 테스트 코드를 다시 돌려보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;python functional_test.py&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 590px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 58.833768494342905%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsSAAALEgHS3X78AAABQUlEQVQoz62S207CQBCG91U8oDFBiRdCRBFsoKGIPq+v4Z3xhliglLIt3R42DT38bklrNg0IF07yZWcyk39nJkNadze4bTWhDDT0+iqa7Q6u6g2cnl0cSQ0ngvPaJerXDZD+cAx19Art5Q2qNoaiDoX4EO1OD/cHaD8946HgsaugqwxAHMcBtW18TL6wWC3hugyOs8aK0v2sKCi1MTcMTGY6vmdT6LqO5dICQWGbLMF/GEmzDJkgt/wt47QkLeI03fKbK/wqpBQqQcUv86VVa3YKysVyt3Jscg+x6KpaV/2Q7NqDXJBbPuL7/BPhJsK+BnYKygVxHCNJEgQhB/ND+J4P5gXwA77N5RzsUBbknMMW52Sa5vYc8lMxhD9dGLAsC0EQHDeyLBpFERhzBQxUCLqeeNkaIQ+PH3nfHv/as+z/AGfkU1YkJyTOAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;테스트 성공&quot;
        title=&quot;테스트 성공&quot;
        src=&quot;/static/f1fb5882a748c28cd84141c97002e425/b9e4f/ch01-02.png&quot;
        srcset=&quot;/static/f1fb5882a748c28cd84141c97002e425/cf440/ch01-02.png 148w,
/static/f1fb5882a748c28cd84141c97002e425/d2d38/ch01-02.png 295w,
/static/f1fb5882a748c28cd84141c97002e425/b9e4f/ch01-02.png 590w,
/static/f1fb5882a748c28cd84141c97002e425/f9b6a/ch01-02.png 885w,
/static/f1fb5882a748c28cd84141c97002e425/67bc4/ch01-02.png 1149w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;사실 직접 크롬을 띄우고 localhost:8000 을 입력한 것을 자동화 한 것 밖에는 없지만 저자가 이 장을 이렇게 장황하게 만든건 의미가 있다고 본다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Testing Goat 를 설명하면서 TDD의 기본적인 전제를 확실히 익히려는 의도&lt;/li&gt;
&lt;li&gt;TDD는 프로젝트 시작하는 순간부터 가능하다는 것을 보여주려는 의도&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;아무튼 첫번째 장을 마쳤다. 어떤 고급진 스킬이 있는 장은 아니지만 이 책의 저자에게는 바둑의 포석과 같은 1장이 아니었나 생각한다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[파이썬을 이용한 클린 코드를 위한 테스트 주도 개발]]></title><description><![CDATA[설치 pyenv-virtualenv, direnv 가 설치되어 있다는 가정하에 시작한다. 참고 글 pyenv-virtualenv 설치 1 : https://www.44bits.io/ko/post/direnv_for_managing_directory…]]></description><link>https://leonkim.dev/tdd/intro/</link><guid isPermaLink="false">https://leonkim.dev/tdd/intro/</guid><pubDate>Mon, 23 Dec 2019 09:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;설치&quot;&gt;&lt;a href=&quot;#%EC%84%A4%EC%B9%98&quot; aria-label=&quot;설치 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;설치&lt;/h1&gt;
&lt;p&gt;pyenv-virtualenv, direnv 가 설치되어 있다는 가정하에 시작한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;# python 3.7 설치
pyenv install 3.7.4

# virtualenv 환경 생성
pyenv virtualenv 3.7.4 tdd-with-python-env

# .envrc 등록
echo &amp;quot;source activate tdd-with-python-env&amp;quot; &amp;gt; .envrc

# 현재 디렉토리를 direnv 경로 등록
direnv allow

# pip 로 pypi 패키지 설치
pip install -r requirements.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;참고-글&quot;&gt;&lt;a href=&quot;#%EC%B0%B8%EA%B3%A0-%EA%B8%80&quot; aria-label=&quot;참고 글 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;참고 글&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;pyenv-virtualenv 설치 1 : &lt;a href=&quot;https://www.44bits.io/ko/post/direnv_for_managing_directory_environment&quot;&gt;https://www.44bits.io/ko/post/direnv_for_managing_directory_environment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;pyenv-virtualenv 설치 2 : &lt;a href=&quot;http://taewan.kim/post/python_virtual_env/&quot;&gt;http://taewan.kim/post/python_virtual_env/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;1부-tdd와-django-개요&quot;&gt;&lt;a href=&quot;#1%EB%B6%80-tdd%EC%99%80-django-%EA%B0%9C%EC%9A%94&quot; aria-label=&quot;1부 tdd와 django 개요 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;1부 TDD와 Django 개요&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/tdd-with-python/ch01&quot;&gt;1장 기능 테스트를 이용한 Django 설치&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/tdd-with-python/ch02&quot;&gt;2장 unittest 모듈을 이용한 기능 테스트 확장&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/tdd-with-python/ch03&quot;&gt;3장 단위 테스트를 이용한 간단한 홈페이지 테스트&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/tdd-with-python/ch04&quot;&gt;4장 왜 테스트를 하는 것인가(그리고 리펙토링)?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/tdd-with-python/ch05&quot;&gt;5장 사용자 입력 저장하기&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;2부-웹-개발-핵심편&quot;&gt;&lt;a href=&quot;#2%EB%B6%80-%EC%9B%B9-%EA%B0%9C%EB%B0%9C-%ED%95%B5%EC%8B%AC%ED%8E%B8&quot; aria-label=&quot;2부 웹 개발 핵심편 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;2부 웹 개발 핵심편&lt;/h1&gt;
&lt;h1 id=&quot;3부-고급편&quot;&gt;&lt;a href=&quot;#3%EB%B6%80-%EA%B3%A0%EA%B8%89%ED%8E%B8&quot; aria-label=&quot;3부 고급편 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3부 고급편&lt;/h1&gt;
&lt;h1 id=&quot;부록-테스트-고트님께-복종하라&quot;&gt;&lt;a href=&quot;#%EB%B6%80%EB%A1%9D-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B3%A0%ED%8A%B8%EB%8B%98%EA%BB%98-%EB%B3%B5%EC%A2%85%ED%95%98%EB%9D%BC&quot; aria-label=&quot;부록 테스트 고트님께 복종하라 permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;부록 테스트 고트님께 복종하라&lt;/h1&gt;</content:encoded></item><item><title><![CDATA[about]]></title><description><![CDATA[Pilhwan Kim  About Pilhwan Kim is a backend Developer at PeopleFund(https://peoplefund.co.kr). I have skills for python and java web…]]></description><link>https://leonkim.dev/resume-en/</link><guid isPermaLink="false">https://leonkim.dev/resume-en/</guid><pubDate>Sun, 27 Jan 2019 16:21:13 GMT</pubDate><content:encoded>&lt;h1 id=&quot;pilhwan-kim&quot;&gt;&lt;a href=&quot;#pilhwan-kim&quot; aria-label=&quot;pilhwan kim permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Pilhwan Kim&lt;/h1&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 200px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAC4jAAAuIwF4pT92AAAEeUlEQVQ4y22Ue0xTVxzH+VcREzELydgiUXRmmJjs4RKc2R+ODeMeyeJ0yoYayTIXxZkwH5tTicIEERdRhMhAUSkoaAuKvBziBHkIKAoIQ3lT21La0vb2qZ+deyuIcyc5uTftuZ/z/f6+v3P8HG43DtfE9OB0e5AcLtzep9jE805rGxVV1VzSlNDUcg+jeZxngOR04XD6vpGfkph2sd7P4fZBfNOr/CG/PzEY2fzjVubMCWHuvFCCg99gXuh8wpcuIys7R2z4TNnYKYt4DrWLTfymwpTdhFKzTWJdVDQBATMFKJiQkBDl3X9GANOmTWdWYCBXr5UhD7vknATKYhSgS8BcslWxw1OxaF/8QaZN92fB3LksXryYhW8tJGh2ILNmziRw9msEBQURHr4U/eiYIkaxPgF0PQc6hTLhgu5HfSwKW8SKJWHk7N5AytYokjavJWH9Z8Qsf5d35ofwevCbzBBqCy+rFZW2KSp9QI9XUSePi5orRLwXRn/R70gNuZhrTmG5ns7o1VQakmLIjP6Y8LAF+PsHkHvuvBKQze54FSjXTh5l1X+TuHkNxtIURqpPY2ktYahaRdH+70nZ9AUZ0ZHErVyq2C4ur8IrvrG+BPR4cAvgRDi9IzpUR/ZSfmwXHaWn0bVU0Zh/gpJz6aRt+Y64Tz8gfs0nfPh2KGePHsbheaoE86KGHp9Cpf+ESq3RzLW801RpzlCYdYSykwfI2htLvSaHS7/FULD1S06ujyQ2YgmHVq/EYrG+1IuTQNm63eHEaHPSfKOSxsxfaFUd5dGVTEr3bKT7wmF0NSoeZsej3hHF/q8+Yt+qFUKdNAXomgL0yEAHZqeX4Y5Ghq+f5UldMYbbalrTf6U68SfGWspFWKn8lbCJ5LXL0aQm4BSWX7TNS0A5aSdmhwd9bwfG24UY64sw1hZRlbwLTdwGOgtS6VMnU3NoE7n7tmEctyk2fUdwEuidBMo/WkSB9aOjaG/lMlSZSX1GIv2VRVxP3MW1A9vpuXSYc1s+R5W8B5PkEglLk0D7f4FyMFbJgdHuQtvViK4ul878DHoLz9P55zGGik9Rl76T1G+WkZe0G4PFhtlq910Uoo6vAH3BuDCJs6w3WRloLsfUVIS+Vi0CKaC3JI3s7atJWrOMk7HrhGW7cORTKf2fwknbYoFxXMJgtvG4oZRHFTncPZuCamc0qVERZMWsJH1jJJUlanqHnijXnNzcNtElfi6vAHmn2BbSrXL7iP4akzxoBx7TKsKo/mMHlYdiuZEcS1XiD+RvW8W374eScfyE0ruyK5PVNkWh2weT6zEu11HY0RpMtN+pp/9mHqa2MgYrsukpTuOBKonLcV+T83MMtfXN3Kxr5J++QXRjFl/bTJwS20T9xK1ssgrbwvK9mgp6yk+hrb3Iw4IUBm+q6Nak0XLmIF0P7tPW1Ufz3XbaOroZ1BrkG9tXTLOo24hhjH5xlvVj49yqa6JFLNQ+7qZdfRxDczEjtQXoGtUM3MhDN9TH/a5+evqGaG3rZGBET/+wjn8BBQkmywSm3B4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;크롬 화면&quot;
        title=&quot;크롬 화면&quot;
        src=&quot;/static/30379c70eb507d5b2acc497b91471984/9ec3c/profile.png&quot;
        srcset=&quot;/static/30379c70eb507d5b2acc497b91471984/cf440/profile.png 148w,
/static/30379c70eb507d5b2acc497b91471984/9ec3c/profile.png 200w&quot;
        sizes=&quot;(max-width: 200px) 100vw, 200px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;h2 id=&quot;about&quot;&gt;&lt;a href=&quot;#about&quot; aria-label=&quot;about permalink&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;About&lt;/h2&gt;
&lt;p&gt;Pilhwan Kim is a backend Developer at PeopleFund(&lt;a href=&quot;https://peoplefund.co.kr&quot;&gt;https://peoplefund.co.kr&lt;/a&gt;). I have skills for python and java web programming. I have focused on verifiable and reliable backend development by TDD(Test-driven Development) and OOP(Object-Oriented Programming) principles.&lt;/p&gt;
&lt;p&gt;I have various experiences in web development of information systems such as IoT(Internet of Things) monitoring solution, SNS, Fin-tech platform.&lt;/p&gt;
&lt;p&gt;The fear of the LORD is the beginning of wisdom, and knowledge of the Holy One is understanding. - Proverbs 9:10 &lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Specialties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Web development experience of Django, Spring&lt;/li&gt;
&lt;li&gt;HTTP and Rest API knowledge, design and implementation&lt;/li&gt;
&lt;li&gt;RDB &amp;#x26; NoSQL configuration and data modeling&lt;/li&gt;
&lt;li&gt;Development in AWS cloud&lt;/li&gt;
&lt;li&gt;CI experience (git, Jenkins, Confluence, AWS codebuild)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Skills:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Back-End     &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Django&lt;/li&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;li&gt;Spring&lt;/li&gt;
&lt;li&gt;Apache&lt;/li&gt;
&lt;li&gt;Nginx&lt;/li&gt;
&lt;li&gt;RabbitMq&lt;/li&gt;
&lt;li&gt;Linux(Ubuntu, Centos)&lt;/li&gt;
&lt;li&gt;AWS&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DB                          &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MySQL &lt;/li&gt;
&lt;li&gt;Redis &lt;/li&gt;
&lt;li&gt;Cassandra&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Development Tool  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pycharm &lt;/li&gt;
&lt;li&gt;Intellij &lt;/li&gt;
&lt;li&gt;Confluence&lt;/li&gt;
&lt;li&gt;Jira&lt;/li&gt;
&lt;li&gt;Git / Github&lt;/li&gt;
&lt;li&gt;Jenkins&lt;/li&gt;
&lt;li&gt;Slack&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>