# 4. 实例: 读取测试数据并测试弱密码




### 背景

接上一节的弱密码例子，我们的用例尽管运行的不错，但还是有点问题。

假如我们需要增加一些测试数据，那么我们就必须去修改```setUp```方法，在```test_data```列表中增加数据，频繁修改代码以适应一些不变的测试场景，这是没有必要的开销，可以想办法去优化。

我们可以把测试数据保存在文件里，通过读取文件的方式，每次动态从测试用例读取数据，这样数据的改变并不会影响用例，用例逻辑相对稳定，维护成本得到一定的降低。

### 设计测试数据

我们可以把测试数据保存成json格式，json格式的数据在各个语言间有较好的通用性，比较适合复用。

新建user_data.json文件，内容如下

```json
[
  {"name":"jack","password":"Iloverose"},
  {"name":"rose","password":"Ilovejack"},
  {"name":"tom","password":"password123"}
]
```

使用python的json库解析上面的json文件，可以得到如上节中```test_data```一致的数据。

### 代码

新建```test_password_2.py```，内容如下

```python
import unittest
import json

class PasswordWithJsonTeseCase(unittest.TestCase):
    data_file_path = './user_data.json'

    def setUp(self):
        print('set up')
        self.test_data = json.loads(open(self.data_file_path).read())

    def test_week_password(self):
        for data in self.test_data:
            passwd = data['password']

            self.assertTrue(len(passwd) >= 6)

            msg = "user %s has a weak password" %(data['name'])
            self.assertTrue(passwd != 'password', msg)
            self.assertTrue(passwd != 'password123', msg)

    def test_dummy(self):
        pass

if __name__ == '__main__':
    unittest.main()
```

跟上一节相比，最大的不同点是现在test_data通过解析json文件的方式来赋值```self.test_data = json.loads(open(self.data_file_path).read())```。

执行测试文件，结果应该与上一节一致。

### 发现问题

上面的代码有2个测试方法:```test_week_password```和```test_dummy```。由于```setUp```会在每个测试方法执行之前执行一次，那么setUp方法会执行2次，相应的json文件也会读取2次。如果测试方法多的话，那么反复读取json文件对性能来说是一个巨大的挑战。

### 优化

对于上面的测试数据读取场景，我们可以在所有测试方法执行前读取一次数据，毕竟测试数据在所有测试方法执行过程中是保持不变的。

### setUpClass()和tearDownClass()

* [setUpClass](https://docs.python.org/2/library/unittest.html#unittest.TestCase.setUpClass)方法在每个测试用例类执行之前会执行一次，接收该class作为唯一的参数，并且必须使用装饰器[classmethod()](https://docs.python.org/2/library/functions.html#classmethod)
* tearDownClass: 在所有测试方法执行完之后被调用1次，调用方式跟上面的方法类似

### 重构

下面我们重构代码以达到只读取1次测试数据的目的，新建文件````test_password_3.py```，内容如下

```python
import unittest
import json

class WeakPasswordTeseCase(unittest.TestCase):

    @classmethod
    def setUpClass(kls):
        data_file_path = './user_data.json'
        print('before all test methods')
        with open(data_file_path) as f:
            kls.test_data = json.loads(f.read())

    def test_week_password(self):
        for data in self.test_data:
            passwd = data['password']

            self.assertTrue(len(passwd) >= 6)

            msg = "user %s has a weak password" %(data['name'])
            self.assertTrue(passwd != 'password', msg)
            self.assertTrue(passwd != 'password123', msg)

    def test_dummy(self):
        pass

if __name__ == '__main__':
    unittest.main()

```

有几点需要提及一下

* 使用```open``` 方法的with模式可以在读取文件后自动关闭文件
* 在setUpClass方法中可以直接设置变量，比如```kls.test_data = json.loads(f.read())```，在其他测试方法中可以被访问




原始封面

![课程图片](https://images.unsplash.com/photo-1487017159836-4e23ece2e4cf?w=300)


