Art of unit testing: How developer should care about code quality

38
The Art of Unit Testing: How developer should care about code quality Dmytro Patserkovskyi

Transcript of Art of unit testing: How developer should care about code quality

The Art of Unit Testing:How developer should care about code quality

Dmytro Patserkovskyi

2 CULTURE

What is

SOFTWARE TESTING?

DIVE INTO THE HISTORY

3

Let’s look over the philosophy of software testing 100 years ago.

DIVE INTO THE HISTORY

Debugging

4

Automated Testing

Manual Testing

SUnit(1989)JUnit(2001)

1970’s1950’s

DIVE INTO THE HISTORY5

Kent Beck: 1989: "Simple Smalltalk Testing: With Patterns"

Software testing it the complex of techniques include the process of executing a program or application with the intent of

finding software bugs.

6

PLACE OF UNIT TESTS

7

Let’s find place of unit tests in the ocean of Software Testing.

AUTOMATION

MANUAL

PLACE OF UNIT TESTS8

DEBUGGING

SOFTWARETESTING

COMPONENT

INTEGRATION

UNIT

PLACE OF UNIT TESTS9

SYSTEM

AUTOMATIONTESTING

UNIT TESTS

10

Unit tests – part of code that are designed to ensure the smallest divisible pieces of code (units or components) are working the way they were intended.

UNIT TESTS@BeforeClass

public void testBeforeSuite() {

prepareMailService();

}

@Test(groups = "mail")

public void testEmailService() {

MailService service= generateMailService();

Assert.assertNotNull(service);

}

@Test(dependsOnMethods = { "testMailService" }, groups="mail")

public void testSending() {

MailService service= generateMailService();

Assert.assertTrue(service.sendMail(subject, message));

}

11 Test

Configuration

Preparations

Assertions

Grouping

UNIT TESTS

Test - part of code, that check logic of another part of code.

Assertions - mechanism for checking results of lome logic, defines if our unit test will be success or failed.

Configuration - all attributes and properties of test.

Preparations - part of code, that prepares state and context for our tests.

12

UNIT TESTS

Grouping - associates unit tests into groups and suites. Each group or suite should have common property or characteristic.

13

GROUP SUITE

Contains Methods

Depends on Groups

Annotations Config

Runs Separately

Contains Groups

Flex Configuring

Configuration in XML

Runs Separately

14 CULTURE

WHY ?

UNIT TESTS

Test Dependencies. If a dependent method is fail, all the subsequent test methods will be skipped, NOT failed.

@Test(groups = "mail")

public void testEmailService() {

MailService service= generateMailService();

Assert.assertNotNull(service);

}

@Test(dependsOnMethods = { "testMailService" }, groups="mail")

public void testSending() {

MailService service= generateMailService();

Assert.assertTrue(service.sendMail(subject, message));

}

15

UNIT TESTS

Parameterized Tests. Run your single test for different data sets.

@DataProvider

public Object[][] smtpHosts() {

return new Object[][]{ {"smtp.host1"}, {"mail.host2"} };

}

@Test(dataProvider = "smtpHosts", groups="mail")

public void testSending(String host) {

MailService service= generateMailService(host);

Assert.assertTrue(service.sendMail(subject, message));

}

16

UNIT TESTS

Rich @Before and @After configuration.

@BeforeSuite - @AfterSuite

@BeforeTest - @AfterTest

@BeforeGroups - @AfterGroups

@BeforeClass - @AfterClass

@BeforeMethod - @AfterMethod

17

PRINCIPLES OF UNIT TESTS

18

We know how to write units! Don’t we?

PRINCIPLES OF UNIT TESTS

19

Principles of Unit Tests:

▹ Easy and fast to run▹ Unit▹ Simple▹ Independent▹ Checking all cases▹ Isolate

PRINCIPLES OF UNIT TESTS20

ARCHITECTURE OF PROJECT21

Let’s imagine component for sending emails, and try to cover it with unit tests.

Services:

▹ MailService - service for sending emails. Based on Javax Mail.▹ LogsStorage - service for collecting logs about mails sending and flushing it

to persistent storage.▹ LogsMailService - service for work with emails and logs.

PRINCIPLES OF UNIT TESTS

// Bad

@Test(groups = "mail")

public void testEmailSending() {

MailService service= generateMailService();

Assert.assertNotNull(service);

MailBuilder builder= new MailBuilder();

Mail mail = new MailBuilder().

.newMail()

.addSubject("my mail")

.addRecipient(firstRecipient)

.addRecipient(secondRecipient)

.build();

Assert.assertNotNull(mail);

Assert.assertEquals(EXPECT, mail.getSubject());

...

Assert.assertTrue(service.sendMail(mail));

}

22

// Good

@BeforeTest

public void initialize() {

service= generateMailService();

}

@Test(groups = "mail")

public void testEmailSending() {

Assert.assertTrue(

service.sendMail(prepareMail())

);

}

Unit & Simple - each test covers one piece of code.

PRINCIPLES OF UNIT TESTS

// Bad@Test(groups = "mail")

public void testEmailLogsSuccessful() {

mailLogsStorage.logGood();

Assert.assertEquals(1, mailLogsStorage.getCountGood());

Assert.assertEquals(1, mailLogsStorage.getCountAll());

}

@Test(groups = "mail")

public void testEmailLogsFailed() {

mailLogsStorage.logBad();

Assert.assertEquals(1, mailLogsStorage.getCountBad());

Assert.assertEquals(2, mailLogsStorage.getCountAll());

}

23

// Good@Test(groups = "mail")

public void testEmailLogsSuccessful() {

mailLogsStorage.logGood();

Assert.assertEquals(1, mailLogsStorage.getCountGood());

Assert.assertEquals(1, mailLogsStorage.getCountAll());

}

@Test(groups = "mail")

public void testEmailLogsFailed() {

mailLogsStorage.logBad();

Assert.assertEquals(1, mailLogsStorage.getCountBad());

Assert.assertEquals(1, mailLogsStorage.getCountAll());

}

@AfterMethodpublic void cleanAfter() {

mailLogsStorage.cleanState();

}

Independent - test should runs with random order and in parallel.

PRINCIPLES OF UNIT TESTS

// Bad

@BeforeTest

public void initialize() {

service= generateMailService();

}

@Test(groups = "mail")

public void testEmailSending() {

Assert.assertTrue(

service.sendMail(prepareGoodMail())

);

}

24

// Good

@BeforeTest

public void initialize() {

service= generateMailService();

}

@Test(groups = "mail")

public void testEmailSendingSuccess() {

Assert.assertTrue(

service.sendMail(prepareGoodMail())

);

}

@Test(groups = "mail")

public void testEmailSendingFailed() {

Assert.assertFalse(

service.sendMail(prepareBadMail())

);

}

Checking all cases - tests for success case is not enough.

PRINCIPLES OF UNIT TESTS

// Bad

@Before

public void before() {

service = new LogMailService();

service.setMailService(new MailService());

}

@Test(groups = "mail")

public void testEmailService() {

Assert.assertTrue(service.sendMail(mail));

}

25

// Good

@Before

public void before() {

service = new LogMailService();

}

@Test(groups = "mail")

public void testEmailService() {

MailService mock = mock(MailService.class)

when(mock.sendMail(mail))

.thenReturn(true);

service.setMailService(mock)

Assert.assertTrue(service.sendMail(mail));

times(1, mock.sendMail(mail));

}

Isolate - encapsulated logic should be covered with separated tests.

ISOLATION PHILOSOPHY

26

Mocking - creating objects that simulate the behaviour of real objects to isolate part of code for unit testing.

ISOLATION PHILOSOPHY

27

ISOLATION PHILOSOPHY28

Mocks - simulated objects that mimic the behavior of real objects in controlled ways.

Partial mocks - object, created as shell for real one, when you need to stub several methods of an object leaving the remainder free to respond to calls

normally.

ISOLATION PHILOSOPHY29

@Before

public void before() {

service = new LogMailService();

}

@Test

public void testEmailService() {

MailService mock = mock(MailService.class)

when(mock.sendMail(mail))

.thenReturn(true);

service.setMailService(mock)

Assert.assertTrue(service.sendMail(mail));

times(1, mock.sendMail(mail));

}

Mocks

ISOLATION PHILOSOPHY30

@Test

public void testLogsRefresh() {

LogsStorage storageSpy = spy(logsStorage);

doReturn(true).when(storageSpy).flush();

storageSpy.refresh();

Assert.assertEquals(1, storageSpy.getCountAll());

times(1, storageSpy.flush());

}

Partial Mocks

ISOLATION PHILOSOPHY31

Looks good… But

ISOLATION PHILOSOPHY

What if… we have private factory method?

Class MailService {

...

private Session createMailSession() {

...

}

}

32

ISOLATION PHILOSOPHY

Use dark power of

33

OR.. create a new class and move all private methods to this as

public. Use dependency injection and mocking. This can force you to

use an unwanted design. Private methods are not an option.

ISOLATION PHILOSOPHY

What if… we have calls to static method of framework?

Class MailService {

...

public Session sendMail() {

...

// Send message Transport.send(message);

}

}

34

ISOLATION PHILOSOPHY

Use dark power of

35

OR... wrap all static method calls in a separate class and use

dependency injection to mock this object. This will create an extra

layer of unnecessary classes in your application. However, this can

of course be useful if you want to encapsulate the framework to be

able to replace it.

ISOLATION PHILOSOPHY

PowerMock for legacy code with antipatterns.Mockito is enough for well constructed architecture.

36

QUESTIONS

37

THANK YOU FOR YOUR TIME

38