哪个网站能叫我做直播回放/软件优化
停止在 SpringBoot 中使用字段注入!
本文为翻译文,同时加入了一些自己的理解,翻译来源:https://medium.com
在 Spring Boot 依赖项注入的上下文中,存在关于注入依赖项最佳实践的争论:字段注入、Setter注入和构造函数注入。
❝在本文中,我们将重点讨论字段注入的缺陷,并提出一个远离它的案例。❞
1 什么是字段注入?
字段注入涉及直接用 @Autowired 注释类的私有字段。这是一个例子:
@Component
public class OrderService {@Autowiredprivate OrderRepository orderRepository;public Order findOrderById(Long id) {return orderRepository.findById(id);}
}
2 为什么应该停止使用字段注入
2.1 可测试性
字段注入使组件的单元测试变得复杂。 由于依赖项直接注入到字段中,因此我们无法在 Spring 上下文之外轻松提供模拟或替代实现。
让我们以 sameOrderService 类为例。
如果我们希望对 OrderService 进行单元测试,那么在模拟 OrderRepository 时会遇到困难,因为它是一个私有字段。下面是对 OrderService 进行单元测试的方法:
@RunWith(SpringJUnit4ClassRunner.class)
public class OrderServiceTest {private OrderService orderService;@Mockprivate OrderRepository orderRepository;@Beforepublic void setUp() throws Exception {orderService = new OrderService();// This will set the mock orderRepository into orderService's private fieldReflectionTestUtils.setField(orderService, "orderRepository", orderRepository);}...
}
尽管可以实现,但使用反射来替换私有字段并不是一个很好的设计。它违背了面向对象的设计原则,使测试难以阅读和维护。
但是,如果我们使用构造函数注入:
@Component
public class OrderService {private final OrderRepository orderRepository;public OrderService(OrderRepository orderRepository) {this.orderRepository = orderRepository;}
}
我们可以在测试期间轻松提供模拟 OrderRepository:
OrderRepository mockRepo = mock(OrderRepository.class);
OrderService orderService = new OrderService(mockRepo);
2.2 不变性
字段注入使我们的 Bean 在构建后可变。而通过构造函数注入,一旦构造了一个对象,它的依赖关系就会保持不变。
举例来说:
字段注入类:
@Component
public class UserService {@Autowiredprivate UserRepository userRepository;
}
这里,userRepository 在创建对象后可以重新分配引用,这就打破了不变性原则。
如果我们使用构造函数注入:
@Component
public class UserService {private final UserRepository userRepository;public UserService(UserRepository userRepository) {this.userRepository = userRepository;}
}
该 userRepository 字段可以声明为最终字段,在构造完成后,就会一直保持不变。
2.2.1 这里的 @Autowired private UserRepository userRepository; 不能在private后加一个final吗?
在Spring框架中,使用字段注入时,你通常不会在被@Autowired
注解的字段后加上final
关键字。原因在于当Spring创建Bean的实例时,它需要能够设置这些没有通过构造函数提供的依赖关系。如果字段被声明为final
,那么它必须在构造对象的时候初始化,之后就不能再改变了。
字段注入通常是通过反射来完成的,反射可以允许即使字段被声明为private
,仍然能够被外部类修改。但是,如果你使用final
来修饰字段,反射也不能用于改变其值,因为final
字段在构造对象后就不可变了。这就是为什么你会在构造函数注入中看到final
字段,而在字段注入中不会看到。
构造函数注入提供了不变性,因为你可以将所有依赖项声明为final
。这意味着一旦构造了对象,这些依赖项就不能更改,从而可以避免很多因为状态改变导致的问题。这也符合不变性原则,是当前推荐的注入方式,因为它提高了代码的安全性和健壮性。
总结一下,@Autowired
注解的字段不能声明为final
,因为Spring需要在对象构造后设置这些字段。如果你想要不变性,你应该使用构造函数注入,并将依赖项声明为final
。
2.2.2 如果我使用构造函数注入一个bean,要怎么样使用他呢?
当你使用构造函数注入来注入一个Bean时,Spring容器会负责创建这个Bean的实例,并且会自动把构造函数需要的依赖项注入进去。这里是一个使用构造函数注入的基本步骤:
- 定义依赖项接口。
- 创建依赖项的实现类,并使用例如
@Component
注解将其标记为Spring管理的Bean。 - 在使用依赖项的类中,创建一个构造函数,该构造函数接受依赖项作为参数,并使用例如
@Autowired
注解(在Spring 4.3之后,如果类只有一个构造函数,可以省略@Autowired
注解)。
以下是一个简单的示例:
// 依赖项接口
public interface UserRepository {// 定义所需要的操作,例如查找用户等
}// 依赖项的实现类
@Component
public class UserRepositoryImpl implements UserRepository {// 实现UserRepository接口中定义的方法
}// 使用依赖项的类
@Component
public class UserService {private final UserRepository userRepository;// 构造函数注入public UserService(UserRepository userRepository) {this.userRepository = userRepository;}// 类中的其他方法可以使用userRepositorypublic void performAction() {// 使用userRepository执行一些操作}
}
在这个例子中,UserService
需要一个UserRepository
的实例。Spring会自动找到匹配UserRepository
类型的Bean(在这个例子中是UserRepositoryImpl
的实例),然后创建一个UserService
的实例,将UserRepositoryImpl
作为参数传递给UserService
的构造函数。
在Spring应用中,你不需要自己去创建UserService
的实例。Spring容器会自动处理这一切。当你需要使用UserService
时,你可以让Spring自动注入它,例如:
@RestController
public class UserController {private final UserService userService;// 在控制器中通过构造函数注入UserServicepublic UserController(UserService userService) {this.userService = userService;}@GetMapping("/users")public ResponseEntity<List<User>> getUsers() {// 使用userService来处理获取用户的请求}
}
在上面的控制器中,UserService
将被自动注入到UserController
中。这样,你就可以在控制器中使用UserService
提供的方法来处理请求了。
2.3 与Spring更紧密的耦合
字段注入使我们的类与 Spring 耦合更紧密,因为它直接在我们的字段上使用 Spring 特定的注释 ( @Autowired)。这可能会在以下场景中出现问题:
「不使用 Spring 的情况」:假设我们正在构建一个不使用 Spring 的轻量级命令行应用程序,但我们仍然想利用 UserService 的逻辑。在这种情况下,@Autowired 注释没有任何意义,不能用于注入依赖项。我们就必须重构该类或实现繁琐的解决方法才能重用UserService.
「切换到另一个 DI 框架」:如果我们决定切换到另一个依赖注入框架,比如 Google Guice,Spring 特定的框架 @Autowired 就会成为一个障碍。那时我们必须重构使用 Spring 特定注释的每一个地方,这会是十分繁琐的。
「可读性和理解性」:对于不熟悉 Spring 的开发人员来说,遇到 @Autowired 注解可能会感到困惑。他们可能想知道如何解决依赖关系,从而增加学习成本(ps:虽然不熟悉 Spring 开发的Java程序员可能很少了)。
2.4 空指针异常
当类利用字段注入并通过其默认构造函数实例化时,依赖字段保持未初始化。
举例来讲:
@Component
public class PaymentGateway {@Autowiredprivate PaymentQueue paymentQueue;public void initiate (PaymentRequest request){paymentQueue.add(request);...}
}public class PaymentService {public void process (PaymentRequest request) {PaymentGateway gateway = new PaymentGateway();gateway.initiate(request);}
}
通过上面的代码,我们不难看出,如果在运行时以这种状态访问PaymentGateway,则会发生 NullPointerException。在Spring上下文之外手动初始化这些字段的唯一方法是使用反射,反射机制的语法比较繁琐且易错,在程序可读性方面存在一定问题,所以不建议这样做。
2.4.1 为什么直接在非spring类中new一个spring的bean会报NPE?
在您提供的代码示例中,PaymentService
类直接通过new
关键字创建了PaymentGateway
的实例,而不是通过Spring的依赖注入来获取。当直接使用new
关键字创建实例时,Spring容器不会介入该对象的生命周期,这意味着Spring不会自动注入PaymentGateway
中的依赖paymentQueue
。
由于paymentQueue
没有被初始化(因为Spring没有注入它),当initiate
方法被调用时,它尝试访问paymentQueue
的add
方法。因为此时paymentQueue
是null
,所以尝试调用其方法会导致NullPointerException
(NPE)。
在Spring应用程序中,为了避免此类问题,应该总是通过Spring容器获取Bean实例,这样Spring就能自动管理Bean的生命周期和依赖注入。如果你需要在Spring管理的Bean中使用PaymentGateway
,你应该让Spring注入它,而不是自己创建实例。
例如,改正后的PaymentService
可能会看起来像这样:
@Service
public class PaymentService {private final PaymentGateway paymentGateway;@Autowiredpublic PaymentService(PaymentGateway paymentGateway) {this.paymentGateway = paymentGateway;}public void process(PaymentRequest request) {paymentGateway.initiate(request);}
}
在这个修改后的版本中,PaymentGateway
由Spring通过构造函数注入到PaymentService
中,这样就确保了PaymentGateway
的paymentQueue
依赖会被Spring容器自动注入,从而避免了NPE。
2.5 循环依赖
字段注入可能会掩盖循环依赖问题,使它们在开发过程中更难被发现。
举例来讲:
考虑两个相互依赖的服务AService和BService:
@Service
public class AService {
@Autowired
private BService bService;
}
@Service
public class BService {
@Autowired
private AService aService;
}
以上可能会导致应用程序中出现意想不到的问题。
使用构造函数注入,Spring会在启动期间立即抛出 BeanCurrentlyInCreationException,让我们意识到循环依赖。不过,要解决循环依赖问题,可以使用@Lazy延迟加载其中一个依赖项。
2.5.1 我记得spring中是允许循环依赖的吧,如何解决的
是的,Spring框架确实支持循环依赖,但是这种支持仅限于字段注入(setter注入)和方法注入。Spring通过使用三级缓存来解决单例作用域下的循环依赖问题,使得在构造函数中注入循环依赖的Bean成为可能。
对于构造函数注入来说,Spring无法处理循环依赖,因为在调用构造函数之前,每个Bean的依赖必须先被解决。如果A需要B才能创建,而B同时也需要A才能创建,Spring就无法决定应该先创建哪个Bean,因此会抛出BeanCurrentlyInCreationException
异常。
对于您提到的例子,如果两个服务A和B都通过构造函数相互注入,Spring会在应用程序启动时检测到循环依赖并抛出异常。如果使用字段注入,Spring可以通过先实例化一个Bean,然后在设置属性时实现注入,从而解决循环依赖的问题。
2.5.2 构造器注入如何解决循环依赖问题
如果想要在构造函数注入中解决循环依赖问题,可以使用@Lazy
注解来延迟依赖项的加载。例如:
@Service
public class AService {private final BService bService;@Autowiredpublic AService(@Lazy BService bService) {this.bService = bService;}
}@Service
public class BService {private final AService aService;@Autowiredpublic BService(AService aService) {this.aService = aService;}
}
在上面的代码中,@Lazy
注解确保了BService
在AService
实例化时不会立即被创建,而是在首次访问BService
时才创建。这样,Spring可以先完成AService
的创建,然后在需要时创建BService
实例,避免了循环依赖问题。
2.6 @Autowired注入不推荐,那么@Resource呢,能够避免@Autowired的这些问题吗?
@Resource
是 Java EE 6 中的注解,它可以用来注入依赖项,其行为略有不同于 Spring 的 @Autowired
。下面是这两个注解的一些区别:
-
来源:
@Autowired
是 Spring 特有的注解。@Resource
来自于 Java 的javax.annotation
包。
-
注入方式:
@Autowired
默认按类型进行自动装配。当需要按名称装配时,可以结合@Qualifier
注解使用。@Resource
默认按名称进行注入,如果没有找到与名称匹配的bean,则会按类型进行注入。它有两个重要属性:name
和type
。
-
兼容性:
@Autowired
与 Spring 紧密集成,支持Spring特有的功能,如@Qualifier
、@Primary
等。@Resource
是标准的 Java 注解,因此它不依赖于 Spring,并且可以用在任何兼容 Java EE 的容器中。
关于能否避免 @Autowired
的问题:
-
循环依赖:
@Resource
并不能解决构造器注入时的循环依赖问题,这是因为循环依赖的问题与注入机制本身相关,而不是特定于某个注解。Spring容器需要创建bean实例时,必须解决所有必需的依赖,不管这些依赖是通过@Autowired
还是@Resource
注入的。 -
依赖不明确:
@Resource
默认按名称注入,这使得它在有多个类型相同的bean时,可以通过名称指定具体依赖哪个bean,从而在某种程度上提高了注入的明确性。 -
自动装配的灵活性:使用
@Resource
时,你失去了 Spring 提供的一些灵活性,比如使用@Primary
注解来指定首选的bean。
总的来说,@Resource
注解提供了另一种依赖注入的方式,但它并不能解决所有 @Autowired
可能引发的问题。在Spring框架中,建议优先使用构造器注入(无论是通过 @Autowired
还是通过参数解析),因为它可以帮助你避免上述提到的大多数问题,并且还能提高代码的可测试性。
2.7 在互联网大厂中,哪一种注入用的多?
在互联网大厂中,构造器注入(Constructor Injection)通常是首选的依赖注入方法,原因如下:
-
不变性和安全性:构造器注入使得依赖项可以是
final
的,这意味着一旦构造了对象,其依赖就不能更改。这种不变性可以减少在多线程环境下的问题,并确保依赖项在使用前已经被完全初始化。 -
可测试性:使用构造器注入,可以在不启动整个Spring容器的情况下,更容易地进行单元测试。
-
明确的依赖:构造器参数强制要求在创建对象时提供依赖项,这使得依赖关系更加明确,避免了
null
引用的可能性。 -
框架无关性:构造器注入不依赖于Spring或者任何其他依赖注入框架,这使得代码更容易迁移和重构。
尽管构造器注入在很多情况下是更好的选择,但在实际开发中,还是会根据具体场景和需求来决定使用哪种注入方式。例如,当存在多个构造器参数,并且这些参数中的某些是可选的,或者在某些复杂的依赖场景中,开发者可能会选择字段注入(Field Injection)或者设值注入(Setter Injection)。
@Autowired
和 @Resource
注解都用于自动装配Spring Bean,但由于 @Autowired
提供了与Spring更紧密的集成和更多的灵活性,它通常是更受青睐的选择。不过,实际使用哪一个还是要基于项目的具体需求、团队习惯以及编码规范来决定。在一些遵循严格的Java EE标准的项目中,可能会倾向于使用 @Resource
。
最终,无论哪种注入方式,重要的是保持一致性、清晰性和可维护性。大厂的代码规范会倾向于推崇这些原则,并通过代码审查、文档和团队培训来确保最佳实践的贯彻执行。
3 结论
虽然字段注入可能看起来更简洁,但它的缺点远远超过了它的简洁性。构造函数注入在应用程序的可测试性、不变性和整体稳健性方面提供了明显的优势。
它与 SOLID 原则非常一致,确保我们的 Spring Boot 应用程序可维护且不易出错。
所以,建议大家停止在 Spring Boot 中使用字段注入!
相关文章:

为什么要停止在 SpringBoot 中使用字段注,改用构造器注入
停止在 SpringBoot 中使用字段注入! 本文为翻译文,同时加入了一些自己的理解,翻译来源:https://medium.com 在 Spring Boot 依赖项注入的上下文中,存在关于注入依赖项最佳实践的争论:字段注入、Setter注入和构造函数…...

数据可视化:地图
1.基础地图的使用 如何添加颜色表示层级 代码实现 """基础地图的使用 """ from pyecharts.charts import Map from pyecharts.options import VisualMapOpts# 准备地图对象 map Map() # 准备数据 data [("北京市", 9),("上海市…...

java 数据结构 ArrayList源码底层 LinkedList 底层源码 迭代器底层
文章目录 数据结构总结ArrayList源码底层LinkedList底层源码 迭代器底层 数据结构 对于数据结构我这边只告诉你右边框框里的 栈的特点:后进先出,先进后出,入栈也成为压栈,出栈也成为弹栈 栈就像一个弹夹 队列先进先出后进后出 队列像排队 链表查询满 但是增删快(相对于数组而…...

如何在Python编程中应用Linux环境下的框架,以实现高效算法?
python是一种广泛使用的编程语言,能够帮助开发人员快速开发高效的算法。与此同时,linux环境下提供了许多优秀的框架,可以进一步提高Python编程的效率。本文将介绍如何在Python编程中应用Linux环境下的框架,以实现高效算法。 一、Python和Linux环境的优势 Python是一种易学…...

多机位直播案例
目录 1、案例简述 2、设备准备: (1)笔记本电脑 (2)手机 (3)触控一体机 (4)教室前端监控摄像机 (5)教室后端监控摄像机 (6&…...

前沿重器[37] | 大模型对任务型对话的作用研究
前沿重器 栏目主要给大家分享各种大厂、顶会的论文和分享,从中抽取关键精华的部分和大家分享,和大家一起把握前沿技术。具体介绍:仓颉专项:飞机大炮我都会,利器心法我还有。(算起来,专项启动已经…...

第三章:boundary-value analysis
文章目录 Boundary-value Analysiscomputational faults 计算错误boundary shift 边界偏移boundary value analysis 的优势Path condition, domain, and domain boundary (路径条件、域和域边界)Open and closed boundaries (闭合边界 / 开放边界)on / off pointGuidelinestr…...

Python模块psutil:系统进程管理与Selenium效率提升的完美结合
前言 在前面编写一个Selenium的自动化程序时候,发现一个问题。 因笔记本配置较为差,所以每次初始化Selenium的WebDriver都会非常慢,整个等待过程是不友好的。 所以我就想到: 在程序中初始化一个全局的WebDriver对象,…...

glibc 里的线程 id
这里讲的是通过 pthread_create() 函数返回的线程 id,其实就是 glibc 库里维护的线程id,它跟内核维护的线程 id 不一样,pthread_create() 返回的线程 id 并不一定是唯一的。我们看 pthread_create 函数的源码,它最后返回的线程 id…...

nacos的部署与配置中心
文章目录 一、nacos部署安装的方式单机模式:集群模式:多集群模式: 二、安装的步骤1、预备环境准备2、载安装包以及安装2.1、Nacos有以下两种安装方式:2.2、更换数据源数据源切换为MySQL 2.3、开启控制台授权登录(可选) 3、配置中心的使用3.1、创建配置信…...

undefined 与 undeclared 的区别?
在 JavaScript 中,undefined 和 undeclared 是两个不同的概念,表示不同的情况: 1:undefined: undefined 是一个特殊的值,表示一个变量已经被声明,但尚未被赋予一个值。当一个变量被声明但未进…...

Leetcode周赛370补题(3 / 3)
目录 1、找到冠军 Ⅰ- 暴力 2、找到冠军 Ⅱ - 寻找入度为0的点 3、在树上执行操作以后得到的最大分数 - dfs树 逆向思考 1、找到冠军 Ⅰ- 暴力 100115. 找到冠军 I class Solution {public int findChampion(int[][] g) {int ng.length;for(int i0;i<n;i){int cnt0;for…...

PyTorch深度学习实战——图像着色
PyTorch深度学习实战——图像着色 0. 前言1. 模型与数据集分析1.1 数据集介绍1.2 模型策略 2. 实现图像着色相关链接 0. 前言 图像着色指的是将黑白或灰度图像转换为彩色图像的过程,传统的图像处理技术通常基于直方图匹配和颜色传递的方法或基于用户交互的方法等完…...

InfiniBand 的前世今生
今年,以 ChatGPT 为代表的 AI 大模型强势崛起,而 ChatGPT 所使用的网络,正是 InfiniBand,这也让 InfiniBand 大火了起来。那么,到底什么是 InfiniBand 呢?下面,我们就来带你深入了解 InfiniBand…...

分享一下微信小程序里怎么添加社区团购功能
随着互联网的快速发展,线上购物已经成为我们日常生活的一部分。而在这个数字化时代,微信小程序作为一种便捷的电商渠道,正逐渐成为新的趋势。其中,社区团购功能更是受到广大用户的热烈欢迎。本文将探讨如何在微信小程序中添加社区…...

软考高项-IT部分
信息化体系 信息化技术应用:龙头 信息资源:核心任务 信息网络:应用基础 信息技术和产业:建设基础 信息化人才:成功之本 信息化法规:保障 信息化趋势 产业信息化、产品信息化、社会生活信息化、国民经济信息化 新型基础设施建设 2018年召开的中央经济工作会议,首…...

hugetlb核心组件
1 概述 hugetlb机制是一种使用大页的方法,与THP(transparent huge page)是两种完全不同的机制,它需要: 管理员通过系统接口reserve一定量的大页,用户通过hugetlbfs申请使用大页, 核心组件如下图: 围绕着…...

vscode配置环境变量
首先点击下面这个链接。 sMinGW-w64 - for 32 and 64 bit Windows - Browse Files at SourceForge.net 然后选择Files这个选项 向下移选择下载这个文件 解压完成之后,找到这个文件的bin目录复制路径后,添加到环境变量中 依次点击后打开cmd࿰…...

react:封装组件
封装 /components/Pagination.tsx import React from react import { Pagination } from antdconst PaginationWarp ({ total, paramsInfo, setParamsInfo }) > {return (<Paginationtotal{total}current{paramsInfo.page}showSizeChangershowQuickJumperdefaultPageSi…...

基于深度学习的视频多目标跟踪实现 计算机竞赛
文章目录 1 前言2 先上成果3 多目标跟踪的两种方法3.1 方法13.2 方法2 4 Tracking By Detecting的跟踪过程4.1 存在的问题4.2 基于轨迹预测的跟踪方式 5 训练代码6 最后 1 前言 🔥 优质竞赛项目系列,今天要分享的是 基于深度学习的视频多目标跟踪实现 …...

linux中各种最新网卡2.5G网卡驱动,不同型号的网卡需要不同的驱动,整合各种网卡驱动,包括有线网卡、无线网卡、Wi-Fi热点
linux中各种最新网卡2.5G网卡驱动,不同型号的网卡需要不同的驱动,整合各种网卡驱动,包括有线网卡、无线网卡、自动安装Wi-Fi热点。 最近在做路由器二次开发,现在市面上卖的新设备,大多数都采用了2.5G网卡,…...

asp.net上传文件
第一种方法 前端: <div> 单文件上传 <form enctype"multipart/form-data" method"post" action"upload.aspx"> <input type"file" name"files" /> …...

JavaEE平台技术——预备知识(Web、Sevlet、Tomcat)
JavaEE平台技术——预备知识(Web、Sevlet、Tomcat) 1. Web基础知识2. Servlet3. Tomcat并发原理 1. Web基础知识 🆒🆒上个CSDN我们讲的是JavaEE的这个渊源,实际上讲了两个小时的历史课,给大家梳理了一下&a…...
基础课23——设计客服机器人
根据调查数据显示,使用纯机器人完全替代客服的情况并不常见,人机结合模式的使用更为普遍。在这两种模式中,不满意用户的占比都非常低,不到1%。然而,在满意用户方面,人机结合模式的用户满意度明显高于其他模…...

mybatis在springboot当中的使用
1.当使用Mybatis实现数据访问时,主要: - 编写数据访问的抽象方法 - 配置抽象方法对应的SQL语句 关于抽象方法: - 必须定义在某个接口中,这样的接口通常使用Mapper作为名称的后缀,例如AdminMapper - Mybatis框架底…...

如何处理前端本地存储和缓存
前端本地存储和缓存的处理是一种重要的技术,它可以帮助改善应用程序的性能和用户体验。下面是一些处理前端本地存储和缓存的常用方法: 1. 使用Web Storage API: 这是一种在浏览器中存储数据的方法,包括两种类型:loca…...

导轨式安装压力应变桥信号处理差分信号输入转换变送器0-10mV/0-20mV/0-±10mV/0-±20mV转0-5V/0-10V/4-20mA
主要特性 DIN11 IPO 压力应变桥信号处理系列隔离放大器是一种将差分输入信号隔离放大、转换成按比例输出的直流信号导轨安装变送模块。产品广泛应用在电力、远程监控、仪器仪表、医疗设备、工业自控等行业。此系列模块内部嵌入了一个高效微功率的电源,向输入端和输…...

人体姿态估计和手部姿态估计任务中神经网络的选择
一、人体姿态估计任务适合使用卷积神经网络(CNN)来解决。 人体姿态估计任务的目标是从给定的图像或视频中推断出人体的关节位置和姿势。这是一个具有挑战性的计算机视觉任务,而CNN在处理图像数据方面表现出色。 使用CNN进行人体姿态估计的一种…...

odoo16 one2many字段的 domain
最近在odoo project模块的基础上做二开,给task表加了一个版本字段version_id,然后重写了 project表的Task_ids, 并且增加了一个domain,结果折腾了大半天才搞定 写法1 这也是最初的写法: version_id fields.Many2one("hx.p…...

一份优秀测试用例的设计策略
日常工作中最为基础核心的内容就是设计测试用例,什么样的测试用例是好的测试用例?我们一般会认为数量越少、发现缺陷越多的用例就是好的用例。那么我们如何才能设计出好的测试用例呢?一份好的用例是设计出来的,是测试人员思路和方法的集合&a…...