scrum gathering 2012 shanghai_工程实践与技术卓越分会场:how to write unit test for new...

34
How to Write Unit Test for New Code on top of Legacy Code Joseph Yao

Upload: letagilefly

Post on 21-May-2015

1.475 views

Category:

Technology


1 download

DESCRIPTION

讲师:姚若舟 拥有十多年的软件开发经验和多年的项目管理经验,目前任职于欧特克(Autodesk)中国研发中心担任项目经理,提供内部团队敏捷实践的指导和培训。 作为组织者参与了2011年敏捷之旅上海的组织工作。同时在敏捷社区的活动中,做过多次有关测试驱动开发的分享,并通过组织Coding Dojo来推广测试驱动开发和结对编程。 个人喜爱Coding Kata,关注如何将测试驱动开发和相关工程实践更好的结合到公司的实际项目开发中,以及如何成为一个杰出的Scrum Master和敏捷教练来帮助团队和组织成长 话题介绍: 希望通过这个演讲,可以让听众明白如何在遗留代码基础上通过简单有效的设计和隔离来给新代码添加单元测试,从而让团队停止产生相应的技术负债。而且,如何拥有具备可测性的设计和做到有效的隔离,并不像有些人想的那么复杂和困难。

TRANSCRIPT

Page 1: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

How to Write Unit Test for

New Code on top of Legacy Code

Joseph Yao

Page 2: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Please Read This Great Book!

Page 3: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Unit Test 101

Page 4: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

What’s Unit Test?

Unit tests is the idea that they are tests in

isolation of individual components of software

- Michael C. Feathers

Page 5: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

What’s Legacy Code?

Legacy code is simply code without tests.

Without tests is bad code. It doesn't matter how well written

it is; it doesn't matter how pretty or object-oriented or well-

encapsulated it is. With tests, we can change the behavior

of our code quickly and verifiably. Without them, we really

don't know if our code is getting better or worse.

- Michael C. Feathers

Page 6: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Make New Code Testable

Page 7: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

How to Initialize Dependency

Need

Legacy Code

New Code

Page 8: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Your Code Looks Like This? public class Car {

private Engine engine;

public Car() {

engine = new Engine();

}

public void run() {

engine.start();

}

public String status() {

return engine.speed() > 0 ? “Move”: “Stop”;

}

}

Page 9: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Your Test Looks Like This?

Are you really do the unit test for Car?

public class TestCar {

@Test public void move() {

Car car = new Car();

car.run();

assertEquals(“Move”, car.status());

}

}

Page 10: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Code for Car with Isolation public class Car {

private IEngine engine;

public Car(IEngine theEngine) {

engine = theEngine;

}

public void run() {

engine.start();

}

public String status() {

return engine.speed() > 0 ? “Move”: “Stop”;

}

}

Page 11: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Test for Car with Isolation public class TestCar {

@Test public void move() {

IEngine engineMock =

new EngineMock(10);

Car car = new Car(engineMock);

car.run();

assertEquals(“Move”, car.status());

}

}

In fact, you don’t care how engine speed is calculated

Page 12: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

I Need to Test a Private Method

Page 13: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Your Code Looks Like This?

How can I test the private method?

public class Order {

public void addItem(Item item) {

if (isValidItem(item)) {

items.add(item);

}

}

private boolean isValidItem(Item item) {

more than 500 lines code…

}

}

Page 14: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Does Your Class Have too many Responsibilities?

Page 15: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Maybe This Code is Better? public class Order {

private ItemValidator validator;

public Order (ItemValidator

theValidator) {

validator = theValidator;

}

public void addItem (Item item) {

if (validator.

isValidItem(item)) {

}

}

}

Page 16: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Single Responsibility Principle

Page 17: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

“Don’t Do it” unless you have a Very Strong Reason

+

Page 18: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Good design is testable, and

design that isn't testable is bad

- Michael C. Feathers

Page 19: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Isolate Legacy Code

Page 20: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Too hard to get Legacy Code under Test

Page 21: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Some New Code needed to be added

public class Customer {

public void purchase() {

more than 500 lines of legacy code…

}

}

We need to log this customer purchase action after it done.

Can you add unit test for new code with no impact to legacy code?

Page 22: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Add new code by Sprout Method

public class Customer {

public void purchase() {

purchaseWithoutLog();

logPurchaseAction();

}

protected void purchaseWithoutLog() {

more than 500 lines of legacy code…

}

private void logPurchaseAction() {

Your new code here…

}

}

Page 23: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Your Test May Look Like This public class TestCustomerPurchaseLog extends

Customer {

@Test public void log() {

Customer customer =

new TestCustomerPurchaseLog();

customer.purchase();

… code to verify the log action …

}

protected void purchaseWithoutLog() {}

}

Page 24: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Some other Alternative Ways

• Sprout Method – the Sample Code

• Wrap Method

• If the legacy class is hard to be put into test

harness

o Sprout Class

o Wrap Class

o This is quite useful when you can’t easily isolate the

dependency for legacy class

Page 25: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

I have a Monster Dependency to Isolate

Page 26: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

How to Isolate “HttpServletRequest”?

public class ARMDispatcher {

public void populate (HttpServletRequest request) {

String [] values

= request.getParameterValues(pageStateName);

if (values != null && values.length > 0) {

marketBindings.put(

pageStateName + getDateStamp(), values[0]);

}

}

}

Page 27: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

You only Need to get the Parameter Value

public class ARMDispatcher {

public void populate (ParameterSource source) {

String value =

source.getParameterForName(pageStateName);

if (value != null) {

marketBindings.put(

ageStateName + getDateStamp(), value);

}

}

}

Page 28: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

I need D, but it’s from A.getB().getC().getD()

Page 29: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Your Code may Look like this public class Customer {

private Orders orders;

public List<String> getAllItemNamesOfLatestOrder(){

List<String> allNames =

new ArrayList<String>();

Item[] items =

orders.getLatestOrder().getAllItems();

for (Item item : items) {

allNames.add(item.getName());

}

return allNames;

}

}

Page 30: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

You need to Isolate the way to Get Items

public class Customer {

private Orders orders;

public List<String> getAllItemNamesOfLatestOrder(){

List<String> allNames =

new ArrayList<String>();

Item[] items = getAllItemsOfLatestOrder();

for (Item item : items) {

allNames.add(item.getName());

}

return allNames;

}

protected Item[] getAllItemsOfLatestOrder() {

return orders.getLatestOrder().getAllItems();

}

}

Page 31: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Suggestions

Page 32: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Test Behavior

instead of

Test Implementation

Page 33: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Dependency Isolation Tool is

Evil

Page 34: Scrum Gathering 2012 Shanghai_工程实践与技术卓越分会场:how to write unit test for new code based on legacy code(姚若舟)

Q & A

新浪微博 @姚若舟

TDD Code Kata @tudou.com/home/yaoruozhou