LLT原则

用例一定要用场景和预期

所谓场景,就是用例一定要有目的,比如检查调用cinder网络异常怎么处理,创建快照失败怎么处理。

def test_create_backup_status_error(self, factory_mock):
        """
        场景:创建备份状态为error
        预期:checkpoint和checkpoint item状态均为error,不会出现非稳态现象
        临时快照被清理,临时备份被清理
        副本中fail_reason为cinder backup中的fail_reason
        :return:
        """

所谓预期,就是用例一定要有检查点,检查点一般是通过断言来表达。绝对不允许没有检查点的用例,之前整机备份plugin就有很多这样的反例, 列举一例如下。

def test2(self):
    try:
        backup_service.on_main(checkpoint_object,resource_object,context_object,None)
    except Exception as e:
        self.failIf(plugin_common.PLUGIN_EXCEPTION_STR != e.__str__())
        return
    self.failIf(True)

db可以不用mock,可以使用sqlite memory db,必要时甚至可以使用sqlite本地数据库(方便查看db数据)

def setup_dummy_db():
    options.cfg.set_defaults(options.database_opts, sqlite_synchronous=False)
    CONF.database.connection = 'sqlite:///:memory:'
    CONF.disable_process_locking = True

    engine = get_engine()

    karbor_migration.db_sync()
    migration.db_sync(engine)
    engine = get_engine()
    engine.connect()

Mock原则

如何确定要mock的路径

总结就一句话:在哪里引用就mock哪个路径。举例如下:
文件路径:kangaroo/kangaroo/plugin/volume/backup_operation.py

from karbor.services.protection.client_factory import ClientFactory
class BackupOperation(protection_plugin.Operation):
    def on_main(self, checkpoint, resource, context, parameters, **kwargs):
        cc = ClientFactory.create_client("cinder", context)
        snap = cc.volume_snapshots.create(volume_id=volume_id)

如果想mock ClientFactory就必须mock kangaroo/plugin/volume/backup_operation.ClientFactory,而不是karbor.services.protection.client_factory.ClientFactory

跨服务调用时mock client,因为LLT运行时周边网络肯定是无法连通的。

@mock.patch("kangaroo.plugin.volume.backup_operation.ClientFactory")

长时间任务有轮询操作时一定要将interval mock掉,否则LLT执行会很慢。

@mock.patch("kangaroo.plugin.volume.backup_operation.POLL_INTERVAL", 0)

异步执行时需要将协程mock,并且捕获协程中的异常,因为协程的异常对主线程不感知。

def mock_spawn(cls, func, *args, **kwargs):
    """
    mock spawn,改成同步调用
    :param func:
    :param args:
    :param kwargs:
    :return:
    """
    try:
        func(*args)
    except Exception:
        # 协程异常对主线程不感知
        pass

Mock技巧

基于类的Mock和基于方法的Mock的区别

@mock.patch("kangaroo.plugin.volume.backup_copy_operation.INTERVAL", 0)
Class TestBackup(BaseTestCase):
    pass
@mock.patch("kangaroo.plugin.base_operation.wf_client", mock.MagicMock())
    def __init__(self, *args, **kwargs):
        pass

mock.MagicMock使用

MagicMock对象可以通过任何属性去访问,返回值是一个子Mock对象,也可以指定该对象的任何属性。

fake = mock.MagicMock()
print fake.a
print fake.b
fake.c = object()

使用side_effect构造多次调用不同返回结果

在LLT中,经常遇到需要构造同一个接口多次请求返回不同的场景,这个时候side_effect就能派上用场了。

client_mock.backups.get.side_effect = [Backup(status='creating'),
                                       Backup(status='error'),
                                       Backup(status='error')]

在这个例子中,我们模拟了cinder backup调用每次返回不同的状态。

使用assert_called_withassert_called_withassert_not_called等断言代码是否被调用。

# 断言快照被清理
client_mock.volume_snapshots.delete.assert_called_once_with(
    snapshot='fake_snapshot_id')
# 断言失败副本被清理
client_mock.backups.delete.assert_called_once_with(
    backup='fake_backup_id1')

LLT中无法mock的坑

sqlite功能不全

LLT运行时使用的时sqlite memory db,受限于sqlite功能约束,有些mock无法实现。包括如下:

  • 外键约束无法mock:外键不存在时也可以保存。
  • 字段长度无限限制:sqlite字段超过列长度定义也可以保存。
  • 级联删除无法限制:sqlite当记录有被其他表外键引用时也可以删除。

在LLT代码中不能修改全局的变量

在LLT中不能修改全局变量,否则会影响其他LLT的运行。例举两例,test_karbor_config_az.py用例会模拟修改AZ配置文件,修改了全局的AZ配置文件路径,从而导致其他用例因为AZ检查不通过了失败。

配置文件中公有云、私有云的配置,默认是私有云,并且在BaseTestCase的tearDown中会置为私有云。如果需要测试公有云场景,需要手动打开开关。

不能在LLT中修改module

Python的module也是全局的,所有用例都是共用一个Module,因此禁止在LLT在修改Module。反而教材:

ClientFactory.create_client = mock.Mock(
            side_effect=create_client)
        cinder.create = mock.Mock(side_effect=client)

这个会导致后面引用ClientFactory的用例失败。

results matching ""

    No results matching ""