diff --git a/src/chap3/chap3_1to3_3/lists/__init__.py b/src/chap3/chap3_1to3_3/lists/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/chap3/chap3_1to3_3/lists/admin.py b/src/chap3/chap3_1to3_3/lists/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/src/chap3/chap3_1to3_3/lists/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/src/chap3/chap3_1to3_3/lists/apps.py b/src/chap3/chap3_1to3_3/lists/apps.py new file mode 100644 index 0000000..efe43f0 --- /dev/null +++ b/src/chap3/chap3_1to3_3/lists/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ListsConfig(AppConfig): + name = 'lists' diff --git a/src/chap3/chap3_1to3_3/lists/migrations/__init__.py b/src/chap3/chap3_1to3_3/lists/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/chap3/chap3_1to3_3/lists/models.py b/src/chap3/chap3_1to3_3/lists/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/src/chap3/chap3_1to3_3/lists/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/src/chap3/chap3_1to3_3/lists/tests.py b/src/chap3/chap3_1to3_3/lists/tests.py new file mode 100644 index 0000000..7408669 --- /dev/null +++ b/src/chap3/chap3_1to3_3/lists/tests.py @@ -0,0 +1,8 @@ +from django.test import TestCase + +# Create your tests here. + +class SmokeTest(TestCase): + + def test_bad_maths(self): + self.assertEqual(1+1,3) \ No newline at end of file diff --git a/src/chap3/chap3_1to3_3/lists/views.py b/src/chap3/chap3_1to3_3/lists/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/src/chap3/chap3_1to3_3/lists/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/src/chap3/chap3_1to3_3/manage.py b/src/chap3/chap3_1to3_3/manage.py new file mode 100755 index 0000000..3e6d019 --- /dev/null +++ b/src/chap3/chap3_1to3_3/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "superlists.settings") + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/src/chap3/chap3_1to3_3/superlists/__init__.py b/src/chap3/chap3_1to3_3/superlists/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/chap3/chap3_1to3_3/superlists/settings.py b/src/chap3/chap3_1to3_3/superlists/settings.py new file mode 100644 index 0000000..a396f71 --- /dev/null +++ b/src/chap3/chap3_1to3_3/superlists/settings.py @@ -0,0 +1,120 @@ +""" +Django settings for superlists project. + +Generated by 'django-admin startproject' using Django 1.11.29. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.11/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '5ty+3qgs$cadiv2l4)a)#ikp1=bfd$41^d^@07#!q(t4_$6igh' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'superlists.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'superlists.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.11/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.11/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.11/howto/static-files/ + +STATIC_URL = '/static/' diff --git a/src/chap3/chap3_1to3_3/superlists/urls.py b/src/chap3/chap3_1to3_3/superlists/urls.py new file mode 100644 index 0000000..5218133 --- /dev/null +++ b/src/chap3/chap3_1to3_3/superlists/urls.py @@ -0,0 +1,21 @@ +"""superlists URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.11/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import url +from django.contrib import admin + +urlpatterns = [ + url(r'^admin/', admin.site.urls), +] diff --git a/src/chap3/chap3_1to3_3/superlists/wsgi.py b/src/chap3/chap3_1to3_3/superlists/wsgi.py new file mode 100644 index 0000000..e2d9e59 --- /dev/null +++ b/src/chap3/chap3_1to3_3/superlists/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for superlists project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "superlists.settings") + +application = get_wsgi_application() diff --git a/textbook/chap3.md b/textbook/chap3.md new file mode 100644 index 0000000..391d229 --- /dev/null +++ b/textbook/chap3.md @@ -0,0 +1,57 @@ +# Chapter 3. Testing a Simple Home Page with Unit Tests + +Chap2 introduced writing a functional test using unittest module (expected failure), now create a working To-Do list app + +## 3.1 Our First Django App, and Our First Unit Test + +* Django encourage structure the code into apps. We can reuse app developed by us or others + +Create an app `lists` (sub-directory) via `python manage.py startapp lists` along other `functional_tests.py` & `manage.py`, this subdir should include `tests.py` be default + +## 3.2 Unit Tests, and How They Differ from Functional Tests + +Unit Tests vs Functional Tests: + +* **Unit Tests (UT)**: tests app from inside as programmer +* **Functional Tests (FT)**: test app from outside as user + +TDD approach will cover both, with following workflow: + +1. Writing FT that describing new functionality +2. After expected failure of FT, design app that can get it pass FT. And write UTs that define how code behave. Benchmark, each production code we write should be tested by at least 1 of our unit tests. +3. After having a failure UT, write the smallest amount of app code to pass the UT. +4. Iterate step 2 & 3 to the point that we can test with FT +5. Rerun FT and it should pass. +6. Keep writing new UT & FT + +Summary: + +* FT evaluate app on high level +* UT drive development on low level + +## 3.3 Unit Testing in Django + +1. `lists/tests.py` provide place to write UT/FT. Adding following to import Django's `TestCase` which is inherited from `unittest` + +```python +from django.test import TestCase + +# Create tests from here +``` + +2. Before writing any new FT/UT, make sure UT can be run by automated test runner, as `lists/tests.py` is created by unittest. while previously we directly call `python functional_tests.py`. Running Django's test framework via: + +```python +python manage.py test +``` + +## 3.4 Django's MVC, URLs, and View Functions + + +## At Last! We Actually Write Some Application Code! + +## urls.py + +## Unit Testing a View + +### The Unit-Test/Code Cycle \ No newline at end of file