Day 18:浅谈软件测试

经过了差不多两周,我认为监控和警报的部分总算是告了个段落,虽然之後还需要透过实际的收集监控资料来修正警报的规则跟 dashboard 就是了。接下来刚好我是读到有关测试的部分,那我今天就来谈谈这个吧。

测试与 SRE 的关联

因为 SRE 关注的是可靠性,而测试便是一个非常直接的手段,它可以帮助我们去判断在每次更新前後,服务的行为是否仍然如同我们的预期,想当然尔,为了避免人力需求与专案规模一同线性成长,这些流程都要尽量的自动化。

不过需要注意的是,测试也不是万灵丹,通过测试并不意味着软件没有任何 bug,即使覆盖率达到 100% 也是如此,因为我们通常没有办法确定所有输入在所有情况下的行为。但至少,没有通过测试可以帮助我们即时发现即将产生的不稳定性(如果它被部署下去的话)。

测试的分类

在 SRE book 里面,把测试分成两类:传统测试与正式环境测试,其中传统测试表示的是在服务部署下去之前所进行的那些测试,主要用於评估服务的正确性,而正式环境测试则是对於已经部署下去的服务能否正常运作。

不过看到这边我倒是有点疑惑,因为压力测试被列在了正式环境测试底下,然而就我的理解,应该是不会直接对生产环境这麽做?应该会在另外的专门用来测试的环境去做。

那麽下面就简单列一下各种测试与目前在 NOJ 上的情形(虽然大部分是都没有做啦)。

单元测试

单元测试我想应该算是在所有测试类别之中最简易最轻的一种类别了,主要就是希望可以针对「单元」去验证它的行为,可能是类别或是函式。以这部分来说我想 NOJ 其实算是勉强有做,只是有些缺点尚需改进。

测试案例之间的相依

首先第一点是测试案例之间有相依性,有部分测试案例的正确性,是取决於前面测试的通过与否,像是这个,它的概念大概像是这样。

def test_signup(self, client):
  '''
  Sign up user "test1" and "test2"
  Assert it will success
  '''
  
def test_used_username(self, client):
  '''
  Sign up user "test1"
  Assert it will fail
  '''

上面的两个案例,其中 test_signup 会在资料库里面去注册两个使用者,分别是 test1 和 test2,然後在 test_used_username 里面去尝试注册 test1 这个名称,并且预期它会因为重复命名而失败,然而这件事情的前提是,我们已经在资料库里面存在 test1 这位使用者。

这在这边或许是影响不大,因为两个测试案例离的非常的近,内容也不多,所以或许我们可以很快地看到 test_signuptest_used_username 之间有所关联。但我想这件事在测试案例逐渐庞大的时候,会让整份测试变得难以维护,因为我们可能会不小心在任意测试案例之间存在相依关系。若是没有在文件里面注明这些前提的话,可能会导致测试里面也容易存有 bug。

顺带一提,我认为在设计上不要做太多对於环境的「假设」,可以写出更易於测试的架构与更加稳定的测试。

只测 web API

另一个问题是,在现行的测试里面,我们测试的行为都专注在 web API 上面,然而这件事代表它会引入额外的依赖,也就是需要有 flask 的 server 才能执行操作。可是比较好的做法应该是让这些操作可以透过 python 的函式呼叫完成,可以避免在处理 HTTP 请求的时候意外造成一些 bug,也可以缩减 bug 的存在范围。

然而这个问题也有关於软件本身的架构,因为我们一开始在设计上并没有考量到这些,所以有不少操作(e.g. 创建 submission)都只能透过 HTTP 请求完成,因此从撰写测试的过程中,其实也可以检视架构的好坏。

整合测试

整合测试所希望测试的对象,是由已经通过测试的各个单元所组成的一个整体,因为单元测试只考虑单元本身,有时候有些 bug 是需要组合多个单元才会发现的。根据前面的定义,这表示整合测试可能会涉及多个不同的类别,另外针对如何解决相依关系,比较常见的解法包含依赖注入或是 mock,可以更好的去控管我们测试的依赖。

然而在做 mock 的时候也要小心,mock 过後的行为是否真的会与本来保持一致,以 NOJ 用来 mock MongoDB 的 mongomock 为例,这个 issue 导致我在写入资料的时候可能会造成错误,但是 MongoDB 应该是不会有这问题的。

话说有关於单元测试与整合测试的分界,似乎一直是个困难的议题,我在搜寻资料的时候看到这篇文章写得挺好,大家可以参考看看。

小结

其他书上所列的测试我看了一下,NOJ 应该是通通没有做。包含冒烟测试、效能测试、金丝雀测试等等。以目前的情况来说,我想先把单元测试(或许还有整合测试)写好应该对於提升整个专案的稳定性来说是比较有效益的选项。

然後,我认为不管测试类型,对於每个已修复的问题或是新开发的功能,都补上对应的测试应是一个提升稳定性的有效且较低成本的方式,因为在那当下,是我们对於各种 context 最熟悉的时机,若是打算拖到之後再补的话,通常就是不会补的意思(除非在那个地方被人戳出 bug 才有机会)。


<<:  Day17 AR装置的编年史(下) 各家公司开始研发各种AR装置

>>:  <Day17>在用API做投资前,先弄懂什麽是"量化交易"?

Node.js安装

昨天介绍了Node.js,今天我们就要来实际安装Node.js啦 首先当然是到官网去下载Node.j...

Day10 Sync.WaitGroup & Sync.Map

Sync.WaitGroup A WaitGroup waits for a collection ...

[Day 28] HDFS

欢迎来到第 28 天,昨天提到 MapReduce 的观念,今天要提到另一个 Hadoop 中的重点...

Day15 X Tree Shaking

在昨天我们学会了 code splitting 与 dynamic import 的技巧,让程序在...

[Day 22] - 『转职工作的Lessons learned』 - GraphQL (Hasura) - Apollo Client

GraphQL (Hasura)系列,忘记介绍最一开始从前端连线到GraphQL (Hasura)一...