哈囉,我們又見面了,今天我們來看看怎麼測試一個網站,在開始實作之前,我們必須先釐清一些觀念,包含 架站的過程中會遇到什麼事情、為什麼需要 測試。
我簡單介紹一下要架站,需要什麼流程,其中 需求與設計、開發、測試 與 佈署 這四個步驟的流程,會依照你們團隊所採用的 開發方法 而有所不同,這是軟體工程中的一部分,典型舊型的開發方法是 瀑布模型(Waterfall
),以及新型熱門的敏捷開發方法有 Scrum、XP 等等,至於怎麼挑選適合團隊的開發方法,會有團隊成員的性格、專案性質等等的變因,再講下去就扯遠了,我們回到流程。
python
與 django
的理解而跟著變動前面 Day1~Day10 已經走到開發的階段,接下來我們來看看測試。
在現在的軟體產業中,需求變化非常快,有可能今天跟你說我需要部落格,明天就跟你說部落格先不要做了,我現在需要購物車,那麼這時候,身為開發人員的你,該怎麼維持你的程式碼品質 ?
你可能會說「我光是要開發就已經來不及了,我怎麼可能還要顧我的品質 ?,都是客戶或老闆的錯啊,他們不應該這樣變來變去,這樣怎麼可能把事情做好呢 !」,但事實就是這樣,山不轉路轉,你勢必需要去習慣這樣的文化,所以再回到問題,如何兼顧 開發速度、開發彈性 與 程式碼品質 ?
在 TDD 的觀念中,品質就是從 測試 來的,當你的測試夠明確,你很知道你要達到什麼樣的效果,就是一個好程式碼、一個好功能,但你又會說「我開發就已經不夠時間了,怎麼還有時間寫測試 !」,但問題是,你先寫好測試,才有機會省下開發的時間,才能繼續寫測試,可是沒有時間怎麼寫測試,那你沒有寫測試怎麼有時間,所以才要先寫測試才有時間阿 ...
恩 ... 相信你感受到這個矛盾的現象了。總之,我們就是想辦法寫好測試,開發 就盡量去符合 測試 的目標。
單元測試就是「最小單位的測試」,可以小到某個 class 底下的變數型別,也可以到 class 底下的 method 行為,是否符合預期。
shop/models.py
這是我們在 Day9 所完成的 model。
from django.db import models
from django.utils import timezone
import os
def get_image_path(instance, filename):
return os.path.join('uploads', filename)
class Product(models.Model):
# basic info
name = models.CharField(max_length=100, blank=False)
price = models.DecimalField(blank=False, max_digits=100, decimal_places=0)
img = models.ImageField(upload_to=get_image_path, default=get_image_path(instance=0, filename='product-1.jpg'))
# discount
on_sale = models.BooleanField(blank=True, null=True)
tag = models.CharField(max_length=20, blank=True, null=True)
percent_off = models.DecimalField(blank=True, null=True, max_digits=30, decimal_places=1)
sale_price = models.DecimalField(blank=True, null=True, max_digits=30, decimal_places=0)
# for analysis
bought_counter = models.DecimalField(default=0, max_digits=30, decimal_places=0)
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
def publish(self):
self.published_date = timezone.now
self.save()
def __str__(self):
return self.name
shop/tests/test_models.py
我們先測試一下 field type 是不是符合預期,這邊只舉出四個欄位,省略其他欄位。
from django.test import TestCase
from django.db.models import DecimalField, CharField, \
ImageField, BooleanField, DateTimeField
from shop.models import Product
class TestProductFieldType(TestCase):
def test_name_field_type(self):
assert_same_type(self, "name", CharField)
def test_price_field_type(self):
assert_same_type(self, "price", DecimalField)
def test_img_field_type(self):
assert_same_type(self, "img", ImageField)
def test_onsale_field_type(self):
assert_same_type(self, "on_sale", BooleanField)
...
def assert_same_type(self, field_name, field_type):
self.assertTrue(
isinstance(
get_product_field(field_name),
field_type
)
)
def get_product_field(field_name):
return Product._meta.get_field(field_name)
現在的專案架構
$ python manage.py runserver
$ python manage.py test shop.tests.test_models
這邊指定只要跑 test_models.py
這個檔案裡面的測試項目。
因為實際上我把十個欄位都寫進測試了,所以出來會有 10 tests。(先忽略我的 branch 名稱,因為我是做完整個 CI 流程才回來寫文章的)
我們在這邊先寫個簡單的整合測試,來測試網站的其中一個頁面是否正常。
shop/tests/test_views.py
我們針對 views.py
裡面的 shop_view
,來測試 shop_view
這個頁面的 function name
、reachable
、template file name
、page title
等等屬性。
from django.test import TestCase
from django.urls import resolve
from shop.views import shop_view
class TestShopPageView(TestCase):
def test_resolve_shop(self):
found = resolve('/')
self.assertEqual(found.func.__name__, shop_view.__name__)
# 期望 found.func.__name__ 會等於 shop_view
def test_reachable_shop(self):
response = self.client.get('/')
self.assertEqual(response.status_code, 200)
# 期望 status_code 會等於 200 (也就是正常)
def test_template_shop(self):
response = self.client.get('/')
self.assertTemplateUsed(response, 'shop/shop.html')
# 期望這頁面的 template 是 shop/shop.html 這個檔案
def test_title_shop(self):
response = self.client.get('/')
self.assertContains(response, 'RS Django Shop')
# 期望這頁面的標題是 RS Django Shop
(先忽略我的 branch 名稱,因為我是做完整個 CI 流程才回來寫文章的)
如此一來,將來我在開發的時候,可能不小心按到鍵盤,更改到了頁面標題名稱變成 RS Django Sho
,我在跑測試的時候就會抓到,不會等到別人來告訴我。也有一種情況是,你去上廁所忘記鎖定,然後你家的貓跳上桌鍵盤踩踩踩 XD。
當你寫了 單元測試
與 整合測試
後,加上有 版本控制
的輔助,你可以 更加大膽地開發新功能、更加大膽地重構,因為有這些工具存在,開發者可以 不用那麼怕 牽一髮動全身的窘境。
我必須先澄清,我假日並沒有偷懶 QQ,因為我又犯上次犯過的錯誤,就是把單篇範圍又訂得太大了,導致一直不知道怎麼收尾。
這篇文章我改了超多次,原定的主題是「試玩 CircleCI
,體驗持續整合」,等我註冊完 CircleCI
、綁定 Github
專案後,我發現我根本還沒寫測試,怎麼持續整合 QQ,再來把測試寫完,在本地端測試成功了,我就在思考一個問題,那... 我該怎麼在 CI
上面執行我的網站 !?,執行了網站才能跑測試阿,心想「該死,我把順序搞反了...」,可是這時候已經過完一整天了,也無法有系統的寫成文章。
隔天我花了一整天研究 Docker,試圖想要把我的執行環境包在 Docker
裡面,然後再放到 CircleCI
裡面去執行我的網站,最後才能跑測試,然而 Docker
這東西真的不是我想像的那麼簡單,就這樣,一整天又過了,我還是無法寫成文章,所以就到今天了。
一直違反自己的承諾的感覺是真的很差,尤其又是公開宣布的承諾,不知道你們有沒有這種感覺,明明想要遵守承諾,可是又不想讓文章淪為散落的雜項日記,自己心理也很清楚,不是因為偷懶造成的,也不是刻意要違背承諾,總之,這種感覺不太妙。
所以呢,今天我們體驗了 測試 帶來的效益,接下來我們試著瞭解 Docker
能帶給我們什麼感受吧 !
我是 RS,這是我的 不做怎麼知道系列 文章,我們 明天見。