0% found this document useful (0 votes)
2 views

Week 15- Test Driven Development_UnitTesting

Test-Driven Development (TDD) is a software development methodology that emphasizes writing tests before the actual code to ensure that the code meets specified requirements. It includes two levels: Acceptance TDD, which focuses on overall system behavior, and Developer TDD, which targets individual functionalities. Unit testing, a critical component of TDD, involves testing individual units of code to validate their correctness and maintain code quality, with JUnit being a popular framework for implementing these tests.

Uploaded by

izhanizi20
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views

Week 15- Test Driven Development_UnitTesting

Test-Driven Development (TDD) is a software development methodology that emphasizes writing tests before the actual code to ensure that the code meets specified requirements. It includes two levels: Acceptance TDD, which focuses on overall system behavior, and Developer TDD, which targets individual functionalities. Unit testing, a critical component of TDD, involves testing individual units of code to validate their correctness and maintain code quality, with JUnit being a popular framework for implementing these tests.

Uploaded by

izhanizi20
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 44

Test-Driven Development

UNIT TESTING
What is Test Driven Development?

Test-Driven Development (TDD) originally was created as part of the


Extreme Programming (XP) methodology, where it was known as ‘Test-
First’ concept. The idea is that developers generally write their tests
after the code is written and therefore are only testing the
functionality as they wrote it, as opposed to testing it to make sure it
works the way it was actually intended!
The usual process follows these steps:

The requirements are defined, from these requirements, the development team creates the design of
a specific part of the application, and the developers then write the necessary production code. They
will then document the code and create tests (manual and/or automated) to test that the system
works as expected. The testers then run the tests, and any bugs found are then fixed.
This approach means that (a) you are only testing the system based on the code that was already
written, and (b) when you change the system to fix one of the identified bugs, there is not an
automated way to make sure you have not changed the design or introduced new bugs.
With a test-driven-development approach, the process is
somewhat different:

 The requirements are used to directly create the acceptance tests (the ones that
determine if the system meets the needs of the users / stakeholders as defined by
the requirements).
 The team uses the requirements and acceptance tests to design the system and create
the appropriate unit tests. These tests are now executed, and (since nothing yet
exists) will naturally fail. However the unit tests are now the detailed definition of the
requirements.
 The team then writes the production code so that the tests now pass. The
new code written at this stage is not perfect, and may, for example, pass
the test in an inelegant way. That is acceptable because later steps
improve it. At this point, the only purpose of the written code is to pass
the test.
 Now the code should be cleaned up as necessary. Move code from where it
was convenient for passing the test to where it logically belongs. Remove
any duplication you can find. Make sure that variable and method names
represent their current use. By re-running the test cases, the developer
can be confident that code refactoring is not damaging any existing
functionality.
What is Acceptance TDD and Developer TDD?
There are two levels of TDD:
Acceptance TDD (ATDD): With ATDD you write a single acceptance test.
This test fulfills the requirement of the specification or satisfies the
behavior of the system. After that write just enough
production/functionality code to fulfill that acceptance test. Acceptance
test focuses on the overall behavior of the system. ATDD is also known
as Behavioral Driven Development (BDD).
Developer TDD: With Developer TDD you write single developer
test i.e. unit test and then just enough production code to fulfill
that test. The unit test focuses on every small functionality of the
system. Developer TDD is simply called as TDD. The main goal of
ATDD and TDD is to specify detailed, executable requirements for
your solution on a just in time (JIT) basis. JIT means taking only
those requirements in consideration that are needed in the system.
So increase efficiency.
Advantages of TDD:
1. Better Designed, cleaner and more extensible code.
 It helps to understand how the code will be used and how it interacts
with other modules.
 It results in better design decision and more maintainable code.
 TDD allows writing smaller code having single responsibility rather
than monolithic procedures with multiple responsibilities. This makes
the code simpler to understand.
 TDD also forces to write only production code to pass tests based on user
requirements.
2. Confidence to Refactor.
If you refactor code, there can be possibilities of breaks in the code. So
having a set of tests you can fix those breaks before release.
3. Good for teamwork.
In the absence of any team member, other team members can easily pick
up and work on the code. It also aids knowledge sharing, thereby making
the team more effective overall.
4. Good for Developers.
Though developers have to spend more time in writing TDD test cases, it
takes a lot less time for debugging and developing new features. You will
write cleaner, less complicated code.
What is Unit Testing?

UNIT TESTING is a type of software testing where individual units or


components of a software are tested. The purpose is to validate that each
unit of the software code performs as expected. Unit Testing is done during
the development (coding phase) of an application by the developers.
Unit Tests isolate a section of code and verify its correctness. A unit may be
an individual function, method, procedure, module, or object. Unit testing
is a part of the test-driven development (TDD) methodology.
Unit Testing Tools:
There are several automated unit test software available to assist with unit testing. For
example:
• Junit: Junit is a free to use testing tool used for Java programming language. It
provides assertions to identify test method. This tool test data first and then inserted
in the piece of code.
• Nunit: NUnit is widely used unit-testing framework use for all .net languages. It
is an open source tool which allows writing scripts manually. It supports data-driven
tests which can run in parallel.
• PHPUnit: PHPUnit is a unit testing tool for PHP programmer. It takes small
portions of code which is called units and test each of them separately.
Since Unit testing is a part of the test-driven development (TDD)
methodology so let’s look at an example.

Here in this example, we will define a class password. For this class, we
will try to satisfy following conditions.
A condition for Password acceptance:
The password should be between 5 to 10 characters.

First, we write the code that fulfills all the above requirements.
Scenario 1: Torun the unit test, we create class PasswordValidator.
Now, We will run above class TestPassword. Output is PASSED as shown
below:
Scenario 2: Here we can see in method TestPasswordLength() there is no
need of creating an instance of class PasswordValidator. Instance means
creating an object of class to refer the members (variables/methods) of
that class.
We can call the isValid () method directly by
PasswordValidator. IsValid ("Abc123"). (See image below)
So we Refactor (change code) as below:
Scenario 3: After refactoring, the output shows failed status (see image
below) this is because we have removed the instance. So there is no
reference to non–static method isValid ().
So we need to change this method by adding "static" word
before Boolean as
public static boolean isValid (String password)
Refactoring Class PasswordValidator () to remove above error
to pass the test.
Output:
After making changes to class PasswordValidator() if we run
the test then the output will be PASSED as shown below.
Assert Methods:
In JUnit, multiple assert methods can be used to test various conditions within a single
test method.
1. assertTrue and assertFalse: These methods verify whether a condition is true or
false, respectively:
import static org.junit.Assert.*; import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;

public class ExampleTest { public class ExampleTest {

@Test @Test
public void testBooleanConditions() { public void testBooleanConditions() {
assertTrue(5 > 2); // Passes because 5 is greater than 2 assertFalse(5 > 2); // Fails because 5 is greater than 2
assertFalse(3 < 1); // Passes because 3 is not less than 1 assertTrue(3 < 1); // Fails because 3 is not less than 1
} }
} }
2. assertNotNull and assertNull

These methods check for non-null and null values, respectively:


import static org.junit.Assert.*; import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;

public class ExampleTest { public class ExampleTest {

@Test @Test
public void testNullConditions() { public void testNullConditions() {
Object obj = new Object(); Object obj = null;
assertNotNull(obj); // Passes because obj is not null assertNotNull(obj); // Fails because obj is null

Object nullObj = null; Object nullObj = new Object();


assertNull(nullObj); // Passes because nullObj is null assertNull(nullObj); // Fails because nullObj is not null
} }
} }
3. assertArrayEquals
This method verifies if two arrays are equal:
import static org.junit.Assert.*; import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;

public class ExampleTest { public class ExampleTest {

@Test @Test
public void testArrayEquality() { public void testArrayEquality() {
int[] expectedArray = {1, 2, 3}; int[] expectedArray = {1, 2, 3};
int[] resultArray = {1, 2, 3}; int[] resultArray = {1, 3, 2}; // Different order

assertArrayEquals(expectedArray, resultArray); // Passes assertArrayEquals(expectedArray, resultArray); // Fails


because arrays have same content in the same order because arrays have different content order
} }
} }
4. assertSame and assertNotSame
These methods check for object references:
import static org.junit.Assert.*; import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;
public class ExampleTest { public class ExampleTest {
@Test @Test
public void testObjectReferences() { public void testObjectReferences() {
String str1 = "Hello"; String str1 = "Hello";
String str2 = new String("Hello"); String str2 = "Hello"; // Same content but different objects
assertNotSame(str1, str2); // Passes because str1 and assertNotSame(str1, str2); // Fails because str1 and str2
str2 refer to different objects refer to the same object

String str3 = str1; String str3 = new String("Hello");


assertSame(str1, str3); // Passes because str1 and str3 assertSame(str1, str3); // Fails because str1 and str3 refer
refer to the same object to different objects
} }
} }
Unit Testing Advantage:
Unit testing allows the programmer to refactor code at a later date,
and make sure the module still works correctly. The procedure is to
write test cases for all functions and methods so that whenever a
change causes a fault, it can be quickly identified and fixed.
Due to the modular nature of the unit testing, we can test parts of the
project without waiting for others to be completed.
What is JUnit?

JUnit is a popular open-source framework for Java that's primarily used


for writing and running unit tests. It provides a set of annotations,
assertions, and APIs to help developers create test cases to verify that
individual parts of their Java code (usually methods) are working as
expected. By using JUnit, developers can automate the testing process,
making it easier to catch bugs, ensure code behaves correctly under
different scenarios, and maintain code quality during development.
Why is unit testing important in software development?

Unit testing is crucial in software development for several reasons:


Bug Detection
Maintain Code Quality
Refactoring Support
Documentation
Regression Testing
Faster Development
Improved Design
Explaining the basic structure of a JUnit test case.
In JUnit, a test case is written as a Java class that contains methods
annotated with specific JUnit annotations. The basic structure of a JUnit
test case involves the following components:
Imports: Import necessary classes from the JUnit framework and other
packages as needed.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
// Other necessary imports

Test Class: Create a Java class for the test case. This class will contain
one or more test methods.
public class MyTestCase {
// Test methods will be defined here
}
Test Methods: Write methods to test specific units (usually methods) of
your code. Each test method should be annotated with @Test to indicate
that it's a test method.
@Test
public void testAddition() {
// Test logic for addition
assertEquals(5, Calculator.add(2, 3)); // Example assertion
}

Assertions: Use assertion methods from the Assertions class to validate the expected
behavior of the code being tested. Assertions verify whether the actual result matches the
expected result.

assertEquals(expected, actual); // Checks if expected equals actual


assertTrue(condition); // Checks if the condition is true
// Other assertion methods provided by JUnit
What are assertions in JUnit? Provide examples.

Assertions in JUnit are methods used to verify whether the expected


result of a test matches the actual result. They're crucial for validating
the behavior of the code being tested. JUnit provides a wide range of
assertion methods in the org.junit.jupiter.api.Assertions class.
assertEquals
assertTrue and assertFalse
assertNull and assertNotNull
assertArrayEquals
assertSame and assertNotSame
JUnit Annotations:
@Test: The @Test annotation in JUnit is significant as it marks a method
as a test method, indicating that it should be executed by the JUnit test
runner.

Public class MyTestCase {

@Test
public void testAddition() {
assertEquals(5, Calculator.add(2, 3)); // Example test case
}
}
JUnit Annotations:

@DisplayName: Allows you to provide a custom name for a test case.


This name is displayed in the test report and can make tests more
readable and descriptive.
@Test
@DisplayName("Test for addition")
public void testAddition() {
// Test logic
}
@BeforeEach and @AfterEach:

@BeforeEach: Denotes a method that should be executed before each


test method in the class. It's used to set up the test environment or
initialize resources.
@AfterEach: Denotes a method that should be executed after each test
method in the class. It's used for cleanup activities or releasing
resources.
@BeforeEach
public void setUp() {
// Set up test environment
}

@AfterEach
public void tearDown() {
// Clean up after the test
}
JUnit Annotations:
@Disabled:
Temporarily disables a test method without removing it from the test
suite. Useful when a test case is not yet implemented or needs to be
skipped temporarily.

@Disabled("Test is under maintenance")


@Test
public void testSomething() {
// Test logic
}
JUnit Annotations:

@Timeout:
Specifies a maximum execution time for a test method. If the test
method takes longer than the specified time, it will fail.
@Test
@Timeout(5) // Time in seconds
public void testMethodWithTimeout() {
// Test logic that should execute within 5
seconds
}
Parameterized testing in JUnit

Parameterized testing in JUnit allows you to run the same test logic with
different sets of parameters. It's useful when you want to test a method
or functionality with multiple inputs and ensure that it behaves correctly
across various scenarios. JUnit provides @ParameterizedTest to enable
parameterized testing.
Let's say we want to test a simple Calculator class's
addition method using parameterized testing.
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.*;

public class CalculatorParameterizedTest {

@ParameterizedTest
@CsvSource({ "2, 3, 5", "-1, 1, 0", "10, -5, 5" }) // Parameters: input1, input2, expectedSum
public void testAddition(int a, int b, int expectedResult) {
Calculator calculator = new Calculator();
int result = calculator.add(a, b);
assertEquals(expectedResult, result);
}
}
In this example:

@ParameterizedTest annotation is used to mark the test method


testAddition as a parameterized test.
@CsvSource provides the input parameters and their expected results in a
CSV format.
The testAddition method takes three parameters: int a, int b, and int
expectedResult.
The Calculator class's add method is tested with different sets of input
parameters to validate the addition functionality.
Explain the concept of test-driven development (TDD)
and its relationship with JUnit.

Test-Driven Development (TDD) is a software development approach that


revolves around writing tests before writing the actual production code.
It follows a cycle of writing a failing test, writing the minimum code to
pass that test, and then refactoring the code while ensuring the tests
still pass. JUnit, being a popular testing framework in Java, is commonly
used to implement TDD.
Steps in Test-Driven Development (TDD):
1. Write a Failing Test:
Start by writing a test that describes the expected behavior of the code you're about to
implement. This test should initially fail because the corresponding code doesn't exist yet.
2. Write Minimum Code to Pass Test:
Write the minimum amount of code required to make the failing test pass. The focus is on
writing just enough code to satisfy the test case.
3. Run Tests:
Run the tests to ensure the newly written test passes and the existing tests still pass.
4. Refactor Code:
Refactor the code to improve its design, readability, or performance while ensuring that all
tests continue to pass after refactoring.
5. Repeat the Cycle:
Repeat this cycle for each new functionality or requirement, continually adding tests,
writing code, and refactoring.
Relationship with JUnit:
JUnit is closely associated with TDD as it provides a framework for writing and
executing unit tests in Java. When practicing TDD with JUnit:
Writing Tests First: Developers write test cases using JUnit that describe the
expected behavior of the code to be implemented before writing the actual
production code.
Red-Green-Refactor Cycle: JUnit helps in the Red-Green-Refactor cycle of
TDD. Initially, the test fails (Red), then the minimum code is written to make it
pass (Green), and finally, the code is refactored while ensuring the tests still
pass.
Continuous Testing: Developers frequently run tests using JUnit to validate
their changes and ensure they don't break existing functionalities.
Example: Here's an example demonstrating the TDD
cycle using JUnit:
Write Failing Test (Red):
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class CalculatorTest {

@Test
public void testAddition() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals(5, result); // Failing test
}
}
Write Minimum Code to Pass Test (Green):
public class Calculator {
public int add(int a, int b) {
return a + b; // Minimum code to pass the test
}
}

Run Tests (Verify Green):


The test should pass now.

Refactor (if needed):


Refactor code for improvements without changing the behavior.

Repeat for Additional Functionality:


Continue the cycle for other functionalities or requirements.

TDD with JUnit encourages developers to focus on writing testable and reliable code, resulting in better-designed
software and a comprehensive suite of tests to validate its behavior.
Lets conclude…
“Unit testing” is writing many small tests that each
test one very simple function or object behavior. TDD is
a thinking process that results in unit tests, and
“thinking in tests” tends to result in more fine-grained
and comprehensive testing, and an easier-to-extend
software design.

You might also like