Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 92ad508

Browse filesBrowse files
authored
feat: added unit test with coverage of 68% (#611)
Add unit tests for many spanner_django modules that add coverage beyond the built-in django tests.
1 parent 3fa1aeb commit 92ad508
Copy full SHA for 92ad508
Expand file treeCollapse file tree

14 files changed

+1057
-103
lines changed

‎noxfile.py

Copy file name to clipboardExpand all lines: noxfile.py
+7-2Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ def lint_setup_py(session):
6666
def default(session):
6767
# Install all test dependencies, then install this package in-place.
6868
session.install(
69-
"django~=2.2", "mock", "mock-import", "pytest", "pytest-cov"
69+
"django~=2.2",
70+
"mock",
71+
"mock-import",
72+
"pytest",
73+
"pytest-cov",
74+
"coverage",
7075
)
7176
session.install("-e", ".")
7277

@@ -79,7 +84,7 @@ def default(session):
7984
"--cov-append",
8085
"--cov-config=.coveragerc",
8186
"--cov-report=",
82-
"--cov-fail-under=20",
87+
"--cov-fail-under=68",
8388
os.path.join("tests", "unit"),
8489
*session.posargs
8590
)

‎tests/conftest.py

Copy file name to clipboard
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import os
2+
import django
3+
from django.conf import settings
4+
5+
# We manually designate which settings we will be using in an environment
6+
# variable. This is similar to what occurs in the `manage.py` file.
7+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.settings")
8+
9+
10+
# `pytest` automatically calls this function once when tests are run.
11+
def pytest_configure():
12+
settings.DEBUG = False
13+
django.setup()

‎tests/settings.py

Copy file name to clipboard
+46Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Copyright 2021 Google LLC
2+
#
3+
# Use of this source code is governed by a BSD-style
4+
# license that can be found in the LICENSE file or at
5+
# https://developers.google.com/open-source/licenses/bsd
6+
7+
DEBUG = True
8+
USE_TZ = True
9+
10+
INSTALLED_APPS = [
11+
"django_spanner", # Must be the first entry
12+
"django.contrib.contenttypes",
13+
"django.contrib.auth",
14+
"django.contrib.sites",
15+
"django.contrib.sessions",
16+
"django.contrib.messages",
17+
"django.contrib.staticfiles",
18+
"tests",
19+
]
20+
21+
TIME_ZONE = "UTC"
22+
23+
DATABASES = {
24+
"default": {
25+
"ENGINE": "django_spanner",
26+
"PROJECT": "emulator-local",
27+
"INSTANCE": "django-test-instance",
28+
"NAME": "django-test-db",
29+
}
30+
}
31+
SECRET_KEY = "spanner emulator secret key"
32+
33+
PASSWORD_HASHERS = [
34+
"django.contrib.auth.hashers.MD5PasswordHasher",
35+
]
36+
37+
SITE_ID = 1
38+
39+
CONN_MAX_AGE = 60
40+
41+
ENGINE = "django_spanner"
42+
PROJECT = "emulator-local"
43+
INSTANCE = "django-test-instance"
44+
NAME = "django-test-db"
45+
OPTIONS = {}
46+
AUTOCOMMIT = True

‎tests/unit/django_spanner/__init__.py

Copy file name to clipboardExpand all lines: tests/unit/django_spanner/__init__.py
Whitespace-only changes.

‎tests/unit/django_spanner/models.py

Copy file name to clipboard
+61Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Copyright 2021 Google LLC
2+
#
3+
# Use of this source code is governed by a BSD-style
4+
# license that can be found in the LICENSE file or at
5+
# https://developers.google.com/open-source/licenses/bsd
6+
"""
7+
Different models used for testing django-spanner code.
8+
"""
9+
from django.db import models
10+
11+
12+
# Register transformations for model fields.
13+
class UpperCase(models.Transform):
14+
lookup_name = "upper"
15+
function = "UPPER"
16+
bilateral = True
17+
18+
19+
models.CharField.register_lookup(UpperCase)
20+
models.TextField.register_lookup(UpperCase)
21+
22+
23+
# Models
24+
class ModelDecimalField(models.Model):
25+
field = models.DecimalField()
26+
27+
28+
class ModelCharField(models.Model):
29+
field = models.CharField()
30+
31+
32+
class Item(models.Model):
33+
item_id = models.IntegerField()
34+
name = models.CharField(max_length=10)
35+
created = models.DateTimeField()
36+
modified = models.DateTimeField(blank=True, null=True)
37+
38+
class Meta:
39+
ordering = ["name"]
40+
41+
42+
class Number(models.Model):
43+
num = models.IntegerField()
44+
decimal_num = models.DecimalField(max_digits=5, decimal_places=2)
45+
item = models.ForeignKey(Item, models.CASCADE)
46+
47+
48+
class Author(models.Model):
49+
name = models.CharField(max_length=40)
50+
last_name = models.CharField(max_length=40)
51+
num = models.IntegerField(unique=True)
52+
created = models.DateTimeField()
53+
modified = models.DateTimeField(blank=True, null=True)
54+
55+
56+
class Report(models.Model):
57+
name = models.CharField(max_length=10)
58+
creator = models.ForeignKey(Author, models.CASCADE, null=True)
59+
60+
class Meta:
61+
ordering = ["name"]
+33Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Copyright 2021 Google LLC
2+
#
3+
# Use of this source code is governed by a BSD-style
4+
# license that can be found in the LICENSE file or at
5+
# https://developers.google.com/open-source/licenses/bsd
6+
7+
from django_spanner.client import DatabaseClient
8+
from django_spanner.base import DatabaseWrapper
9+
from django_spanner.operations import DatabaseOperations
10+
from unittest import TestCase
11+
import os
12+
13+
14+
class SpannerSimpleTestClass(TestCase):
15+
@classmethod
16+
def setUpClass(cls):
17+
cls.PROJECT = os.environ["GOOGLE_CLOUD_PROJECT"]
18+
19+
cls.INSTANCE_ID = "instance_id"
20+
cls.DATABASE_ID = "database_id"
21+
cls.USER_AGENT = "django_spanner/2.2.0a1"
22+
cls.OPTIONS = {"option": "dummy"}
23+
24+
cls.settings_dict = {
25+
"PROJECT": cls.PROJECT,
26+
"INSTANCE": cls.INSTANCE_ID,
27+
"NAME": cls.DATABASE_ID,
28+
"user_agent": cls.USER_AGENT,
29+
"OPTIONS": cls.OPTIONS,
30+
}
31+
cls.db_client = DatabaseClient(cls.settings_dict)
32+
cls.db_wrapper = cls.connection = DatabaseWrapper(cls.settings_dict)
33+
cls.db_operations = DatabaseOperations(cls.connection)

‎tests/unit/django_spanner/test_base.py

Copy file name to clipboardExpand all lines: tests/unit/django_spanner/test_base.py
+29-68Lines changed: 29 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -4,59 +4,24 @@
44
# license that can be found in the LICENSE file or at
55
# https://developers.google.com/open-source/licenses/bsd
66

7-
import sys
8-
import unittest
9-
import os
10-
11-
from mock_import import mock_import
127
from unittest import mock
8+
from tests.unit.django_spanner.simple_test import SpannerSimpleTestClass
139

1410

15-
@mock_import()
16-
@unittest.skipIf(
17-
sys.version_info < (3, 6), reason="Skipping Python versions <= 3.5"
18-
)
19-
class TestBase(unittest.TestCase):
20-
PROJECT = os.environ["GOOGLE_CLOUD_PROJECT"]
21-
INSTANCE_ID = "instance_id"
22-
DATABASE_ID = "database_id"
23-
USER_AGENT = "django_spanner/2.2.0a1"
24-
OPTIONS = {"option": "dummy"}
25-
26-
settings_dict = {
27-
"PROJECT": PROJECT,
28-
"INSTANCE": INSTANCE_ID,
29-
"NAME": DATABASE_ID,
30-
"user_agent": USER_AGENT,
31-
"OPTIONS": OPTIONS,
32-
}
33-
34-
def _get_target_class(self):
35-
from django_spanner.base import DatabaseWrapper
36-
37-
return DatabaseWrapper
38-
39-
def _make_one(self, *args, **kwargs):
40-
return self._get_target_class()(*args, **kwargs)
41-
11+
class TestBase(SpannerSimpleTestClass):
4212
def test_property_instance(self):
43-
settings_dict = {"INSTANCE": "instance"}
44-
db_wrapper = self._make_one(settings_dict=settings_dict)
45-
4613
with mock.patch("django_spanner.base.spanner") as mock_spanner:
4714
mock_spanner.Client = mock_client = mock.MagicMock()
4815
mock_client().instance = mock_instance = mock.MagicMock()
49-
_ = db_wrapper.instance
50-
mock_instance.assert_called_once_with(settings_dict["INSTANCE"])
16+
_ = self.db_wrapper.instance
17+
mock_instance.assert_called_once_with(self.INSTANCE_ID)
5118

52-
def test_property__nodb_connection(self):
53-
db_wrapper = self._make_one(None)
19+
def test_property_nodb_connection(self):
5420
with self.assertRaises(NotImplementedError):
55-
db_wrapper._nodb_connection()
21+
self.db_wrapper._nodb_connection()
5622

5723
def test_get_connection_params(self):
58-
db_wrapper = self._make_one(self.settings_dict)
59-
params = db_wrapper.get_connection_params()
24+
params = self.db_wrapper.get_connection_params()
6025

6126
self.assertEqual(params["project"], self.PROJECT)
6227
self.assertEqual(params["instance_id"], self.INSTANCE_ID)
@@ -65,54 +30,50 @@ def test_get_connection_params(self):
6530
self.assertEqual(params["option"], self.OPTIONS["option"])
6631

6732
def test_get_new_connection(self):
68-
db_wrapper = self._make_one(self.settings_dict)
69-
db_wrapper.Database = mock_database = mock.MagicMock()
33+
self.db_wrapper.Database = mock_database = mock.MagicMock()
7034
mock_database.connect = mock_connection = mock.MagicMock()
7135
conn_params = {"test_param": "dummy"}
72-
db_wrapper.get_new_connection(conn_params)
36+
self.db_wrapper.get_new_connection(conn_params)
7337
mock_connection.assert_called_once_with(**conn_params)
7438

7539
def test_init_connection_state(self):
76-
db_wrapper = self._make_one(self.settings_dict)
77-
db_wrapper.connection = mock_connection = mock.MagicMock()
40+
self.db_wrapper.connection = mock_connection = mock.MagicMock()
7841
mock_connection.close = mock_close = mock.MagicMock()
79-
db_wrapper.init_connection_state()
42+
self.db_wrapper.init_connection_state()
8043
mock_close.assert_called_once_with()
8144

8245
def test_create_cursor(self):
83-
db_wrapper = self._make_one(self.settings_dict)
84-
db_wrapper.connection = mock_connection = mock.MagicMock()
46+
self.db_wrapper.connection = mock_connection = mock.MagicMock()
8547
mock_connection.cursor = mock_cursor = mock.MagicMock()
86-
db_wrapper.create_cursor()
48+
self.db_wrapper.create_cursor()
8749
mock_cursor.assert_called_once_with()
8850

89-
def test__set_autocommit(self):
90-
db_wrapper = self._make_one(self.settings_dict)
91-
db_wrapper.connection = mock_connection = mock.MagicMock()
51+
def test_set_autocommit(self):
52+
self.db_wrapper.connection = mock_connection = mock.MagicMock()
9253
mock_connection.autocommit = False
93-
db_wrapper._set_autocommit(True)
54+
self.db_wrapper._set_autocommit(True)
9455
self.assertEqual(mock_connection.autocommit, True)
9556

9657
def test_is_usable(self):
97-
from google.cloud.spanner_dbapi.exceptions import Error
98-
99-
db_wrapper = self._make_one(self.settings_dict)
100-
db_wrapper.connection = None
101-
self.assertFalse(db_wrapper.is_usable())
58+
self.db_wrapper.connection = None
59+
self.assertFalse(self.db_wrapper.is_usable())
10260

103-
db_wrapper.connection = mock_connection = mock.MagicMock()
61+
self.db_wrapper.connection = mock_connection = mock.MagicMock()
10462
mock_connection.is_closed = True
105-
self.assertFalse(db_wrapper.is_usable())
63+
self.assertFalse(self.db_wrapper.is_usable())
10664

10765
mock_connection.is_closed = False
108-
self.assertTrue(db_wrapper.is_usable())
66+
self.assertTrue(self.db_wrapper.is_usable())
67+
68+
def test_is_usable_with_error(self):
69+
from google.cloud.spanner_dbapi.exceptions import Error
10970

71+
self.db_wrapper.connection = mock_connection = mock.MagicMock()
11072
mock_connection.cursor = mock.MagicMock(side_effect=Error)
111-
self.assertFalse(db_wrapper.is_usable())
73+
self.assertFalse(self.db_wrapper.is_usable())
11274

113-
def test__start_transaction_under_autocommit(self):
114-
db_wrapper = self._make_one(self.settings_dict)
115-
db_wrapper.connection = mock_connection = mock.MagicMock()
75+
def test_start_transaction_under_autocommit(self):
76+
self.db_wrapper.connection = mock_connection = mock.MagicMock()
11677
mock_connection.cursor = mock_cursor = mock.MagicMock()
117-
db_wrapper._start_transaction_under_autocommit()
78+
self.db_wrapper._start_transaction_under_autocommit()
11879
mock_cursor.assert_called_once_with()

‎tests/unit/django_spanner/test_client.py

Copy file name to clipboardExpand all lines: tests/unit/django_spanner/test_client.py
+4-33Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,12 @@
44
# license that can be found in the LICENSE file or at
55
# https://developers.google.com/open-source/licenses/bsd
66

7-
import sys
8-
import unittest
9-
import os
107

8+
from google.cloud.spanner_dbapi.exceptions import NotSupportedError
9+
from tests.unit.django_spanner.simple_test import SpannerSimpleTestClass
1110

12-
@unittest.skipIf(
13-
sys.version_info < (3, 6), reason="Skipping Python versions <= 3.5"
14-
)
15-
class TestClient(unittest.TestCase):
16-
PROJECT = os.environ["GOOGLE_CLOUD_PROJECT"]
17-
INSTANCE_ID = "instance_id"
18-
DATABASE_ID = "database_id"
19-
USER_AGENT = "django_spanner/2.2.0a1"
20-
OPTIONS = {"option": "dummy"}
21-
22-
settings_dict = {
23-
"PROJECT": PROJECT,
24-
"INSTANCE": INSTANCE_ID,
25-
"NAME": DATABASE_ID,
26-
"user_agent": USER_AGENT,
27-
"OPTIONS": OPTIONS,
28-
}
29-
30-
def _get_target_class(self):
31-
from django_spanner.client import DatabaseClient
32-
33-
return DatabaseClient
34-
35-
def _make_one(self, *args, **kwargs):
36-
return self._get_target_class()(*args, **kwargs)
3711

12+
class TestClient(SpannerSimpleTestClass):
3813
def test_runshell(self):
39-
from google.cloud.spanner_dbapi.exceptions import NotSupportedError
40-
41-
db_wrapper = self._make_one(self.settings_dict)
42-
4314
with self.assertRaises(NotSupportedError):
44-
db_wrapper.runshell(parameters=self.settings_dict)
15+
self.db_client.runshell(parameters=self.settings_dict)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.