- 主题:大家公司的代码都做自动化测试吗
CI/CD 可以说是这些年发展最快的方向之一。以前 jenkins 什么的部署、设置啥的还有点儿小麻烦,现在几大 repo 服务商 github 等都自带 docker + yaml 的解决方案,可以设成 build 跑通了才能 merge PR。aws 也有 codebuild/pipeline/deploy 等类似的解决方案或者接口。
现在不少开源项目都实现了完全自动 CI/CD,之前给 DefinitelyTyped 提过一次代码,整个过程有个 bot 辅佐。只要测试全过,至少一个 member 已经 review 了,merge 的主动权可以在提 PR 的人手中,之后大概几分钟就能发布到 npm。前脚 merge 了,后脚直接就给公司的项目提了个 PR 移除自己的类型声明补丁。整个过程在我开源项目 issue/PR 中是体验最好的一次,不像很多项目半年都没人理,等收到 review 邮件,我都不记得有提过代码这回事了。而且 merge 的主动权还给提 PR 的人一些成就感,反正我是很乐意下次发现问题再提代码的。
测试也是分很多层,gui 的项目至少也是能做 unittest 的。当然还是取决于项目的设计,我们公司最复杂的 rails 项目是从90年代 oracle db 起家的,核心逻辑都在数据库里。导致的结果就是,unittest 没多大意义,因为 ruby 这边业务逻辑不完整。而 db 这边所有人都不知道该怎么搞 unittest。不重构的话,目前最好的解决方案就是 e2e test,不过这方面 web 还是工具非常多的,直接在 container 里面跑 headless 浏览器。
理想化的设计,还是尽可能解耦 UI 和业务逻辑,把界面操作转换成 action + params 的设计。毕竟就算是能做 e2e test,速度跟 unittest 也是没法比的。就算是需要人工测,把测试量降到最低也算是部分达到了目的。
关于 e2e test,我们对老项目的思路是这样的,首先是要有 happy path 作为基准。开发新功能至少不能有明显的问题,直接干掉 baseline。之后每发现一个新 bug,就把它做成一个 test case,确保不会发生第二次。至于老的 bug,基本上是不可能有时间写测试的,再发生就当新的写测试。
想法很美好,但现实还是非常残酷的。我们的项目老到一直都是 export/import db,然后删数据,因为很多年没有人做过从空 db 建出一个能跑的项目来了。最关键的还是人的问题,我们的 rails 是一个 monolithic app,不管能不能该不该的功能都一股脑塞进去。导致的结果就是,一个完全不重要的模块没配置好,也会导致整个 app 跑不起来。但是做这些决策的人既不会用 docker,又不理解 decouple 的意义,还在继续往里面塞垃圾……
我们最惨的就是 db,因为是各种复制出来的,哪里多了少了个 column/constraint 是完全不知道的,直到客户发现问题了才能人肉发现。SQL 代码直到进了 QA 环境,所有依赖它的都编译不过了,很多功能用不了,然后人肉去查哪个出的问题。我花了很多时间精力,终于在引入 liquibase 后,把结构(tables/constraints/indexes/foreign keys)从 SQL 变成了 xml,然后 objects(views/mviews/triggers/procedures/functions/packages)按照依赖关系顺序执行,这样终于能在 build 里解决 SQL 编译不过这种低级问题了。而且利用 xml 去验证生产数据库的结构,也是很容易实现的。但我们这哥们儿对 liquibase 不是用 ruby 写的超级有意见,目前又在子项目里变着法子写 rails 的 DSL。我问他一个很实际的问题,怎么 drop constraint,他说没试过不知道。我估计要是问,怎么去验证数据库结构的话,他又得山寨一堆 regexpr 出来。跟他说无数次,不管是 SQL 还是 DSL 都是数据跟逻辑 coupled,很难直接用数据;XML 只有数据,想生成啥都没问题,就是不听。总之不管能不能满足我们的需求,只要是 ruby 就是好的,SQL 也是好的(因为数据库只能用它),其它都是坏的。我们的一堆应用初始化的数据也是,各不相同的 ruby 代码,完全是可以写成 CSV 之类的文件,然后用写代码导入,还能写测试。现在就是直到生产环境里某个参数被意外改了,才发现导入的代码有逻辑问题,还没法写测试。
e2e test 也是,我主张用 playwright 写,这样任何人都可以跑。我们 support 团队把功能做成视频,相当于文档,给客户用。因为 node 环境很容易在 windows 上面装,理论上可以他们本机跑,直接出视频或者本地录屏,然后就可以直接拿去剪。而且 support 里面有几个人懂 sql,也很愿意学东西。加上目前 chromium 有把操作录成 json 的功能,给出些示例,做个培训教他们自己写也不是不可能。我们还能拿过来直接当 happy path 用。他偏要用 ruby,因为自己不用学新东西。我就不理解了,为啥天天写 js,反倒需要学新东西了。测试数据也是,有一个写 java 的、CI/CD 经验丰富的跟我们推 factory pattern。后来我演示了自己的思路,把测试数据写成数据文件(yaml 或者 json),然后利用 orm 直接导入,他马上就意识到我 decouple 数据和逻辑还能完成许多其它事。我们这哥们儿只用过 ruby,之前有个做测试的公司给我们做推广,他们用 java 自然讲了些 patterns 在测试里怎么用。我们这哥们儿好像发现了新大陆,如获至宝,然后开始各种俄罗斯套娃,测试也非要用 factory_bot。结果现在 sales 团队自己雇了个实习生,做数据导出和导入的工作。基本上就是本来 dev team 能做,或者应该主导的工作,都被别的团队给干了。不但捞不着名,人家做出来的好处你也用不上。我们老板不懂技术,我也不知道他会不会怀疑我们团队的能力……不过我们开发任务重,倒也算是有借口。
因为一堆东西敲不定,我给出解决方案甚至 poc 都出来了,人家不同意,又给不出自己的方案,所以现在只能雇外面的人来搞。看过 demo 人家自己基于 java 开发的 BDD 架功能非常强大,我很多只有初步想法的东西,人家都已经有完整的实现了。结果老板又觉得太贵,好像说先用一个月看看,估计也不会有下文了。不过听说已经发现好多 bug 了。顺便一提,人家的框架主要是自己写的 java,然后说也可以写 python。我觉得人家说的挺明白的,虽然 bdd 的形式,但核心代码是十来年的积累,那 py 八成是张皮调用一下呗。我们这哥们儿一听,不行,我们 ruby 有 cucumber,拿过来应该可以直接用的,你们写 ruby。于是对方很委婉的表达,开源库没有他们的独家功能,我们这位非得说 ruby 也行……
反正我现在的感觉就是,人的思路和想法改变不了,再好技术也没用。只要有意愿、有资源,而且思路开阔,一切都不是问题。
【 在 xunery (寻) 的大作中提到: 】
: 看很多老外的项目管理书籍,自动化测试是很重要的一环,国内实施的公司多吗?
: 尤其是有gui的应用程序,我都想不到方法怎么搞自动化
: web api可能相对容易点
: ...................
--
修改:eGust FROM 203.211.108.*
FROM 203.211.108.*
过奖了,我只是因为放假了,想发发牢骚而已……
本地的小公司,应该是基于 java 的流行 bdd 框架开发的,应该提过但我没注意。bdd 是比较主流的测试形式吧,主流语言都有实现
【 在 timshaw (过完老年再过青年骚年) 的大作中提到: 】
: 赞eGust,现在来本版,都要看看eGust的干货。
: 请问你推荐的这家javabdd是哪家的?
: cucumber也有py的版本吧?
: ...................
--
FROM 203.211.108.*
那是不可能的,人家职位是 architect,而且是这个 rails app 的元老级人物。我估计假如跟总经理闹矛盾到必须走一个的程度,老板估计也只能让总经理走人了。
【 在 KEILLY (米饭) 的大作中提到: 】
: 踹了这位用ruby的
--
FROM 203.211.108.*
大的小的都一样啊,正常都是拿成熟的测试框架直接用啊。
unittest 是非常成熟的,稍微新点儿的语言都直接自带了,老点儿的也都有主流的包。比如你说的 go 就不算老,直接就能 go test。像 python、ruby 这类脚本语言就算老,加个测试也就一个官方包而已。js 的话,jest 是占主导地位的,还有 mocha 也比较流行。
理论也很简单,就是保证每个单元都能单独正常工作,出 bug 就在相关测试里加 case。设计上就是分层,尽量相对独立,可以单独测试。不同层之间 interface 尽量稳定,必要时按照接口 mock 其它层,这样尽量保证每一层的功能稳定。很多静态语言的框架里,有时甚至专门套一层,就是为了方便 mocking,然后就搞成了俄罗斯套娃。
测试有一些主流的标准,然后以单独的包形式提供扩展功能。比如想用 jenkins,一般是加个 junit report,然后设置去哪找测试生成的 junit xml 文件就可以了。jest 只是一个基本测试框架,想用 bdd 的话就加个 jest-cucumber;同理再加个 jest-puppeteer 就可以用 bdd 的形式跑 e2e 的 puppeteer。e2e 的 web 测试框架非常多,也非常成熟,老一点儿的有 selenium/webdriverio 基本上啥语言都有封装,不那么老的比如 cypress 也挺流行,新一点的 puppeteer/playwright 能做的都不止测试了。我直接用 playwright 爬网页,而且它自己还有个跟 jest 接口类似的测试框架,还能直接 mock request。放以前不但得搞个 mock 用的 web service 模拟第三方 api(虽然其实也就几个静态 json 而已),还得改代码测试的时候指向自己的服务。
按照现在的主流 web ui 框架,比如 react、vue 之类,组件化的设计可以单独测试,比较流行的有 storybook。但是都对组件的组织形式有相当高的要求,不然需要 mock 的东西太多,就很难持续进行下去了。
而开发也有 tdd,先把测试写好,这样基本的工作单元和接口都已经设计好了。然后再按照测试写代码,自然跑通,而且 coverage 直接就100%。这也是为啥 pure function 最适合写 unittest,每个函数都可以单独测,跑通了就一劳永逸。
另外,liquibase 不是测试用的,而是专门做 db migration 的工具。我们之前 rails 里写的 migration 只管跑 sql,之后什么状态完全不管。liquibase 对于一般的东西会做个状态认证,比如 foreign key 加不成就挂掉,而 stored procedure 也是手动加了一行代码(当然是自动生成的)看一下状态,不正常就直接挂掉。
【 在 xunery (寻) 的大作中提到: 】
: 感谢这么大篇幅的回复
: 但是不同类型的软件可能测试方法天差地别。对于一个小团队来说,如果想实施自动化测试的话是不是只能依赖现成的测试框架,就是想问问大家有什么好的方法或测试框架。
: 每个模块暴露的接口都要写各种测试case吗?那些不向外暴露接口的模块怎么处理?比方说exe程序。
: ...................
--
FROM 203.211.108.*
说实话这是正经做产品的。很多非技术出身的老板都搞不明白,虽然短期搞测试是增加成本的,但长期来看绝对更省钱。
我们公司目前大概也就6个人主要写代码(不算 plsql),俩 QA 纯人工搞测试,说是打算明年再招个10人。目前基本上平均每个月,我会有一个礼拜时间专门处理 prod/uat 环境发现的 bug,或者回答其他团队的问题,为啥这俩东西输入的数值一样,结果却不同。我感觉就我们现在这水平,到时候破坏力就超过建设速度了。换句话说,bug/feature 的产出比会超过1,到时候我除了给人擦屁股就不用干别的了(当然我自己屁股也不干净)。
【 在 dpblue (deep blue) 的大作中提到: 】
: 我前公司用的是MFC开发的gui,是的你没看错,用的是MFC,但仍然有自动化测试,公司自己开发了一个测试框架,用Python通过框架去调用Windows的API,点击界面上的菜单按钮什么的,还能读到弹出来的对话框的文字来自动检查。我们写的新代码必须全部被自动化测试覆盖,不然
--
修改:eGust FROM 203.211.108.*
FROM 203.211.108.*
c++ 的测试框架我没概念,毕竟没做过相关的方面。
不是不能提代码,你一样能 commit,能建 PR,只不过不能 merge 而已。对于成熟的老程序,最暴力的方式就是跑完测试之后看 coverage,没到100%挂掉就完了,当然前提是已经有100%的底子。另外,至少 review 的时候,就算看不出来全不全,至少有没有还是很容易的。
另外要明白自动测试的价值所在,并不能避免 bug 的发生,但是能够避免同样的 bug 再次发生。有个经典的笑话:
A QA engineer walks into a bar. Orders a beer. Orders 0 beers. Orders 99999999999 beers. Orders a lizard. Orders -1 beers. Orders a ueicbksjdhd.
First real customer walks in and asks where the bathroom is. The bar bursts into flames, killing everyone.
你不可能指望有人能完整考虑所有方面,测试做不到避免新 bug 的发生,而是一个迭代的过程避免破坏已有功能。另外要考虑的价值,从无到有的变化是巨大的,有 PR 没跑过的价值更高。先把测试跑起来再说,慢慢积累 case,让人意识到写测试是磨刀不误砍柴工。
【 在 xunery (寻) 的大作中提到: 】
: 这几天一直在消化你说的这些框架,以及查找资料。
: 发现很多框架是针对编程语言或程序类型的,web测试工具、框架就比较多。像c++写应用程序好像就不怎么好测试,c++写的服务程序,gui程序等等。如果是针对功能、接口写测试代码,比方说我新加一个接口,可能需要写5个case,但是如果开发人员偷懒或者没想到那种情况,写两
: 那些开源项目没有通过自动化测试就不能提交代码是如何做到的呢?
: ...................
--
FROM 203.211.111.*
我们也有类似的问题,有时 QA 环境更新收到大量 PR。在测试 A 功能时,由于功能 B 出了问题,导致 QA 无法进行测试。而 QA 又无法判断是谁导致的,于是就随机 bounce 某一个 ticket。然后开发这边就得花时间 debug,找到出错的代码,再告诉 QA 这个问题是由另外一个 jira 导致的,你 bounce 错了。如果有 work around 的话我可能也会讲一下,不然可能不光我一个人的没法继续测。有时候实在太严重,只好 ssh 过去直接修了,不然按照我们的流程,可能半天时间 QA 都干不了活儿了。一来二去,浪费至少两个人的时间。
这方面我们感觉很大程度上是 branching model 导致的,因为 QA 用的 branch 包含所有人的代码。而 QA 只是随机的拿一个 jira,并不知道有哪些变动,更不具备可以判断问题出在哪里的能力。出了问题,自然也做了一定的研究,看看别人的经验。不过这方面我并没有非常成熟的想法,主要也是很多相关 model 的介绍中都并没有提到 QA 的角色。
比如 git flow,并没有明确说 QA 应该测试哪个 branch。如果测 develop branch,那就跟我们的现状没有差别,完全解决不了问题。测 feature branches 的话,首先就是可能某些 features 单独测没问题,但是它们又并非完全独立的,merge 到 develop 后可能会产生新的问题。其次,某些 feature 本身的工作量也很大,需要几个人协作完成,并且也无法避免产生依赖关系。最后需要给 QA 能够主动切换 branch 的能力,但我们最大的问题是数据库,想要 rollback 是非常困难的。当然技术上的问题不光数据库一个,但个人感受技术上的问题都是有办法解决的,更大的问题还是在人的观念上。
不知道你们的每次 build 要多久,如果让 QA 单独测 PR(rebase PR on develop),通了直接 merge,那就可以一定程度上避免一个 bug 耽误所有人的事。缺点就是我前面说的,有些功能不是完全独立的,后 merge 的测了没问题,但前面的可能需要重新测一遍。
【 在 xunery (寻) 的大作中提到: 】
: 是,bug不可避免,只要是人去做,就有出错的可能。
: 其实,我主要想把严重的bug在编译完成之后,打包之前能筛选出来。以前碰到过什么情况,就是一个人引入一个fatal bug,导致QA拿到新的包不可用,不光影响QA的工作,别人修改的代码也无法验证。
: 这时候如果有自动化测试环节,可能影响就会降低很多。长期来看,对整个团队的效率都有提高。
: ...................
--
FROM 203.211.111.*
feature branch 并入主 branch 之后,再让 qa 把 feature 重新测一遍吗?如果发现问题,之后的 fix 是直接进主 branch 还是有其它的流程?我其实挺好奇其他公司都是怎么做的,毕竟网上能找到的文章大都忽略了 QA 的角色,而在实际中真的 CI/CD 后敢直接拿用户当小白鼠的还是极少数。所以不管有没有自动测试,不至于一个小错误就浪费许多人的时间,应该是每个项目都会遇到的问题。
这么来看,我们 rails app 还算是很容易搞了,如果能用上 container 的话。我们最主要的问题是数据库,我本地电脑上的测试,启动 oracle container 大概要30秒左右能用,然后大概3分钟建好空的数据库。一旦数据库准备好,启动应用本身不会超过1分钟。按照我的估计,技术上不超过10分钟就能从0跑起来,跟10个小时比起来几乎就没有时间成本。上个厕所,倒杯水,再读一下 jira,差不多也就能用了。
【 在 leadu (leadu) 的大作中提到: 】
: 赞!
: 我之前参加过的项目有这种情况,这都是大型项目了。
: 严重影响QA的主要有两种情况,一种是break build,另一种是build不能用。
: ...................
--
FROM 203.211.111.*
你说的书里提到的概念,可能跟 git flow 里的概念并不完全相同。一个 branch 如果长期没有 merge/rebase,理所当然的会有大量的 conflicts,不管代码上的还是逻辑上的。
个人认为,branching model 也是项目管理的一种,总体上来说是经验总结。所以,很难有一种放到任何情况都适用的方法。或者说,某个模型在某个机构里用起来没问题,换个地方应用,可能一个非常不起眼的差别,就导致用起来很不顺畅。
我们的 model 槽点太多,老板希望做成 saas,但一直以来是按照版本号(v1.2.3.0)来的。我们号称是 agile,每个 sprint 选几个比较重要的东西进来,以前想做成大概1个月的工作量,就升一个版本(1.2.4.0)。然后就是常规的需求描述不清,或者改需求,总之就是可能搞到3个月都没法完成(1.2.3.3)。结果就是不同客户运行的版本不一样,有的可能还在跑一年前的版本。如果发现一个新 bug,大概需要经过15个版本号才能到 develop。项目用了 sub-module,直接用的 hash 而不是 tag 或版本号。8月份还迁移了 bitbucket 的服务器(之前是自己的服务器),如果问题出在 sub-module 里,不花点儿时间都找不到应该基于哪个 commit 修。修完之后,按道理就又得挨个小版本改 sub-module 的 hash,反正我是不知道现在是咋搞的……
迁 git 也是一堆破事儿,反正就他一个人有 write 的权限,结果丢了一堆 commits。等过了一个星期 QA 问我问题,我才注意到丢代码了。然后花了大半天时间,我才终于确定丢了哪几个 PR。而且隔太久才发现,都出 conflicts 了。可能都懒得 git fetch --all 一下,就直接 push 本地的上去了,tags 也全都没了。很自然的也没 git fetch --prune,非说 sub-modules 没问题,是你们的问题。有次早会太多人抱怨,他终于 prune 了,然后回头默默修了大半天。小20个版本号,平均4个 sub-modules,每个小版本平均变2个,一直到上个月才修干净。
顺便说一下,这些 sub-modules 起到的作用相当于 ruby gem,也就是一个相对独立的包,对应着 node package 的概念,跟 dll 的作用类似。之所以做成 sub-module,而不是打包发布,也是有一堆历史原因的。最重要的两条,一个是之前的私有 bitbucket server 在公司内网里,需要走 vpn/gateway,sub-module 的话比较容易 deploy;另外一个是每次都得打包,开发和更新的体验太差。反正第一个问题已经不存在了,第二个问题我个人认为,相比之下个人的开发体验相对次要。我们的这种版本号(m)+ sub-module(n)的模型,把问题的复杂度放大成 m*n 了。
我们目前也想尝试其它的 branching model,但还是老话,人的因素。反正我已经努力了,也可能不是母语表达没那么清楚,感觉很多点人家还是没理解。
【 在 xunery (寻) 的大作中提到: 】
: 《持续交付:发布可靠软件的系统方法》这本书里是不提倡feature branch这种方式的,比方说要不要而对这个分支配置CI/CD,如果长时间不能合并到主干,合并的时候还有很多问题。还有很多其他理由
: 实际操作中,我们是小地迭代都在主干做(哪怕有新feature),非常大的版本升级,会新拉一个主干,以后都基于新主干,CI/CD和QA的工作主要基于主干来做,感觉效果还是可以的
: 我们主要是desktop应用程序,不是web程序,也许不同类型可能有区别
: ...................
--
FROM 203.211.111.*
我理解 scrum 就是开发和 QA 组成的针对功能的小组,基本上按 sprint 来?
我们目前开发是5个左右 ruby(偶尔写 plsql),2.5个 plsql,一共就俩 QA,其中一个觉得心太累下个月离职了……
我们这 senior 破坏力更大,有时候本机都没测就提交了,然后 QA 环境一堆500。而且超级没耐心,bitbucket 上面一个 build 要跑大概20分钟,没等跑完就 merge,结果自然出问题。前段时间加了点儿东西到 bitbucket-pipeline.yml 里,版本之间有 conflicts。人家特别勤快的亲自去修,结果缩进搞错了格式不对,两天跑不了 build……
反正我们公司也不光 dev 有问题,比如 BA 也搞不清用户需求,能经营得风生水起也算是奇迹了。
【 在 leadu (leadu) 的大作中提到: 】
: feature branch merge之后,scrum团队就解散了...
: 主branch上的qa会做一些简单测试,确保没有严重问题。
: 如果出现问题,会是原scrum团队中的senior dev在主branch修复,修复一般不会出现严重问题。
: ...................
--
FROM 203.211.111.*