Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

Bryan Liu / LINE TW Automation Engineer Improve Automated Acceptance Tests through Test Isolations

Slide 3

Slide 3 text

Containerization and Cloud Native › Streamline delivery and deployment › Simplify environments management › Fasten delivery cycle

Slide 4

Slide 4 text

What’s Acceptance Test Production Acceptance Test Stage User Acceptance Test Commit Stage Capacity Testing Confidence ↑ Shift (Acceptance) Tests Left & Right

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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) )}

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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') }) })

Slide 13

Slide 13 text

Summary › Achieve level I & II isolations › Show test intentions (important !) › Improve readability & maintainability › EASY parallel test execution !! TestContext & Default Object

Slide 14

Slide 14 text

Productivity "Productivity is not everything, but in the long run, it is almost everything." ~ Paul Krugman

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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 › . . .

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Monitoring & Testing in Production Shift-Right Testing

Slide 19

Slide 19 text

API Integration - Contract Testing

Slide 20

Slide 20 text

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 } })

Slide 21

Slide 21 text

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() }) })

Slide 22

Slide 22 text

PACT Integration Flow

Slide 23

Slide 23 text

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)

Slide 24

Slide 24 text

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') }) })

Slide 25

Slide 25 text

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') }) })

Slide 26

Slide 26 text

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') }) })

Slide 27

Slide 27 text

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!

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

Thank you