Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Improve Automated Acceptance Tests through Test...

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for Bryan Liu Bryan Liu
August 14, 2023

Improve Automated Acceptance Tests through Test Isolations

雲端與容器的技術的確讓快速迭代更容易達成,但在持續集成裡的自動化驗收測試要如何一起來搭配呢?讓我們從測試隔離的角度來看,如何讓測試向左及向右移來讓自動化測試能更穩定且更有效率!

Avatar for Bryan Liu

Bryan Liu

August 14, 2023

More Decks by Bryan Liu

Other Decks in Programming

Transcript

  1. Containerization and Cloud Native › Streamline delivery and deployment ›

    Simplify environments management › Fasten delivery cycle
  2. What’s Acceptance Test Production Acceptance Test Stage User Acceptance Test

    Commit Stage Capacity Testing Confidence ↑ Shift (Acceptance) Tests Left & Right
  3. What’s Hard on Automated Testing The Problems in Fast Delivery

    Cycle › Flaky tests due to timing issues, logical errors › Infrastructure, network, library versions › Unreliable test data Stability Ownership › Cross components, services and systems › Ownership relies on readability and maintainability Run slow › System and environment provision, cleanups takes time › Integrated and end-to-end tests run slow › Test cases increase quickly
  4. Why Test Isolation “「acceptance testing should be focused on providing

    a controllable environment … Integrating with real external systems removes our ability to do this.」” ~ Jaz Humble & David Farley: Continuous Delivery
  5. Why Test Isolation “「When the isolation of your acceptance test

    is good, another possibility to speed things up presents itself: running the tests in parallel .」” ~ Jaz Humble & David Farley: Continuous Delivery
  6. Three Levels of Test Isolation › NO dependency between tests

    and their results › NOT depends on the running order L2: Isolating test cases from each other L3: Isolating the system under test › Leveraging tools like: mock, stub, service virtualization tools › TestContainers: provision runtime dependencies with containers L1: Isolating test cases from themselves › Each test case is repeatable (run multiple times) › Run multiple times against the same env
  7. TestContext & Default Object const firstProduct = new Product('Test Product

    NO1') const secondProduct = new Product('Test Product NO2') let testContext = new TestContext() testContext.products.push(firstProduct, secondProduct) before(function() { testContext.products.forEach(prod => { cy.task('createDocument', {collectionName: ‘products’, …}) }) }) it('can checkout all items successfully', function() { // add to cart & do checkout cy.contains('div', testContext.products[0].title).click() cy.contains('div', testContext.products[1].title).click() cy.checkout(testContext.userName) )}
  8. Default Object class Product { constructor(name) { // An unique

    alias name this.title = `${name}_${new ShortUniqueId.randomUUID6}`, this.description = `Test description product ${this.title}! ...` this.imagePath = 'images/test_the_test.png', this.price = 22, // assign default values this.stock = 50 } } › Find 'functional entities’ › Shopping App -> create new product › Event App -> create new campaign › GitHub App -> create new account & repo › Give alias name for each entity
  9. TestContext class TestContext { constructor() { this.products = [], //

    to store test objects, status this.userName = 'defaultUser', // assign default values this.userEmail = '[email protected]', this.userPassword = 'test1234' } } › TextContext to store test intermediate status and data › TC create / teardown it's own entities › Easy parallel execution
  10. Show Test Intentions context('Test purchasing when short of inventory', ()

    => { let outOfStock = new Product('No Enough Inventory') outOfStock.stock = 0 // clearly shows what you want to TEST! let testContext = new TestContext() testContext.products.push(outOfStock) testContext.userName = 'ValidUser' // perhaps other variations testContext.userPassword = 'Wrong Password' it('add to cart should shows error message', function () { // add to cart ... cy.contains('div', testContext.products[0].title).click() cy.get('div#success').contains('short of inventory') }) })
  11. Summary › Achieve level I & II isolations › Show

    test intentions (important !) › Improve readability & maintainability › EASY parallel test execution !! TestContext & Default Object
  12. Productivity is Everything What kills our productivity? › Clarity 條理分明

    (roles, audits, processes) › Accountability 專⼈專責 (someone to blame) › Measurement 衡量指標 (on personal / team?) TED Talks: How Too Many Rules at Work Keep You From Getting Things Done
  13. Productivity is Everything What should we do? › Get rid

    of rigid spec, details verification › Embrace fault tolerance, fuzzy, flexibility What DevOps tells us? › Deliver, recover and feedback fast › . . .
  14. API Test in Acceptance Test Accountability? APIs are way too

    important, so we assign a QA to verify that … › API is about interaction › Not just status code & response body › Contract can be verified in pre-commit phase (no need integrated env) › Test in production, E2E and integrated testing will verify the runtime integrity › Guarded by monitoring in production
  15. PACT Demo - Consumer provider.addInteraction({ state: 'single prod', uponReceiving: 'a

    request for JSON data', withRequest: { method: 'GET', path: '/prod/details', query: { t: 'LINE POP2' } }, willRespondWith: { status: 200, headers: {'Content-Type': 'application/json; charset=utf-8'}, body: EXPECTED_BODY } })
  16. PACT Demo - Consumer it('assert the JSON payload from the

    provider', done => { const shopClient = new ShopClient( ‘http://localhost:4000' // PACT runs stub server automatically ) const verificationPromise = shopClient.getOneProd('LINE POP2’) expect(verificationPromise) .to.eventually.have.property('title','LINE POP2’) expect(verificationPromise) .to.eventually.have.property('price') .notify(done) }) it('should validate the interactions and create a contract', () => { return provider.verify() }) })
  17. TestContainers Increase Confidence of Acceptance Test Component / Service Level

    Acceptance Test › Easier to model behaviors (inputs & outputs) › With much higher confidence (vs. mock / in-memory DB)
  18. Demo - TestContainers const { GenericContainer } = require('testcontainers') describe('RedisCache',

    () => { before(async () => { container = await new GenericContainer(‘redis’) .withExposedPorts(6379).start() redisClient = redis.createClient(container.getMappedPort(6379)) }) after(async () => { await container.stop() }) it('should cache a value', async () => { await redisClient.set('key', ‘value’) // perform test scenario & assert result chai.expect(await redisClient.get('key')).eq('value') }) })
  19. Demo - TestContainers const { GenericContainer } = require('testcontainers') describe('RedisCache',

    () => { before(async () => { container = await new GenericContainer(‘redis’) .withExposedPorts(6379).start() redisClient = redis.createClient(container.getMappedPort(6379)) }) after(async () => { await container.stop() }) it('should cache a value', async () => { await redisClient.set('key', ‘value’) // perform test scenario & assert result chai.expect(await redisClient.get('key')).eq('value') }) })
  20. Demo - TestContainers const { GenericContainer } = require('testcontainers') describe('RedisCache',

    () => { before(async () => { container = await new GenericContainer(‘redis’) .withExposedPorts(6379).start() redisClient = redis.createClient(container.getMappedPort(6379)) }) after(async () => { await container.stop() }) it('should cache a value', async () => { await redisClient.set('key', ‘value’) // perform test scenario & assert result chai.expect(await redisClient.get('key')).eq('value') }) })
  21. TestContainers Runs Fast with More Confidence › Start a MongoDB

    container › Publish testing records in DB › Invoke the API call on Express › Assert responses › Tear down container › All in 52 ms with test coverage!
  22. Summary › Make test isolation part of test strategies ›

    Write more integrated testing › Test less with higher confidence › Leverage tools to achieve isolations › Shift-left & shift-right of tests Test with Isolations in Mind