iT邦幫忙

3

[不做怎麼知道系列之Android開發者的30天後端養成故事 Day11] - 測試可以吃嗎? #高品質程式 #單元測試 #整合測試

Sam 2020-02-17 16:39:031162 瀏覽

https://ithelp.ithome.com.tw/upload/images/20200217/20124548js8w2rqAYF.png

哈囉,我們又見面了,今天我們來看看怎麼測試一個網站,在開始實作之前,我們必須先釐清一些觀念,包含 架站的過程中會遇到什麼事情、為什麼需要 測試

架站流程

我簡單介紹一下要架站,需要什麼流程,其中 需求與設計開發測試佈署 這四個步驟的流程,會依照你們團隊所採用的 開發方法 而有所不同,這是軟體工程中的一部分,典型舊型的開發方法是 瀑布模型(Waterfall),以及新型熱門的敏捷開發方法有 ScrumXP 等等,至於怎麼挑選適合團隊的開發方法,會有團隊成員的性格、專案性質等等的變因,再講下去就扯遠了,我們回到流程。

  • 架設開發環境
    • 安裝程式語言
    • 安裝框架
    • 安裝編輯器/IDE
  • 需求與設計
    • 我的需求在 Day1 已經提完 (雖然是很粗略的版本),但因為我是一個人的開發團隊,所以有很多需求細節,都在我腦中,只是沒有文件化而已
    • 設計則是隨著我對 pythondjango 的理解而跟著變動
  • 開發
    • 就是實際產生網站功能的地方
  • 測試
    • 測試 功能 是否符合 需求
    • 有些公司可能將測試獨立出一個 QA 部門 或 測試人員,來負責這部分
  • 佈署
    • 將網站放到一個地方給人使用,而這個地方可能是 公司內部的伺服器,可能是 雲端伺服器,也可能是我自己的電腦 等等

前面 Day1~Day10 已經走到開發的階段,接下來我們來看看測試。

測試,對於開發的重要性 (痛點在哪裡?)

在現在的軟體產業中,需求變化非常快,有可能今天跟你說我需要部落格,明天就跟你說部落格先不要做了,我現在需要購物車,那麼這時候,身為開發人員的你,該怎麼維持你的程式碼品質 ?

你可能會說「我光是要開發就已經來不及了,我怎麼可能還要顧我的品質 ?,都是客戶或老闆的錯啊,他們不應該這樣變來變去,這樣怎麼可能把事情做好呢 !」,但事實就是這樣,山不轉路轉,你勢必需要去習慣這樣的文化,所以再回到問題,如何兼顧 開發速度、開發彈性 與 程式碼品質 ?

TDD 的觀念中,品質就是從 測試 來的,當你的測試夠明確,你很知道你要達到什麼樣的效果,就是一個好程式碼、一個好功能,但你又會說「我開發就已經不夠時間了,怎麼還有時間寫測試 !」,但問題是,你先寫好測試,才有機會省下開發的時間,才能繼續寫測試,可是沒有時間怎麼寫測試,那你沒有寫測試怎麼有時間,所以才要先寫測試才有時間阿 ...

恩 ... 相信你感受到這個矛盾的現象了。總之,我們就是想辦法寫好測試,開發 就盡量去符合 測試 的目標。

單元測試 (Unit Testing)

單元測試就是「最小單位的測試」,可以小到某個 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)

現在的專案架構

https://ithelp.ithome.com.tw/upload/images/20200217/20124548yODbwbSpv8.png

別忘了先把 django 跑起來

$ python manage.py runserver

單元測試跑起來

$ python manage.py test shop.tests.test_models

這邊指定只要跑 test_models.py 這個檔案裡面的測試項目。

單元測試結果

https://ithelp.ithome.com.tw/upload/images/20200217/201245488RACEAI425.png

因為實際上我把十個欄位都寫進測試了,所以出來會有 10 tests。(先忽略我的 branch 名稱,因為我是做完整個 CI 流程才回來寫文章的)

單元測試結束,接著來到 整合測試 (Integration Testing)

我們在這邊先寫個簡單的整合測試,來測試網站的其中一個頁面是否正常。

shop/tests/test_views.py

我們針對 views.py 裡面的 shop_view,來測試 shop_view 這個頁面的 function namereachabletemplate file namepage 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

整合測試結果

https://ithelp.ithome.com.tw/upload/images/20200217/20124548JqJb8AloZm.png
(先忽略我的 branch 名稱,因為我是做完整個 CI 流程才回來寫文章的)

如此一來,將來我在開發的時候,可能不小心按到鍵盤,更改到了頁面標題名稱變成 RS Django Sho,我在跑測試的時候就會抓到,不會等到別人來告訴我。也有一種情況是,你去上廁所忘記鎖定,然後你家的貓跳上桌鍵盤踩踩踩 XD。

測試所帶來的效益

當你寫了 單元測試整合測試 後,加上有 版本控制 的輔助,你可以 更加大膽地開發新功能、更加大膽地重構,因為有這些工具存在,開發者可以 不用那麼怕 牽一髮動全身的窘境。

單日心得總結

我必須先澄清,我假日並沒有偷懶 QQ,因為我又犯上次犯過的錯誤,就是把單篇範圍又訂得太大了,導致一直不知道怎麼收尾。

這篇文章我改了超多次,原定的主題是「試玩 CircleCI,體驗持續整合」,等我註冊完 CircleCI、綁定 Github 專案後,我發現我根本還沒寫測試,怎麼持續整合 QQ,再來把測試寫完,在本地端測試成功了,我就在思考一個問題,那... 我該怎麼在 CI 上面執行我的網站 !?,執行了網站才能跑測試阿,心想「該死,我把順序搞反了...」,可是這時候已經過完一整天了,也無法有系統的寫成文章。

隔天我花了一整天研究 Docker,試圖想要把我的執行環境包在 Docker 裡面,然後再放到 CircleCI 裡面去執行我的網站,最後才能跑測試,然而 Docker 這東西真的不是我想像的那麼簡單,就這樣,一整天又過了,我還是無法寫成文章,所以就到今天了。

一直違反自己的承諾的感覺是真的很差,尤其又是公開宣布的承諾,不知道你們有沒有這種感覺,明明想要遵守承諾,可是又不想讓文章淪為散落的雜項日記,自己心理也很清楚,不是因為偷懶造成的,也不是刻意要違背承諾,總之,這種感覺不太妙。

所以呢,今天我們體驗了 測試 帶來的效益,接下來我們試著瞭解 Docker 能帶給我們什麼感受吧 !

我是 RS,這是我的 不做怎麼知道系列 文章,我們 明天見。



尚未有邦友留言

立即登入留言