热门角色不仅是灵感来源,更是你的效率助手。通过精挑细选的角色提示词,你可以快速生成高质量内容、提升创作灵感,并找到最契合你需求的解决方案。让创作更轻松,让价值更直接!
我们根据不同用户需求,持续更新角色库,让你总能找到合适的灵感入口。
根据提供的代码片段与测试场景自动生成高质量、可运行的单元测试用例,涵盖正向、反向、边界条件及依赖项模拟。适配常见测试框架/语言,可输出完整测试文件与运行指引,便于直接集成到 CI 流水线,提升测试覆盖与维护效率,减少手动编写与回归成本。
以下是完整的 pytest 单元测试文件内容,以及运行指引。测试使用 pytest + pytest-mock + monkeypatch,对 requests.Session.get 进行桩件,覆盖正向、反向、边界、TTL 和环境变量相关场景,并严格校验调用次数、参数与异常路径。
文件:test_price_calculator.py
import pytest import requests import price_calculator as pc from price_calculator import PriceCalculator
class StubResponse: """自定义响应对象,支持 status_code、json()、raise_for_status() 和故障注入。""" def init(self, status_code=200, json_data=None, raise_http_error=False, json_exc=None): self.status_code = status_code self._json_data = json_data or {} self._raise_http_error = raise_http_error self._json_exc = json_exc
def raise_for_status(self):
if self._raise_http_error or self.status_code >= 400:
raise requests.HTTPError(f"HTTP {self.status_code}")
def json(self):
if self._json_exc:
raise self._json_exc
return self._json_data
def test_percent_discount_final_and_cache_hit(mocker, monkeypatch): # 环境:覆盖 TAX_API_URL monkeypatch.setenv("TAX_API_URL", "https://tax.test.local") # 桩件 session.get 返回固定税率 session = mocker.Mock(spec=requests.Session) session.get.return_value = StubResponse(status_code=200, json_data={"rate": 0.2}) calc = PriceCalculator(session=session)
items = [
{"price": 19.99, "qty": 2}, # 39.98
{"price": 5.50, "qty": 1}, # 45.48
{"price": 1.25, "qty": 3}, # 49.23
]
discount = {"type": "percent", "value": 10} # 10% 折扣
total1 = calc.calculate_final(items, "US", discount)
total2 = calc.calculate_final(items, "US", discount)
# 校验两位小数和总价
assert total1 == 53.17
assert total2 == 53.17
assert f"{total1:.2f}" == "53.17"
# 缓存命中:仅一次 HTTP 请求
assert session.get.call_count == 1
session.get.assert_called_once_with("https://tax.test.local/v1/tax/US", timeout=2)
def test_unknown_discount_type_raises_valueerror(mocker): session = mocker.Mock(spec=requests.Session) calc = PriceCalculator(session=session) items = [{"price": 10.0, "qty": 1}] discount = {"type": "oops", "value": 5} with pytest.raises(ValueError) as exc: calc.calculate_final(items, "US", discount) assert "unknown discount type" in str(exc.value) # 在折扣类型校验前抛出,不应触发税率查询 session.get.assert_not_called()
def test_negative_discount_value_raises_valueerror(mocker): session = mocker.Mock(spec=requests.Session) calc = PriceCalculator(session=session) items = [{"price": 10.0, "qty": 1}] discount = {"type": "percent", "value": -5} with pytest.raises(ValueError) as exc: calc.calculate_final(items, "US", discount) assert "discount value cannot be negative" in str(exc.value) session.get.assert_not_called()
def test_negative_qty_raises_valueerror(mocker): session = mocker.Mock(spec=requests.Session) calc = PriceCalculator(session=session) items = [{"price": 10.0, "qty": -1}] discount = {"type": "absolute", "value": 1} with pytest.raises(ValueError) as exc: calc.calculate_final(items, "US", discount) assert "invalid qty at index 0" in str(exc.value) session.get.assert_not_called()
def test_negative_price_raises_valueerror(mocker): session = mocker.Mock(spec=requests.Session) calc = PriceCalculator(session=session) items = [{"price": -10.0, "qty": 1}] discount = {"type": "absolute", "value": 1} with pytest.raises(ValueError) as exc: calc.calculate_final(items, "US", discount) assert "invalid price at index 0" in str(exc.value) session.get.assert_not_called()
def test_empty_items_total_zero(mocker, monkeypatch): # 环境可设置但结果应为 0.00 monkeypatch.setenv("TAX_API_URL", "https://tax.empty.local") session = mocker.Mock(spec=requests.Session) session.get.return_value = StubResponse(json_data={"rate": 0.25}) calc = PriceCalculator(session=session)
total = calc.calculate_final([], "GB", {"type": "percent", "value": 10})
assert total == 0.0
# 仍会进行税率查询,但总价为 0
session.get.assert_called_once_with("https://tax.empty.local/v1/tax/GB", timeout=2)
def test_absolute_discount_clamps_to_zero_when_negative(mocker): session = mocker.Mock(spec=requests.Session) session.get.return_value = StubResponse(json_data={"rate": 0.2}) calc = PriceCalculator(session=session)
items = [{"price": 10.0, "qty": 1}] # subtotal = 10
discount = {"type": "absolute", "value": 50} # 使小计变负
total = calc.calculate_final(items, "US", discount)
assert total == 0.0 # 归 0 后税也为 0
def test_tax_api_http_error_raises_runtimeerror(mocker): session = mocker.Mock(spec=requests.Session) session.get.return_value = StubResponse(status_code=500, raise_http_error=True) calc = PriceCalculator(session=session)
with pytest.raises(RuntimeError) as exc:
calc.calculate_final([{"price": 10.0, "qty": 1}], "US", {"type": "absolute", "value": 1})
msg = str(exc.value)
assert "tax lookup failed" in msg
assert "HTTP 500" in msg
assert session.get.call_count == 1
def test_tax_api_timeout_raises_runtimeerror(mocker): session = mocker.Mock(spec=requests.Session) session.get.side_effect = requests.Timeout("connection timed out") calc = PriceCalculator(session=session)
with pytest.raises(RuntimeError) as exc:
calc.calculate_final([{"price": 10.0, "qty": 1}], "US", {"type": "absolute", "value": 1})
assert "tax lookup failed" in str(exc.value)
assert session.get.call_count == 1
def test_tax_api_bad_json_raises_runtimeerror(mocker): session = mocker.Mock(spec=requests.Session) session.get.return_value = StubResponse(json_exc=ValueError("bad json")) calc = PriceCalculator(session=session)
with pytest.raises(RuntimeError) as exc:
calc.calculate_final([{"price": 10.0, "qty": 1}], "US", {"type": "percent", "value": 10})
assert "tax lookup failed" in str(exc.value)
assert session.get.call_count == 1
def test_cache_ttl_expiry_triggers_second_http_request(mocker, monkeypatch): session = mocker.Mock(spec=requests.Session) # 两次调用返回相同税率,便于验证二次请求 session.get.side_effect = [ StubResponse(json_data={"rate": 0.1}), StubResponse(json_data={"rate": 0.1}), ] calc = PriceCalculator(session=session, cache_ttl_seconds=300)
# 控制 time.time,先在缓存有效期内,再推进到过期之后
now = {"t": 1000.0}
monkeypatch.setattr(pc.time, "time", lambda: now["t"])
# 第一次取税率 -> 触发 HTTP
rate1 = calc.get_tax_rate("FR")
assert rate1 == 0.1
assert session.get.call_count == 1
# 缓存未过期 -> 不触发 HTTP
now["t"] = 1000.0 + calc.cache_ttl_seconds - 1
rate2 = calc.get_tax_rate("FR")
assert rate2 == 0.1
assert session.get.call_count == 1
# 缓存过期 -> 再次触发 HTTP
now["t"] = 1000.0 + calc.cache_ttl_seconds + 1
rate3 = calc.get_tax_rate("FR")
assert rate3 == 0.1
assert session.get.call_count == 2
# 校验请求参数
expected_url = f"{calc.base_url}/v1/tax/FR"
assert session.get.call_args_list[0] == mocker.call(expected_url, timeout=2)
assert session.get.call_args_list[1] == mocker.call(expected_url, timeout=2)
def test_env_tax_api_url_used_in_request(mocker, monkeypatch): monkeypatch.setenv("TAX_API_URL", "https://api.override.local") session = mocker.Mock(spec=requests.Session) session.get.return_value = StubResponse(json_data={"rate": 0.15}) calc = PriceCalculator(session=session)
rate = calc.get_tax_rate("DE")
assert rate == 0.15
session.get.assert_called_once_with("https://api.override.local/v1/tax/DE", timeout=2)
pytest 运行指引:
下面给出使用 JUnit 5 + Mockito 的完整、严格覆盖的单元测试类,放置路径建议为:src/test/java/app/order/OrderProcessorTest.java(与被测类在同一包 app.order 下,以便访问包级可见的类型)。
代码: // File: src/test/java/app/order/OrderProcessorTest.java package app.order;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Arrays; import java.util.List;
import static org.junit.jupiter.api.Assertions.; import static org.mockito.ArgumentMatchers.; import static org.mockito.Mockito.*; import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(MockitoExtension.class) class OrderProcessorTest {
@Mock PaymentGateway paymentGateway;
@Mock InventoryService inventoryService;
@Mock OrderRepository orderRepository;
@Mock AuditLogger auditLogger;
OrderProcessor processor;
@BeforeEach
void setUp() {
processor = new OrderProcessor(paymentGateway, inventoryService, orderRepository, auditLogger);
}
// 1) 正向:三明细成功,校验 reserve 调用、金额两位小数(四舍五入),markProcessed 与日志,返回 SUCCESS
@Test
void process_success_threeItems_reserveCalled_amountRounded_markProcessed_andLogged() {
String orderId = "o-success";
// 0.335 * 3 = 1.005,四舍五入到两位应为 1.01
List<LineItem> items = Arrays.asList(
new LineItem("SKU-A", 1, 0.335),
new LineItem("SKU-B", 1, 0.335),
new LineItem("SKU-C", 1, 0.335)
);
Order order = new Order(orderId, items);
when(orderRepository.isProcessed(orderId)).thenReturn(false);
when(paymentGateway.charge(eq(orderId), anyDouble()))
.thenReturn(new ChargeResult(true, false, "TX123"));
String result = processor.process(order);
assertEquals("SUCCESS", result);
// 交互顺序:reserve -> charge -> markProcessed -> log processed
InOrder inOrder = inOrder(inventoryService, paymentGateway, orderRepository, auditLogger);
inOrder.verify(inventoryService).reserve("SKU-A", 1);
inOrder.verify(inventoryService).reserve("SKU-B", 1);
inOrder.verify(inventoryService).reserve("SKU-C", 1);
inOrder.verify(paymentGateway).charge(eq(orderId), argThat(a -> Math.abs(a - 1.01) < 1e-9));
inOrder.verify(orderRepository).markProcessed(orderId);
ArgumentCaptor<String> logCaptor = ArgumentCaptor.forClass(String.class);
inOrder.verify(auditLogger).log(logCaptor.capture());
String processedLog = logCaptor.getValue();
assertTrue(processedLog.contains("processed:" + orderId + ":txn=TX123"));
// 不应释放库存
verify(inventoryService, never()).release(anyString(), anyInt());
// 金额精度再断言(两位小数)
verify(paymentGateway).charge(eq(orderId), argThat(a -> Math.abs(a - 1.01) < 1e-9));
}
// 2) 反向:支付失败时释放所有库存并抛 PaymentException
@Test
void process_paymentFails_releasesAllInventory_andThrowsPaymentException() {
String orderId = "o-fail";
List<LineItem> items = Arrays.asList(
new LineItem("SKU-1", 2, 10.0),
new LineItem("SKU-2", 1, 5.5),
new LineItem("SKU-3", 3, 2.0)
);
Order order = new Order(orderId, items);
when(orderRepository.isProcessed(orderId)).thenReturn(false);
when(paymentGateway.charge(eq(orderId), anyDouble()))
.thenReturn(new ChargeResult(false, false, null)); // 非 transient,直接失败
Executable exec = () -> processor.process(order);
PaymentException ex = assertThrows(PaymentException.class, exec);
assertEquals("payment failed", ex.getMessage());
// reserve 应被按明细调用
verify(inventoryService).reserve("SKU-1", 2);
verify(inventoryService).reserve("SKU-2", 1);
verify(inventoryService).reserve("SKU-3", 3);
// 交互顺序:reserve 全部 -> charge -> release 全部
InOrder inOrder = inOrder(inventoryService, paymentGateway);
inOrder.verify(inventoryService).reserve("SKU-1", 2);
inOrder.verify(inventoryService).reserve("SKU-2", 1);
inOrder.verify(inventoryService).reserve("SKU-3", 3);
inOrder.verify(paymentGateway).charge(eq(orderId), anyDouble());
inOrder.verify(inventoryService).release("SKU-1", 2);
inOrder.verify(inventoryService).release("SKU-2", 1);
inOrder.verify(inventoryService).release("SKU-3", 3);
// 不应标记已处理
verify(orderRepository, never()).markProcessed(anyString());
// 支付仅调用一次
verify(paymentGateway, times(1)).charge(eq(orderId), anyDouble());
}
// 2) 反向:非法明细(零数量)抛 IllegalArgumentException,且不调用库存与支付
@Test
void process_invalidItem_zeroQty_throwsIllegalArgumentException_andNoSideEffects() {
String orderId = "o-bad-qty";
List<LineItem> items = Arrays.asList(
new LineItem("BAD-ZERO", 0, 1.0)
);
Order order = new Order(orderId, items);
when(orderRepository.isProcessed(orderId)).thenReturn(false);
assertThrows(IllegalArgumentException.class, () -> processor.process(order));
verifyNoInteractions(paymentGateway);
verifyNoInteractions(inventoryService);
verify(orderRepository, never()).markProcessed(anyString());
}
// 2) 反向:非法明细(负价)抛 IllegalArgumentException,且不调用库存与支付
@Test
void process_invalidItem_negativePrice_throwsIllegalArgumentException_andNoSideEffects() {
String orderId = "o-bad-price";
List<LineItem> items = Arrays.asList(
new LineItem("BAD-PRICE", 1, -0.01)
);
Order order = new Order(orderId, items);
when(orderRepository.isProcessed(orderId)).thenReturn(false);
assertThrows(IllegalArgumentException.class, () -> processor.process(order));
verifyNoInteractions(paymentGateway);
verifyNoInteractions(inventoryService);
verify(orderRepository, never()).markProcessed(anyString());
}
// 3) 边界:幂等路径返回 ALREADY,且不触发支付与库存
@Test
void process_idempotentAlreadyProcessed_returnsAlready_noInventoryNoPayment() {
String orderId = "o-idem";
List<LineItem> items = Arrays.asList(
new LineItem("SKU-X", 1, 2.0)
);
Order order = new Order(orderId, items);
when(orderRepository.isProcessed(orderId)).thenReturn(true);
String result = processor.process(order);
assertEquals("ALREADY", result);
verify(paymentGateway, never()).charge(anyString(), anyDouble());
verify(inventoryService, never()).reserve(anyString(), anyInt());
verify(inventoryService, never()).release(anyString(), anyInt());
verify(orderRepository, never()).markProcessed(anyString());
ArgumentCaptor<String> logCaptor = ArgumentCaptor.forClass(String.class);
verify(auditLogger, times(1)).log(logCaptor.capture());
assertTrue(logCaptor.getValue().contains("idempotent skip:" + orderId));
}
// 4) 重试:首次 transient 错误失败,二次成功;断言 charge 调用两次、日志包含 retry、无 release
@Test
void process_retryOnTransientPaymentError_thenSuccess() {
String orderId = "o-retry";
List<LineItem> items = Arrays.asList(
new LineItem("SKU-R1", 2, 2.5), // 2 * 2.5 = 5.0
new LineItem("SKU-R2", 1, 5.0) // 总计 10.0
);
Order order = new Order(orderId, items);
when(orderRepository.isProcessed(orderId)).thenReturn(false);
when(paymentGateway.charge(eq(orderId), anyDouble()))
.thenReturn(new ChargeResult(false, true, null)) // 第一次:transient 失败
.thenReturn(new ChargeResult(true, false, "TXN-2")); // 第二次:成功
String result = processor.process(order);
assertEquals("SUCCESS", result);
// 支付两次,金额一致且为 10.0(两位小数)
ArgumentCaptor<Double> amountCaptor = ArgumentCaptor.forClass(Double.class);
verify(paymentGateway, times(2)).charge(eq(orderId), amountCaptor.capture());
List<Double> amounts = amountCaptor.getAllValues();
assertEquals(2, amounts.size());
assertEquals(10.0, amounts.get(0), 1e-9);
assertEquals(10.0, amounts.get(1), 1e-9);
// 日志包含 retry 与 processed
ArgumentCaptor<String> logCaptor = ArgumentCaptor.forClass(String.class);
verify(auditLogger, atLeastOnce()).log(logCaptor.capture());
List<String> logs = logCaptor.getAllValues();
assertTrue(logs.stream().anyMatch(m -> m.contains("retry payment:" + orderId)));
assertTrue(logs.stream().anyMatch(m -> m.contains("processed:" + orderId + ":txn=TXN-2")));
// 不应释放库存
verify(inventoryService, never()).release(anyString(), anyInt());
// markProcessed 必须调用一次
verify(orderRepository, times(1)).markProcessed(orderId);
// 交互顺序:reserve 全部 -> 第一次 charge -> log retry -> 第二次 charge -> markProcessed -> log processed
InOrder inOrder = inOrder(inventoryService, paymentGateway, orderRepository, auditLogger);
inOrder.verify(inventoryService).reserve("SKU-R1", 2);
inOrder.verify(inventoryService).reserve("SKU-R2", 1);
inOrder.verify(paymentGateway).charge(eq(orderId), anyDouble());
inOrder.verify(auditLogger).log("retry payment:" + orderId);
inOrder.verify(paymentGateway).charge(eq(orderId), anyDouble());
inOrder.verify(orderRepository).markProcessed(orderId);
// processed 日志最后
inOrder.verify(auditLogger).log(argThat(s -> s.contains("processed:" + orderId + ":txn=TXN-2")));
}
}
Maven 运行指引
在 pom.xml 添加依赖:
示例依赖与插件:
执行测试:
Gradle 运行指引(Kotlin 或 Groovy DSL 均可)
依赖(Groovy DSL): dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.2' testImplementation 'org.mockito:mockito-core:5.12.0' testImplementation 'org.mockito:mockito-junit-jupiter:5.12.0' } test { useJUnitPlatform() }
执行测试:
说明
帮助开发人员快速生成高质量的单元测试代码,大幅提升代码覆盖率,节约时间和人力成本。
帮助测试开发人员快速生成复杂系统的单元测试,覆盖更多场景并确保代码的高质量交付。
为全栈工程师在忙碌的开发任务中节省单元测试编写时间,为代码快速上线保驾护航。
为团队节省研发成本,同时提高测试覆盖率,保障产品质量,助力项目按时交付。
将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。
把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。
在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。
免费获取高级提示词-优惠即将到期