为什么我们要做单元测试?(二)

探讨了单元测试在软件开发中的重要性,分析了其在企业中的应用现状与挑战,提供了提高代码覆盖率的小技巧,强调了良好的单元测试习惯对开发者的重要性。

引子

当我第一篇博客发布,并被张善友老师的公众号转载之后,在公众号文章和博客园的留言中,许多开发者纷纷表示,单元测试作为企业行为,与实施的技术栈不同,不是开发者个人行为,实施单元测试花费的时间精力过于庞大,与实际效果严重不对等,而且如果过度的采用单元测试,也会增加新的测试点,因为单元测试代码本身就需要进行测试等。

 从这些回复可以看出,程序员要不要编写单元测试这种话题,大概做传统开发时,问程序员要不要写项目文档一样充满争议。正如大家都深刻明白项目文档的重要性,但是一旦程序员需要编写项目文档了,往往会对这个事情产生抵触情绪,这实际上是绝大多数开发者的通病。

单元测试也是这样的矛盾纠结体在长沙.net技术社区就有不少朋友纷纷表示,他们都曾经试图在公司推动单元测试的应用,但是受到了自上而下的反对声,最终迫于压力,只能放弃。 而之所以阻力这么大,其主要原因是编写单元测试会增加额外的时间,因为编写单元测试,不仅仅只是编写一个简单的测试入口,而是一系列步骤,但是领导要求在最短的时间看到效果,并且有的领导还经常改变主意,如果设计了一个优秀的单元测试用例,有时候甚至会因为无法适应需求的变化,而最终腐烂。 

显然,使用单元测试和更高的单元测试的代码覆盖率,大概是一个试金石。过去在长沙还很少有企业会问开发者会不会使用单元测试,但是近年来越来越多的企业会问候选人代码覆盖率的问题,所以作为开发者,可以尝试从单元测试开始,努力提高自己的代码习惯,编写更加高质量的代码。

有哪些公司在要求编写单元测试?

过去,单元测试一直是专业软件公司的首选,有一位原诺基亚核心部门的开发者说为诺基亚内部,对单元测试的要求很高,虽然没有达到百分之百,但也有非常高的要求,在《构建之法》中,邹欣老师介绍了微软的开发实践,也对单元测试覆盖率提出了很高的要求。 当然有的读者或许会嗤之以鼻,这些都是古典软件公司,举这些例子有什么意义呢?中国式IT 公司,哪家都是996,哪里还有什么时间实行单元测试? 

然而,优秀的互联网公司都开始推行devops 作为企业信息化建设过程中的最佳实践标准,持续集成和持续发布都对单元测试有很高的要求,例如在阿里巴巴java 开发者手册中,就明确提出了以下一系列指标,要求开发者务必采用单元测试方法,尽可能的提高代码质量。

  1. 【强制】好的单元测试必须遵守 AIR 原则。

  2. ...此处省略七百字

  3. 【推荐】单元测试的基本目标:语句覆盖率达到 70% ;核心模块的语句覆盖率和分支覆盖率都要达到 100% 

参见原文阿里巴巴Java 开发者手册,由此可见,单元测试已经作为一种行之有效的手段,显然已经成为了中国优秀互联网企业的必然之选。

单元测试中的代码覆盖率有什么用?

代码覆盖率是单元测试的重要衡量指标,反映了单元测试中测试用例对被测代码的覆盖程度,是对代码的测试质量衡量的重要指标。

在十年前,博客园《代码覆盖率浅谈》(参考资料1)一文,深入浅出的介绍了单元测试的四种类型, 包括语句覆盖,判定覆盖,条件覆盖,路径覆盖四种类型,作者指出,单元测试覆盖率结果,有以下作用:

a. 覆盖率数据只能代表你测试过哪些代码,不能代表你是否测试好这些代码。 

b. 不要过于相信覆盖率数据。

c. 不要只拿语句覆盖率(行覆盖率)来考核你的测试人员。 

d. 路径覆盖率 > 判定覆盖 > 语句覆盖 

e. 测试人员不能盲目追求代码覆盖率,而应该想办法设计更多更好的案例,哪怕多设计出来的案例对覆盖率一点影响也没有。 

在软件开发过程中盲目的追求的高代码覆盖率,往往得不偿失,尤其是为了提高代码覆盖率而做的单元测试,往往只会成为累赘。

合理的操作形式应该是基于实际用例出发,设定更多的用例场景,实现基于用户场景驱动的单元测试覆盖,像在阿里巴巴开发者手册中说的,测试人员与开发人员配合,共同完成测试用例覆盖,就是一种不错的应用实践。

当然,即便测试缺位,开发者也完全应该主动的承担更多单元测试的职能,尽可能多的思考用户场景中可能存在的变数。

编写好的单元测试的一些小技巧


技巧一,多看书肯定是不错的

有朋友问,怎么写好单元测试?有什么书推荐么?我很惭愧,我自己的代码单元测试的覆盖率还相当低,可能没办法给出指导,我想多看书肯定是没错的,而编写单元测试的书,还挺多的,例如这一本,《单元测试的艺术》,一看就是基于C#的,可以试一试。 


 而想入单元测试的门,可以看看我后面找到的一系列引文,相信能给你带来方便。 


技巧二,运用测试框架 

1、单元测试框架:XUnit、NUnit、MSTest等.

2、测试运行工具:xunit.runner.visualstudio 。类似如:Resharper的xUnit runner插件。 

3、模拟框架:Moq、RhinoMocks、NSubstitute、FakeItEasy等。  


技巧三,灵活的运用事务回滚或内存数据库,避免单元测试数据污染正常数据

前者是阿里巴巴开发者手册中提到的一种方法,在有的场景下也挺实用的,不过有开发者指出,可以使用模拟内存数据库来解决这个问题更为妥当,例如使用Effort.EF6,通过nuget获取,使得创建一个伪造的、供EF容易使用的内存数据库成为可能。与这类似的,还可以使用HttpSimulator来模拟http 请求。 


技巧四,使用依赖注入和单例模式改良不可测代码

静态类为代码编写带来了许多便利,但是也使得代码测试变得相对困难,而使用单例模式进行改良则使得操作更可控。 

总结

人生苦短,撸码不易。从选择成为开发者的那一天起,我们就被迫承受了许多压力,尤其是技术发展的不确定性,更是如此,你永远也不知道自己当下的选择是否正确,说不定你今天最为熟悉的技术或框架,明天就凉凉了。尤其是现在的各种自媒体,时不时的发几篇文章来输出焦虑,巴不得天天说优胜劣汰才能获得读者的关注一般,让开发者们压力更大。


我觉得,技术是解决问题的方法,而良好的代码习惯则是自身心法,尤其是单元测试,更是一种好习惯,先别总想着担心自己被淘汰,努力的使自己习惯更好,总会获得无穷收获。


参考资料1:《代码覆盖率浅谈》https://www.cnblogs.com/coderzh/archive/2009/03/29/1424344.html?tt_from=copy_link&utm_source=copy_link&utm_medium=toutiao_ios&utm_campaign=client_share.

参考资料2:《代码覆盖率强迫症》https://www.infoq.cn/article/test-coverage-ocd.
参考资料3:《
代码覆盖率 (Code Coverage)从简到繁

https://www.cnblogs.com/jacksundatashare/p/5083352.html.
参考资料4,《
C#单元测试,带你快速入门http://www.cnblogs.com/zhaopei/p/UnitTesting.html?from=singlemessage&isappinstalled=0.

码字不易啊..哈哈smiley_21.png

(本文是作者为社区贡献的内容,赞赏的所有费用都将作为长沙.NET技术社区运营费用,感谢您的关注,让我们一起努力,共同创造长沙.NET技术社区更好的明天)


<think>好的,我现在需要回答用户关于如何进行单元测试以及最佳实践的问题。用户还提到了想了解实施方法和实践,所以我应该结合之前的引用内容来组织答案。首先,我得明确单元测试的基本概念,然后分步骤说明实施方法,再列举一些最佳实践。还要注意引用标记的正确使用,比如在提到代码覆盖率工具时引用[^2],在Spring Boot测试时引用[^3]。 可能用户对单元测试的具体步骤不太清楚,需要详细说明如何编写测试用例、使用框架、模拟依赖等。同时,最佳实践部分需要包括测试独立性、覆盖率、持续集成等。需要注意引用中的JaCoCo和Spring Boot的测试注解,这些是重要的点,应该包含在回答中。 另外,用户可能混淆了单元测试和集成测试,所以需要适当区分两者,但根据问题,重点在单元测试。确保回答结构清晰,分步骤和分点,便于用户理解。还要生成相关问题,可能涉及工具选择、常见问题、与集成测试的区别等。需要检查是否所有引用都正确对应,比如引用[^1]提到单元测试和集成测试的定义,引用[^2]关于覆盖率和重构建议,引用[^3]关于Spring Boot的具体实践。</think>### 如何进行单元测试及最佳实践 #### 一、单元测试实施方法 1. **明确测试目标** 单元测试针对代码的最小可测试单元(如函数、类方法),验证其输入输出是否符合预期。需隔离被测代码与其他依赖,通常通过模拟(Mock)或存根(Stub)实现[^1]。 2. **选择测试框架** - **Java**: JUnit + Mockito(模拟依赖) - **Python**: pytest + unittest - **Spring Boot**: 使用 `@SpringBootTest` 轻量级容器,结合 `@MockBean` 隔离外部依赖[^3]。 3. **编写测试用例** - **覆盖场景**: 正常路径、边界条件、异常处理。 - **命名规范**: 如 `methodName_scenario_expectedResult()`。 - **示例(Java)**: ```java @Test void add_positiveNumbers_returnSum() { Calculator calc = new Calculator(); assertEquals(5, calc.add(2, 3)); } ``` 4. **使用代码覆盖率工具** 通过 **JaCoCo** 监测覆盖率指标[^2]: - **行覆盖率** > 80% - **分支覆盖率** > 70% - **圈复杂度** ≤ 10(降低代码逻辑复杂度) --- #### 单元测试最佳实践 1. **测试独立性** 每个测试用例不依赖外部状态(如数据库、文件),使用 `@BeforeEach` 初始化,`@AfterEach` 清理资源。 2. **FIRST原则** - **Fast**(快速): 测试应在毫秒级完成。 - **Isolated**(独立): 不依赖其他测试结果。 - **Repeatable**(可重复): 在任何环境一致通过。 - **Self-Validating**(自验证): 结果自动判断,无需人工检查。 - **Timely**(及时): 与代码同步编写(TDD模式)。 3. **Mock依赖的正确使用** - **Spring Boot示例**: ```java @TestService @Import(LabelService.class) public class LabelServiceTest { @MockBean private DatabaseClient dbClient; // 模拟数据库调用 @Autowired private LabelService labelService; @Test void getLabel_validId_returnLabel() { when(dbClient.find(any())).thenReturn(new Label("test")); Label result = labelService.getLabel(1L); assertEquals("test", result.getName()); } } ``` [^3] 4. **持续集成(CI)集成** 将单元测试加入CI流水线,确保每次提交触发测试,失败时阻断部署。 --- #### 三、常见问题与解决 - **问题1**: 测试代码比业务代码还复杂? **解决**: 重构代码以提高可测试性(如依赖注入、单一职责原则)[^2]。 - **问题2**: 依赖外部服务(如API、数据库)? **解决**: 使用Mock工具(如Mockito)模拟返回值。 - **问题3**: 测试覆盖率低? **解决**: 优先覆盖核心逻辑,逐步补充边缘场景。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值