繁体   English   中英

如何测试AWS Lambda

[英]How to test AWS lambdas

因此,我有一些AWS lambda代码可以连接到Redis群集实例(使用ioredis)来存储数据。 我们希望在lambda容器中实例化集群以进行重用,因为我们打算使此lambda受到足够的攻击,以便我们从容器重用中获得性能收益。

我们编写了一个包装器类(称为RedisCluster),该类扩展了Redis集群以提供其他检查和功能,因为我们在各种不同的lambda中使用了该类。

过去,我能够在测试时将Redis Cluster实例存根,但是当我在容器中实例化Redis Cluster实例时,似乎在加载Lambda源代码时(即在测试执行之前),群集被启动。 运行测试时,我收到一条错误消息,指示集群无法连接到节点实例。

λ

let redisCluster = new RedisCluster([{ host: process.env.HOST, port: process.env.PORT }]);

function isKeyInCache(cacheKey) {
    logger.info(`Searching for key::${cacheKey}`);
    return redisCluster.get(cacheKey);
}

exports.handler = baseHandler((e, ctx, cb) => {
    ctx.callbackWaitsForEmptyEventLoop = false;
    const cacheKey = `${key}`;
    isKeyInCache(cacheKey).then((response) => {
        if (response) {
            logger.info('Key is registered');
            redisCluster.removeKey(cacheKey).then(() => {
                const result = { status: 'Registered' };
                cb(null, result);
            }).catch((err) => {
                logger.error(err);
                cb(err, 'Error');
            });
        } else {
            const result = { status: 'NotFound' };
            cb(null, result);
        }
    });

    redisCluster.on('error', () => {
        cb('An error has occurred with the redis cluster');
    });

这是包装器类

class RedisCluster extends Redis.Cluster {
    constructor(opts) {
        super(opts);
    }

    removeKey(cacheKey) {
        return new Promise((resolve, reject) => {
            super.del(cacheKey, (err, reply) => {
                if (err) {
                    logger.error(`Failed to remove key::${cacheKey} Error response: ${err}`);
                    reject(err);
                } else {
                    logger.info(`Successfully removed key::${cacheKey} response: ${reply}`);
                    resolve();
                }
            });
        });
    }

    quitRedisCluster() {
        return new Promise((resolve, reject) => {
            if (this) {
                super.quit(() => {
                    logger.info('Quitting redis cluster connection...');
                    resolve();
                }).catch((err) => {
                    logger.error('Error closing cluster');
                    reject(err);
                });
            } else {
                logger.error('Cluster not defined');
                reject();
            }
        });
    }
}
module.exports = RedisCluster;

我无法正确注入任何依赖项(这是lambda),并且对Redis群集进行存根处理似乎不起作用,因为在加载源代码时已实例化它。 但是,我可以通过添加导出功能来替换Redis Cluster,然后再进行测试。 这很丑陋,该方法用于测试...所以我想必须有一种更好的方法来执行此操作。

这是我添加到lambda中以模拟集群的方法。 不幸的是,当装入lambda代码时,初始Cluster仍然会启动,因此我收到连接错误,乱扔了我的测试输出,尽管在注入间谍程序或存根时确实可以工作。 我不喜欢这样做,因为它会产生代码异味,并且只是为了满足测试而添加的一种方法。

exports.injectCluster = (injectedDependency) => {
    redisCluster.disconnect();
    redisCluster = injectedDependency;
};

我的测试看起来像这样。

import Promise from 'bluebird';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import Context from 'mock-ctx';
import sinon from 'sinon';
import { handler, injectCluster } from '../src';

chai.use(chaiAsPromised);

let redisClusterConstructor;
let removeKey;
let on;
let disconnect;
let get;
const ctx = new Context();

describe('lambda', () => {
    beforeEach(() => {
        removeKey = sinon.spy(() => Promise.resolve({}));
        on = sinon.spy((e, cb) => {});
        disconnect = sinon.spy(() => {});

        redisClusterConstructor = {
            removeKey,
            on,
            disconnect
         };
    });

    it('should get key in redis if key exist', () => {
        get = sinon.spy((k) => Promise.resolve('true'));
        redisClusterConstructor['get'] = get;
        injectCluster(redisClusterConstructor);
        const promise = Promise.fromCallback(cb => handler(e, ctx, cb));
        return chai.expect(promise).to.be.fulfilled.then((response) => {
            chai.assert.isTrue(get.calledOnce);
            chai.assert.isTrue(removeKey.calledOnce);
            chai.expect(response).to.deep.equal({ status: 'Registered' });
        });
    });
});

我尝试过的事情:

1:使用sinon对“类”进行存根

无法正常工作,因为javascript对象不是真正的类。 我似乎无法仅对方法进行构造,因此群集仍然最初会在结构中启动。

2:重新导入

由于测试中执行事情的顺序似乎不起作用。 加载lambda代码后,RedisCluster立即旋转。 因此,到测试实际运行时,群集已经存在,将不再使用重新导入的导入。

3:“依赖注入”

可行,但很难看,可能无法通过PR程序...

4:重写包装器类以等待连接,直到执行第一个命令

这就是我现在正在尝试的内容,我还没有完成代码以了解它是否可以工作。

我在正确的轨道上吗?...在​​Java中,这很简单,但是我一生都无法弄清楚在节点中做什么以完全模拟这种依赖关系。

我找到了解决问题的方法,尽管它不是理想的方法,但确实可以解决我先前提出的模拟依赖注入解决方案中的缺点。

我只是将包装RedisCluster类的实例更改为使用process.env.opts而不是{ host: process.env.HOST, port: process.env.PORT }实例化,其中opts只是上面使用的映射。

然后,在我的测试文件中,在加载lambda源代码之前,我加入了语句process.env.opts = { mock: "stuff" } ,以避免Redis Cluster尝试连接到不存在的Cluster实例。 然后,像往常一样使用sinon将方法存入包装类。

我希望这可以帮助别人。 它不会像eslint那样通过linter,因为eslint要求import语句位于顶部。 因此,在将源文件包含在内之前,我将该语句移至另一个js文件并包含了它。

有点hacky,但是可以用。 如果有更好的方法,请有人发出声音。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM