单元测试的必要性

很多人都在质疑单元测试的必要性,今天我们就来探讨下,单元测试对一个服务或者框架来说,到底有多重要
查看更多 →

如何在Go中优雅地进行测试

如何在Go中优雅地进行测试 本文是 快速迭代中保证项目的高质量——DevOps实践漫谈,一文中关于测试的延伸阅读,主要展示在 go 项目中该如何去做测试,以及测试是如何驱动设计的演化。 虽然是以 go 为示例,但里面的多数测试方法和思想都与语言无关,不会 go 的同学也可以阅读。 简述 测试的重要性这里不多复述,可以参考我关于 单元测试的必要性?由谁来写 的 回答。 测试分为很多种,本文这次要讨论的是关乎后台服务最重要的两种测试:单元测试 与 集成测试。它们两属于测试金字塔图的最下两层,如下图: 图中位于第二层的测试名为服务测试(Service Tests) ,其实它职责包含了狭义的集成测试,我将其理解为广义的集成测试。 开始进入主题之前,我觉得应该和阅读的同学对齐一下一些概念,因为关于测试的术语网上各种概念都各不相同,我这里只列举本文会使用到的一些概念( 这些是我的理解,并不一定完全恰当与正确 ): 模块: 指逻辑上紧密耦合的一组函数,通常它们会以对象形式实现,以接口的形式提供给其他模块使用。 外部环境:数据库,文件系统,其他服务等 单元测试:对于单个模块的测试,此时应该通过桩模块隔离被测模块以外的模块 桩(stub):用于在单元测试中替换被测模块的依赖模块,使其总是返回我们期望的值 集成测试:狭义的集成测试只测试那些访问外部环境的代码,比如: 读写数据库 调用外部服务的 API 读写队列 读写文件 广义的集成测试指对整个服务进行测试,包括外部环境与所有模块。 从概念上我们可以看出集成测试是最能保证业务价值的,但是由于需要集成的东西过多,所以它的执行很缓慢且用例容易失效(任何一个子模块的变动都可能让相关用例的数据不再有效),维护成本很高。所以测试金字塔为我们指出了测试中的一个最佳实践:使用单元测试保障更多的边界境况,集成测试来确保模块与模块,模块与外部环境的正常集成 接下来,让我们一起来看看在一个 go 项目中该如何落地这个最佳实践。 从一个简单的业务开始 让我们从一个简单的银行来开始我们的实践,假设有以下需求: 用户需要用一个手机号码来创建一个账户,支持更换手机号 支持付款、转账、销户 需要记录账单流水 我们的需求非常简单,但已经足够用于展示了。 基础环境准备 由于需求的简单性,这里我们直接跳过建模分析的过程,把DDL放在下面: SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for accounts -- ---------------------------- DROP TABLE IF EXISTS `accounts`; CREATE TABLE `accounts` ( `id` int(11) NOT NULL AUTO_INCREMENT, `phone` varchar(16) NOT NULL, `balance` double NOT NULL, `status` varchar(16) NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for bills -- ---------------------------- DROP TABLE IF EXISTS `bills`; CREATE TABLE `bills` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `desc` varchar(128) NOT NULL, `account_id` int(11) NOT NULL, `amount` double NOT NULL, `created_at` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 开始开发 本次的业务场景采用 DDD(领域驱动设计) 的设计风格开发,但不会展开说明,因为这是一个很大的话题,有兴趣了解的同学可以找我私聊,或者评论留言,我可以把自己过往实践 DDD 的经验再单独写一篇文章。
查看更多 →