整合testing在Heroku爱好开发中偶尔超时

由于我们没有从help.heroku.com得到任何有用的支持,我们在这里做最后的尝试。

我们正在开发一个经典的networking应用程序,包括:

----- ----- ---- |WEB| <----> |API| <----> |DB| ----- ----- ---- 

我们目前正在与以下Heroku Dynos / Datastores合作

  • Heroku Postgres:业余爱好基础
  • Heroku API Dyno:业余爱好
  • Heroku WEB Dyno:业余爱好

技术堆栈是:

  • 运行时:nodejs(4.4.0)
  • db:postgres(9.6.1)
  • testframework:jasminejs(2.5.1)
  • 查询生成器:knexjs(0.10.0)

我们最近从自托pipedocker环境迁移到Heroku,并configuration了Herokus CIpipe道,这对于unit testing来说工作正常 – 但不是集成testing。 testing偶尔失败(平均每次同一提交的第三次testing)。 这不足以build立CI / CD。

这里是我们得到的错误信息:

 ************************************************** * Failures * ************************************************** 1) integration test collections repository create() should return AUTHENTICATION_REQUIRED if user is anonymous - Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. - Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. 2) integration test collections repository create() should return AUTHORIZATION_REQUIRED if user is not space MEMBER - Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. - Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. - Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. 3) integration test collections repository create() should return collection if user is space MEMBER - Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. - Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. - Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. 

出于testing目的,我们将knex连接池configuration为仅使用一个连接:

 export default { client: 'pg', connection: DB_URL, pool: { max: 1, min: 1 } }; 

典型的集成testing设置是:

 describe('integration test', () => { describe('collections repository create()', () => { ////////////////////////////////////////////////////////////////////// //Before the test block is execute all fixtures are loaded into the DB ////////////////////////////////////////////////////////////////////// beforeAll((callback) => { seed(path.join(__dirname, 'fixtures')) .then(callback) .catch((error) => { fail(error); callback(); }); }); //////////////////////////////////////////////////////////////////////// //Truncate all data from the DB before the next test block gets executed //////////////////////////////////////////////////////////////////////// afterAll(resetDB); it('should return AUTHENTICATION_REQUIRED if user is anonymous', (callback) => { testUtils.testAsync({ callback, catchTest: (error) => { expect(error).toEqual(jasmine.any(repositoryErrors.AUTHENTICATION_REQUIRED)); }, runAsync: create({ props: { space: { id: 1 } } }) }); }); it('should return AUTHORIZATION_REQUIRED if user is not space MEMBER', (callback) => { testUtils.testAsync({ callback, catchTest: (error) => { expect(error).toEqual(jasmine.any(repositoryErrors.AUTHORIZATION_REQUIRED)); }, runAsync: create({ props: { space: { id: 1 } }, userId: 1 }) }); }); it('should return collection if user is space MEMBER', (callback) => { testUtils.testAsync({ callback, runAsync: create({ props: { space: { id: 1 } }, userId: 2 }), thenTest: (outcome) => { expect(outcome).toEqual({ id: '1' }); } }); }); ... 

种子:

 const tableOrder = [ 'users', 'guidelines', 'collections', 'spaces', 'details', 'files', 'guidelinesAuthors', 'collectionsUsers', 'spacesUsers', 'guidelinesCollections', 'spacesCollections', 'guidelinesDetails', 'guidelinesFiles', 'comments', 'commentsReplies', 'notifications' ]; export default function seed(path) { return db.raw('BEGIN;') .then(() => { return tableOrder.reduce((promise, table) => { return promise .then(() => slurp({ path, table })) .then(() => { updateIdSequence(table) .catch((error) => { // eslint-disable-next-line no-console console.log( `Updating id sequence for table '${table}' failed! Error: `, error.message ); }); }); }, Promise.resolve()).then(() => db.raw('COMMIT;')); }) .catch((error) => { // eslint-disable-next-line no-console console.log('SEED DATA FAILED', error); return db.raw('ROLLBACK;'); }); } ... 

resetDB:

 export default function resetDB(callback) { const sql = 'BEGIN; ' + 'SELECT truncateAllData(); ' + 'SELECT restartSequences(); ' + 'COMMIT;'; return db.raw(sql) .then(callback) .catch((error) => { // eslint-disable-next-line no-console console.log('TRUNCATE TABLES FAILED', error); return db.raw('ROLLBACK;'); }); } 

到目前为止,这些testing已经在本地机器(Linux / Mac)和Codeship上运行,没有任何问题。

经过近两周的努力,我们在这个问题上取得了零进展。 我看不出这个configuration有什么问题,我开始相信Heroku对数据存储有严重的问题。

有没有人在Heroku上遇到类似的问题? 任何想法,我们可以尝试其他方式来获得这项工作?