项目环境:
- python 3.6
- Django 1.11
- Coverage 5.0
减少测试代码运行时间的方法:
- 把单一的管道改为多管道并行,最后合并测试结果
- 减少数据库迁移的次数
具体做法:
1、把测试代码拆分成四部分,分别放在不同的文件夹
配置文件.gitlab.yml参考:
stages:
- test
- result
Part_one:
stage: test
script:
- if [ ! -d "~/$CI_RUNNER_DESCRIPTION/env" ]; then mkdir -p ~/$CI_RUNNER_DESCRIPTION/env; fi
- if [ ! -d "~/$CI_RUNNER_DESCRIPTION/env/$CI_PROJECT_NAME" ]; then cd ~/$CI_RUNNER_DESCRIPTION/env/ && python3 -m venv $CI_PROJECT_NAME; fi
- source ~/$CI_RUNNER_DESCRIPTION/env/$CI_PROJECT_NAME/bin/activate
- pip install --trusted-host mirrors.aliyun.com -i http://mirrors.aliyun.com/pypi/simple/ -r $CI_PROJECT_DIR/requirements.txt
- cd $CI_PROJECT_DIR
- if [ ! -d "$CI_PROJECT_DIR/logs" ]; then mkdir -p $CI_PROJECT_DIR/logs; fi
- touch $CI_PROJECT_DIR/logs/request.log
- touch $CI_PROJECT_DIR/logs/biotools.log
- echo $CI_JOB_ID >job_id.txt
- python manage.py runscript check_test_database_migrations --traceback --settings=base_settings
- coverage run -p manage.py test app.tests.part_one --noinput --settings=base_settings --debug-mode --keepdb
- python manage.py runscript rename_database --traceback --settings=base_settings
- python manage.py runscript remove_testdb --traceback --settings=base_settings
retry: 2
artifacts:
untracked: true
name: "$CI_JOB_NAME"
expire_in: 1 week
Part_two:
stage: test
script:
- if [ ! -d "~/$CI_RUNNER_DESCRIPTION/env" ]; then mkdir -p ~/$CI_RUNNER_DESCRIPTION/env; fi
- if [ ! -d "~/$CI_RUNNER_DESCRIPTION/env/$CI_PROJECT_NAME" ]; then cd ~/$CI_RUNNER_DESCRIPTION/env/ && python3 -m venv $CI_PROJECT_NAME; fi
- source ~/$CI_RUNNER_DESCRIPTION/env/$CI_PROJECT_NAME/bin/activate
- pip install --trusted-host mirrors.aliyun.com -i http://mirrors.aliyun.com/pypi/simple/ -r $CI_PROJECT_DIR/requirements.txt
- cd $CI_PROJECT_DIR
- if [ ! -d "$CI_PROJECT_DIR/logs" ]; then mkdir -p $CI_PROJECT_DIR/logs; fi
- touch $CI_PROJECT_DIR/logs/request.log
- touch $CI_PROJECT_DIR/logs/biotools.log
- echo $CI_JOB_ID >job_id.txt
- python manage.py runscript check_test_database_migrations --traceback --settings=base_settings
- coverage run -p manage.py test app.tests.part_two --noinput --settings=base_settings --debug-mode --keepdb
- python manage.py runscript remove_testdb --traceback --settings=base_settings
retry: 2
artifacts:
untracked: true
name: "$CI_JOB_NAME"
expire_in: 1 week
Part_three:
stage: test
script:
- if [ ! -d "~/$CI_RUNNER_DESCRIPTION/env" ]; then mkdir -p ~/$CI_RUNNER_DESCRIPTION/env; fi
- if [ ! -d "~/$CI_RUNNER_DESCRIPTION/env/$CI_PROJECT_NAME" ]; then cd ~/$CI_RUNNER_DESCRIPTION/env/ && python3 -m venv $CI_PROJECT_NAME; fi
- source ~/$CI_RUNNER_DESCRIPTION/env/$CI_PROJECT_NAME/bin/activate
- pip install --trusted-host mirrors.aliyun.com -i http://mirrors.aliyun.com/pypi/simple/ -r $CI_PROJECT_DIR/requirements.txt
- cd $CI_PROJECT_DIR
- if [ ! -d "$CI_PROJECT_DIR/logs" ]; then mkdir -p $CI_PROJECT_DIR/logs; fi
- touch $CI_PROJECT_DIR/logs/request.log
- touch $CI_PROJECT_DIR/logs/biotools.log
- echo $CI_JOB_ID >job_id.txt
- python manage.py runscript check_test_database_migrations --traceback --settings=base_settings
- coverage run -p manage.py test app.tests.part_three --noinput --settings=base_settings --debug-mode --keepdb
- python manage.py runscript remove_testdb --traceback --settings=base_settings
retry: 2
artifacts:
untracked: true
name: "$CI_JOB_NAME"
expire_in: 1 week
Part_four:
stage: test
script:
- if [ ! -d "~/$CI_RUNNER_DESCRIPTION/env" ]; then mkdir -p ~/$CI_RUNNER_DESCRIPTION/env; fi
- if [ ! -d "~/$CI_RUNNER_DESCRIPTION/env/$CI_PROJECT_NAME" ]; then cd ~/$CI_RUNNER_DESCRIPTION/env/ && python3 -m venv $CI_PROJECT_NAME; fi
- source ~/$CI_RUNNER_DESCRIPTION/env/$CI_PROJECT_NAME/bin/activate
- pip install --trusted-host mirrors.aliyun.com -i http://mirrors.aliyun.com/pypi/simple/ -r $CI_PROJECT_DIR/requirements.txt
- cd $CI_PROJECT_DIR
- if [ ! -d "$CI_PROJECT_DIR/logs" ]; then mkdir -p $CI_PROJECT_DIR/logs; fi
- touch $CI_PROJECT_DIR/logs/request.log
- touch $CI_PROJECT_DIR/logs/biotools.log
- echo $CI_JOB_ID >job_id.txt
- python manage.py runscript check_test_database_migrations --traceback --settings=base_settings
- coverage run -p manage.py test app.tests.part_four --noinput --settings=base_settings --debug-mode --keepdb
- python manage.py runscript remove_testdb --traceback --settings=base_settings
retry: 2
artifacts:
untracked: true
name: "$CI_JOB_NAME"
expire_in: 1 week
result:
stage: result
script:
- if [ ! -d "~/$CI_RUNNER_DESCRIPTION/env" ]; then mkdir -p ~/$CI_RUNNER_DESCRIPTION/env; fi
- if [ ! -d "~/$CI_RUNNER_DESCRIPTION/env/$CI_PROJECT_NAME" ]; then cd ~/$CI_RUNNER_DESCRIPTION/env/ && python3 -m venv $CI_PROJECT_NAME; fi
- source ~/$CI_RUNNER_DESCRIPTION/env/$CI_PROJECT_NAME/bin/activate
- pip install --trusted-host mirrors.aliyun.com -i http://mirrors.aliyun.com/pypi/simple/ coverage
- cd $CI_PROJECT_DIR
- if [ ! -d "$CI_PROJECT_DIR/logs" ]; then mkdir -p $CI_PROJECT_DIR/logs; fi
- touch $CI_PROJECT_DIR/logs/request.log
- touch $CI_PROJECT_DIR/logs/biotools.log
- coverage combine --append
- coverage report
dependencies:
- Part_one
- Part_two
- Part_three
- Part_four
减少数据库迁移的次数的具体做法是把测试用到的sqlite数据库文件放在/dev/shm/目录下,每条管道运行测试代码之前,先在/dev/shm/目录下用job id创建一个文件夹,job id是每个job运行时唯一的id,可以查阅gitlab的官方文档了解更多,把sqlite数据库复制一份到新建的文件夹里面,再运行测试代码。运行测试代码的语句是:
coverage run -p manage.py test app.tests.part_one --noinput --settings=base_settings --debug-mode --keepdb
注意一定要加上–keepdb
还有其他细节,例如检查数据库有没有更新,测试完成后删除测试用到的数据库等,我全都粘贴出来。
check_test_database_migrations.py
# -*- coding: utf-8 -*-
import logging
import os
import sqlite3
import sys
import base_settings
import shutil
import os
import stat
from django.conf import settings
__author__ = 'JayChen'
logger = logging.getLogger('scripts')
def read_text():
"""
读取文件中的job_id
:return:
"""
with open(settings.PROJECT_PATH + '/job_id.txt', 'r') as f:
for line in f.readlines():
job_id = line.strip()
return job_id
def remove_db_files():
logger.info("model有更新,删除本地数据库")
for root, dirs, files in os.walk('/dev/shm', topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
os.rmdir(os.path.join(root, name))
job_id = read_text()
os.mkdir("/dev/shm/test_{}".format(job_id))
if os.path.exists("/dev/shm/test_{}".format(job_id)):
logger.info('文件夹-------' + "/dev/shm/test_{}".format(job_id) + '------创建成功')
def coppy_and_rename():
job_id = read_text()
new_file_name = '/dev/shm/test_{}/vb_en.test_{}.db.sqlite3'.format(job_id, job_id) # 文件新名字
origin_path = '/dev/shm/vb_en.test.db.sqlite3' # 原始文件完整目录
# 数据库文件存在
if os.path.exists(origin_path):
if os.path.exists("/dev/shm/test_{}".format(job_id)):
logger.info('文件夹-------' + "/dev/shm/test_{}".format(job_id) + '------创建成功')
shutil.copy(origin_path, new_file_name)
os.chmod(new_file_name, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
logger.info('复制数据库并重新命名为:--------' + new_file_name.split('/')[4] + '------------')
else:
os.mkdir("/dev/shm/test_{}".format(job_id))
logger.info('文件夹-------' + "/dev/shm/test_{}".format(job_id) + '------创建成功')
shutil.copy(origin_path, new_file_name)
os.chmod(new_file_name, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
logger.info('复制数据库并重新命名为:--------' + new_file_name.split('/')[4] + '------------')
else:
logger.info('找不到数据库源文件')
if os.path.exists("/dev/shm/test_{}".format(job_id)):
for root, dirs, files in os.walk("/dev/shm/test_{}".format(job_id), topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
os.rmdir(os.path.join(root, name))
else:
os.mkdir("/dev/shm/test_{}".format(job_id))
logger.info('文件夹-------' + "/dev/shm/test_{}".format(job_id) + '------创建成功')
for i, j, k in os.walk('/dev/shm'):
print(i, j, k)
def check_migrations():
base_dir = os.path.dirname(__file__)
migrations_data = []
if os.path.exists('/dev/shm/vb_en.test.db.sqlite3'):
try:
conn = sqlite3.connect('/dev/shm/vb_en.test.db.sqlite3')
migrations_data = conn.cursor().execute("select name from django_migrations where app='app'").fetchall()
conn.close()
data_length = len(migrations_data)
migrations_files = []
for f in os.listdir(base_settings.PROJECT_PATH + '/app/migrations'):
if f.endswith('.py') and f != '__init__.py':
migrations_files.append(f)
files_length = len(migrations_files)
if data_length != files_length:
remove_db_files()
else:
for m in migrations_data:
if m[0] + '.py' not in migrations_files:
remove_db_files()
except sqlite3.OperationalError:
logger.info('读取本地数据库失败')
coppy_and_rename()
else:
coppy_and_rename()
def run(*args):
logger.info('Start checking the test database migrations file.')
logger.info(args)
check_migrations()
logger.info('End checking.')
'''
./manage.py runscript check_test_database_migrations --traceback --settings=base_settings --script-args=one
'''
remove_testdb.py
# -*- coding: utf-8 -*-
import os
import logging
from django.conf import settings
__author__ = 'JayChen'
logger = logging.getLogger('scripts')
def read_text():
"""
读取文件中的job_id
:return:
"""
with open(settings.PROJECT_PATH + '/job_id.txt', 'r') as f:
for line in f.readlines():
job_id = line.strip()
return job_id
def remove_db():
# 清空文件夹里面的文件,再删除文件夹
job_id = read_text()
for root, dirs, files in os.walk('/dev/shm/test_{}'.format(job_id), topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
os.rmdir(os.path.join(root, name))
os.rmdir("/dev/shm/test_{}".format(job_id))
def remove_db_history():
"""
job_id 是从小排到大的,只保留最近的20个文件夹
清理gitlab上运行失败后留下的数据库文件夹和文件
"""
job_id = read_text()
job_id = int(round(float(job_id) - 20, 0))
filePath = '/dev/shm'
name_list = os.listdir(filePath)
for name in name_list:
if os.path.isfile('/dev/shm/' + name):
"""
如果是文件而不是文件夹,则跳过,因为这个文件就是我用于复制到其他文件夹的数据库
"""
continue
else:
data = int(round(float(name.split('_')[1]), 0))
if data < job_id:
for root, dirs, files in os.walk('/dev/shm/{}'.format(name), topdown=False):
for file in files:
os.remove(os.path.join(root, file))
for dir in dirs:
os.rmdir(os.path.join(root, dir))
os.rmdir("/dev/shm/{}".format(name))
else:
continue
def run():
logger.info('删除测试数据库')
remove_db()
remove_db_history()
'''
./manage.py runscript remove_testdb --traceback --settings=base_settings
'''
rename_database.py
# -*- coding: utf-8 -*-
import os
import logging
import shutil
from django.conf import settings
__author__ = 'JayChen'
logger = logging.getLogger('scripts')
def read_text():
"""
读取文件中的job_id
:return:
"""
with open(settings.PROJECT_PATH + '/job_id.txt', 'r') as f:
for line in f.readlines():
job_id = line.strip()
return job_id
def move_and_rename():
job_id = read_text()
origin_path = '/dev/shm/test_{}/vb_en.test_{}.db.sqlite3'.format(job_id, job_id)
new_file_name = '/dev/shm/vb_en.test.db.sqlite3'
if os.path.exists(origin_path):
shutil.copy(origin_path, new_file_name)
logger.info('更新数据库文件')
else:
pass
def run():
move_and_rename()
'''
./manage.py runscript rename_database --traceback --settings=base_settings
'''
settings.py文件最后加上:
if os.path.exists(PROJECT_PATH + '/job_id.txt'):
# 读取文件中的job_id, gitlab上每条管道的job_id, 这个id是唯一的
with open(PROJECT_PATH + '/job_id.txt', 'r') as f:
for line in f.readlines():
job_id = line.strip()
TEST_SQLITE_PREFIX = '/dev/shm/test_{}/vb_en.test_{}.db'.format(job_id, job_id)
else:
TEST_SQLITE_PREFIX = 'vb_en_testdb'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': TEST_SQLITE_PREFIX + '.sqlite3',
'TEST': {
'NAME': TEST_SQLITE_PREFIX + '.sqlite3'
},
'OPTIONS': {
'timeout': 20,
}
}
}
来源:CSDN
作者:发飙的蜗牛222
链接:https://blog.csdn.net/weixin_42935779/article/details/104748620