<span id="mktg5"></span>

<i id="mktg5"><meter id="mktg5"></meter></i>

        <label id="mktg5"><meter id="mktg5"></meter></label>
        最新文章專題視頻專題問答1問答10問答100問答1000問答2000關(guān)鍵字專題1關(guān)鍵字專題50關(guān)鍵字專題500關(guān)鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關(guān)鍵字專題關(guān)鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
        問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
        當(dāng)前位置: 首頁(yè) - 科技 - 知識(shí)百科 - 正文

        在Python的Flask框架中實(shí)現(xiàn)單元測(cè)試的教程

        來源:懂視網(wǎng) 責(zé)編:小采 時(shí)間:2020-11-27 14:39:33
        文檔

        在Python的Flask框架中實(shí)現(xiàn)單元測(cè)試的教程

        在Python的Flask框架中實(shí)現(xiàn)單元測(cè)試的教程: 概要 在前面的章節(jié)里我們專注于在我們的小應(yīng)用程序上一步步的添加功能上。到現(xiàn)在為止我們有了一個(gè)帶有數(shù)據(jù)庫(kù)的應(yīng)用程序,可以注冊(cè)用戶,記錄用戶登陸退出日志以及查看修改配置文件。 在本節(jié)中,我們不為應(yīng)用程序添加任何新功能,相反,我們要尋找一種方法來
        推薦度:
        導(dǎo)讀在Python的Flask框架中實(shí)現(xiàn)單元測(cè)試的教程: 概要 在前面的章節(jié)里我們專注于在我們的小應(yīng)用程序上一步步的添加功能上。到現(xiàn)在為止我們有了一個(gè)帶有數(shù)據(jù)庫(kù)的應(yīng)用程序,可以注冊(cè)用戶,記錄用戶登陸退出日志以及查看修改配置文件。 在本節(jié)中,我們不為應(yīng)用程序添加任何新功能,相反,我們要尋找一種方法來

        概要

        在前面的章節(jié)里我們專注于在我們的小應(yīng)用程序上一步步的添加功能上。到現(xiàn)在為止我們有了一個(gè)帶有數(shù)據(jù)庫(kù)的應(yīng)用程序,可以注冊(cè)用戶,記錄用戶登陸退出日志以及查看修改配置文件。

        在本節(jié)中,我們不為應(yīng)用程序添加任何新功能,相反,我們要尋找一種方法來增加我們已寫代碼的穩(wěn)定性,我們還將創(chuàng)建一個(gè)測(cè)試框架來幫助我們防止將來程序中出現(xiàn)的失敗和回滾。

        讓我們來找bug

        在上一章的結(jié)尾談到,我故意在應(yīng)用程序中引入一個(gè)bug。接下來讓我描述一下它是什么樣的bug,然后看看當(dāng)我們的程序不按照我們意愿執(zhí)行的時(shí)候,它在其中又起了什么樣的影響。

        應(yīng)用程序的問題在于,沒有保證用戶昵稱的唯一性。用戶昵稱是由應(yīng)用程序自動(dòng)初始化的。我們首先會(huì)考慮使用OpenID provider給出的用戶的昵稱,然后再考慮使用Email信息中的用戶名部分作為用戶的昵稱。但如果出現(xiàn)重復(fù)的昵稱,則后面的用戶將無法注冊(cè)成功。更糟糕的是,在修改用戶配置的表單中,我們?cè)试S用戶任意更改他們的昵稱,但我們?nèi)匀粵]有對(duì)昵稱沖突進(jìn)行檢查。

        當(dāng)我們分析完錯(cuò)誤產(chǎn)生時(shí)應(yīng)用程序的行為之后,我們將會(huì)定位這些問題。

        Flask 的調(diào)試功能

        那么讓我們看看當(dāng)bug被觸發(fā)時(shí),會(huì)出現(xiàn)什么現(xiàn)象。

        讓我們從創(chuàng)建一個(gè)嶄新的數(shù)據(jù)庫(kù),在linux下,執(zhí)行:

        rm app.db
        ./db_create.py
        

        在Windows下,執(zhí)行:

        del app.db
        flask/Scripts/python db_create.py
        
        

        我們需要兩個(gè)OpenID的賬號(hào)來重現(xiàn)這個(gè)bug。當(dāng)然這兩個(gè)賬號(hào)最理想的狀態(tài)是來自來個(gè)不同的擁有者,那樣可以避免他們的cookie把情況搞的更復(fù)雜。通過如下步驟創(chuàng)建沖突的昵稱:

      1. 用第一個(gè)賬號(hào)登陸
      2. 進(jìn)入用戶信息屬性編輯頁(yè)面,將昵稱改為“dup”
      3. 登出系統(tǒng)
      4. 用第二個(gè)賬號(hào)登陸
      5. 修改第二個(gè)賬號(hào)的用戶信息屬性,將昵稱改為“dup”

      6. 哎喲!sqlalchemy中拋出了一個(gè)異常,來看一下錯(cuò)誤信息:

        lalchemy.exc.IntegrityError
        IntegrityError: (IntegrityError) column nickname is not unique u'UPDATE user SET nickname=?, about_me=? WHERE user.id = ?' (u'dup', u'', 2)
        
        

        錯(cuò)誤的后面是這個(gè)錯(cuò)誤的堆棧信息,事實(shí)上,這是一個(gè)相當(dāng)不錯(cuò)的錯(cuò)誤提示,你可以轉(zhuǎn)向任何框架檢查代碼或者在瀏覽器里執(zhí)行正確的表達(dá)式。

        這個(gè)錯(cuò)誤信息相當(dāng)明確,我們?cè)噲D在數(shù)據(jù)插入一個(gè)重復(fù)的昵稱,數(shù)據(jù)庫(kù)的昵稱字段是一個(gè)衛(wèi)衣鍵,因此這樣的操作是無效的。

        除了實(shí)際的錯(cuò)誤,在我們手頭上還有一個(gè)次要的錯(cuò)誤。如果一個(gè)用戶不注意在我們應(yīng)用程序里引起了一個(gè)錯(cuò)誤(這一個(gè)錯(cuò)誤或者任何其他原因引起的異常),應(yīng)用程序?qū)⑾蛩?她暴漏錯(cuò)誤信息和堆棧信息,而不是暴露給我們。對(duì)于我們開發(fā)者來說這是個(gè)很好的特性,但是很多時(shí)候我們不想讓用戶看到這些信息。

        這么長(zhǎng)時(shí)間以來,我們一直在debug模式下運(yùn)行我們的應(yīng)用程序,我們通過設(shè)置debug=True的參數(shù)來啟用應(yīng)用程序的debug模式。這里我們?cè)谶\(yùn)行腳本run.py里配置。

        當(dāng)我們這樣開發(fā)應(yīng)用是方便的,但是我們需要在生產(chǎn)環(huán)境上關(guān)閉debug模式。 讓我們創(chuàng)建另一個(gè)啟動(dòng)腳本文件設(shè)置關(guān)閉dubug模式(filerunp.py):

        #!flask/bin/python
        from app import app
        app.run(debug = False)
        

        現(xiàn)在重新啟動(dòng)應(yīng)用:

        ./runp.py
        

        并且現(xiàn)在再嘗試重命名第二個(gè)賬號(hào)nickname成‘dup'

        這次我們沒有獲取到一個(gè)錯(cuò)誤信息,取而代之,我們得到了一個(gè)HTTP 500錯(cuò)誤碼,這是個(gè)內(nèi)部服務(wù)器錯(cuò)誤。雖然這不容易定位錯(cuò)誤,但至少?zèng)]有暴露我們應(yīng)用程序的任何細(xì)節(jié)給陌生人。當(dāng)調(diào)試關(guān)閉后出現(xiàn)一個(gè)異常時(shí),F(xiàn)lask會(huì)產(chǎn)生一個(gè)500頁(yè)面。

        雖然這樣好些了,但現(xiàn)在仍存在兩個(gè)問題。首先美化問題:默認(rèn)的500頁(yè)面很丑陋。第二個(gè)問題更重要些,當(dāng)用戶操作失敗時(shí),我們無法獲取到錯(cuò)誤信息了,因?yàn)殄e(cuò)誤在后臺(tái)默默的處理了。幸運(yùn)的是有個(gè)簡(jiǎn)單方式來處理這兩個(gè)問題。

        定制HTTP錯(cuò)誤處理程序

        Flask為應(yīng)用程序提供了一個(gè)機(jī)制來安裝他們自己的錯(cuò)誤頁(yè)面,作為例子,讓我們定義兩個(gè)最常見的HTTP 404和500錯(cuò)誤的自定義頁(yè)面。定制其他錯(cuò)誤頁(yè)面也是同樣的方式。

        使用一個(gè)修飾來聲明一個(gè)定制的錯(cuò)誤處理程序 (fileapp/views.py):

        @app.errorhandler(404)
        def internal_error(error):
         return render_template('404.html'), 404
         
        @app.errorhandler(500)
        def internal_error(error):
         db.session.rollback()
         return render_template('500.html'), 500
        

        這地方無需多言,因?yàn)樗麄兌际遣谎远鞯摹Nㄒ挥腥さ牡胤綍r(shí)錯(cuò)誤500處理中的rollack語(yǔ)句,這個(gè)地方是不可缺少的因?yàn)檫@個(gè)方法會(huì)被當(dāng)做一個(gè)異常調(diào)用。如果因?yàn)閿?shù)據(jù)庫(kù)錯(cuò)誤導(dǎo)致一個(gè)異常,那么數(shù)據(jù)庫(kù)的會(huì)話將變成一個(gè)無效狀態(tài),因此我們需要回滾它,以防止一個(gè)會(huì)話轉(zhuǎn)向一個(gè)500錯(cuò)誤的模板。

        這是一個(gè)404錯(cuò)誤在模版

        
        {% extends "base.html" %}
         
        {% block content %}
        

        File Not Found

        Back

        {% endblock %}

        這是一個(gè)500錯(cuò)誤的模版

        
        {% extends "base.html" %}
         
        {% block content %}
        

        An unexpected error has occurred

        The administrator has been notified. Sorry for the inconvenience!

        Back

        {% endblock %}

        注意,我們會(huì)繼續(xù)使用我們base.html 布局, 這樣我們的錯(cuò)誤頁(yè)看起來比較舒服

        通過email發(fā)送錯(cuò)誤日志

        為了處理第二個(gè)問題我們需要配置應(yīng)用的錯(cuò)誤報(bào)告機(jī)制。

        第一個(gè)是每當(dāng)有錯(cuò)誤發(fā)生時(shí)把錯(cuò)誤日志通過郵件發(fā)送給我們。

        首先,我們需要在我們的應(yīng)用配置郵件服務(wù)器和管理員列表 (fileconfig.py):

        # mail server settings
        MAIL_SERVER = 'localhost'
        MAIL_PORT = 25
        MAIL_USERNAME = None
        MAIL_PASSWORD = None
         
        # administrator list
        ADMINS = ['you@example.com']
        

        當(dāng)然,你要把上面的配置改成你自己的才有意義

        Flask 使用通用的Python logging模塊, 所以設(shè)置發(fā)送錯(cuò)誤日志郵件非常簡(jiǎn)單. (fileapp/__init__.py):

        from config import basedir, ADMINS, MAIL_SERVER, MAIL_PORT, MAIL_USERNAME, MAIL_PASSWORD
         
        if not app.debug:
         import logging
         from logging.handlers import SMTPHandler
         credentials = None
         if MAIL_USERNAME or MAIL_PASSWORD:
         credentials = (MAIL_USERNAME, MAIL_PASSWORD)
         mail_handler = SMTPHandler((MAIL_SERVER, MAIL_PORT), 'no-reply@' + MAIL_SERVER, ADMINS, 'microblog failure', credentials)
         mail_handler.setLevel(logging.ERROR)
         app.logger.addHandler(mail_handler)
        

        Note that we are only enabling the emails when we run without debugging.
        注意,我們要的非dubug模式下開啟郵件功能.

        在沒有郵件服務(wù)器的pc上測(cè)試郵件功能也很容易,幸好Python有SMTP的測(cè)試排錯(cuò)的服務(wù)器(SMTP debugging server)。打開一個(gè)控制臺(tái)窗口,并且運(yùn)行下面的命令:

        python -m smtpd -n -c DebuggingServer localhost:25
        

        當(dāng)程序運(yùn)行的時(shí)候,應(yīng)用接收和發(fā)送郵件會(huì)在控制臺(tái)窗口中顯示出來。

        打印日志到文件

        通過郵件接收錯(cuò)誤日志非常不錯(cuò),但是,這是不夠的。有些導(dǎo)致失敗的條件不會(huì)觸發(fā)異常并且不是主要的問題,所以我們需要將日志保存到log文件中,在某些情況下,需要日志來進(jìn)行排錯(cuò)。

        出于這個(gè)原因,我們的應(yīng)用需要一個(gè)日志文件。

        開啟文件日志和郵件日志很相似(fileapp/__init__.py):

        if not app.debug:
         import logging
         from logging.handlers import RotatingFileHandler
         file_handler = RotatingFileHandler('tmp/microblog.log', 'a', 1 * 1024 * 1024, 10)
         file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
         app.logger.setLevel(logging.INFO)
         file_handler.setLevel(logging.INFO)
         app.logger.addHandler(file_handler)
         app.logger.info('microblog startup')
        


        日志文件將在tmp目錄下生成,文件名叫microblog.log。我們使用的RotatingFileHandler方法中有個(gè)限制日志數(shù)量的參數(shù)。在這種情況下,我們限制了一個(gè)日志文件大小為1M,并且把最后的十個(gè)文件作為備份。
        logging.Formatter類提供了日志信息的定義格式,由于這些信息將寫到一個(gè)文件中,我們想獲取到盡可能多的信息,因此除了日志信息和堆棧信息,我們還寫了個(gè)時(shí)間戳,日志級(jí)別和文件名、信息的行號(hào)。


        為了使日志更有用,我們降低了應(yīng)用程序日志和文件日志處理程序的日志級(jí)別,因?yàn)檫@樣我們將有機(jī)會(huì)在沒有錯(cuò)誤情況下把有用信息寫入到日志中。作為一個(gè)例子,我們啟動(dòng)時(shí)將日志的級(jí)別設(shè)置為信息級(jí)別。從現(xiàn)在開始,每次你啟動(dòng)應(yīng)用程序?qū)⒂涗浤愕恼{(diào)試信息。

        當(dāng)我們沒有使用日志時(shí),調(diào)試一個(gè)在線和使用中的web服務(wù)時(shí)是件非常困難的事,把日志信息寫入到文件中,將是我們?cè)\斷和解決問題的一個(gè)有用工具,所以現(xiàn)在讓我們準(zhǔn)備好使用這個(gè)功能吧。

        bug修復(fù)

        讓我們來修復(fù)下昵稱重復(fù)的bug.

        前面討論過,有兩個(gè)地方目前還沒有處理重復(fù)。首先是Flask-Login的after_login處理,這個(gè)方法將在用戶成功登陸到系統(tǒng)后調(diào)用,我們需要?jiǎng)?chuàng)建一個(gè)新的User實(shí)例。這是受影響的一個(gè)代碼片段,我們做了修復(fù) (fileapp/views.py):

        if user is None:
         nickname = resp.nickname
         if nickname is None or nickname == "":
         nickname = resp.email.split('@')[0]
         nickname = User.make_unique_nickname(nickname)
         user = User(nickname = nickname, email = resp.email, role = ROLE_USER)
         db.session.add(user)
         db.session.commit()
        


        我們解決這個(gè)問題的方法是讓User類選擇一個(gè)唯一的名字給我們,這也是 make_unique_nickname方法所做的(fileapp/models.py):

        class User(db.Model):
         # ...
         @staticmethod
         def make_unique_nickname(nickname):
         if User.query.filter_by(nickname = nickname).first() == None:
         return nickname
         version = 2
         while True:
         new_nickname = nickname + str(version)
         if User.query.filter_by(nickname = new_nickname).first() == None:
         break
         version += 1
         return new_nickname
         # ...
        

        這個(gè)方法簡(jiǎn)單的添加一個(gè)計(jì)數(shù)器來生成一個(gè)唯一的昵稱名。例如,如果用戶名“miguel”存在,這個(gè)方法將建議你使用“miguel2”,但是如果它也存在就會(huì)生成“miguel3”···。注意我們把這個(gè)方法設(shè)定為靜態(tài)方法,因?yàn)檫@個(gè)操作不適用于任何類的實(shí)例。

        第二個(gè)導(dǎo)致重復(fù)昵稱的地方是編輯頁(yè)面視圖函數(shù),這算是用戶選擇昵稱的一個(gè)小惡作劇,正確的方式是不允許用戶輸入重復(fù)名稱,讓用戶更換為另一個(gè)名稱。我們通過添加form表單驗(yàn)證來解決這個(gè)問題,如果用戶輸入一個(gè)無效的昵稱,將會(huì)得到一個(gè)字段驗(yàn)證失敗信息,添加我們的驗(yàn)證只需重寫form的validate方法 (fileapp/forms.py):

         
        class EditForm(Form):
         nickname = TextField('nickname', validators = [Required()])
         about_me = TextAreaField('about_me', validators = [Length(min = 0, max = 140)])
         
         def __init__(self, original_nickname, *args, **kwargs):
         Form.__init__(self, *args, **kwargs)
         self.original_nickname = original_nickname
         
         def validate(self):
         if not Form.validate(self):
         return False
         if self.nickname.data == self.original_nickname:
         return True
         user = User.query.filter_by(nickname = self.nickname.data).first()
         if user != None:
         self.nickname.errors.append('This nickname is already in use. Please choose another one.')
         return False
         return True
        

        表單的構(gòu)造函數(shù)增加了一個(gè)新的參數(shù)original_nickname,驗(yàn)證方法validate使用這個(gè)參數(shù)來判斷昵稱是否修改了,如果沒有修改就直接返回它,如果已經(jīng)修改了,方法會(huì)確認(rèn)下新的昵稱在數(shù)據(jù)庫(kù)是否已經(jīng)存在。

        接下來我們?cè)谝晥D函數(shù)中添加新的構(gòu)造器參數(shù):

        @app.route('/edit', methods = ['GET', 'POST'])
        @login_required
        def edit():
         form = EditForm(g.user.nickname)
         # ...
        

        完成這個(gè)修改我們還必須在表單的模板中啟用錯(cuò)誤顯示字段 (文件app/templates/edit.html):


        Your nickname:
         
         {{form.nickname(size = 24)}}
         {% for error in form.errors.nickname %}
         
        [{{error}}] {% endfor %}

        現(xiàn)在這個(gè)bug已經(jīng)修復(fù)了,阻止了重復(fù)數(shù)據(jù)的出現(xiàn)···除非這些驗(yàn)證方法不能正常工作了。在兩個(gè)或者多個(gè)線程/進(jìn)程并行存取數(shù)據(jù)庫(kù)時(shí),這仍然存在一個(gè)潛在的問題,但這些都是以后我們文章討論的主題。

        在這里你可以嘗試選擇一個(gè)重復(fù)的名稱來看看表單如何處理這些錯(cuò)誤的。

        單元測(cè)試框架

        先把上面關(guān)于測(cè)試的會(huì)話放一下,咱們來討論下關(guān)于自動(dòng)化測(cè)試的話題。

        隨著應(yīng)用程序規(guī)模的增長(zhǎng),越來越難以確定代碼的改變是否會(huì)影響到現(xiàn)有的功能。

        傳統(tǒng)的方法防止回歸是一個(gè)很好的方式,你通過編寫單元測(cè)試來測(cè)試應(yīng)用程序所有不同功能,每一個(gè)測(cè)試集中于一個(gè)點(diǎn)來驗(yàn)證結(jié)果是否和預(yù)期的一致。測(cè)試程序通過定期的執(zhí)行來確認(rèn)應(yīng)用程序是否在正常工作。當(dāng)測(cè)試覆蓋率變大時(shí),你就可以自信的修改和添加新功能,只需通過測(cè)試程序來驗(yàn)證下是否影響到了應(yīng)用程序現(xiàn)有功能。


        現(xiàn)在我們使用python的unittest測(cè)試組件來創(chuàng)建個(gè)簡(jiǎn)單的測(cè)試框架 (tests.py):

        #!flask/bin/python
        import unittest
         
        from config import basedir
        from app import app, db
        from app.models import User
         
        class TestCase(unittest.TestCase):
         def setUp(self):
         app.config['TESTING'] = True
         app.config['CSRF_ENABLED'] = False
         app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'test.db')
         self.app = app.test_client()
         db.create_all()
         
         def tearDown(self):
         db.session.remove()
         db.drop_all()
         
         def test_avatar(self):
         u = User(nickname = 'john', email = 'john@example.com')
         avatar = u.avatar(128)
         expected = 'http://www.gravatar.com/avatar/d4c74594d841139328695756648b6bd6'
         assert avatar[0:len(expected)] == expected
         
         def test_make_unique_nickname(self):
         u = User(nickname = 'john', email = 'john@example.com')
         db.session.add(u)
         db.session.commit()
         nickname = User.make_unique_nickname('john')
         assert nickname != 'john'
         u = User(nickname = nickname, email = 'susan@example.com')
         db.session.add(u)
         db.session.commit()
         nickname2 = User.make_unique_nickname('john')
         assert nickname2 != 'john'
         assert nickname2 != nickname
         
        if __name__ == '__main__':
         unittest.main()
        

        unittest測(cè)試組件的討論超出了本文的范圍了,我們這里只需知道TestCase是我們的測(cè)試類。setUp和tearDown方法有些特殊,它們分別在每個(gè)測(cè)試方法前后執(zhí)行,復(fù)雜點(diǎn)的設(shè)置可以包含幾組測(cè)試,每個(gè)代表一個(gè)單元測(cè)試,TestCase的子類和每個(gè)組都將擁有獨(dú)立的setUp和tearDown方法。


        這些特殊的setUp和tearDown方法都是非常通用的,在setUp可以方便的修改配置,例如,我們想測(cè)試不同的數(shù)據(jù)庫(kù)作為主數(shù)據(jù)庫(kù),在tearDown里面只需簡(jiǎn)單設(shè)置下數(shù)據(jù)庫(kù)內(nèi)容就可以。

        測(cè)試作為方法被實(shí)現(xiàn),一個(gè)測(cè)試應(yīng)該運(yùn)行一些已知結(jié)果的應(yīng)用程序方法,也應(yīng)當(dāng)能夠斷言出結(jié)果和預(yù)期的不同。


        到目前為止,在我們的測(cè)試框架里有兩個(gè)測(cè)試。第一個(gè)驗(yàn)證來自于上一篇文章的Gravatar avatar URLs生成的是否正確,注意預(yù)期的avatar被硬編碼在測(cè)試中,和User類中返回的對(duì)象作比較。

        第二個(gè)測(cè)試驗(yàn)證是test_make_unique_nickname方法,同樣也是在User類中。這個(gè)測(cè)試有點(diǎn)詳細(xì),它創(chuàng)建了一個(gè)新的用戶并且寫入數(shù)據(jù)庫(kù)中,同時(shí)確定名字的唯一性。接下來創(chuàng)建第二個(gè)用戶,建議使用唯一名稱,你可以嘗試下使用第一個(gè)用戶名稱。在第二部分測(cè)試預(yù)期結(jié)果是建議使用與之前不同的名稱。


        運(yùn)行這個(gè)測(cè)試套件你只需運(yùn)行tests.py腳本:

        ./tests.py
        

        如果出現(xiàn)錯(cuò)誤信息,你將會(huì)在控制臺(tái)得到一個(gè)報(bào)告。
        結(jié)語(yǔ)

        今天關(guān)于調(diào)試,錯(cuò)誤和測(cè)試的討論到此為止,我希望這篇文章能對(duì)你有用。

        老規(guī)矩,如果你有任何評(píng)論請(qǐng)寫在下面.

        微博應(yīng)用程序的代碼今天修改的更新,你可以在這里下載:


        下載 microblog-0.7.zip.

        聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

        文檔

        在Python的Flask框架中實(shí)現(xiàn)單元測(cè)試的教程

        在Python的Flask框架中實(shí)現(xiàn)單元測(cè)試的教程: 概要 在前面的章節(jié)里我們專注于在我們的小應(yīng)用程序上一步步的添加功能上。到現(xiàn)在為止我們有了一個(gè)帶有數(shù)據(jù)庫(kù)的應(yīng)用程序,可以注冊(cè)用戶,記錄用戶登陸退出日志以及查看修改配置文件。 在本節(jié)中,我們不為應(yīng)用程序添加任何新功能,相反,我們要尋找一種方法來
        推薦度:
        • 熱門焦點(diǎn)

        最新推薦

        猜你喜歡

        熱門推薦

        專題
        Top
        主站蜘蛛池模板: 亚洲中文字幕无码mv| 国产免费黄色无码视频| 亚洲丁香婷婷综合久久| 国产精品免费αv视频| 精品久久8x国产免费观看| 国产午夜免费福利红片| 香蕉视频在线观看亚洲| 国产精品亚洲av色欲三区| 最近免费中文字幕大全高清大全1| 日韩一区二区在线免费观看| 亚洲韩国—中文字幕| 日韩精品无码永久免费网站| 久久国内免费视频| 国产成人无码综合亚洲日韩 | 国产亚洲婷婷香蕉久久精品 | 国产青草亚洲香蕉精品久久| 1000部拍拍拍18勿入免费凤凰福利| 伊人久久综在合线亚洲2019| 免费在线观影网站| 亚洲国产精品无码久久九九| 亚洲AV无码专区在线电影成人| 亚洲视频免费播放| 亚洲国产成人久久综合碰碰动漫3d| 国产高清不卡免费视频| 国产亚洲av片在线观看播放| 国产精成人品日日拍夜夜免费| 亚洲精品不卡视频| 久久免费观看国产99精品| 亚洲国产成人久久综合一区| 3344免费播放观看视频| 自拍偷区亚洲国内自拍| 无人在线观看免费高清视频| 亚洲精品亚洲人成在线播放| 国产精品免费精品自在线观看| 亚洲黄色在线视频| 四虎在线最新永久免费| 亚洲av日韩av永久在线观看| 日本亚洲欧洲免费天堂午夜看片女人员| 桃子视频在线观看高清免费完整| 美女啪啪网站又黄又免费| 免费人妻av无码专区|