APP Lab. 7 Lecture 20.01.2024

Download as pdf or txt
Download as pdf or txt
You are on page 1of 39

Advanced Programming Practices

Cezary Hołub, lab. 7 20.01.2024

2024, Wrocławska Wyższa Szkoła Informatyki


Stosowanej
Unit tests
Software testing
● Determining software quality
● Testing
○ Verification – examining whether the manufactured
system meets the assumed requirements
○ Validation – checking whether the created system
meets the needs of the client (investor) - it is also
checked whether the assumed requirements meet the
needs of the client
Classification of testing methods
white-box testing - structural testing:
● unit testing - we will present later
● integration testing
● system testing
black-box testing - functional testing:
● user acceptance test (UAT)
● performance test
● load tests
● stress tests
● security tess
grey-box testing
Unit tests - what is it?
Verifying the correct operation of individual
program elements (units) in isolation from the
rest of the system
Unit tests - what is it? cont.
● Verification of the functionality of the source
code snippet - usually a function or method of a
given class
● Usually written by developers responsible for
writing the tested source code fragment -
white-box testing
● Verification of correct operation of individual
parts of the code separately before their
integration
Advantages of unit tests
The advantage of unit tests is the ability to perform fully
automated tests on an ongoing basis on modified elements
of the program, which allows you to often detect the error
immediately after its appearance and quickly locate it before
the introduction of the incorrect fragment into the program.
Unit tests are also a form of specification.
Good unit tests

understandable
easy to maintain
repeatable
granular
fast
Unit testing principles
● The goal is to verify the actual behavior of a single instance
(object) of a given class with the expected behavior
● The effect of the method called for the tested object is checked for
various arguments (parameters), the returned values are
examined
● There is at least one test case for each method - there should be
one test case for each type of initial condition and arguments
● Test cases related to one test object are gathered inside one test
class
Testing with … main() { …} ?
public class ArithmeticService {

public int add(int augend1, int augend2) {


return augend1 + augend2;
}

public static void main(String[] args) {


ArithmeticService service = new ArithmeticService();
int sum = service.add(13, 4);
if (sum == 17){
System.out.println("SUCCESS");
} else {
System.out.println("TEST FAILED");
}
}
}
Testing with … main() { …} - problems

duplicate assertion code


checking multiple conditions
displaying error or test success
initialization of the environment, test data
"Cleaning" after testing
running and adding up test results
Can be more comfortable?
Restito

Hamcrest
Unit testing on the example of JUnit
● JUnit it is a tool - programming platform - a framework within Java
technology used to create unit tests for software written in Java
● JUnit is a representative of the whole family of frameworks for unit
testing called collectively xUnit
● JUnit is in the package
○ junit.framework in JUnit version 3.8 and older
○ org.junit in JUnit version 4.0 and newer
JUnit
JUnit - Java library for creating unit tests

Features of JUnit:
● method is the smallest code snipped being under testing unit,
● test cases
● separation of tests from the code,
● many launch mechanisms
● different reports,
● integration with various programming environments

The latest version of the library can be downloaded from www.junit.org/junit4/


(junit.jar and hamcrest-core.jar).
JUnit class hierarchy in version 3.8

Test

TestResult TestCase TestSuite

MyTest
TestCase in JUnit
The base class for all test cases is TestCase, due to the somewhat unfortunate
name, it is often confused with a test case (meanwhile TestCase is a test class that
contains test cases saved as methods; therefore, the test case is a single method, not
a class). This class provides basic functions that help in testing, including default
assertions.

TestCase is with TestSuite implementation of Test interface. Together, these


classes form a tree structure in which all nodes except the leaves of the tree are
represented by TestSuite, and the leaves - by class TestCase. Thanks to this,
creation is possible suit (tests suit) and running them in the same way as individual
classes TestCase.

TestResult is a class that stores the results of completed test cases. It is created
and filled with data by subsequent classes TestCase, which are executed.

We do not inherit from the TestCase in Unit 4.x version


Test Case in JUnit - example
import static org.junit.Assert.*; //you may need to add such imports
import org.junit.Test;

public class MyClassTest {//from Junit 4.X version we do not inherit from TestCase
// We will test the MyClass class
MyClass tester = new MyClass();

@Test //annotation saying that it will be a test method


public void testMultiplication() {
assertEquals ("Multiplication", 4, 2*2); //assertion, we test if 2x2 = 4
}

@Test
public void testMultiply() {
assertEquals("10 x 0 must be 0", 0, tester.multiply(10, 0));
assertEquals("0 x 10 must be 0", 0, tester.multiply(0, 10));
assertEquals("0 x 0 must be 0", 0, tester.multiply(0, 0));
}
}
Assertions
Assertions - it is a logical condition which, if not met (false), causes the program
to be interrupted. You may need to add manual import in the class to make the
assertions visible: import static org.junit.Assert.*;

● comparison assertions
assertEquals([message], expected, actual)
checks that the values passed as parameters are equal. For primitive
types, the comparison is performed using the == operator, while for
object types the method equals()is called

● identity assertions
assertSame([message], expected, actual)
assertNotSame([message], expected, actual)
assertSame() (and analogous assertNotSame()) checks the identity
of both parameters, i.e. the operator == in all cases
Assertions cont.
reference assertions null
assertNull([message], reference)
assertNotNull([message], reference)
assertNull() and assertNotNull() they check whether the given
reference points to an object or not

logical assertions
assertTrue([message], condition)
assertFalse([message], condition)
assertTrue() and assertFalse() examine the veracity of the given
conditions

unconditional failure
fail([message])
fail() is an unconditional declaration of false assertions. This method is
useful in special situations where a specific code should never be
executed
assertEquals / assertNotEquals

import static org.junit.Assert.*;

@Test
public void shouldSumIntegers() {
ArthService service = new ArthService();
int result = service.add(12, 5);
assertEquals(17, result);
}
assertEquals / assertNotEquals

import static org.junit.Assert.*;

@Test
public void shouldSumIntegers() {
ArthService service = new ArthService();
int result = service.add(12, 5);
assertEquals("Didn’t sum correctly",17,result);
}
assertTrue / assertFalse

@Test
public void shouldRecognizeEvenNumbers() {
boolean isEven1 = service.isEven(12); //even - even
numbers, odd - odd numbers

assertTrue("Didn’t recognize even", isEven1);

}
assertTrue / assertFalse

@Test
public void shouldRecognizeEvenNumbers() {
boolean isEven1 = service.isEven(12);
boolean isEven2 = service.isEven(3);
assertTrue("Didn’t recognize even", isEven1);
assertFalse("Didn’t recognize odd", isEven2);
}
granularity of tests
@Test
public void shouldReturnTrueForEvens() {
boolean isEven = service.isEven(12);
assertTrue("Didn’t recognize even", isEven);
}

@Test
public void shouldReturnFalseForOdds() {
boolean isEven = service.isEven(3);
assertFalse("Didn’t recognize odd", isEven);
}
other assertions

String label = null;


assertNull(label); // SUCCESS
assertNotNull(label); // FAIL

String otherLabel = label;


assertSame(label, otherLabel); // SUCCESS
assertNotSame(label, otherLabel); // FAIL
other assertions

int[] expectedTab1 = new int[]{1,2,3,5,8};


int[] tab = new int[]{1,2,3,8};

assertArrayEquals(expectedTab1,tab); // FAIL
other assertions

boolean condition = false;

assertTrue(condition); // FAIL
assertFalse(condition); // SUCCESS

assertThat(condition, is(true)); // FAIL


Annotation @Ignore and timeout parameter
Annotation @Ignore allows you to temporarily remove a test case from the
group of active cases: it will not be executed at the time of testing.

@Ignore
@Test
public void badTest() {//...}

Parameter timeout in annotation @Test it is used to determine the maximum


duration of a test case. If it is exceeded, it is interrupted and the appropriate
information goes to the programmer.

@Test(timeout=500) //time in milliseconds


public void slowTest() {//...}
Test class structure
Class RomanNumberTest

create
void setUp() @Before

void testOne()

execute
Class RomanNumber void testFive()

void testNineteen()

void testThousand()

remove
void tearDown() @After
@Before - “constructor” of classes type of TestCase
In the case of TestCase class objects, the logical constructor plays any method with
the @Before annotation, and it is the correct place to create an instance of the tested
object.
public class RomanNumberTest{
RomanNumber rn = null;

@Before
public void initialize() {
rn = new RomanNumber(5); //test object
}
}
initialize() with annotation @Before is used to initiate each test case; it is
performed before each of them and is usually used to create a test object of other
objects required to run it.
@Before

private ArthService service;

@Before
public void setUp() {
service = new ArthService();
}
@Test
public void shouldSumIntegers() {
int result = service.add(12, 5);
assertEquals("Don’t sum correctly",17,result);
}
@After - “destructor” of classes type of TestCase
Method with @After annotation is complementary to the @Before method, i.e. it is used
to restore the state prior to the test. If the @Before method only created objects then no
@After implementation is needed; it only matters if the test case has allocated a
resource that should be released, e.g. a network or database connection, etc. Then this
method should release this resource.

public class RomanNumberTest{


RomanNumber rn = null;
@Before
public void initialize() {
rn = new RomanNumber(5);
}
@After
public void clean() {
rn = null; //restore state from before initialize ()
}
}
clean() with annotation @After is used for cleaning in every test case; is performed after each
of them
Global "constructor" and "destructor"
Method with @BeforeClass annotation is performed only once in the class
before all test cases

@BeforeClass
public void initializeClass() { //... }

Method with @AfterClass annotation is performed only once in the class after
all test cases have been completed

@AfterClass
public void cleanClass() { //…}
Exceptions testing

JUnit also implemented annotation-based exception testing mechanism. The expected


parameter in the @Test annotation lets you specify what exception you are expecting
and which will be an error.

@Test(expected = ArithmeticException.class)//the effect of the test case is to be an


exception
public void divisionWithException() {
int i = 1/0; //throws ArithmeticException
}

In particular, you can test your own methods that throw exceptions in certain situations.
We can check if exceptions are thrown when we expect them.
Running tests @RunWith and @Suit
@Suite.SuiteClasses(...) it is used to group many test classes into one package.
This allows you to run multiple tests at once and have better control over them.

@RunWith - annotation saying which "Test Suit" should be run.

@RunWith(Suite.class)
@Suite.SuiteClasses({JunitTest1.class,JunitTest2.class, JunitTest3.class})
public class AllTests {
}

How to run tests? - Having an open class with @RunWith, in Eclipse we choose from the
menu "Run-> Run As-> JUnit test"
JUnit test results in Eclipse
Features of correct unit tests
● Automation - running tests must be easy.
● Completeness - everything that can fail should be
tested.
● Repeatability - multiple tests give the same results.
● Independence - from the environment and other tests.
● Professionalism - the test code is as important as the
code provided to the customer
● Convention
○ the name of the test method starts with the word:
test, np. testMultiplication()
○ in the name of the testing class we add the word
Test to: MyClassTest
Why is it worth unit testing?

● small functionality -> easier to describe what and how


it should work
● quick feedback that something has stopped working
● high granularity of tests -> more accurate information
what does not work
● increased code usability
● documentation of expected behavior

You might also like