testing business-logic-in-dsls

56
Testing Business Logic Using DSLs in Clojure Mayank Jain Test Engineer

Upload: mayank-jain

Post on 17-Aug-2015

107 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Testing business-logic-in-dsls

Testing Business Logic Using DSLs in Clojure

Mayank Jain Test Engineer

Page 2: Testing business-logic-in-dsls

A TALK, IN 6 PARTS

1. What is the problem?

2. Real World Example.

3. Demo of the actual code.

4. Other features tested using same ideas.

5. Advantages/Disadvantages of writing DSLs for testing.

6. QA.

Page 3: Testing business-logic-in-dsls

WHAT IS THE PROBLEM?

Testing real world stateful business logic is hard

Page 4: Testing business-logic-in-dsls

Microwave Oven State Machine

Business logic is Stateful

Page 5: Testing business-logic-in-dsls

Microwave Oven State Machine

Large number transition states

Page 6: Testing business-logic-in-dsls

Difficult to enumerate all possible cases

Microwave Oven State Machine

Page 7: Testing business-logic-in-dsls

Tests become unreadable

Microwave Oven State Machine

Page 8: Testing business-logic-in-dsls

HELPSHIFT

• Embeddable support desk for native apps

• Main Features:

• Frequently Asked Questions which customers can search

• File issues/tickets from within the app

Page 9: Testing business-logic-in-dsls

Helpshift

Supercell

Clash of Clans

Gaana

General Billing

Domain

App

Section

FAQ 1 FAQ 2FAQs

Boom Beach Gaana App

Translations English Content Hindi Content

….

….

….

….

….

Top Down View of FAQs

Page 10: Testing business-logic-in-dsls

Example of Gaana FAQ Page

Customers can search FAQs

Available FAQs FAQ Sections

Domain

App

Page 11: Testing business-logic-in-dsls

FAQ Title

FAQ Body

FAQ is Visible?

Page 12: Testing business-logic-in-dsls

PROBLEM

You cannot share FAQs across apps.

Big Customers have multiple apps which have same FAQ translations content example “Privacy Policy”

Page 13: Testing business-logic-in-dsls

FEATURE: LINKED FAQS

Page 14: Testing business-logic-in-dsls

{:published? false :id “faq-id-1” :app_id “app-1” :section_id “section-1” :translations {:en {:published? false :stags [] :body “Privacy Body” :title “Privacy Title”}} :linked_faq_ids [“faq-id-2”] :publish_id “1”}

App-1

{:published? false :id “faq-id-2” :app_id “app-2” :section_id “section-2” :translations {:en {:published? false :stags [] :body “Privacy Body" :title “Privacy Title"}} :linked_faq_ids [“faq-id-1”] :publish_id “2”}

App-2

faq 1

(Sync faq-1 to App-2)General

faq 2

General

Page 15: Testing business-logic-in-dsls

{:published? false :id “faq-id-1” :app_id “app-1” :section_id “section-1” :translations {:en {:published? false :stags [] :body “Update Body” :title “Privacy Title”}} :linked_faq_ids [“faq-id-2”] :publish_id “1”}

{:published? false :id “faq-id-2” :app_id “app-2” :section_id “section-2” :translations {:en {:published? false :stags [] :body “Privacy Body" :title “Privacy Title"}} :linked_faq_ids [“faq-id-1”] :publish_id “2”}

faq 1Sync

faq 2

Update FAQ 1’s Body

Page 16: Testing business-logic-in-dsls

{:published? false :id “faq-id-1” :app_id “app-1” :section_id “section-1” :translations {:en {:published? false :stags [] :body “Update Body” :title “Privacy Title”}} :linked_faq_ids [“faq-id-2”] :publish_id “1”}

{:published? false :id “faq-id-2” :app_id “app-2” :section_id “section-2” :translations {:en {:published? false :stags [] :body “Update Body" :title “Privacy Title"}} :linked_faq_ids [“faq-id-1”] :publish_id “2”}

faq 1Sync

faq 2

Update FAQ 1’s Body

Page 17: Testing business-logic-in-dsls

EXAMPLE TEST CASE

Page 18: Testing business-logic-in-dsls

1. Add 1st App with only English languages.

2. Add 2nd App with only English languages

3. Add 1st FAQ under 1st App

4. Link 1st FAQ to 2nd App to create 2nd FAQ

5. Check -> translations of 2nd FAQ == 1st FAQ

6. Update English title of FAQ-1

7. Check -> translations of 2nd FAQ == 1st FAQ

8. Delete FAQ-1

9. Check if 1st FAQ is deleted in DB

10.Assert 2nd FAQ should remain as it is in database.

Simulation Verification

Page 19: Testing business-logic-in-dsls

PARTS OF EACH ACTION

“Add APP-1 with only English language”

1. Type of Action Add an App

2. Names App-1

3. Arguments English Language

4. Expected Result As per Spec/Modal of the system

5. Actual Result Database

Page 20: Testing business-logic-in-dsls

EXPRESS ACTIONS AS CLOJURE DATA

“Add app APP-1 with English language”:add-app :app-1 {:langs-config [:en]}

Page 21: Testing business-logic-in-dsls

ONE UNIT OF ACTION

[:add-app :app-1 {:langs-config [:en]}]

Page 22: Testing business-logic-in-dsls

SERIES OF ACTIONS[[:add-app :app-1 {:langs-config [:en]]

[:add-app :app-2 {:langs-config nil}]

[:add-faq :faq-1 {:app-var :app-1 ...}]

[:link-faq :faq-2 {:faq-var :faq-1 :app-var :app-2 ...}]

[:update-faq nil {:app-var :app-1 :faq var :faq-1 ...}]

[:update-faq nil {:app-var :app-2 :faq-var :faq-2 ...}]

[:delete-faq nil {:faq-var :faq-1 ...}]]

Page 23: Testing business-logic-in-dsls

REDUCE ON ACTIONS

Reduce

{}

[:add-app :app-1 {:langs-config nil}])

{:env {:apps {:app-1 {…}}} :result [{result-1…}]} …..}

Reduce

[:add-app :app-2 {:langs-config nil}])

{:env {:apps {:app-1 {…} :app-2 {…}}} :result [{result-1…} {result-2…}]} …..}

{:env {:apps {:app-1 {…}}} :result [{result-1…}]} …..}

And so on…

Page 24: Testing business-logic-in-dsls

RESULT HASH-MAP COMPRISES OF

{:env {:apps {:app-1 {:app-id “970"} :app-2 {:app-id “142”}}} :faqs {:faq-1 {:faq-id “112”}}}} :result [{:action-type :add-faq :expected {….} :actual {….}} {…more}] “112” {…state…} …more}

Environment (:env) - Contains bindings of vars

Page 25: Testing business-logic-in-dsls

RESULT HASH-MAP COMPRISES OF

{:env {:apps {:app-1 {:app-id “970"} :app-2 {:app-id “142”}}} :faqs {:faq-1 {:faq-id “112”}}}} :result [{:action-type :add-faq :expected {….} :actual {….}} {…more}] “112” {…state…} …more}

Result (:result) - Contains Actual And Expected Result

Page 26: Testing business-logic-in-dsls

RESULT HASH-MAP COMPRISES OF

{:env {:apps {:app-1 {:app-id “970"} :app-2 {:app-id “142”}}} :faqs {:faq-1 {:faq-id “112”}}}} :result [{:action-type :add-faq :expected {….} :actual {….}} {…more}] “112” {…state…} …more}

Current Generated Expected State

Page 27: Testing business-logic-in-dsls

Dispatch On Action Type

Update The Variables in arguments to its

bindings

Call args with relevant function

Bind the result to the given var in Global Data

Store Expected current state

Store Actual current database state

What happens

inside the

reducer?

Page 28: Testing business-logic-in-dsls

Dispatch On Action Type

Update The Variables in arguments to its

bindings

Call args with relevant function

Bind the result to the given var in Global Data

Store Expected current state

Store Actual current database state

[:add-app :app-1 {:langs-config nil}])

[:add-app :app-1 {:langs-config nil}])

(add-app {:langs-config nil})

{:env {:apps {:app-1 {:app-id "970"}}}}

Page 29: Testing business-logic-in-dsls

Dispatch On Action Type

Update The Variables in arguments to its

bindings

Call args with relevant function

Bind the result to the given var in Global Data

Store Expected current state

Store Actual current database state

[:add-app :app-2 {:langs-config nil}])

[:add-app :app-2 {:langs-config nil}])

(add-app {:langs-config nil})

{:env {:apps {:app-1 {:app-id “970"} :app-2 {:app-id “142"}}}}

Page 30: Testing business-logic-in-dsls

Dispatch On Action Type

Update The Variables in arguments to its

bindings

[:add-faq :faq-1 {:app-var :app-1 ….}])

{:env {:apps {:app-1 {:app-id “970"} :app-2 {:app-id “142”}}}

Page 31: Testing business-logic-in-dsls

Dispatch On Action Type

Update The Variables in arguments to its

bindings

[:add-faq :faq-1 {:app-var :app-1 ….}])

[:add-faq :faq-1 {:app-id “970” …}])

{:env {:apps {:app-1 {:app-id “970"} :app-2 {:app-id “142”}}}

Page 32: Testing business-logic-in-dsls

Dispatch On Action Type

Update The Variables in arguments to its

bindings

Call args with relevant function

Bind the result to the given var in Global Data

[:add-faq :faq-1 {:app-var :app-1 ….}])

[:add-faq :faq-1 {:app-id “970” …}])

(add-faq {:app-id “970” ….})

Page 33: Testing business-logic-in-dsls

Dispatch On Action Type

Update The Variables in arguments to its

bindings

Call args with relevant function

Bind the result to the given var in Global Data

[:add-faq :faq-1 {:app-var :app-1 ….}])

[:add-faq :faq-1 {:app-id “970” …}])

(add-faq {:app-id “970” ….}){:env {:apps {:app-1 {:app-id “970"} :app-2 {:app-id “142”}} :faqs {:faq-1 {:faq-id “112”}}}}

Page 34: Testing business-logic-in-dsls

Dispatch On Action Type

Update The Variables in arguments to its

bindings

Call args with relevant function

Bind the result to the given var in Global Data

Store Expected current state

[:add-faq :faq-1 {:app-var :app-1 ….}])

[:add-faq :faq-1 {:app-id “970” …}])

(add-faq {:app-id “970” ….}){:env {:apps {:app-1 {:app-id “970"} :app-2 {:app-id “142”}} :faqs {:faq-1 {:faq-id “112”}}}}{:env {:apps {:app-1 {:app-id “970"} :app-2 {:app-id “142”}}} :faqs {:faq-1 {:faq-id “112”}}}} :result [{:action-type :add-faq :expected {….}}

Page 35: Testing business-logic-in-dsls

Dispatch On Action Type

Update The Variables in arguments to its

bindings

Call args with relevant function

Bind the result to the given var in Global Data

Store Expected current state

Store Actual current database state

[:add-faq :faq-1 {:app-var :app-1 ….}])

[:add-faq :faq-1 {:app-id “970” …}])

(add-faq {:app-id “970” ….}){:env {:apps {:app-1 {:app-id “970"} :app-2 {:app-id “142”}} :faqs {:faq-1 {:faq-id “112”}}}}{:env {:apps {:app-1 {:app-id “970"} :app-2 {:app-id “142”}}} :faqs {:faq-1 {:faq-id “112”}}}} :result [{:action-type :add-faq :expected {….} :actual {….}}

Page 36: Testing business-logic-in-dsls

VERIFICATION:COMPARE EXPECTED VS ACTUAL FOR

EACH STEP

Page 37: Testing business-logic-in-dsls

COMPARE RESULT FORFIRST ACTION

{"112" {:translations {:en {:published? false, :stags [], :body "Temp body", :title “Title 1"}}, :linked-faq-ids #{}}}

Actual Data in DBExpected Data

from my Generated result

{"112" {:translations {:en {:published? false, :stags [], :body "Temp body", :title “Title 1"}}, :linked-faq-ids #{}}}

==PASS

Page 38: Testing business-logic-in-dsls

{"112" {:translations {:en {:published? false, :stags [], :body "Temp body", :title "Title 1"}}, :linked-faq-ids #{}}}

Actual Data in DBExpected Data

from my Generated result

{"112" {:translations {:en {:published? false, :stags [], :body "Temp body", :title "Title 2"}}, :linked-faq-ids #{}}}

==FAIL

COMPARE RESULT FORSECOND ACTION

Page 39: Testing business-logic-in-dsls

DEMO TIME

Page 40: Testing business-logic-in-dsls

APPROACHES TO VERIFY

Actions : HardcodedExpected Output : Hardcoded

Actions : SimulatedExpected Output : Hardcoded

Actions : SimulatedExpected Output : Generated

Actions : GeneratedExpected Output : Generated

Page 41: Testing business-logic-in-dsls

Hardcoded Actions, Hardcoded Expected Output

ADVANTAGES DISADVANTAGES

Requires no extra knowledge to understand

Very cumbersome to enumerate

Anyone can add/edit tests Modification is hard

False negatives are not possible

Page 42: Testing business-logic-in-dsls

Simulate list of Actions, Hardcoded Final Expected

Output

ADVANTAGES DISADVANTAGES

Easy to enumerateRequires knowledge of

the DSL

Very readableMaintenance overhead of

DSL

Shareable with dev to simulate bugs

Does not check intermediate state

False negatives are not possible

Expected output may have data which is

available only at runtime like faq-ids

Page 43: Testing business-logic-in-dsls

Simulate list of actions, Generate Expected Output

ADVANTAGES DISADVANTAGES

Easy to enumerateRequires knowledge of

the DSL

Very readableMaintenance overhead of

DSL

Shareable with dev to simulate bugs

Maintenance overhead of Expected Modal

Checks intermediate stateFalse negatives are

possibleRuntime data is available

like faq-ids

Page 44: Testing business-logic-in-dsls

Generate Actions, Generate Expected Output

ADVANTAGES DISADVANTAGES

Possible to generate large number of tests

Requires knowledge of the DSL

Possible to Shrink failed test case using test.check library

Maintenance overhead of DSL

Shareable with dev to simulate bugs

Maintenance overhead of Expected Modal

Checks intermediate stateFalse negatives are

possible

Maintenance overhead of generative code for list of

actions.

Page 45: Testing business-logic-in-dsls

Generate Actions, Generate Expected Output

Feature - Issue Audit Trail

Page 46: Testing business-logic-in-dsls

Issue Audit Trail• Maintains a log of

• Who Took an action

• What Action they took

Page 47: Testing business-logic-in-dsls

Who - Types of Users

App User Support User

Agents

Admins

Helpshift

Page 48: Testing business-logic-in-dsls

What - Types of Actions

App User

• Create Issue

• Reply Issue

• etc…

Admin User

• Create Issue

• Reply Issue

• Resolve Issue

• Edit Tags

• etc…

Agent User

• Create Issue

• Reply Issue

• Resolve Issue

• Edit Tags

• etc…

Page 49: Testing business-logic-in-dsls

App User

Admin“Mayank

Agent“Agent-2”

Example Issue

Page 50: Testing business-logic-in-dsls

Example Issue

Issue Audit Logs

Page 51: Testing business-logic-in-dsls

DEMO - ISSUE AUDIT TRAIL

Generate Actions, Generate Expected Output

Page 52: Testing business-logic-in-dsls

ADVANTAGES OF WRITING DSL

Discovering very hard to find bugs, for example we found:

Duplicate issue messages being rendered only if the number of messages were "just right".

Finding ordering bugs.

Increase in developer/tester productivity

Page 53: Testing business-logic-in-dsls

DISADVANTAGES OF WRITING DSL

Cost of maintaining DSLs is high.

If your DSL is just data being evaluated at run time, changes in feature code will not throw any compile time errors like function parameters being changed in DSL code.

You have to educate your team members to learn your specific DSL for that feature to be able to understand the tests.

Page 54: Testing business-logic-in-dsls

CONCLUSION

DSL can be used as a workflow for writing tests.

Separating simulation vs verification of tests.

A step towards ability to generate tests instead of writing them.

Page 55: Testing business-logic-in-dsls

Further Resources• Clojure Made Simple - Rich Hickey

• Growing a DSL with Clojure

• Jeanine Adkisson - Design and Prototype a Language In Clojure

• John Hughes - Testing the Hard Stuff and Staying Sane

• Reid Draper - Powerful Testing with test.check

• Clojure Tutorials on DSLs with Tim Baldridge (Paid)

Page 56: Testing business-logic-in-dsls

Any Questions?@firesofmay

facebook.com/firesofmay/

[email protected]