Django 模型管理器创建与测试实践
1. 创建模型管理器的必要性
为了提高应用程序的可读性,避免业务逻辑使端点变得杂乱,我们需要为模型类创建管理器。管理器是为 Django 模型提供查询操作的接口。默认情况下,Django 会为每个模型添加一个名为
objects
的管理器。虽然默认管理器有时已足够,但将所有与数据库相关的代码放在模型中是一个很好的实践,这能使代码更一致、易读,且便于测试和维护。
2. 创建自定义异常类
在创建
Order
模型的自定义管理器之前,我们需要创建一些辅助类,首先是自定义异常类。以下是我们要创建的异常类:
-
InvalidArgumentError
:当向管理器中定义的函数传递无效参数时抛出。
class InvalidArgumentError(Exception):
def __init__(self, argument_name):
message = f'The argument {argument_name} is invalid'
super().__init__(message)
-
OrderAlreadyCompletedError:当尝试将已完成的订单状态再次设置为完成时抛出。
class OrderAlreadyCompletedError(Exception):
def __init__(self, order):
message = f'The order with ID: {order.id} is already completed.'
super().__init__(message)
-
OrderAlreadyCancelledError:当尝试取消已取消的订单时抛出。
class OrderAlreadyCancelledError(Exception):
def __init__(self, order):
message = f'The order with ID: {order.id} is already cancelled.'
super().__init__(message)
-
OrderCancellationError和OrderNotFoundError:这两个异常类仅继承自Exception,用于特定场景下抛出。
class OrderCancellationError(Exception):
pass
class OrderNotFoundError(Exception):
pass
3. 创建状态枚举类
为了避免在代码中使用魔法数字,我们创建一个枚举类来表示订单的不同状态。
from enum import Enum, auto
class Status(Enum):
Received = auto()
Processing = auto()
Payment_Complete = auto()
Shipping = auto()
Completed = auto()
Cancelled = auto()
这样,我们可以使用
Status
枚举来过滤订单,例如:
Order.objects.filter(status=Status.Completed.value)
。
4. 创建
OrderManager
类
在
managers.py
文件中创建
OrderManager
类,并定义以下方法:
4.1
set_status
方法
用于设置订单的状态。
class OrderManager(Manager):
def set_status(self, order, status):
if status is None or not isinstance(status, Status):
raise InvalidArgumentError('status')
if order is None or not isinstance(order, models.Order):
raise InvalidArgumentError('order')
if order.status is Status.Completed.value:
raise OrderAlreadyCompletedError()
order.status = status.value
order.save()
4.2
cancel_order
方法
用于取消订单,仅允许取消状态为
Received
的订单。
def cancel_order(self, order):
if order is None or not isinstance(order, models.Order):
raise InvalidArgumentError('order')
if order.status != Status.Received.value:
raise OrderCancellationError()
self.set_status(order, Status.Cancelled)
4.3
get_all_orders_by_customer
方法
根据客户 ID 获取所有订单,并按状态和创建时间排序。
def get_all_orders_by_customer(self, customer_id):
try:
return self.filter(
order_customer_id=customer_id).order_by(
'status', '-created_at')
except ValueError:
raise InvalidArgumentError('customer_id')
4.4
get_customer_incomplete_orders
和
get_customer_completed_orders
方法
分别用于获取特定客户的未完成订单和已完成订单。
def get_customer_incomplete_orders(self, customer_id):
try:
return self.filter(
~Q(status=Status.Completed.value),
order_customer_id=customer_id).order_by('status')
except ValueError:
raise InvalidArgumentError('customer_id')
def get_customer_completed_orders(self, customer_id):
try:
return self.filter(
status=Status.Completed.value,
order_customer_id=customer_id)
except ValueError:
raise InvalidArgumentError('customer_id')
4.5
get_orders_by_status
方法
根据订单状态获取订单。
def get_orders_by_status(self, status):
if status is None or not isinstance(status, Status):
raise InvalidArgumentError('status')
return self.filter(status=status.value)
4.6
get_orders_by_period
方法
根据日期范围获取订单。
def get_orders_by_period(self, start_date, end_date):
if start_date is None or not isinstance(start_date, datetime):
raise InvalidArgumentError('start_date')
if end_date is None or not isinstance(end_date, datetime):
raise InvalidArgumentError('end_date')
result = self.filter(created_at__range=[start_date, end_date])
return result
4.7
set_next_status
方法
将订单状态设置为下一个状态。
def set_next_status(self, order):
if order is None or not isinstance(order, models.Order):
raise InvalidArgumentError('order')
if order.status is Status.Completed.value:
raise OrderAlreadyCompletedError()
order.status += 1
order.save()
5. 使用自定义管理器
在
model.py
文件的
Order
类末尾添加以下代码,使
Order
模型使用我们创建的
OrderManager
。
objects = OrderManager()
这样,我们可以通过
Order.objects
访问
OrderManager
中定义的所有方法。
6. 测试的重要性
测试可以让我们知道方法或函数是否按预期工作,并且在对代码进行更改时给我们更多信心。Django 提供了强大的单元和集成测试工具,结合 Selenium 等框架,几乎可以测试整个应用程序。
7. 创建测试文件
在 Django 创建的
test.py
文件中编写测试代码。首先添加必要的导入:
from dateutil.relativedelta import relativedelta
from django.test import TestCase
from django.utils import timezone
from .models import OrderCustomer, Order
from .status import Status
from .exceptions import OrderAlreadyCompletedError
from .exceptions import OrderCancellationError
from .exceptions import InvalidArgumentError
8. 设置测试数据
创建
OrderModelTestCase
类,并定义
setUpTestData
方法来设置测试数据。
class OrderModelTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.customer_001 = OrderCustomer.objects.create(
customer_id=1,
email='customer_001@test.com'
)
Order.objects.create(order_customer=cls.customer_001)
Order.objects.create(order_customer=cls.customer_001,
status=Status.Completed.value)
cls.customer_002 = OrderCustomer.objects.create(
customer_id=1,
email='customer_002@test.com'
)
Order.objects.create(order_customer=cls.customer_002)
9. 测试
cancel_order
方法
我们将对
cancel_order
方法进行以下测试:
- 测试是否可以取消订单并将其状态设置为
Cancelled
。
- 测试是否不能取消未收到的订单。
- 测试传递无效参数时是否抛出正确的异常。
def test_cancel_order(self):
order = Order.objects.get(pk=1)
self.assertIsNotNone(order)
self.assertEqual(Status.Received.value, order.status)
Order.objects.cancel_order(order)
self.assertEqual(Status.Cancelled.value, order.status)
def test_cancel_completed_order(self):
order = Order.objects.get(pk=2)
self.assertIsNotNone(order)
self.assertEqual(Status.Completed.value, order.status)
with self.assertRaises(OrderCancellationError):
Order.objects.cancel_order(order)
def test_cancel_order_with_invalid_argument(self):
with self.assertRaises(InvalidArgumentError):
Order.objects.cancel_order({'id': 1})
以下是创建自定义管理器和测试的流程:
graph TD;
A[创建自定义异常类] --> B[创建状态枚举类];
B --> C[创建OrderManager类];
C --> D[在Order模型中使用OrderManager];
D --> E[创建测试文件];
E --> F[设置测试数据];
F --> G[测试cancel_order方法];
| 步骤 | 操作 |
|---|---|
| 1 | 创建自定义异常类 |
| 2 | 创建状态枚举类 |
| 3 |
创建
OrderManager
类
|
| 4 |
在
Order
模型中使用
OrderManager
|
| 5 | 创建测试文件 |
| 6 | 设置测试数据 |
| 7 |
测试
cancel_order
方法
|
Django 模型管理器创建与测试实践
10. 测试
set_status
方法
接下来,我们对
set_status
方法进行测试,确保它能正确设置订单状态,并且在异常情况下抛出相应的错误。
def test_set_status_valid(self):
order = Order.objects.get(pk=1)
self.assertIsNotNone(order)
self.assertEqual(Status.Received.value, order.status)
Order.objects.set_status(order, Status.Processing)
self.assertEqual(Status.Processing.value, order.status)
def test_set_status_completed_order(self):
order = Order.objects.get(pk=2)
self.assertIsNotNone(order)
self.assertEqual(Status.Completed.value, order.status)
with self.assertRaises(OrderAlreadyCompletedError):
Order.objects.set_status(order, Status.Shipping)
def test_set_status_invalid_status(self):
order = Order.objects.get(pk=1)
with self.assertRaises(InvalidArgumentError):
Order.objects.set_status(order, None)
-
test_set_status_valid:测试正常情况下,能否将订单状态设置为指定状态。 -
test_set_status_completed_order:测试当订单已经是完成状态时,尝试更改状态是否会抛出OrderAlreadyCompletedError。 -
test_set_status_invalid_status:测试传递无效的状态参数时,是否会抛出InvalidArgumentError。
11. 测试
get_all_orders_by_customer
方法
对
get_all_orders_by_customer
方法进行测试,验证其能否根据客户 ID 正确获取订单列表,以及在传递无效客户 ID 时是否抛出异常。
def test_get_all_orders_by_customer_valid(self):
customer_id = 1
orders = Order.objects.get_all_orders_by_customer(customer_id)
self.assertEqual(len(orders), 2)
def test_get_all_orders_by_customer_invalid_id(self):
with self.assertRaises(InvalidArgumentError):
Order.objects.get_all_orders_by_customer('invalid_id')
-
test_get_all_orders_by_customer_valid:测试传递有效客户 ID 时,能否正确获取该客户的订单列表。 -
test_get_all_orders_by_customer_invalid_id:测试传递无效客户 ID 时,是否会抛出InvalidArgumentError。
12. 测试
get_customer_incomplete_orders
和
get_customer_completed_orders
方法
分别对获取客户未完成订单和已完成订单的方法进行测试。
def test_get_customer_incomplete_orders(self):
customer_id = 1
incomplete_orders = Order.objects.get_customer_incomplete_orders(customer_id)
self.assertEqual(len(incomplete_orders), 1)
def test_get_customer_completed_orders(self):
customer_id = 1
completed_orders = Order.objects.get_customer_completed_orders(customer_id)
self.assertEqual(len(completed_orders), 1)
-
test_get_customer_incomplete_orders:测试能否正确获取指定客户的未完成订单列表。 -
test_get_customer_completed_orders:测试能否正确获取指定客户的已完成订单列表。
13. 测试
get_orders_by_status
方法
对根据订单状态获取订单列表的方法进行测试。
def test_get_orders_by_status_valid(self):
orders = Order.objects.get_orders_by_status(Status.Received)
self.assertEqual(len(orders), 2)
def test_get_orders_by_status_invalid_status(self):
with self.assertRaises(InvalidArgumentError):
Order.objects.get_orders_by_status(None)
-
test_get_orders_by_status_valid:测试传递有效状态时,能否正确获取该状态的订单列表。 -
test_get_orders_by_status_invalid_status:测试传递无效状态时,是否会抛出InvalidArgumentError。
14. 测试
get_orders_by_period
方法
对根据日期范围获取订单列表的方法进行测试。
import datetime
def test_get_orders_by_period_valid(self):
start_date = timezone.now() - relativedelta(days=1)
end_date = timezone.now() + relativedelta(days=1)
orders = Order.objects.get_orders_by_period(start_date, end_date)
self.assertEqual(len(orders), 3)
def test_get_orders_by_period_invalid_start_date(self):
end_date = timezone.now() + relativedelta(days=1)
with self.assertRaises(InvalidArgumentError):
Order.objects.get_orders_by_period(None, end_date)
-
test_get_orders_by_period_valid:测试传递有效日期范围时,能否正确获取该日期范围内的订单列表。 -
test_get_orders_by_period_invalid_start_date:测试传递无效的开始日期时,是否会抛出InvalidArgumentError。
15. 测试
set_next_status
方法
对将订单状态设置为下一个状态的方法进行测试。
def test_set_next_status_valid(self):
order = Order.objects.get(pk=1)
self.assertIsNotNone(order)
self.assertEqual(Status.Received.value, order.status)
Order.objects.set_next_status(order)
self.assertEqual(Status.Processing.value, order.status)
def test_set_next_status_completed_order(self):
order = Order.objects.get(pk=2)
self.assertIsNotNone(order)
self.assertEqual(Status.Completed.value, order.status)
with self.assertRaises(OrderAlreadyCompletedError):
Order.objects.set_next_status(order)
-
test_set_next_status_valid:测试正常情况下,能否将订单状态设置为下一个状态。 -
test_set_next_status_completed_order:测试当订单已经是完成状态时,尝试设置下一个状态是否会抛出OrderAlreadyCompletedError。
16. 测试流程总结
以下是整个测试流程的 mermaid 流程图:
graph TD;
A[测试set_status方法] --> B[测试get_all_orders_by_customer方法];
B --> C[测试get_customer_incomplete_orders和get_customer_completed_orders方法];
C --> D[测试get_orders_by_status方法];
D --> E[测试get_orders_by_period方法];
E --> F[测试set_next_status方法];
| 步骤 | 测试方法 | 测试目的 |
|---|---|---|
| 1 |
test_set_status
|
验证
set_status
方法的正常使用和异常处理
|
| 2 |
test_get_all_orders_by_customer
| 验证根据客户 ID 获取订单列表的功能和异常处理 |
| 3 |
test_get_customer_incomplete_orders
和
test_get_customer_completed_orders
| 验证获取客户未完成和已完成订单列表的功能 |
| 4 |
test_get_orders_by_status
| 验证根据订单状态获取订单列表的功能和异常处理 |
| 5 |
test_get_orders_by_period
| 验证根据日期范围获取订单列表的功能和异常处理 |
| 6 |
test_set_next_status
| 验证将订单状态设置为下一个状态的功能和异常处理 |
通过以上一系列的测试,我们可以确保
OrderManager
中的各个方法在不同情况下都能正常工作,并且在出现异常时能正确抛出相应的错误,从而提高代码的健壮性和可维护性。在实际开发中,我们可以根据需求添加更多的测试用例,进一步完善测试覆盖度。
超级会员免费看
1014

被折叠的 条评论
为什么被折叠?



