设置Django DiscoverRunner以始终使用萝卜重建数据库

时间:2017-11-01 21:28:03

标签: python django unit-testing selenium bdd

我正在使用带有selenium的萝卜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()

但我不知道怎么做。有什么帮助吗?

2 个答案:

答案 0 :(得分:1)

@Wyatt,我再次修改你的答案。我尝试运行您的解决方案,但它没有设法使每个场景独立,当我尝试在场景中创建模型对象时,我甚至遇到了完整性错误。无论我仍然使用你的解决方案(特别是RadishTestRunner,因为这个想法来自你。我修改了它,所以我可以django unittest单独运行radish。我直接使用LiveServerTestCase删除LiveServer,因为我注意到两者之间的相似性,但LiveServerTestCase继承自TransactionTestCase并且内置了LiveServerThread_StaticFilesHandler。这里&# 39;它是怎么回事:

# 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在我指出的另一个问题上的问题。我还打开并退出每个场景的浏览器,因为它让我可以更好地控制场景中的浏览器。非常感谢你努力指出我正确的方向。

最后我回到PostgreSQL。 在速度方面,PostgreSQL似乎比MySQL更好。它大大减少了运行测试的时间。

哦,是的,我需要在./manage.py collectstatic设置文件中指定STATIC_ROOT后先运行django

我还修改了RadishTestRunner,因此我可以使用RADISH_ONLY=1运行python manage.py radish /path/to/features/file,而不是# 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) 。这是我的萝卜命令:

var str = '(a (b) c) d e (f g) h';
var match;
var myRe = /\([^]+?\)|\S+/g;
var result = [];

while (match = myRe.exec(str)) {
  result.push(match[0]);
}

var tmp = "";
var final = [];
for (var i = 0; i < result.length; i++) {
  var leftP = (result[i].match(/\(/g) || []).length;
  var rightP = (result[i].match(/\)/g) || []).length;
  if (leftP !== rightP) {
    tmp += result[i];
    for (var j = i + 1; j < result.length; j++) {
      tmp += result[j];
      if ((tmp.match(/\(/g) || []).length === (tmp.match(/\)/g) || []).length) {
        final.push(tmp);
        tmp = "";
        i = j + 1;
        break;
      }
    }
  } else {
    final.push(result[i]);
  }
}
for (var i = 0; i < final.length; i++) {
  final[i] = final[i].replace(/\)(\S+)/g, ') $1');
}
for (var i = 0; i < final.length; i++) {
  final[i] = final[i].replace(/^\(([^]+)\)$/, '$1');
}

通过使用萝卜与django管理命令,我们可以控制我们想要运行的功能文件。

答案 1 :(得分:1)

在我看来,为每个场景重新创建数据库最终会导致效率极低(并且速度极慢)。应该只需要在每次测试运行时创建一次数据库,然后将其放在最后。

我想出了一个我认为与Django更好地集成的解决方案。它允许您使用manage.py test运行测试,每次测试运行只创建/删除数据库一次,并在测试每个功能后清除数据库表。

请注意,默认情况下,它会同时运行Django单元测试和萝卜测试。要只运行萝卜测试,你可以RADISH_ONLY=1 manage.py test。此外,要使实时服务器/ 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()