註釋:運行環境linux+python3.7.3+pytest5.2.2+postgresql+flask-sqlalchemy2.4.1python
公司以前用的NoSQL做數據管理,最近讓我把數據庫使用關係型數據庫翻譯一下,老大決定使用postgresql並採用ORM管理數據庫,數據庫翻譯完,老大說爲了保證數據穩定遷移,讓我用pytest寫一下測試用例,第一次寫啊,遇到各類坑,第一就是測試項目的目錄搭建,由於以前會一點unittest,unittest實現自動化測試能夠寫在同一個類中進行測試,當時不知道怎麼搭合適,各類實驗,最終採用下邊的目錄結構,很好的作到解耦。第二坑就是轉化過來的數據表各類外鍵約束,直接使用pytest運行測試他會自動的搜索test開頭的測試用例,這樣沒有順序,我某些表中用到的外鍵這張表輸入尚未插入,就會測試失敗,後來又使用pytest按照表的外鍵關係一個個的添加,等價於手動排序linux
1:搭建測試項目結構(目錄結構)這裏每一個文件都是包,是由於你在使用pytest執行用例的時候他會自動搜索包中test開頭的文件sql
. ├── manage.py # 項目啓動文件 ├── moduls # 模型類存放包 │ ├── __init__.py │ └── moduls.py # 模型類py文件 └── test # 單元測試包 ├── conftest.py # pytest配置(初始化及測試後的清理工做) ├── data # 測試用例使用到的數據管理包 │ ├── __init__.py │ └── test_db_add_delete # 測試用例使用到的數據管理包(名字以test開頭+什麼測試+測試了什麼功能) │ ├── __init__.py │ └── test_user.json # 數據json文件 (名字以test開頭+測試的那個模型類) ├── __init__.py ├── tests # 測試用例管理包 │ ├── __init__.py │ └── test_db_add_delete # 測試用例管理包(名字以test開頭+什麼測試+測試了什麼功能, 與測試數據管理包名字一致) │ ├── __init__.py │ └── test_user.py # 測試用例py文件 (名字以test開頭+測試的那個模型類,與json文件一致) └── utils # 工具管理包 ├── get_data.py # 用例與數據的解耦(獲取測試用例使用到的數據) └── __init__.py
2:文件解釋及demo數據庫
2-1:conftest.pyjson
註釋:這個文件我用了pytest.fixture(scope="session", autouse=True)這個裝飾器內的scope參數能夠設置被裝飾的函數何時執行及執行多少次(session:整個測試開始到所有測試完成只執行一次。->class:表示你測試用例若是寫在類中,那麼每執行一個類中的用例就會執行一次這個函數。->function:表示你用例是寫在函數中,那麼每執行一個函數用的用例,這個函數就會被執行一次。->model:表示你執行這個模塊中的全部用例的時候,這個函數就會執行一次),我這裏要使用數據庫的關聯關係,全部我用的是session,意思就是在我使用pytest執行用例的時候,這個函數只執行一次,由於我使用這個被裝飾的函數作了數據庫的初始化和測試完成後的清理工做。這個函數中的yield相似於unittest.TestCase中的setUp和tearDown方法yield以前的等價於setUp方法中的邏輯,以後的等價於tearDown中的邏輯flask
import os import pytest @pytest.fixture(scope="session", autouse=True) def app_db_session(): """ 數據庫fixture :return: """ # 引入項目中的app和db from manage import app, db # 測試數據庫與開發數據庫的解耦 app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql+psycopg2://dbname:password@127.0.0.1:5432/test" app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False # 配置數據庫(數據庫的開銷設置爲False) app.testing = True db.drop_all() # 執行數據庫的清理工做確保測試使用的是新的數據庫,若是要用到原始數據請忽略 db.create_all() # 執行數據表的建立工做 yield db.session.remove() # 關閉pytest數據庫鏈接 db.drop_all() # 清理數據表
2-2:moduls.py 我就不貼代碼了,這個就是你建立的模型類,唉仍是貼一個簡單的demo吧session
# -*- coding:utf-8 -*- import datetime from sqlalchemy import Column, TIMESTAMP, SmallInteger, String, func, Integer from sqlalchemy.dialects.postgresql import ARRAY, array from manage import db class BaseModel(db.Model): __abstract__ = True create_time = Column(TIMESTAMP, default=datetime.datetime.now(), comment="建立時間") update_time = Column(TIMESTAMP, default=datetime.datetime.now(), onupdate=func.now(), comment="更新時間") def to_dict(self): """ 單對象序列化 :return: """ _dict = {} _dict.update(self.__dict__) if "_sa_instance_state" in _dict: del _dict['_sa_instance_state'] return _dict class User(BaseModel): id = Column(Integer, primary_key=True, comment="主鍵ID") name = Column(String(32), comment="名字") age = Column(SmallInteger, comment="年齡")
2-3:utils->get_data.py 這個裏邊的代碼仍是要貼出來的,這個裏邊我定義了2個方法,一個方法是獲取data中的數據路徑和tests中的測試用例路徑,另一個方法是讀取測試數據用的數據,將json數據處理成爲元祖套列表的形式返回架構
import os import json def get_data_path(case_path: os.path) -> os.path: file_name = os.path.dirname(case_path).split(os.sep + 'tests' + os.sep, 1) return os.sep.join([file_name[0], 'data', file_name[1], os.path.basename(case_path).replace('.py', '.json')]) def get_test_data(case_path: os.path) -> list: test_data_path = get_data_path(case_path) with open(test_data_path, encoding='utf-8') as f: dat = json.loads(f.read()) return [tuple(i.values()) for i in dat['test']]
2-4:test_user.py文件,這個文件編寫測試用例,我以爲要明白一點,你要測試什麼,拿數據庫來講,我要測試的是正確的數據類型能夠插入到數據庫中,數據類型、約束、外鍵不對就沒法插入數據,我這裏測試的就是數據插入功能和刪除功能,那麼我這裏測試用例的編寫就明確了,我初始化一個模型類對象,給對象添加屬性,而後在add->commit插入這條數據,而後我可以查詢到剛纔插入的這條數據,assert 取出來的值和我存入的值一直,那麼我就認爲是測試成功的。下邊來看代碼app
這裏我要用紅字標明一些注意事項,必定要注意,要了解更詳細的操做,請關注我pytest框架講解框架
1:parametrize第一個參數是字符串,參數用逗號分隔
2:parametrize第二個參數是[(), (), ()] 有幾個元祖就表示這個用例執行幾回
3:被裝飾的函數中的個數要和注意事項1的參數個數同樣
4:注意事項1,和注意事項2,他們的參數位置要對應起來和後邊要寫的json文件同樣,若是使用個人架構來測試,只要第一個參數的位置和json文件內的位置對應就能夠了
import pytest import datetime from manage import db from moduls.moduls import User from test.utils.get_data import get_test_data data = get_test_data(__file__) # 這裏咱們使用了數據與用例的解耦,咱們就使用paramtrize來傳參 # 注意 1:它的第一個參數是字符串, 第二個參數是[(), ()] 這個類型的數據,列表中幾個元祖就會執行幾回測試(注意:他要和字符串參數位置對應) # 2:字符串內的參數要和函數中的參數個數一致 @pytest.mark.parametrize("name, age", data) def test_user(name, age): obj = User() obj.name = age obj.age = age db.session.add(obj) db.session.commit() obj = User.query.filter_by(name=name).first() # 最好這裏用unique類型的字段查詢,我這裏demo中只有2個字段,因此就這樣寫了
assert obj.age == age
2-5:test_user.json文件,上邊也提了一下,這個json文件你key的順序要和你paramtrize的第一個參數位置一致, 我這裏test中[ ] 中有5個字典,那麼我用例在運行起來的時候就會執行5次測試
{ "test": [ { "name": "5ee4ef28013f48f69a76bc16c7089245", "age": 30 }, { "name": "5ee4ef28013f48f69a76bc16c7089246", "age": 40 }, { "name": "5ee4ef28013f48f69a76bc16c7089247", "age": 50 }, { "name": "5ee4ef28013f48f69a76bc16c7089248", "age": 60 },{ "name": "5ee4ef28013f48f69a76bc16c7089249", "age": 70 } ] }
3:運行 在test文件內直接pytest就能夠執行用例了
到這裏本次測試就講完了,若是想要更詳細的講解,後續我會出一個pytest框架的講解