SlideShare une entreprise Scribd logo
1  sur  87
Télécharger pour lire hors ligne
The Evolution of
Development Testing
Overview
•

Automating

•

Testing Categories + Scope

•

Development Testing

•

Principles, Practices + Tool Support
Overview
•

Automating
Automating
•

We automate “stuff” for a living

•

Developer/Programmer/Engineer/Automator

•

When we’re doing things manually we’re usually missing
an opportunity to automate
Automating
•

The stuff we automate might be
•

validation of a web form

•

reporting of a message parsing error

•

calculation of an average execution price

•

execution + status reporting of tests for all of the above
Automating
•

That automation might look like
•

choose validation rules, implement, enforce, report

•

log parsing error, automate monitoring/reporting of error

•

define calculation rules, implement, provide result

•

define inputs, context + expectations, execute scenario,
determine + report status
Automating
•

Manual solutions might look like
•

review user errors later in workflow e.g.“back office” manual
activity

•

message loss caused an incident, manually track it down

•

export raw data to csv, load into Excel, apply formula

•

run UI functional scenarios that use/execute the code in mind
•

fire up debugger, step through mis-behaving code
•

add detailed logging, monitor logs for hints
Automating
•

We automate “stuff” for a living

•

Developer/Programmer/Engineer/Automator

•

When we’re doing things manually we’re usually missing
an opportunity to automate
Overview
•

Automating

•

Testing Categories + Scope
Test Categorisation
•

Useful concept - up to a point

•

Blurred lines + Overloaded terminology

•

Difficulties conversing about testing
Test Categorisation
•

Unit

•

Build Acceptance

•

Component

•

System

•

API

•

End-to-End

•

Acceptance

•

User Acceptance

•

Integration

•

Non-functional

•

Functional
Test Categorisation
Test Categorisation
Test Categorisation
•

Useful concept - up to a point

•

Blurred lines + Overloaded terminology

•

Difficulties conversing about testing
Test Categorisation
Test Categorisation
Overview
•

Automating

•

Testing Categories + Scope

•

Development Testing
Development Testing
•

What it is

•

What it should not be

•

How it has evolved
What it is
•

A Development practice first and foremost

•

White box / Black box / Grey box

•

Has Quality aims

•

Has Efficiency aims
JUnit
•

Test automation framework of choice for most Java
developers

•

Synonymous with Unit testing

•

Widely used for testing beyond the Unit scope

•

Ported to many languages - generically known as xUnit
JUnit
“Never in the field of software development have so
many owed so much to so few lines of code”
Martin Fowler
(Development big-wig)
What it is
•

A Development practice first and foremost

•

White box / Black box / Grey box

•

Has Quality aims

•

Has Efficiency aims
What it is
•

Can take a technical perspective on scenarios

•

Can take a user functionality perspective

•

Exercises precise control over the test context

•

Used + extended by Dev during initial and future coding
What it is
•

Often used to discover and refine solution design

•

Often leaves fine-grained regression coverage safety net

•

Enables instant feedback on failures due to future
enhancements and refactorings
What it is not
•

Automated full System tests a.k.a E2E, System Integration

•

Zero control over the state of the system (the test context)

•

Test scope coupled to other external systems, reference
data quality, env availability

•

Heavily reliant on driving a UI to execute many finegrained scenarios
Its Evolution
•

OOP - slow evolution

•

Automated Developer Testing around a lot less time

•

A lot less mainstream than OOP

•

Growing body of knowledge / experience available
Overview
•

Automating

•

Testing Categories + Scope

•

Development Testing

•

Principles, Practices + Tool Support
Principles, Practices + Tool Support
•

Structural Principles / Characteristics

•

Qualities of a “good” test / spec

•

Practices + Tools for improving test qualities
Test structure
•

Arrange, Act, Assert

•

Specify Inputs

•

Given, When, Then

•

Specify the state of the System

•

Specify the Event

•

Specify the Expectations
Test structure
•

Naming conventions + structure
@Test	
public
//
	
//
	
//
}	

void methodUnderTest_GivenABC_ThenExpectXYZ() {	
given	
when	
then	

@Test	
public void methodUnderTest_GivenInputsABC_AndSystemStateDEF_ThenExpectXYZ() {	
// given - inputs	
	
// given - system state	
!

// when	
	
// then	
}
Test structure
•

IDE assistance for consistency + evolve conventions
Principles, Practices + Tool Support
•

Structural Principles / Characteristics

•

Qualities of a “good” test / spec
“Any fool can write code
that a computer can understand”
!

Martin Fowler
Test Qualities
•

Readability
•

What is it? Why does it matter?
Test Qualities
•

Readability == …?

•

Code that you can understand with ease

•

Language affects Thought

•

Thought affects Action
Test Qualities
•

Tools can directly address Readability qualities

•

Domain Specific Languages = a fancy term
•

•

JUnit, Hamcrest, FEST, JMock, Mockito, Gherkin

Language -> Thought -> Action
“Test automation is a first class software
engineering problem”
!

Brian Marick?
Test Qualities
•

Maintainability
•

What is it? Why does it matter?
Test Qualities
•

Maintainability == …?

•

To change, evolve with ease

•

Keeping the cost of change down
Test Qualities
•

They can be context sensitive. Many are not.

•

You may opt to sacrifice:
•
•

Readability for Maintainability

•
•

DRY Principle for Readability

Maintainability for Obviousness

Expect debate
Principles, Practices + Tool Support
•

Structural Principles / Characteristics

•

Qualities of a “good” test / spec

•

Practices + Tools for improving test qualities
Specifying Expectations
•

Stating Expectations / Asking Questions

•

From Computer dialect ——> Human dialect
// then	
assertTrue(trader.getPermissions().isEmpty());
assertThat(trader.getPermissions().isEmpty(), is(true));
assertThat(trader.getPermissions()).isEmpty();
assertThat(trader).hasNoPermissions();

//
//
//
//
//
//
//

Computer dialect	
.	
.	
.	
.	
.	
Human dialect
Specifying Expectations
•

Stating Expectations / Asking Questions

•

From Computer dialect ——> Human dialect
// Using Basic JUnit	
assertEquals(orders.size(), 2);	
Iterator<Order> itr = orders.iterator();	
assertEquals(itr.next(), order3);	
assertEquals(itr.next(), order4);	
// Using Hamcrest	
assertThat(orders, hasItems(order3, order4));	
assertThat(orders.size(), is(2));	
// Using FEST Assertions	
assertThat(orders).containsOnly(order3, order4);	
// Using FEST Assertions (chained assertions)	
assertThat(orders).containsOnly(order3, order4)	
.containsSequence(order3, order4);
Specifying Expectations
•

Easier + Faster to write expressive statements

•

Faster to read + review

•

Easier to maintain

•

Encourages other better practices
Specifying Inputs
•

Trivial example. Test with very narrow scope
•

e.g.Input = Single Object
@Test	
public void getPermissions_GivenNewUser_ThenReturnsNoPermissions() {	
// given	
Trader trader = new Trader();	
// when + then	
assertThat(trader.getPermissions()).isEmpty();	
}
Specifying Inputs
•

Non-trivial example. Test with broader scope
•

e.g. Input = Object Graph
•

i.e. objects linked to objects
@Test public void 	
getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v1() {	
// given - inputs	
Trader trader = new Trader();	
TradingAccount tradingAcct = new TradingAccount(new TradingFirm("TraderzRUs", "TRDRZ"), 	
new ClearingFirm("TooBig2Fail", "2BIG2F"), ACTIVE);	
trader.setPermissions(Arrays.asList(new Permission(tradingAcct)));	
String goldIsinCode = "COF24680";	
// given - system state	
Future oil3MnthFuture = new Future("OIL.3MNTH", new ISIN("OIL3M0123"));	
Future oil6MnthFuture = new Future("OIL.6MNTH", new ISIN("OIL6M0456"));	
Future goldFuture = new Future("GLD.3MNTH", new ISIN(goldIsinCode));	
String orderId = "ordId";	
Order order1 = new Order(orderId + 1, tradingAcct, oil3MnthFuture, 1000, 2500);	
Order order2 = new Order(orderId + 2, tradingAcct, oil6MnthFuture, 1500, 2600);	
Order order3 = new Order(orderId + 3, tradingAcct, goldFuture, 200, 4300);	
Order order4 = new Order(orderId + 4, tradingAcct, goldFuture, 150, 4300);	
OrdersDAO orderDAO = new OrdersDAOInMemory(order1, order2, order3, order4);	
OrderSearchService orderSearchService = new OrderSearchServiceImpl(orderDAO);	
// when	
List<Order> orders = orderSearchService.getOrders(trader, tradingAcct, goldIsinCode);	
// then	
assertThat(orders).containsOnly(order3, order4);	
}
Specifying Inputs
•

Consider many non-trivial tests, with “raw” setup
•

Impact of changing one setter or constructor

•

Quantity of code there is to read, understand, change
Specifying Inputs - Techniques
•

Helper methods

•

Object Mother Pattern

•

Test Data Builder Pattern
@Test public void 	
getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v1() {	
// given - inputs	
Trader trader = new Trader();	
TradingAccount tradingAcct = new TradingAccount(new TradingFirm("TraderzRUs", "TRDRZ"), 	
new ClearingFirm("TooBig2Fail", "2BIG2F"), ACTIVE);	
trader.setPermissions(Arrays.asList(new Permission(tradingAcct)));	
String goldIsinCode = "COF24680";	
// given - system state	
Future oil3MnthFuture = new Future("OIL.3MNTH", new ISIN("OIL3M0123"));	
Future oil6MnthFuture = new Future("OIL.6MNTH", new ISIN("OIL6M0456"));	
Future goldFuture = new Future("GLD.3MNTH", new ISIN(goldIsinCode));	
String orderId = "ordId";	
Order order1 = new Order(orderId + 1, tradingAcct, oil3MnthFuture, 1000, 2500);	
Order order2 = new Order(orderId + 2, tradingAcct, oil6MnthFuture, 1500, 2600);	
Order order3 = new Order(orderId + 3, tradingAcct, goldFuture, 200, 4300);	
Order order4 = new Order(orderId + 4, tradingAcct, goldFuture, 150, 4300);	
OrdersDAO orderDAO = new OrdersDAOInMemory(order1, order2, order3, order4);	
OrderSearchService orderSearchService = new OrderSearchServiceImpl(orderDAO);	
// when	
List<Order> orders = orderSearchService.getOrders(trader, tradingAcct, goldIsinCode);	
// then	
assertThat(orders).containsOnly(order3, order4);	
}
@Test public void // Using helpers	
getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v2() {	
// given - inputs	
String accountCode = "TRDRZ";	
String goldIsinCode = "GLD24680";	
TradingAccount tradingAcct = createTradingAccount(accountCode);	
Trader trader = createTraderWithPermissionsFor(tradingAcct);	
// given - system state	
Future oil3MnthFuture = createFuture("OIL.3MNTH", "OIL3M0123");	
Future oil6MnthFuture = createFuture("OIL.6MNTH", "OIL6M0456");	
Future goldFuture = createFuture("GLD.3MNTH", goldIsinCode);	
Order order1 = createOrder(accountCode, oil3MnthFuture, qty(1000), price(2500));	
Order order2 = createOrder(accountCode, oil6MnthFuture, qty(1500), price(2600));	
Order order3 = createOrder(accountCode, goldFuture, qty(200), price(4300));	
Order order4 = createOrder(accountCode, goldFuture, qty(150), price(4300));	
OrderSearchService orderSearchService = createOrderService(	
createOrdersDAO(order1, order2, order3, order4));	
// when	
List<Order> orders = orderSearchService.getOrders(trader, accountCode, goldIsinCode);	
// then	
assertThat(orders).containsOnly(order3, order4);	
}
@Test public void // Using helpers + hiding irrelevant details	
getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v3() {	
// given - inputs	
String searchAccount = "TRDRZ", searchIsin = "GLD24680";	
Trader trader = createTraderWithPermissionsFor(createTradingAccount("TRDRZ"));	
// given - system state	
Order order1 = createOrder("TRDRZ", dummyFuture()),	
order2 = createOrder("TRDRZ", dummyFuture()),	
order3 = createOrder("TRDRZ", createFuture("GLD24680")),	
order4 = createOrder("TRDRZ", createFuture("GLD24680"));	
OrderSearchService service = createOrderService(createOrdersDAO(order1, order2, order3, order4));	
// when	
List<Order> orders = service.getOrders(trader, searchAccount, searchIsin);	
// then	
assertThat(orders).containsOnly(order3, order4);	
}
@Test public void 	
getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v1() {	
// given - inputs	
Trader trader = new Trader();	
TradingAccount tradingAcct = new TradingAccount(new TradingFirm("TraderzRUs", "TRDRZ"), 	
new ClearingFirm("TooBig2Fail", "2BIG2F"), ACTIVE);	
trader.setPermissions(Arrays.asList(new Permission(tradingAcct)));	
String goldIsinCode = "COF24680";	
// given - system state	
Future oil3MnthFuture = new Future("OIL.3MNTH", new ISIN("OIL3M0123"));	
Future oil6MnthFuture = new Future("OIL.6MNTH", new ISIN("OIL6M0456"));	
Future goldFuture = new Future("GLD.3MNTH", new ISIN(goldIsinCode));	
String orderId = "ordId";	
Order order1 = new Order(orderId + 1, tradingAcct, oil3MnthFuture, 1000, 2500);	
Order order2 = new Order(orderId + 2, tradingAcct, oil6MnthFuture, 1500, 2600);	
Order order3 = new Order(orderId + 3, tradingAcct, goldFuture, 200, 4300);	
Order order4 = new Order(orderId + 4, tradingAcct, goldFuture, 150, 4300);	
OrdersDAO orderDAO = new OrdersDAOInMemory(order1, order2, order3, order4);	
OrderSearchService orderSearchService = new OrderSearchServiceImpl(orderDAO);	
// when	
List<Order> orders = orderSearchService.getOrders(trader, tradingAcct, goldIsinCode);	
// then	
assertThat(orders).containsOnly(order3, order4);	
}
@Test public void // Using helpers	
getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v2() {	
// given - inputs	
String accountCode = "TRDRZ";	
String goldIsinCode = "GLD24680";	
TradingAccount tradingAcct = createTradingAccount(accountCode);	
Trader trader = createTraderWithPermissionsFor(tradingAcct);	
// given - system state	
Future oil3MnthFuture = createFuture("OIL.3MNTH", "OIL3M0123");	
Future oil6MnthFuture = createFuture("OIL.6MNTH", "OIL6M0456");	
Future goldFuture = createFuture("GLD.3MNTH", goldIsinCode);	
Order order1 = createOrder(accountCode, oil3MnthFuture, qty(1000), price(2500));	
Order order2 = createOrder(accountCode, oil6MnthFuture, qty(1500), price(2600));	
Order order3 = createOrder(accountCode, goldFuture, qty(200), price(4300));	
Order order4 = createOrder(accountCode, goldFuture, qty(150), price(4300));	
OrderSearchService orderSearchService = createOrderService(	
createOrdersDAO(order1, order2, order3, order4));	
// when	
List<Order> orders = orderSearchService.getOrders(trader, accountCode, goldIsinCode);	
// then	
assertThat(orders).containsOnly(order3, order4);	
}
@Test public void // Using helpers + hiding irrelevant details	
getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v3() {	
// given - inputs	
String searchAccount = "TRDRZ", searchIsin = "GLD24680";	
Trader trader = createTraderWithPermissionsFor(createTradingAccount("TRDRZ"));	
// given - system state	
Order order1 = createOrder("TRDRZ", dummyFuture()),	
order2 = createOrder("TRDRZ", dummyFuture()),	
order3 = createOrder("TRDRZ", createFuture("GLD24680")),	
order4 = createOrder("TRDRZ", createFuture("GLD24680"));	
OrderSearchService service = createOrderService(createOrdersDAO(order1, order2, order3, order4));	
// when	
List<Order> orders = service.getOrders(trader, searchAccount, searchIsin);	
// then	
assertThat(orders).containsOnly(order3, order4);	
}
Specifying Inputs - Techniques
•

Object Mother Pattern
public class ObjectMother {	

!
public static OrdersDAOInMemory createOrdersDAO(Order order1, Order order2, Order order3, Order order4) {	
return new OrdersDAOInMemory(order1, order2, order3, order4);	
}	

!
public static OrderSearchService createOrderService(OrdersDAO ordersDAO) {	
OrderSearchService orderSearchService = new OrderSearchServiceImpl(ordersDAO);	
return orderSearchService;	
}	
	
private OrderSearchService prepareOrderService(Order order1, Order order2, Order order3, Order order4) {	
OrdersDAO orderDAO = createOrdersDAO(order1, order2, order3, order4);	
OrderSearchService orderSearchService = new OrderSearchServiceImpl(orderDAO);	
return orderSearchService;	
}	

!
public static Order createOrder(String accountCode, Future dummyFuture) {	
return createOrder(accountCode, dummyFuture, qty(0), price(0));	
}	

!
public static Order createOrder(String accountCode, Future future, int qty, int price) {	
return new Order(nextOrderId(), accountCode, future, qty, price);	
}	
	
private Order createOrder(String orderId, TradingAccount tradingAcct, Future future, int qty, int price) {	
return new Order(orderId, tradingAcct.getTradingFirm().getCode(), future, qty, price);	
}	

!
public static Future createFuture(String desc, String isinCode) {	
return new Future(desc, new ISIN(isinCode));	
}	
	
public static Future createFuture(String isinCode) {	
return createFuture(DEFAULT_FUTURE_DESC, isinCode);	
}	

!
public static Future dummyFuture() {	
return createFuture(DEFAULT_FUTURE_DESC, DEFAULT_ISIN_CODE);	
}	

!
Specifying Inputs - Techniques
•

Test Data Builder Pattern
@Test public void // Using Test Data Builders	
getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v4() {	
// given - inputs	
String searchAccount = "TRDRZ", searchIsin = "GLD24680";	
Trader trader = aTrader().with(aPermission()	
.with(aTradingAccount()	
.with(aTradingFirm().withCode("TRDRZ")))).build();	
// given - system state	
OrderBuilder matchingOrder = anOrder().withAccountCode("TRDRZ")	
.with(aFuture().with(anISIN().withIsinCode("GLD24680")).build());	
Order order1 = anOrder().withAccountCode("TRDRZ").build(),	
order2 = anOrder().withAccountCode("TRDRZ").build(),	
order3 = matchingOrder.build(),	
order4 = matchingOrder.build();	
OrderSearchService service = createOrderService(createOrdersDAO(order1, order2, order3, order4));	
// when	
List<Order> orders = service.getOrders(trader, searchAccount, searchIsin);	
// then	
assertThat(orders).containsOnly(order3, order4);	
}
public class TraderBuilder {	
public static String DEFAULT_USERNAME = "trader1";	
public static UserDetail DEFAULT_USERDETAIL = new UserDetail();	
public static List<Permission> NO_PERMISSIONS = Collections.emptyList();	
public static List<Restriction> NO_RESTRICTIONS = Collections.emptyList();	

!

	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	

private String userName = DEFAULT_USERNAME;	
private UserDetail userDetail = DEFAULT_USERDETAIL;	
private List<Permission> permissions = NO_PERMISSIONS;	
private List<Restriction> restrictions = NO_RESTRICTIONS;	
	
private TraderBuilder() { }	
	
public static TraderBuilder aTrader() {	
	 return new TraderBuilder();	
}	
	
public Trader build() {	
	 Trader trader = new Trader();	
	 trader.setUserName(userName);	
	 trader.setUserDetail(userDetail);	
	 trader.setPermissions(permissions);	
	 trader.setRestrictions(restrictions);	
	 return trader;	
}	
	
public TraderBuilder withUserName(String userName) {	
	 this.userName = userName;	
	 return this;	
}	

!
	
	
	
	
	
	
	
	
	

public TraderBuilder with(UserDetail userDetail) {	
	 this.userDetail = userDetail;	
	 return this;	
}	
	
public TraderBuilder withPermissions(List<Permission> permissions) {	
	 this.permissions = new ArrayList<>(permissions);	
	 return this;	
}	

public TraderBuilder
this.permissions
return this;	
}	
	
public TraderBuilder
this.permissions
return this;	
}	

with(PermissionBuilder permission) {	
= new ArrayList<>(Arrays.asList(permission.build()));	

withNoPermissions() {	
= Collections.emptyList();	

!

public TraderBuilder with(RestrictionBuilder restriction) {	
this.restrictions = new ArrayList<>(Arrays.asList(restriction.build()));	
return this;	
}	
	
public TraderBuilder withRestrictions(List<Restriction> restrictions) {	
this.restrictions = new ArrayList<>(restrictions);	
return this;	
}	
	
public TraderBuilder but() {	
return new TraderBuilder()	
.withUserName(userName)	
.with(userDetail)	
.withPermissions(permissions)	
.withRestrictions(restrictions);	
}	
}
@Test public void // Using Test Data Builders	
getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v4() {	
// given - inputs	
String searchAccount = "TRDRZ", searchIsin = "GLD24680";	
Trader trader = aTrader().with(aPermission()	
.with(aTradingAccount()	
.with(aTradingFirm().withCode("TRDRZ")))).build();	
// given - system state	
OrderBuilder matchingOrder = anOrder().withAccountCode("TRDRZ")	
.with(aFuture().with(anISIN().withIsinCode("GLD24680")).build());	
Order order1 = anOrder().withAccountCode("TRDRZ").build(),	
order2 = anOrder().withAccountCode("TRDRZ").build(),	
order3 = matchingOrder.build(),	
order4 = matchingOrder.build();	
OrderSearchService service = createOrderService(createOrdersDAO(order1, order2, order3, order4));	
// when	
List<Order> orders = service.getOrders(trader, searchAccount, searchIsin);	
// then	
assertThat(orders).containsOnly(order3, order4);	
}
@Test public void // Using helpers + hiding irrelevant details	
getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v3() {	
// given - inputs	
String searchAccount = "TRDRZ", searchIsin = "GLD24680";	
Trader trader = createTraderWithPermissionsFor(createTradingAccount("TRDRZ"));	
// given - system state	
Order order1 = createOrder("TRDRZ", dummyFuture()),	
order2 = createOrder("TRDRZ", dummyFuture()),	
order3 = createOrder("TRDRZ", createFuture("GLD24680")),	
order4 = createOrder("TRDRZ", createFuture("GLD24680"));	
OrderSearchService service = createOrderService(createOrdersDAO(order1, order2, order3, order4));	
// when	
List<Order> orders = service.getOrders(trader, searchAccount, searchIsin);	
// then	
assertThat(orders).containsOnly(order3, order4);	
}
@Test public void // Using Test Data Builders to create input data variations	
getOrders_GivenTraderWithVariousPermissions_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v5() {	
// given - inputs	
String searchAccount = "TRDRZ", searchIsin = "GLD24680";	
TradingAccountBuilder tradingAccount = aTradingAccount().with(aTradingFirm().withCode("TRDRZ"));	
Trader traderWithNoPerms = aTrader().withNoPermissions().build();	
Trader traderWithPerms = aTrader().with(aPermission().with(tradingAccount)).build();	
Trader traderWithInactivePerms = aTrader().with(aPermission().with(	
tradingAccount.but().isInActive())).build();	
// given - system state	
OrderBuilder matchingOrder = anOrder().withAccountCode("TRDRZ")	
.with(aFuture().with(anISIN().withIsinCode("GLD24680")).build());	
Order order1 = anOrder().withAccountCode("TRDRZ").build(),	
order2 = anOrder().withAccountCode("TRDRZ").build(),	
order3 = matchingOrder.build(),	
order4 = matchingOrder.build();	
OrderSearchService service = createOrderService(createOrdersDAO(order1, order2, order3, order4));	
// when	
List<Order> orders1 = service.getOrders(traderWithNoPerms, searchAccount, searchIsin);	
List<Order> orders2 = service.getOrders(traderWithPerms, searchAccount, searchIsin);	
List<Order> orders3 = service.getOrders(traderWithInactivePerms, searchAccount, searchIsin);	
// then	
assertThat(orders1).isEmpty();	
assertThat(orders2).containsOnly(order3, order4);	
assertThat(orders3).isEmpty();	
}
Specifying Inputs - Techniques
•

Test Data Builder Pattern - with tool support
•

Make-It-Easy - addresses Builder class boiler-plate

•

Model Citizen - annotation-based “Blueprints”
Specifying Inputs - Techniques
•

Test Data Builder Pattern - Advantages
•

Your test / spec documents only the inputs that matter

•

Decouples your test from being impacted by a wide
range of changes

•

Supports “declarative” code style + useful templates
•

user = EXPIRED_USER.but( with ( aBalance, 100));
Specifying Inputs - Techniques
•

Test Data Builder Pattern - Disadvantages
•

Requires much new test-supporting code

•

Chained methods take getting used to
Specifying the System State
•

State of the SUT / the Test Context

•

Communicating what the context, limitations and
boundaries are

•

Isolating your test from certain dependencies / interactions

•

Use of Test Doubles
•

Hand-rolled Test Double

•

common misnomer Mocking Frameworks
Specifying the System State
•

Problems + solutions for Specifying Inputs normally apply

•

Unique to System State is your Test Isolation requirements
Specifying the System State - Techniques
•

Hand-rolling Test Doubles

•

Using Test Isolation frameworks to provide Test Doubles

•

“Mocking” + “Mocks Aren’t Stubs”

http://martinfowler.com/articles/mocksArentStubs.html

•

Dummy / Fake / Stubs / Mocks / Spies
Specifying the System State - Techniques
•

Stub being used in a state-based test
@Test public void // Stubbing system state (Mockito) + using helpers + hiding irrelevant details	
getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v2() {	
// given - inputs	
String searchAccount = "TRDRZ", searchIsin = "GLD24680";	
Trader trader = createTraderWithPermissionsFor(createTradingAccount("TRDRZ"));	
// given - system state	
Order order1 = createOrder("TRDRZ", createFuture("GLD24680")),	
order2 = createOrder("TRDRZ", createFuture("GLD24680"));	
OrdersDAO ordersDAOStub = mock(OrdersDAO.class);	
given(ordersDAOStub.findBy(searchAccount, searchIsin)).willReturn(Arrays.asList(order1, order2));	
OrderSearchService service = createOrderService(ordersDAOStub);	
// when	
List<Order> orders = service.getOrders(trader, searchAccount, searchIsin);	
// then	
assertThat(orders).containsOnly(order1, order2);	
}
@Test public void // Stubbing system state + stubbing inputs (Mockito)	
getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v3() {	
// given - inputs	
String searchAccount = "TRDRZ", searchIsin = "GLD24680";	
// Trader trader = createTraderWithPermissionsFor(createTradingAccount("TRDRZ"));	
Trader traderStub = mock(Trader.class);	
Permission permissionStub = mock(Permission.class);	
given(traderStub.getPermissions()).willReturn(Arrays.asList(permissionStub));	
TradingAccount tradingAccountStub = mock(TradingAccount.class);	
given(permissionStub.getTradingAccount()).willReturn(tradingAccountStub);	
given(tradingAccountStub.isActive()).willReturn(Boolean.TRUE);	
given(tradingAccountStub.getTradingFirm()).willReturn(new TradingFirm("", searchAccount));	
// given - system state	
Order order1 = createOrder("TRDRZ", createFuture("GLD24680")),	
order2 = createOrder("TRDRZ", createFuture("GLD24680"));	
OrdersDAO ordersDAOStub = mock(OrdersDAO.class);	
given(ordersDAOStub.findBy(searchAccount, searchIsin)).willReturn(Arrays.asList(order1, order2));	
OrderSearchService service = createOrderService(ordersDAOStub);	
// when	
List<Order> orders = service.getOrders(traderStub, searchAccount, searchIsin);	
// then	
assertThat(orders).containsOnly(order1, order2);	
}
@Test public void // Stubbing system state + stubbing input (Mockito w/ Deep Stubs)	
getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v3_1() {	
// given - inputs	
String searchAccount = "TRDRZ", searchIsin = "GLD24680";	
// Trader trader = createTraderWithPermissionsFor(createTradingAccount("TRDRZ"));	
Trader traderStub = mock(Trader.class);	
Permission permissionStub = mock(Permission.class, RETURNS_DEEP_STUBS);	
given(traderStub.getPermissions()).willReturn(Arrays.asList(permissionStub));	
given(permissionStub.getTradingAccount().isActive()).willReturn(Boolean.TRUE);	
given(permissionStub.getTradingAccount().getTradingFirm().getCode()).willReturn(searchAccount);	
// given - system state	
Order order1 = createOrder("TRDRZ", createFuture("GLD24680")),	
order2 = createOrder("TRDRZ", createFuture("GLD24680"));	
OrdersDAO ordersDAOStub = mock(OrdersDAO.class);	
given(ordersDAOStub.findBy(searchAccount, searchIsin)).willReturn(Arrays.asList(order1, order2));	
OrderSearchService service = createOrderService(ordersDAOStub);	
// when	
List<Order> orders = service.getOrders(traderStub, searchAccount, searchIsin);	
// then	
assertThat(orders).containsOnly(order1, order2);	
}
Specifying the System State - Techniques
•

A Mock being used in a interaction-based test
@Test public void // Using a Mock to test interactions + Stubbing system state (Mockito)	
getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v4_1() {	
// given - inputs	
String searchAccount = "TRDRZ", searchIsin = "GLD24680";	
Trader trader = createTraderWithPermissionsFor(createTradingAccount("TRDRZ"));	
// given - system state	
Order order1 = createOrder("TRDRZ", createFuture("GLD24680")),	
order2 = createOrder("TRDRZ", createFuture("GLD24680"));	
OrdersDAO ordersDAOStub = mock(OrdersDAO.class);	
given(ordersDAOStub.findBy(searchAccount, searchIsin)).willReturn(Arrays.asList(order1, order2));	
PermissionService permissionServiceMock = mock(PermissionService.class);	
OrderSearchService service = createOrderServiceV2(ordersDAOStub, permissionServiceMock);	
// when	
List<Order> orders = service.getOrders(trader, searchAccount, searchIsin);	
// then	
verify(permissionServiceMock).hasViewPermissions(trader, order1);	
verify(permissionServiceMock).hasViewPermissions(trader, order2);	
}
@Test public void // Using a Mock to test interactions + Stubbing system state (Mockito)	
getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v4_2() {	
// given - inputs	
String searchAccount = "TRDRZ", searchIsin = "GLD24680";	
Trader trader = createTraderWithPermissionsFor(createTradingAccount("TRDRZ"));	
// given - system state	
Order order1 = createOrder("TRDRZ", createFuture("GLD24680")),	
order2 = createOrder("TRDRZ", createFuture("GLD24680"));	
OrdersDAO ordersDAOStub = mock(OrdersDAO.class);	
given(ordersDAOStub.findBy(searchAccount, searchIsin)).willReturn(Arrays.asList(order1, order2));	
PermissionService permissionServiceMock = mock(PermissionService.class);	
given(permissionServiceMock.hasViewPermissions(	
any(Trader.class), any(Order.class))).willReturn(Boolean.TRUE);	
OrderSearchService service = createOrderServiceV2(ordersDAOStub, permissionServiceMock);	
// when	
List<Order> orders = service.getOrders(trader, searchAccount, searchIsin);	
// then	
verify(permissionServiceMock).hasViewPermissions(trader, order1);	
verify(permissionServiceMock).hasViewPermissions(trader, order2);	
assertThat(orders).containsOnly(order1, order2);	
}
Specifying the System State - Techniques
•

Often a test will have
•

Zero to many Stubs. Zero or one Mock

•

Mocks help you answer questions about what happened

•

When a Mock is present, you ask it something at the end

•

A Stub should not be asserted against

•

There to help create + control the System State for the test
Specifying the Event
•

Often trivial

•

Might be a sequence of events that we want to fire

•

Sequence might actually be context / SUT setup

•

Practices? Tools?
Specifying it All Together
•

Spring MVC Test support / DSL

@Test 	
public void helloWorld() throws Exception {	
mockMvc.perform(get("/hello").accept(MediaType.TEXT_PLAIN))	
.andDo(print()) // print the request/response in the console	
.andExpect(status().isOk())	
.andExpect(content().contentType(MediaType.TEXT_PLAIN))	
.andExpect(content().string("Hello World!"));	
}
Principles, Practices + Tool Support
•

Structural Principles / Characteristics

•

Qualities of a “good” test / spec

•

Practices + Tools for improving test qualities
Continuous Testing
•

Continuous Integration - development practice

•

CI tools - run automated tests frequently

•

Continuous Testing - development practice

•

CT tools - run automated tests locally, frequently

•

e.g. Infinitest, JUnitMax
Overview
•

Automating

•

Testing Categories + Scope

•

Development Testing

•

Principles, Practices + Tool Support
Summary
•

Tools/APIs just help with heavy lifting

•

Sound coding principles are paramount

•

Creating unreadable and unmaintainable test, even with
“the right” tools

•

Unreadable + high maintenance tests will hinder efficiency
Summary
•

The power of marginal improvements

•

for … each, <> operator, closure support, Guava

•

Continuous iterative improvements nudge us forward

•

Continuous iterative improvements push down cost of
reading, writing, evolving tests

•

Drives the cost down to “mass market” level

•

Beyond Java
Resources
•

Books
•

GOOS - Growing Object Oriented Software, Guided By
Tests

•

Effective Unit Testing

•

Working Effectively with Legacy Code

•

xUnit Test Patterns - Refactoring Test Code
Resources
•

Podcasts
•
•

Hanselminutes - Roy Asherove - The Art of Unit Testing

•
•

Sustainable Test Driven Development (Series)

Hanselminutes - Quetzal Bradley - Beyond Unit Testing

All Code Examples are available at
•

http://github.com/cathalking/devtestevolution
The Evolution of Development Testing

Contenu connexe

Tendances

Unit Testing Best Practices
Unit Testing Best PracticesUnit Testing Best Practices
Unit Testing Best Practices
Tomaš Maconko
 
Pitfalls Of Tdd Adoption by Bartosz Bankowski
Pitfalls Of Tdd Adoption by Bartosz BankowskiPitfalls Of Tdd Adoption by Bartosz Bankowski
Pitfalls Of Tdd Adoption by Bartosz Bankowski
Agileee
 
Test-Driven Development
Test-Driven DevelopmentTest-Driven Development
Test-Driven Development
Meilan Ou
 
Code review for secure web applications
Code review for secure web applicationsCode review for secure web applications
Code review for secure web applications
silviad74
 

Tendances (20)

Unit Testing Best Practices
Unit Testing Best PracticesUnit Testing Best Practices
Unit Testing Best Practices
 
Unit Testing
Unit TestingUnit Testing
Unit Testing
 
Pitfalls Of Tdd Adoption by Bartosz Bankowski
Pitfalls Of Tdd Adoption by Bartosz BankowskiPitfalls Of Tdd Adoption by Bartosz Bankowski
Pitfalls Of Tdd Adoption by Bartosz Bankowski
 
Software Quality via Unit Testing
Software Quality via Unit TestingSoftware Quality via Unit Testing
Software Quality via Unit Testing
 
Rv11
Rv11Rv11
Rv11
 
Test-Driven Development
Test-Driven DevelopmentTest-Driven Development
Test-Driven Development
 
An Introduction to Unit Testing
An Introduction to Unit TestingAn Introduction to Unit Testing
An Introduction to Unit Testing
 
Unit Testing Concepts and Best Practices
Unit Testing Concepts and Best PracticesUnit Testing Concepts and Best Practices
Unit Testing Concepts and Best Practices
 
Principles and patterns for test driven development
Principles and patterns for test driven developmentPrinciples and patterns for test driven development
Principles and patterns for test driven development
 
White Box Testing
White Box TestingWhite Box Testing
White Box Testing
 
Must.kill.mutants. TopConf Tallinn 2016
Must.kill.mutants. TopConf Tallinn 2016Must.kill.mutants. TopConf Tallinn 2016
Must.kill.mutants. TopConf Tallinn 2016
 
TDD And Refactoring
TDD And RefactoringTDD And Refactoring
TDD And Refactoring
 
Test Driven Development (TDD)
Test Driven Development (TDD)Test Driven Development (TDD)
Test Driven Development (TDD)
 
White Box Testing V0.2
White Box Testing V0.2White Box Testing V0.2
White Box Testing V0.2
 
Unit testing
Unit testingUnit testing
Unit testing
 
Unit Testing and TDD 2017
Unit Testing and TDD 2017Unit Testing and TDD 2017
Unit Testing and TDD 2017
 
Code review for secure web applications
Code review for secure web applicationsCode review for secure web applications
Code review for secure web applications
 
White box
White boxWhite box
White box
 
Dependency Injection in iOS
Dependency Injection in iOSDependency Injection in iOS
Dependency Injection in iOS
 
Design for Testability
Design for Testability Design for Testability
Design for Testability
 

En vedette (8)

My cv 2
My cv 2My cv 2
My cv 2
 
Suisse tourisme stratégie web - ET8
Suisse tourisme stratégie web  - ET8Suisse tourisme stratégie web  - ET8
Suisse tourisme stratégie web - ET8
 
Coffee and tv week 1
Coffee and tv   week 1Coffee and tv   week 1
Coffee and tv week 1
 
Yoyopresentasi 1225941108853502-8 2
Yoyopresentasi 1225941108853502-8 2Yoyopresentasi 1225941108853502-8 2
Yoyopresentasi 1225941108853502-8 2
 
A14 web to store
A14 web to storeA14 web to store
A14 web to store
 
Sta moramo uraditi da bi videli nocu
Sta moramo uraditi da bi videli nocuSta moramo uraditi da bi videli nocu
Sta moramo uraditi da bi videli nocu
 
Coffee and tv week 2
Coffee and tv   week 2Coffee and tv   week 2
Coffee and tv week 2
 
Coffee and tv week 3
Coffee and tv   week 3Coffee and tv   week 3
Coffee and tv week 3
 

Similaire à The Evolution of Development Testing

ISTQB / ISEB Foundation Exam Practice
ISTQB / ISEB Foundation Exam PracticeISTQB / ISEB Foundation Exam Practice
ISTQB / ISEB Foundation Exam Practice
Yogindernath Gupta
 
Pekka_Aho_Complementing GUI Testing Scripts - Testing Assembly 2022.pdf
Pekka_Aho_Complementing GUI Testing Scripts -  Testing Assembly 2022.pdfPekka_Aho_Complementing GUI Testing Scripts -  Testing Assembly 2022.pdf
Pekka_Aho_Complementing GUI Testing Scripts - Testing Assembly 2022.pdf
FiSTB
 

Similaire à The Evolution of Development Testing (20)

Test automation lesson
Test automation lessonTest automation lesson
Test automation lesson
 
Context Driven Automation Gtac 2008
Context Driven Automation Gtac 2008Context Driven Automation Gtac 2008
Context Driven Automation Gtac 2008
 
The QA/Testing Process
The QA/Testing ProcessThe QA/Testing Process
The QA/Testing Process
 
The Automation Firehose: Be Strategic & Tactical With Your Mobile & Web Testing
The Automation Firehose: Be Strategic & Tactical With Your Mobile & Web TestingThe Automation Firehose: Be Strategic & Tactical With Your Mobile & Web Testing
The Automation Firehose: Be Strategic & Tactical With Your Mobile & Web Testing
 
Testing- Fundamentals of Testing-Mazenet solution
Testing- Fundamentals of Testing-Mazenet solutionTesting- Fundamentals of Testing-Mazenet solution
Testing- Fundamentals of Testing-Mazenet solution
 
Getting Started with Test-Driven Development at PHPtek 2023
Getting Started with Test-Driven Development at PHPtek 2023Getting Started with Test-Driven Development at PHPtek 2023
Getting Started with Test-Driven Development at PHPtek 2023
 
Testing - How Vital and How Easy to use
Testing - How Vital and How Easy to useTesting - How Vital and How Easy to use
Testing - How Vital and How Easy to use
 
Software Testing- Principles of testing- Mazenet Solution
Software Testing- Principles of testing- Mazenet SolutionSoftware Testing- Principles of testing- Mazenet Solution
Software Testing- Principles of testing- Mazenet Solution
 
The Automation Firehose: Be Strategic and Tactical by Thomas Haver
The Automation Firehose: Be Strategic and Tactical by Thomas HaverThe Automation Firehose: Be Strategic and Tactical by Thomas Haver
The Automation Firehose: Be Strategic and Tactical by Thomas Haver
 
Agile testing
Agile testingAgile testing
Agile testing
 
Software Testing - Tool support for testing (CAST) - Mazenet Solution
Software Testing - Tool support for testing (CAST) - Mazenet SolutionSoftware Testing - Tool support for testing (CAST) - Mazenet Solution
Software Testing - Tool support for testing (CAST) - Mazenet Solution
 
Design Like a Pro: Scripting Best Practices
Design Like a Pro: Scripting Best PracticesDesign Like a Pro: Scripting Best Practices
Design Like a Pro: Scripting Best Practices
 
Chromatography Data System: Getting It “Right First Time” Seminar Series – Pa...
Chromatography Data System: Getting It “Right First Time” Seminar Series – Pa...Chromatography Data System: Getting It “Right First Time” Seminar Series – Pa...
Chromatography Data System: Getting It “Right First Time” Seminar Series – Pa...
 
ISTQB / ISEB Foundation Exam Practice
ISTQB / ISEB Foundation Exam PracticeISTQB / ISEB Foundation Exam Practice
ISTQB / ISEB Foundation Exam Practice
 
Not Your Grandfather's Requirements-Based Testing Webinar – Robin Goldsmith, ...
Not Your Grandfather's Requirements-Based Testing Webinar – Robin Goldsmith, ...Not Your Grandfather's Requirements-Based Testing Webinar – Robin Goldsmith, ...
Not Your Grandfather's Requirements-Based Testing Webinar – Robin Goldsmith, ...
 
Design Like a Pro: Scripting Best Practices
Design Like a Pro: Scripting Best PracticesDesign Like a Pro: Scripting Best Practices
Design Like a Pro: Scripting Best Practices
 
Iseb, ISTQB Static Testing
Iseb, ISTQB Static TestingIseb, ISTQB Static Testing
Iseb, ISTQB Static Testing
 
Pekka_Aho_Complementing GUI Testing Scripts - Testing Assembly 2022.pdf
Pekka_Aho_Complementing GUI Testing Scripts -  Testing Assembly 2022.pdfPekka_Aho_Complementing GUI Testing Scripts -  Testing Assembly 2022.pdf
Pekka_Aho_Complementing GUI Testing Scripts - Testing Assembly 2022.pdf
 
Design testabilty
Design testabiltyDesign testabilty
Design testabilty
 
ISTQB Foundation - Chapter 3
ISTQB Foundation - Chapter 3ISTQB Foundation - Chapter 3
ISTQB Foundation - Chapter 3
 

Dernier

EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
Earley Information Science
 

Dernier (20)

Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 

The Evolution of Development Testing

  • 2. Overview • Automating • Testing Categories + Scope • Development Testing • Principles, Practices + Tool Support
  • 4. Automating • We automate “stuff” for a living • Developer/Programmer/Engineer/Automator • When we’re doing things manually we’re usually missing an opportunity to automate
  • 5. Automating • The stuff we automate might be • validation of a web form • reporting of a message parsing error • calculation of an average execution price • execution + status reporting of tests for all of the above
  • 6. Automating • That automation might look like • choose validation rules, implement, enforce, report • log parsing error, automate monitoring/reporting of error • define calculation rules, implement, provide result • define inputs, context + expectations, execute scenario, determine + report status
  • 7. Automating • Manual solutions might look like • review user errors later in workflow e.g.“back office” manual activity • message loss caused an incident, manually track it down • export raw data to csv, load into Excel, apply formula • run UI functional scenarios that use/execute the code in mind • fire up debugger, step through mis-behaving code • add detailed logging, monitor logs for hints
  • 8. Automating • We automate “stuff” for a living • Developer/Programmer/Engineer/Automator • When we’re doing things manually we’re usually missing an opportunity to automate
  • 10. Test Categorisation • Useful concept - up to a point • Blurred lines + Overloaded terminology • Difficulties conversing about testing
  • 14. Test Categorisation • Useful concept - up to a point • Blurred lines + Overloaded terminology • Difficulties conversing about testing
  • 17. Overview • Automating • Testing Categories + Scope • Development Testing
  • 18. Development Testing • What it is • What it should not be • How it has evolved
  • 19. What it is • A Development practice first and foremost • White box / Black box / Grey box • Has Quality aims • Has Efficiency aims
  • 20. JUnit • Test automation framework of choice for most Java developers • Synonymous with Unit testing • Widely used for testing beyond the Unit scope • Ported to many languages - generically known as xUnit
  • 21. JUnit “Never in the field of software development have so many owed so much to so few lines of code” Martin Fowler (Development big-wig)
  • 22. What it is • A Development practice first and foremost • White box / Black box / Grey box • Has Quality aims • Has Efficiency aims
  • 23. What it is • Can take a technical perspective on scenarios • Can take a user functionality perspective • Exercises precise control over the test context • Used + extended by Dev during initial and future coding
  • 24. What it is • Often used to discover and refine solution design • Often leaves fine-grained regression coverage safety net • Enables instant feedback on failures due to future enhancements and refactorings
  • 25. What it is not • Automated full System tests a.k.a E2E, System Integration • Zero control over the state of the system (the test context) • Test scope coupled to other external systems, reference data quality, env availability • Heavily reliant on driving a UI to execute many finegrained scenarios
  • 26. Its Evolution • OOP - slow evolution • Automated Developer Testing around a lot less time • A lot less mainstream than OOP • Growing body of knowledge / experience available
  • 27. Overview • Automating • Testing Categories + Scope • Development Testing • Principles, Practices + Tool Support
  • 28. Principles, Practices + Tool Support • Structural Principles / Characteristics • Qualities of a “good” test / spec • Practices + Tools for improving test qualities
  • 29. Test structure • Arrange, Act, Assert • Specify Inputs • Given, When, Then • Specify the state of the System • Specify the Event • Specify the Expectations
  • 30. Test structure • Naming conventions + structure @Test public // // // } void methodUnderTest_GivenABC_ThenExpectXYZ() { given when then @Test public void methodUnderTest_GivenInputsABC_AndSystemStateDEF_ThenExpectXYZ() { // given - inputs // given - system state ! // when // then }
  • 31. Test structure • IDE assistance for consistency + evolve conventions
  • 32. Principles, Practices + Tool Support • Structural Principles / Characteristics • Qualities of a “good” test / spec
  • 33. “Any fool can write code that a computer can understand” ! Martin Fowler
  • 35. Test Qualities • Readability == …? • Code that you can understand with ease • Language affects Thought • Thought affects Action
  • 36. Test Qualities • Tools can directly address Readability qualities • Domain Specific Languages = a fancy term • • JUnit, Hamcrest, FEST, JMock, Mockito, Gherkin Language -> Thought -> Action
  • 37. “Test automation is a first class software engineering problem” ! Brian Marick?
  • 39. Test Qualities • Maintainability == …? • To change, evolve with ease • Keeping the cost of change down
  • 40. Test Qualities • They can be context sensitive. Many are not. • You may opt to sacrifice: • • Readability for Maintainability • • DRY Principle for Readability Maintainability for Obviousness Expect debate
  • 41. Principles, Practices + Tool Support • Structural Principles / Characteristics • Qualities of a “good” test / spec • Practices + Tools for improving test qualities
  • 42. Specifying Expectations • Stating Expectations / Asking Questions • From Computer dialect ——> Human dialect // then assertTrue(trader.getPermissions().isEmpty()); assertThat(trader.getPermissions().isEmpty(), is(true)); assertThat(trader.getPermissions()).isEmpty(); assertThat(trader).hasNoPermissions(); // // // // // // // Computer dialect . . . . . Human dialect
  • 43. Specifying Expectations • Stating Expectations / Asking Questions • From Computer dialect ——> Human dialect // Using Basic JUnit assertEquals(orders.size(), 2); Iterator<Order> itr = orders.iterator(); assertEquals(itr.next(), order3); assertEquals(itr.next(), order4); // Using Hamcrest assertThat(orders, hasItems(order3, order4)); assertThat(orders.size(), is(2)); // Using FEST Assertions assertThat(orders).containsOnly(order3, order4); // Using FEST Assertions (chained assertions) assertThat(orders).containsOnly(order3, order4) .containsSequence(order3, order4);
  • 44. Specifying Expectations • Easier + Faster to write expressive statements • Faster to read + review • Easier to maintain • Encourages other better practices
  • 45. Specifying Inputs • Trivial example. Test with very narrow scope • e.g.Input = Single Object @Test public void getPermissions_GivenNewUser_ThenReturnsNoPermissions() { // given Trader trader = new Trader(); // when + then assertThat(trader.getPermissions()).isEmpty(); }
  • 46. Specifying Inputs • Non-trivial example. Test with broader scope • e.g. Input = Object Graph • i.e. objects linked to objects
  • 47. @Test public void getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v1() { // given - inputs Trader trader = new Trader(); TradingAccount tradingAcct = new TradingAccount(new TradingFirm("TraderzRUs", "TRDRZ"), new ClearingFirm("TooBig2Fail", "2BIG2F"), ACTIVE); trader.setPermissions(Arrays.asList(new Permission(tradingAcct))); String goldIsinCode = "COF24680"; // given - system state Future oil3MnthFuture = new Future("OIL.3MNTH", new ISIN("OIL3M0123")); Future oil6MnthFuture = new Future("OIL.6MNTH", new ISIN("OIL6M0456")); Future goldFuture = new Future("GLD.3MNTH", new ISIN(goldIsinCode)); String orderId = "ordId"; Order order1 = new Order(orderId + 1, tradingAcct, oil3MnthFuture, 1000, 2500); Order order2 = new Order(orderId + 2, tradingAcct, oil6MnthFuture, 1500, 2600); Order order3 = new Order(orderId + 3, tradingAcct, goldFuture, 200, 4300); Order order4 = new Order(orderId + 4, tradingAcct, goldFuture, 150, 4300); OrdersDAO orderDAO = new OrdersDAOInMemory(order1, order2, order3, order4); OrderSearchService orderSearchService = new OrderSearchServiceImpl(orderDAO); // when List<Order> orders = orderSearchService.getOrders(trader, tradingAcct, goldIsinCode); // then assertThat(orders).containsOnly(order3, order4); }
  • 48. Specifying Inputs • Consider many non-trivial tests, with “raw” setup • Impact of changing one setter or constructor • Quantity of code there is to read, understand, change
  • 49. Specifying Inputs - Techniques • Helper methods • Object Mother Pattern • Test Data Builder Pattern
  • 50. @Test public void getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v1() { // given - inputs Trader trader = new Trader(); TradingAccount tradingAcct = new TradingAccount(new TradingFirm("TraderzRUs", "TRDRZ"), new ClearingFirm("TooBig2Fail", "2BIG2F"), ACTIVE); trader.setPermissions(Arrays.asList(new Permission(tradingAcct))); String goldIsinCode = "COF24680"; // given - system state Future oil3MnthFuture = new Future("OIL.3MNTH", new ISIN("OIL3M0123")); Future oil6MnthFuture = new Future("OIL.6MNTH", new ISIN("OIL6M0456")); Future goldFuture = new Future("GLD.3MNTH", new ISIN(goldIsinCode)); String orderId = "ordId"; Order order1 = new Order(orderId + 1, tradingAcct, oil3MnthFuture, 1000, 2500); Order order2 = new Order(orderId + 2, tradingAcct, oil6MnthFuture, 1500, 2600); Order order3 = new Order(orderId + 3, tradingAcct, goldFuture, 200, 4300); Order order4 = new Order(orderId + 4, tradingAcct, goldFuture, 150, 4300); OrdersDAO orderDAO = new OrdersDAOInMemory(order1, order2, order3, order4); OrderSearchService orderSearchService = new OrderSearchServiceImpl(orderDAO); // when List<Order> orders = orderSearchService.getOrders(trader, tradingAcct, goldIsinCode); // then assertThat(orders).containsOnly(order3, order4); }
  • 51. @Test public void // Using helpers getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v2() { // given - inputs String accountCode = "TRDRZ"; String goldIsinCode = "GLD24680"; TradingAccount tradingAcct = createTradingAccount(accountCode); Trader trader = createTraderWithPermissionsFor(tradingAcct); // given - system state Future oil3MnthFuture = createFuture("OIL.3MNTH", "OIL3M0123"); Future oil6MnthFuture = createFuture("OIL.6MNTH", "OIL6M0456"); Future goldFuture = createFuture("GLD.3MNTH", goldIsinCode); Order order1 = createOrder(accountCode, oil3MnthFuture, qty(1000), price(2500)); Order order2 = createOrder(accountCode, oil6MnthFuture, qty(1500), price(2600)); Order order3 = createOrder(accountCode, goldFuture, qty(200), price(4300)); Order order4 = createOrder(accountCode, goldFuture, qty(150), price(4300)); OrderSearchService orderSearchService = createOrderService( createOrdersDAO(order1, order2, order3, order4)); // when List<Order> orders = orderSearchService.getOrders(trader, accountCode, goldIsinCode); // then assertThat(orders).containsOnly(order3, order4); }
  • 52. @Test public void // Using helpers + hiding irrelevant details getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v3() { // given - inputs String searchAccount = "TRDRZ", searchIsin = "GLD24680"; Trader trader = createTraderWithPermissionsFor(createTradingAccount("TRDRZ")); // given - system state Order order1 = createOrder("TRDRZ", dummyFuture()), order2 = createOrder("TRDRZ", dummyFuture()), order3 = createOrder("TRDRZ", createFuture("GLD24680")), order4 = createOrder("TRDRZ", createFuture("GLD24680")); OrderSearchService service = createOrderService(createOrdersDAO(order1, order2, order3, order4)); // when List<Order> orders = service.getOrders(trader, searchAccount, searchIsin); // then assertThat(orders).containsOnly(order3, order4); }
  • 53. @Test public void getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v1() { // given - inputs Trader trader = new Trader(); TradingAccount tradingAcct = new TradingAccount(new TradingFirm("TraderzRUs", "TRDRZ"), new ClearingFirm("TooBig2Fail", "2BIG2F"), ACTIVE); trader.setPermissions(Arrays.asList(new Permission(tradingAcct))); String goldIsinCode = "COF24680"; // given - system state Future oil3MnthFuture = new Future("OIL.3MNTH", new ISIN("OIL3M0123")); Future oil6MnthFuture = new Future("OIL.6MNTH", new ISIN("OIL6M0456")); Future goldFuture = new Future("GLD.3MNTH", new ISIN(goldIsinCode)); String orderId = "ordId"; Order order1 = new Order(orderId + 1, tradingAcct, oil3MnthFuture, 1000, 2500); Order order2 = new Order(orderId + 2, tradingAcct, oil6MnthFuture, 1500, 2600); Order order3 = new Order(orderId + 3, tradingAcct, goldFuture, 200, 4300); Order order4 = new Order(orderId + 4, tradingAcct, goldFuture, 150, 4300); OrdersDAO orderDAO = new OrdersDAOInMemory(order1, order2, order3, order4); OrderSearchService orderSearchService = new OrderSearchServiceImpl(orderDAO); // when List<Order> orders = orderSearchService.getOrders(trader, tradingAcct, goldIsinCode); // then assertThat(orders).containsOnly(order3, order4); }
  • 54. @Test public void // Using helpers getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v2() { // given - inputs String accountCode = "TRDRZ"; String goldIsinCode = "GLD24680"; TradingAccount tradingAcct = createTradingAccount(accountCode); Trader trader = createTraderWithPermissionsFor(tradingAcct); // given - system state Future oil3MnthFuture = createFuture("OIL.3MNTH", "OIL3M0123"); Future oil6MnthFuture = createFuture("OIL.6MNTH", "OIL6M0456"); Future goldFuture = createFuture("GLD.3MNTH", goldIsinCode); Order order1 = createOrder(accountCode, oil3MnthFuture, qty(1000), price(2500)); Order order2 = createOrder(accountCode, oil6MnthFuture, qty(1500), price(2600)); Order order3 = createOrder(accountCode, goldFuture, qty(200), price(4300)); Order order4 = createOrder(accountCode, goldFuture, qty(150), price(4300)); OrderSearchService orderSearchService = createOrderService( createOrdersDAO(order1, order2, order3, order4)); // when List<Order> orders = orderSearchService.getOrders(trader, accountCode, goldIsinCode); // then assertThat(orders).containsOnly(order3, order4); }
  • 55. @Test public void // Using helpers + hiding irrelevant details getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v3() { // given - inputs String searchAccount = "TRDRZ", searchIsin = "GLD24680"; Trader trader = createTraderWithPermissionsFor(createTradingAccount("TRDRZ")); // given - system state Order order1 = createOrder("TRDRZ", dummyFuture()), order2 = createOrder("TRDRZ", dummyFuture()), order3 = createOrder("TRDRZ", createFuture("GLD24680")), order4 = createOrder("TRDRZ", createFuture("GLD24680")); OrderSearchService service = createOrderService(createOrdersDAO(order1, order2, order3, order4)); // when List<Order> orders = service.getOrders(trader, searchAccount, searchIsin); // then assertThat(orders).containsOnly(order3, order4); }
  • 56. Specifying Inputs - Techniques • Object Mother Pattern
  • 57. public class ObjectMother { ! public static OrdersDAOInMemory createOrdersDAO(Order order1, Order order2, Order order3, Order order4) { return new OrdersDAOInMemory(order1, order2, order3, order4); } ! public static OrderSearchService createOrderService(OrdersDAO ordersDAO) { OrderSearchService orderSearchService = new OrderSearchServiceImpl(ordersDAO); return orderSearchService; } private OrderSearchService prepareOrderService(Order order1, Order order2, Order order3, Order order4) { OrdersDAO orderDAO = createOrdersDAO(order1, order2, order3, order4); OrderSearchService orderSearchService = new OrderSearchServiceImpl(orderDAO); return orderSearchService; } ! public static Order createOrder(String accountCode, Future dummyFuture) { return createOrder(accountCode, dummyFuture, qty(0), price(0)); } ! public static Order createOrder(String accountCode, Future future, int qty, int price) { return new Order(nextOrderId(), accountCode, future, qty, price); } private Order createOrder(String orderId, TradingAccount tradingAcct, Future future, int qty, int price) { return new Order(orderId, tradingAcct.getTradingFirm().getCode(), future, qty, price); } ! public static Future createFuture(String desc, String isinCode) { return new Future(desc, new ISIN(isinCode)); } public static Future createFuture(String isinCode) { return createFuture(DEFAULT_FUTURE_DESC, isinCode); } ! public static Future dummyFuture() { return createFuture(DEFAULT_FUTURE_DESC, DEFAULT_ISIN_CODE); } !
  • 58. Specifying Inputs - Techniques • Test Data Builder Pattern
  • 59. @Test public void // Using Test Data Builders getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v4() { // given - inputs String searchAccount = "TRDRZ", searchIsin = "GLD24680"; Trader trader = aTrader().with(aPermission() .with(aTradingAccount() .with(aTradingFirm().withCode("TRDRZ")))).build(); // given - system state OrderBuilder matchingOrder = anOrder().withAccountCode("TRDRZ") .with(aFuture().with(anISIN().withIsinCode("GLD24680")).build()); Order order1 = anOrder().withAccountCode("TRDRZ").build(), order2 = anOrder().withAccountCode("TRDRZ").build(), order3 = matchingOrder.build(), order4 = matchingOrder.build(); OrderSearchService service = createOrderService(createOrdersDAO(order1, order2, order3, order4)); // when List<Order> orders = service.getOrders(trader, searchAccount, searchIsin); // then assertThat(orders).containsOnly(order3, order4); }
  • 60. public class TraderBuilder { public static String DEFAULT_USERNAME = "trader1"; public static UserDetail DEFAULT_USERDETAIL = new UserDetail(); public static List<Permission> NO_PERMISSIONS = Collections.emptyList(); public static List<Restriction> NO_RESTRICTIONS = Collections.emptyList(); ! private String userName = DEFAULT_USERNAME; private UserDetail userDetail = DEFAULT_USERDETAIL; private List<Permission> permissions = NO_PERMISSIONS; private List<Restriction> restrictions = NO_RESTRICTIONS; private TraderBuilder() { } public static TraderBuilder aTrader() { return new TraderBuilder(); } public Trader build() { Trader trader = new Trader(); trader.setUserName(userName); trader.setUserDetail(userDetail); trader.setPermissions(permissions); trader.setRestrictions(restrictions); return trader; } public TraderBuilder withUserName(String userName) { this.userName = userName; return this; } ! public TraderBuilder with(UserDetail userDetail) { this.userDetail = userDetail; return this; } public TraderBuilder withPermissions(List<Permission> permissions) { this.permissions = new ArrayList<>(permissions); return this; } public TraderBuilder this.permissions return this; } public TraderBuilder this.permissions return this; } with(PermissionBuilder permission) { = new ArrayList<>(Arrays.asList(permission.build())); withNoPermissions() { = Collections.emptyList(); ! public TraderBuilder with(RestrictionBuilder restriction) { this.restrictions = new ArrayList<>(Arrays.asList(restriction.build())); return this; } public TraderBuilder withRestrictions(List<Restriction> restrictions) { this.restrictions = new ArrayList<>(restrictions); return this; } public TraderBuilder but() { return new TraderBuilder() .withUserName(userName) .with(userDetail) .withPermissions(permissions) .withRestrictions(restrictions); } }
  • 61. @Test public void // Using Test Data Builders getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v4() { // given - inputs String searchAccount = "TRDRZ", searchIsin = "GLD24680"; Trader trader = aTrader().with(aPermission() .with(aTradingAccount() .with(aTradingFirm().withCode("TRDRZ")))).build(); // given - system state OrderBuilder matchingOrder = anOrder().withAccountCode("TRDRZ") .with(aFuture().with(anISIN().withIsinCode("GLD24680")).build()); Order order1 = anOrder().withAccountCode("TRDRZ").build(), order2 = anOrder().withAccountCode("TRDRZ").build(), order3 = matchingOrder.build(), order4 = matchingOrder.build(); OrderSearchService service = createOrderService(createOrdersDAO(order1, order2, order3, order4)); // when List<Order> orders = service.getOrders(trader, searchAccount, searchIsin); // then assertThat(orders).containsOnly(order3, order4); }
  • 62. @Test public void // Using helpers + hiding irrelevant details getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v3() { // given - inputs String searchAccount = "TRDRZ", searchIsin = "GLD24680"; Trader trader = createTraderWithPermissionsFor(createTradingAccount("TRDRZ")); // given - system state Order order1 = createOrder("TRDRZ", dummyFuture()), order2 = createOrder("TRDRZ", dummyFuture()), order3 = createOrder("TRDRZ", createFuture("GLD24680")), order4 = createOrder("TRDRZ", createFuture("GLD24680")); OrderSearchService service = createOrderService(createOrdersDAO(order1, order2, order3, order4)); // when List<Order> orders = service.getOrders(trader, searchAccount, searchIsin); // then assertThat(orders).containsOnly(order3, order4); }
  • 63. @Test public void // Using Test Data Builders to create input data variations getOrders_GivenTraderWithVariousPermissions_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v5() { // given - inputs String searchAccount = "TRDRZ", searchIsin = "GLD24680"; TradingAccountBuilder tradingAccount = aTradingAccount().with(aTradingFirm().withCode("TRDRZ")); Trader traderWithNoPerms = aTrader().withNoPermissions().build(); Trader traderWithPerms = aTrader().with(aPermission().with(tradingAccount)).build(); Trader traderWithInactivePerms = aTrader().with(aPermission().with( tradingAccount.but().isInActive())).build(); // given - system state OrderBuilder matchingOrder = anOrder().withAccountCode("TRDRZ") .with(aFuture().with(anISIN().withIsinCode("GLD24680")).build()); Order order1 = anOrder().withAccountCode("TRDRZ").build(), order2 = anOrder().withAccountCode("TRDRZ").build(), order3 = matchingOrder.build(), order4 = matchingOrder.build(); OrderSearchService service = createOrderService(createOrdersDAO(order1, order2, order3, order4)); // when List<Order> orders1 = service.getOrders(traderWithNoPerms, searchAccount, searchIsin); List<Order> orders2 = service.getOrders(traderWithPerms, searchAccount, searchIsin); List<Order> orders3 = service.getOrders(traderWithInactivePerms, searchAccount, searchIsin); // then assertThat(orders1).isEmpty(); assertThat(orders2).containsOnly(order3, order4); assertThat(orders3).isEmpty(); }
  • 64. Specifying Inputs - Techniques • Test Data Builder Pattern - with tool support • Make-It-Easy - addresses Builder class boiler-plate • Model Citizen - annotation-based “Blueprints”
  • 65. Specifying Inputs - Techniques • Test Data Builder Pattern - Advantages • Your test / spec documents only the inputs that matter • Decouples your test from being impacted by a wide range of changes • Supports “declarative” code style + useful templates • user = EXPIRED_USER.but( with ( aBalance, 100));
  • 66. Specifying Inputs - Techniques • Test Data Builder Pattern - Disadvantages • Requires much new test-supporting code • Chained methods take getting used to
  • 67. Specifying the System State • State of the SUT / the Test Context • Communicating what the context, limitations and boundaries are • Isolating your test from certain dependencies / interactions • Use of Test Doubles • Hand-rolled Test Double • common misnomer Mocking Frameworks
  • 68. Specifying the System State • Problems + solutions for Specifying Inputs normally apply • Unique to System State is your Test Isolation requirements
  • 69. Specifying the System State - Techniques • Hand-rolling Test Doubles • Using Test Isolation frameworks to provide Test Doubles • “Mocking” + “Mocks Aren’t Stubs”
 http://martinfowler.com/articles/mocksArentStubs.html • Dummy / Fake / Stubs / Mocks / Spies
  • 70. Specifying the System State - Techniques • Stub being used in a state-based test
  • 71. @Test public void // Stubbing system state (Mockito) + using helpers + hiding irrelevant details getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v2() { // given - inputs String searchAccount = "TRDRZ", searchIsin = "GLD24680"; Trader trader = createTraderWithPermissionsFor(createTradingAccount("TRDRZ")); // given - system state Order order1 = createOrder("TRDRZ", createFuture("GLD24680")), order2 = createOrder("TRDRZ", createFuture("GLD24680")); OrdersDAO ordersDAOStub = mock(OrdersDAO.class); given(ordersDAOStub.findBy(searchAccount, searchIsin)).willReturn(Arrays.asList(order1, order2)); OrderSearchService service = createOrderService(ordersDAOStub); // when List<Order> orders = service.getOrders(trader, searchAccount, searchIsin); // then assertThat(orders).containsOnly(order1, order2); }
  • 72. @Test public void // Stubbing system state + stubbing inputs (Mockito) getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v3() { // given - inputs String searchAccount = "TRDRZ", searchIsin = "GLD24680"; // Trader trader = createTraderWithPermissionsFor(createTradingAccount("TRDRZ")); Trader traderStub = mock(Trader.class); Permission permissionStub = mock(Permission.class); given(traderStub.getPermissions()).willReturn(Arrays.asList(permissionStub)); TradingAccount tradingAccountStub = mock(TradingAccount.class); given(permissionStub.getTradingAccount()).willReturn(tradingAccountStub); given(tradingAccountStub.isActive()).willReturn(Boolean.TRUE); given(tradingAccountStub.getTradingFirm()).willReturn(new TradingFirm("", searchAccount)); // given - system state Order order1 = createOrder("TRDRZ", createFuture("GLD24680")), order2 = createOrder("TRDRZ", createFuture("GLD24680")); OrdersDAO ordersDAOStub = mock(OrdersDAO.class); given(ordersDAOStub.findBy(searchAccount, searchIsin)).willReturn(Arrays.asList(order1, order2)); OrderSearchService service = createOrderService(ordersDAOStub); // when List<Order> orders = service.getOrders(traderStub, searchAccount, searchIsin); // then assertThat(orders).containsOnly(order1, order2); }
  • 73. @Test public void // Stubbing system state + stubbing input (Mockito w/ Deep Stubs) getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v3_1() { // given - inputs String searchAccount = "TRDRZ", searchIsin = "GLD24680"; // Trader trader = createTraderWithPermissionsFor(createTradingAccount("TRDRZ")); Trader traderStub = mock(Trader.class); Permission permissionStub = mock(Permission.class, RETURNS_DEEP_STUBS); given(traderStub.getPermissions()).willReturn(Arrays.asList(permissionStub)); given(permissionStub.getTradingAccount().isActive()).willReturn(Boolean.TRUE); given(permissionStub.getTradingAccount().getTradingFirm().getCode()).willReturn(searchAccount); // given - system state Order order1 = createOrder("TRDRZ", createFuture("GLD24680")), order2 = createOrder("TRDRZ", createFuture("GLD24680")); OrdersDAO ordersDAOStub = mock(OrdersDAO.class); given(ordersDAOStub.findBy(searchAccount, searchIsin)).willReturn(Arrays.asList(order1, order2)); OrderSearchService service = createOrderService(ordersDAOStub); // when List<Order> orders = service.getOrders(traderStub, searchAccount, searchIsin); // then assertThat(orders).containsOnly(order1, order2); }
  • 74. Specifying the System State - Techniques • A Mock being used in a interaction-based test
  • 75. @Test public void // Using a Mock to test interactions + Stubbing system state (Mockito) getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v4_1() { // given - inputs String searchAccount = "TRDRZ", searchIsin = "GLD24680"; Trader trader = createTraderWithPermissionsFor(createTradingAccount("TRDRZ")); // given - system state Order order1 = createOrder("TRDRZ", createFuture("GLD24680")), order2 = createOrder("TRDRZ", createFuture("GLD24680")); OrdersDAO ordersDAOStub = mock(OrdersDAO.class); given(ordersDAOStub.findBy(searchAccount, searchIsin)).willReturn(Arrays.asList(order1, order2)); PermissionService permissionServiceMock = mock(PermissionService.class); OrderSearchService service = createOrderServiceV2(ordersDAOStub, permissionServiceMock); // when List<Order> orders = service.getOrders(trader, searchAccount, searchIsin); // then verify(permissionServiceMock).hasViewPermissions(trader, order1); verify(permissionServiceMock).hasViewPermissions(trader, order2); }
  • 76. @Test public void // Using a Mock to test interactions + Stubbing system state (Mockito) getOrders_GivenTraderWithAccountPerms_SearchByAccountAndIsin_ThenReturnsMatchingOrders_v4_2() { // given - inputs String searchAccount = "TRDRZ", searchIsin = "GLD24680"; Trader trader = createTraderWithPermissionsFor(createTradingAccount("TRDRZ")); // given - system state Order order1 = createOrder("TRDRZ", createFuture("GLD24680")), order2 = createOrder("TRDRZ", createFuture("GLD24680")); OrdersDAO ordersDAOStub = mock(OrdersDAO.class); given(ordersDAOStub.findBy(searchAccount, searchIsin)).willReturn(Arrays.asList(order1, order2)); PermissionService permissionServiceMock = mock(PermissionService.class); given(permissionServiceMock.hasViewPermissions( any(Trader.class), any(Order.class))).willReturn(Boolean.TRUE); OrderSearchService service = createOrderServiceV2(ordersDAOStub, permissionServiceMock); // when List<Order> orders = service.getOrders(trader, searchAccount, searchIsin); // then verify(permissionServiceMock).hasViewPermissions(trader, order1); verify(permissionServiceMock).hasViewPermissions(trader, order2); assertThat(orders).containsOnly(order1, order2); }
  • 77. Specifying the System State - Techniques • Often a test will have • Zero to many Stubs. Zero or one Mock • Mocks help you answer questions about what happened • When a Mock is present, you ask it something at the end • A Stub should not be asserted against • There to help create + control the System State for the test
  • 78. Specifying the Event • Often trivial • Might be a sequence of events that we want to fire • Sequence might actually be context / SUT setup • Practices? Tools?
  • 79. Specifying it All Together • Spring MVC Test support / DSL @Test public void helloWorld() throws Exception { mockMvc.perform(get("/hello").accept(MediaType.TEXT_PLAIN)) .andDo(print()) // print the request/response in the console .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.TEXT_PLAIN)) .andExpect(content().string("Hello World!")); }
  • 80. Principles, Practices + Tool Support • Structural Principles / Characteristics • Qualities of a “good” test / spec • Practices + Tools for improving test qualities
  • 81. Continuous Testing • Continuous Integration - development practice • CI tools - run automated tests frequently • Continuous Testing - development practice • CT tools - run automated tests locally, frequently • e.g. Infinitest, JUnitMax
  • 82. Overview • Automating • Testing Categories + Scope • Development Testing • Principles, Practices + Tool Support
  • 83. Summary • Tools/APIs just help with heavy lifting • Sound coding principles are paramount • Creating unreadable and unmaintainable test, even with “the right” tools • Unreadable + high maintenance tests will hinder efficiency
  • 84. Summary • The power of marginal improvements • for … each, <> operator, closure support, Guava • Continuous iterative improvements nudge us forward • Continuous iterative improvements push down cost of reading, writing, evolving tests • Drives the cost down to “mass market” level • Beyond Java
  • 85. Resources • Books • GOOS - Growing Object Oriented Software, Guided By Tests • Effective Unit Testing • Working Effectively with Legacy Code • xUnit Test Patterns - Refactoring Test Code
  • 86. Resources • Podcasts • • Hanselminutes - Roy Asherove - The Art of Unit Testing • • Sustainable Test Driven Development (Series) Hanselminutes - Quetzal Bradley - Beyond Unit Testing All Code Examples are available at • http://github.com/cathalking/devtestevolution