将 Django DiscoverRunner 设置为始终在使用萝卜进行测试时重新创建数据库
Set up Django DiscoverRunner to always recreate database on testing with radish
我正在使用带有 selenium 的 radish bdd 来测试我的 django 应用程序,但是有时 django 要求删除数据库,因为它已经存在于数据库中。这是我的 terrain.py
:
import os
import django
from django.test.runner import DiscoverRunner
from django.test import LiveServerTestCase
from radish import before, after
from selenium import webdriver
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tangorblog.settings.features')
BASE_URL = os.environ.get('BASE_URL', 'http://localhost:8000')
@before.each_scenario
def setup_django_test(scenario):
django.setup()
scenario.context.test_runner = DiscoverRunner()
scenario.context.test_runner.setup_test_environment()
scenario.context.old_db_config =\
scenario.context.test_runner.setup_databases()
scenario.context.base_url = BASE_URL
scenario.context.test_case = LiveServerTestCase()
scenario.context.test_case.setUpClass()
scenario.context.browser = webdriver.Chrome()
@after.each_scenario
def teardown_django(scenario):
scenario.context.browser.quit()
scenario.context.test_case.tearDownClass()
del scenario.context.test_case
scenario.context.test_runner.teardown_databases(
scenario.context.old_db_config)
scenario.context.test_runner.teardown_test_environment()
我想,我可以在
上以某种方式改变它
scenario.context.old_db_config =\
scenario.context.test_runner.setup_databases()
但我不知道怎么做。有帮助吗?
@Wyatt,我又要修改你的答案了。我已经尝试 运行 你的解决方案,但是它没有设法使每个场景独立,当我尝试在场景中创建模型对象时,我什至遇到了完整性错误。不管怎样我仍然使用你的解决方案(特别是 RadishTestRunner
,因为这个想法来自你。我修改了它所以我可以 运行 django unittest
与 radish
分开。我使用 LiveServerTestCase
直接删除 LiveServer
因为我注意到两者之间的相似性,除了 LiveServerTestCase
继承自 TransactionTestCase
并且它还构建了 LiveServerThread
和 _StaticFilesHandler
in. 是这样的:
# package/test/runner.py
import os
from django.test.runner import DiscoverRunner
import radish.main
class RadishTestRunner(DiscoverRunner):
radish_features = ['features']
def run_suite(self, suite, **kwargs):
# run radish test
return radish.main.main(self.radish_features)
def suite_result(self, suite, result, **kwargs):
return result
def set_radish_features(self, features):
self.radish_features = features
# radish/world.py
from django.test import LiveServerTestCase
from radish import pick
from selenium import webdriver
@pick
def get_browser():
return webdriver.Chrome()
@pick
def get_live_server():
live_server = LiveServerTestCase
live_server.setUpClass()
return live_server
# radish/terrain.py
from radish import world, before, after
from selenium import webdriver
@before.all
def set_up(features, marker):
world.get_live_server()
@after.all
def tear_down(features, marker):
live_server = world.get_live_server()
live_server.tearDownClass()
@before.each_scenario
def set_up_scenario(scenario):
live_server = world.get_live_server()
scenario.context.browser = webdriver.Chrome()
scenario.context.base_url = live_server.live_server_url
scenario.context.test_case = live_server()
scenario.context.test_case._pre_setup()
@after.each_scenario
def tear_down_scenario(scenario):
scenario.context.test_case._post_teardown()
scenario.context.browser.quit()
就是这样。这也解决了 PostgreSQL 在您指出的我的其他问题上的问题。我还在每个场景中打开和退出浏览器,因为它让我可以更好地控制场景中的浏览器。非常感谢您为我指出正确的方向所做的努力。
最后我return 到PostgreSQL。
就速度而言,PostgreSQL 似乎优于 MySQL。它大大减少了 运行 测试的时间。
哦,是的,在 django
设置文件中指定 STATIC_ROOT
之后,我需要先 运行 ./manage.py collectstatic
。
我也修改了 RadishTestRunner
,所以我可以 运行 用 python manage.py radish /path/to/features/file
代替 运行 RADISH_ONLY=1
。这是我的萝卜命令:
# package.management.commands.radish
from __future__ import absolute_import
import sys
from django.core.management.base import BaseCommand, CommandError
from package.test.runner import RadishTestRunner
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('features', nargs='+', type=str)
def handle(self, *args, **options):
test_runner = RadishTestRunner(interactive=False)
if options['features']:
test_runner.set_radish_features(options['features'])
result = test_runner.run_suite(None)
if result:
sys.exit(result)
通过使用带有 django 管理命令的萝卜,我们可以控制我们想要的特征文件 运行。
在我看来,为每个场景重新创建数据库最终会非常低效(而且非常慢)。每次测试只需要创建一次数据库 运行 然后在最后删除它。
我想出了一个我认为与 Django 集成得更好的解决方案。它允许您 运行 使用 manage.py test
进行测试,每次测试仅 creates/drops 一次数据库 运行,并在测试每个功能后清除数据库表。
请注意,此 运行 默认情况下同时进行 Django 单元测试和萝卜测试。要运行 只是萝卜测试,你可以RADISH_ONLY=1 manage.py test
。此外,要使实时 server/Selenium 测试生效,您必须先 运行 manage.py collectstatic
。
# package/settings.py
TEST_RUNNER = 'package.test.runner.RadishTestRunner'
# package/test/runner
import os
from django.test.runner import DiscoverRunner
import radish.main
class RadishTestRunner(DiscoverRunner):
def run_suite(self, suite, **kwargs):
# Run unit tests
if os.getenv('RADISH_ONLY') == '1':
result = None
else:
result = super().run_suite(suite, **kwargs)
# Run radish behavioral tests
self._radish_result = radish.main.main(['features'])
return result
def suite_result(self, suite, result, **kwargs):
if result is not None:
# Django unit tests were run
result = super().suite_result(suite, result, **kwargs)
else:
result = 0
result += self._radish_result
return result
# radish/world.py
from django.db import connections
from django.test.testcases import LiveServerThread, _StaticFilesHandler
from django.test.utils import modify_settings
from radish import pick
from selenium import webdriver
@pick
def get_browser():
return webdriver.Chrome()
@pick
def get_live_server():
live_server = LiveServer()
live_server.start()
return live_server
class LiveServer:
host = 'localhost'
port = 0
server_thread_class = LiveServerThread
static_handler = _StaticFilesHandler
def __init__(self):
connections_override = {}
for conn in connections.all():
if conn.vendor == 'sqlite' and conn.is_in_memory_db():
conn.allow_thread_sharing = True
connections_override[conn.alias] = conn
self.modified_settings = modify_settings(ALLOWED_HOSTS={'append': self.host})
self.server_thread = self.server_thread_class(
self.host,
self.static_handler,
connections_override=connections_override,
port=self.port,
)
self.server_thread.daemon = True
@property
def url(self):
self.server_thread.is_ready.wait()
return 'http://{self.host}:{self.server_thread.port}'.format(self=self)
def start(self):
self.modified_settings.enable()
self.server_thread.start()
self.server_thread.is_ready.wait()
if self.server_thread.error:
self.stop()
raise self.server_thread.error
def stop(self):
if hasattr(self, 'server_thread'):
self.server_thread.terminate()
for conn in connections.all():
if conn.vendor == 'sqlite' and conn.is_in_memory_db():
conn.allow_thread_sharing = False
self.modified_settings.disable()
# radish/terrain.py
from django.db import connections, transaction
from radish import world, before, after
@before.all
def set_up(features, marker):
world.get_live_server()
@after.all
def tear_down(features, marker):
browser = world.get_browser()
live_server = world.get_live_server()
browser.quit()
live_server.stop()
@before.each_scenario
def set_up_scenario(scenario):
live_server = world.get_live_server()
scenario.context.base_url = live_server.url
scenario.context.browser = world.get_browser()
# XXX: Only works with the default database
# XXX: Assumes the default database supports transactions
scenario.context.transaction = transaction.atomic(using='default')
scenario.context.transaction.__enter__()
@after.each_scenario
def tear_down_scenario(scenario):
transaction.set_rollback(True, using='default')
scenario.context.transaction.__exit__(None, None, None)
for connection in connections.all():
connection.close()
我正在使用带有 selenium 的 radish bdd 来测试我的 django 应用程序,但是有时 django 要求删除数据库,因为它已经存在于数据库中。这是我的 terrain.py
:
import os
import django
from django.test.runner import DiscoverRunner
from django.test import LiveServerTestCase
from radish import before, after
from selenium import webdriver
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tangorblog.settings.features')
BASE_URL = os.environ.get('BASE_URL', 'http://localhost:8000')
@before.each_scenario
def setup_django_test(scenario):
django.setup()
scenario.context.test_runner = DiscoverRunner()
scenario.context.test_runner.setup_test_environment()
scenario.context.old_db_config =\
scenario.context.test_runner.setup_databases()
scenario.context.base_url = BASE_URL
scenario.context.test_case = LiveServerTestCase()
scenario.context.test_case.setUpClass()
scenario.context.browser = webdriver.Chrome()
@after.each_scenario
def teardown_django(scenario):
scenario.context.browser.quit()
scenario.context.test_case.tearDownClass()
del scenario.context.test_case
scenario.context.test_runner.teardown_databases(
scenario.context.old_db_config)
scenario.context.test_runner.teardown_test_environment()
我想,我可以在
上以某种方式改变它scenario.context.old_db_config =\
scenario.context.test_runner.setup_databases()
但我不知道怎么做。有帮助吗?
@Wyatt,我又要修改你的答案了。我已经尝试 运行 你的解决方案,但是它没有设法使每个场景独立,当我尝试在场景中创建模型对象时,我什至遇到了完整性错误。不管怎样我仍然使用你的解决方案(特别是 RadishTestRunner
,因为这个想法来自你。我修改了它所以我可以 运行 django unittest
与 radish
分开。我使用 LiveServerTestCase
直接删除 LiveServer
因为我注意到两者之间的相似性,除了 LiveServerTestCase
继承自 TransactionTestCase
并且它还构建了 LiveServerThread
和 _StaticFilesHandler
in. 是这样的:
# package/test/runner.py
import os
from django.test.runner import DiscoverRunner
import radish.main
class RadishTestRunner(DiscoverRunner):
radish_features = ['features']
def run_suite(self, suite, **kwargs):
# run radish test
return radish.main.main(self.radish_features)
def suite_result(self, suite, result, **kwargs):
return result
def set_radish_features(self, features):
self.radish_features = features
# radish/world.py
from django.test import LiveServerTestCase
from radish import pick
from selenium import webdriver
@pick
def get_browser():
return webdriver.Chrome()
@pick
def get_live_server():
live_server = LiveServerTestCase
live_server.setUpClass()
return live_server
# radish/terrain.py
from radish import world, before, after
from selenium import webdriver
@before.all
def set_up(features, marker):
world.get_live_server()
@after.all
def tear_down(features, marker):
live_server = world.get_live_server()
live_server.tearDownClass()
@before.each_scenario
def set_up_scenario(scenario):
live_server = world.get_live_server()
scenario.context.browser = webdriver.Chrome()
scenario.context.base_url = live_server.live_server_url
scenario.context.test_case = live_server()
scenario.context.test_case._pre_setup()
@after.each_scenario
def tear_down_scenario(scenario):
scenario.context.test_case._post_teardown()
scenario.context.browser.quit()
就是这样。这也解决了 PostgreSQL 在您指出的我的其他问题上的问题。我还在每个场景中打开和退出浏览器,因为它让我可以更好地控制场景中的浏览器。非常感谢您为我指出正确的方向所做的努力。
最后我return 到PostgreSQL。 就速度而言,PostgreSQL 似乎优于 MySQL。它大大减少了 运行 测试的时间。
哦,是的,在 django
设置文件中指定 STATIC_ROOT
之后,我需要先 运行 ./manage.py collectstatic
。
我也修改了 RadishTestRunner
,所以我可以 运行 用 python manage.py radish /path/to/features/file
代替 运行 RADISH_ONLY=1
。这是我的萝卜命令:
# package.management.commands.radish
from __future__ import absolute_import
import sys
from django.core.management.base import BaseCommand, CommandError
from package.test.runner import RadishTestRunner
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('features', nargs='+', type=str)
def handle(self, *args, **options):
test_runner = RadishTestRunner(interactive=False)
if options['features']:
test_runner.set_radish_features(options['features'])
result = test_runner.run_suite(None)
if result:
sys.exit(result)
通过使用带有 django 管理命令的萝卜,我们可以控制我们想要的特征文件 运行。
在我看来,为每个场景重新创建数据库最终会非常低效(而且非常慢)。每次测试只需要创建一次数据库 运行 然后在最后删除它。
我想出了一个我认为与 Django 集成得更好的解决方案。它允许您 运行 使用 manage.py test
进行测试,每次测试仅 creates/drops 一次数据库 运行,并在测试每个功能后清除数据库表。
请注意,此 运行 默认情况下同时进行 Django 单元测试和萝卜测试。要运行 只是萝卜测试,你可以RADISH_ONLY=1 manage.py test
。此外,要使实时 server/Selenium 测试生效,您必须先 运行 manage.py collectstatic
。
# package/settings.py
TEST_RUNNER = 'package.test.runner.RadishTestRunner'
# package/test/runner
import os
from django.test.runner import DiscoverRunner
import radish.main
class RadishTestRunner(DiscoverRunner):
def run_suite(self, suite, **kwargs):
# Run unit tests
if os.getenv('RADISH_ONLY') == '1':
result = None
else:
result = super().run_suite(suite, **kwargs)
# Run radish behavioral tests
self._radish_result = radish.main.main(['features'])
return result
def suite_result(self, suite, result, **kwargs):
if result is not None:
# Django unit tests were run
result = super().suite_result(suite, result, **kwargs)
else:
result = 0
result += self._radish_result
return result
# radish/world.py
from django.db import connections
from django.test.testcases import LiveServerThread, _StaticFilesHandler
from django.test.utils import modify_settings
from radish import pick
from selenium import webdriver
@pick
def get_browser():
return webdriver.Chrome()
@pick
def get_live_server():
live_server = LiveServer()
live_server.start()
return live_server
class LiveServer:
host = 'localhost'
port = 0
server_thread_class = LiveServerThread
static_handler = _StaticFilesHandler
def __init__(self):
connections_override = {}
for conn in connections.all():
if conn.vendor == 'sqlite' and conn.is_in_memory_db():
conn.allow_thread_sharing = True
connections_override[conn.alias] = conn
self.modified_settings = modify_settings(ALLOWED_HOSTS={'append': self.host})
self.server_thread = self.server_thread_class(
self.host,
self.static_handler,
connections_override=connections_override,
port=self.port,
)
self.server_thread.daemon = True
@property
def url(self):
self.server_thread.is_ready.wait()
return 'http://{self.host}:{self.server_thread.port}'.format(self=self)
def start(self):
self.modified_settings.enable()
self.server_thread.start()
self.server_thread.is_ready.wait()
if self.server_thread.error:
self.stop()
raise self.server_thread.error
def stop(self):
if hasattr(self, 'server_thread'):
self.server_thread.terminate()
for conn in connections.all():
if conn.vendor == 'sqlite' and conn.is_in_memory_db():
conn.allow_thread_sharing = False
self.modified_settings.disable()
# radish/terrain.py
from django.db import connections, transaction
from radish import world, before, after
@before.all
def set_up(features, marker):
world.get_live_server()
@after.all
def tear_down(features, marker):
browser = world.get_browser()
live_server = world.get_live_server()
browser.quit()
live_server.stop()
@before.each_scenario
def set_up_scenario(scenario):
live_server = world.get_live_server()
scenario.context.base_url = live_server.url
scenario.context.browser = world.get_browser()
# XXX: Only works with the default database
# XXX: Assumes the default database supports transactions
scenario.context.transaction = transaction.atomic(using='default')
scenario.context.transaction.__enter__()
@after.each_scenario
def tear_down_scenario(scenario):
transaction.set_rollback(True, using='default')
scenario.context.transaction.__exit__(None, None, None)
for connection in connections.all():
connection.close()