> * 原文地址：[Unit testing, Lean Startup, and everything in-between](https://codewithoutrules.com/2017/03/12/software-testing-big-picture/)
> * 原文作者：[Itamar Turner-Trauring](https://twitter.com/itamarst)
> * 译文出自：[掘金翻译计划](https://github.com/xitu/gold-miner)
> * 译者：[gy134340](https://github.com/gy134340)
> * 校对者：[zhaochuanxing](https://github.com/zhaochuanxing)，[yifili09](https://github.com/yifili09)

# 单元测试，精益创业，以及两者之间的关系

为什么软件需要测试？

我曾经以为是为了产出高质量的代码：你总是需要测试因为你总是需要写出高质量的代码。
但是这个观点有几点问题。

**有时候质量不是主要问题。**
在“精益创业” 这本书中，作者 Rric Ries 说过有时候发布一个软件最终发现没人真的想用它。
这也是他创作的动机之一: 为创业初期建立一套更好的方法论，在真正投入时间去构建一个高质量的产品时，就能够发现这款产品是否能够成功的方法论。
如果没人用你的软件的话那么确保高质量纯属浪费时间。

**即使高质量很有必要，但高质量与测试之间的关系却很模糊的。**
一个 QA 的团队跟自动化的单元测试又什么不同？
他们的确不一样，但他们又分别给出什么样的质量？
什么时候需要特别的测试？

**另外，测试是有成本的：你怎样辨别成本的花费是否超出回报？**
比如说，有一家做税务申报软件的公司（我稍微改了一下细节）。
他们使用 Selenium 来对他们网站的 UI 来测试... 但是他们的应用依然很烂，而且每次改变 UI 测试都会崩溃。
这个测试并没有改变产品的质量，相反浪费了程序员的时间来维护测试。
他们做错了什么？

说我们都需要写出高质量的软件并不能帮助解决这些问题。
那我们回头来更加深入的讨论一下。

## 测试的意义是什么？

[康熙字典 :) ]((http://jsomers.net/blog/dictionary))里告诉我们测试是为了 “举证，通过一定原则或标准或实验来，证明真理，真实性。“ 
软件质量就在那里，是的，但事实却又不仅如此。

准确的说，这只是英语定义，可以肯定，有很多不说英语的开发者。
我不想被字典来束缚我们的行为。
人类语言是数世纪以来对世界的观察和理解，也是我们可以拿来借鉴的宝库。

那我们来以这个为出发点来看看能学到点什么。

## 测试的第一个方面

下面这个是测试吧？

    def test_add():
        assert add(2, 2) == 5
    

没错，他还真是，没毛病。
看函数名，一点都没错。
测试说明 `add()`  做了他该做的：将两个数相加得到结果。

你注意到这个测试是**错**的。
幸运的是我们的开发流程进入到了另一步：代码审查。
亲爱的读者们，代码审查告诉我我的代码是错的，2 + 2 = 4，不是 5。

代码审查是不是测试的一种？

根据字典定义来说是的：代码审查就是根据标准来验证代码的 “正确，真实性和质量”，这个从小我们就知道。

**那我们假设代码审查跟单元测试一样都是测试的一种。**
他们都是测试，却又相当不同。
那主要的区别在哪里？

一种是自动化的，一种是人来做的。

自动化测试具有一致性和可重复性。
你可以这样写：

    def test_add_twice():
        for i in range(10000000):
            assert add(i, i) == 2 * i
    

电脑每次都跑一遍一摸一样的代码。
代码可以保证根据输入每次调用`add()`返回他们的结果。
人在手动验证一千万种不同的计算时会遇到一些困难，比如无聊、分心、失误、缓慢啦等等。

另一方面，任何人都可以很快的告诉你下面的代码是错的：

    def add(a, b):
        return a + b + 1
    

计算机只按照指令执行操作，孰对孰错，人类能赋予它意义。
只有人才知道软件是为何而生。

现在我们知道每种测试的不同，以及如何组织它：**人类来发现意义，自动化测试确保一致性。**

## 测试的第二个方面

我们来看一下测试的另一个方面。
“A／B  测试”是一种尝试不同分类来看哪种结果更好的测试。
比如你为了测试网站新的设计：给 90% 的访问者原有的设计，同时给 10% 的访问者新的设计，看看哪种注册人数多一点。

这是测试吗？
这就叫 “A/B 测试”，跟它的名字一样。

我们来重新看一下字典定义：“举证，**通过一定原则**或**标准**或**实验**，来证明真理，真实性。”

字典上说这也是测试，因为**通过实验**。
我们通过实验来看看哪个版本更受欢迎。

单于测试和代码审查，对比来说，就是**通过一定原则**或**标准**来测试。
我们对软件有一些特定规格，一些我们希望软件的行为，同时我们确保它符合规格。

现在我们有了第二种理解与组织测试的方法：**通过实验测试** vs **针对规格测试**

## 测试的象限图

将它们放在一起我们得到下面这张关于测试的图表：

![](https://ww2.sinaimg.cn/large/006tKfTcly1fdorbapge0j312c13k0xb.jpg)

### 用户行为

- 有人买你的产品吗？
- 设计的改变会影响注册人数吗？
- 用户知道软件是如何工作的吗？

这些都是无法通过软件是否符合规格来回答。
相反需要你的经验知识：你需要观察人对软件的真实反映。

### 软件表现

- 你的软件在负载下表现如何？
- 你的产品抛出异常吗？

这些问题不能通过对比规范来解答，
你需要把软件跑起来看看到底会发生什么。

### 功能正确性

- 你的软件符合规范吗？
- 它做了它该做的吗？

很容易说自动化的测试可以证明这一点，但有没有想过单元测试在检查 2 + 2 = 5。
在基本的层面上，软件可以在技术上符合规范却完全无法达成规范的初衷。
但只有人明白规范的含义，和辨别是否匹配这个规范。

### 功能的稳定性

- 你的公有 API 对于相同输入返回相同的值吗？
- 你的代码是否提供了它该提供的？

人不是测试这个问题的好办法。
所有人都会忽略小问题：如果一个按钮从 “Send Now” 变成 “Send now”，很多人都不会注意到。
对比来说，如果你的 API 从 `sendNow()` 变成 `send_now()`，或者返回一个不同类型的值，你的软件就会崩溃。

这就是说公有的 API，或者其他软件依赖的 API，需要稳定性来确保正确性。
为私有的接口写自动化测试，或者对于迭代较快的代码，更新测试将导致极高的维护成本。

## 应用上述模型

如何应用模型？

### 选择如何测试

首先，模型可以帮助你根据你的目标选择合适的测试。

如果一家初创公司做一个没人用的软件。
写自动化测试纯属浪费时间，因为他连用户想要什么都不知道就开始专心实施了。

这里需要用精益创业的方法论，一个专注于用实验找到什么产品将满足客户的需求的方法来解决。
这意味着专注于用户行为象限。
只有证明他值得花费时间来进行下去，才值得对这个产品来做一些为了功能性和稳定性的测试。

### 了解你是否选择了错误的测试类型

第二，这个模型可以帮助你改变错误的行进路线。
比如说那家初创的税务公司，如果他们对于 UI 进行自动化测试但是并没有发现问题，然后每改变一次 UI，整个系统都要重新来进行一遍测试。
他们的问题在于系统的两个方面：

1. 税务机制是相当稳定的：税率每年只变一次。
这就需要他们对核心的税务计算部分进行稳定性或者单元测试。
正确性可以通过代码审查和税务会计来反馈。
2. 基于 web 的用户界面
UI 一直在变，说明不需要稳定性测试。
正确性测试可以通过人工测试来解决（比如说写代码的开发者）。

### 讨论测试的根据

最后，这个模型提供了一个公有的术语，来讨论的测试的意义及其不同的目标。

- 对于人工测试还是单元测试的优异性选择，你可以从一个很清楚地表明它们之间的差异的模型开始。
- 你也可以从一个完全不同的角度对公司的其他方面（比如市场）来讨论测试。

## 总结

- 无论是选择人还是自动化测试来保持持续性，都是有意义的，自动化测试提供准确性，人手工测试提供意义性。
- 即需要通过实验，也需要对比规范来进行测试。
- 每个组合提供了不同的测试形式：用户行为、软件行为、正确性、稳定性。
- 确保根据你的目标和情况来选择合适的测试方式。

想更多的讨论这个问题，可以给我发邮件[itamar@codewithoutrules.com](mailto:itamar@codewithoutrules.com)。
