By the end of this course, you will be able to: - Define unit testing - Identify reasons for unit testing - Identify unit testing activities for the Software Development lifecycle (SDLC) - Demonstrate how to design a test case for unit testing - Demonstrate what should be covered when conducting a unit test - Identify the benefits and limitations of unit testing This course will take approximately 90 minutes to complete. The content is split into three modules: Unit Testing Essentials, Unit Test Activities, and Unit Test Condition Design. Let's get started on the first module – Unit Testing Essentials. In this module, we will define a unit, discuss what unit testing is all about, outline why unit testing is needed, and show you an example of a unit test case. Here we define a unit to be the smallest testable part of an application. In procedural programming a unit may be an individual program, function or procedure. In object-oriented programming, the smallest unit is a method. A Unit should be clearly defined in the software detailed design document. Sometimes the word component is used to refer to a unit. A unit should have no external dependencies across boundaries of trust or control, which could introduce failure sources. Such external dependencies can include networks, databases, files, registries, clocks and user interfaces. In other words, the unit in question can be isolated at a level below the whole system and its interactions with the environment. Unit testing validates the source code of a defined unit to make sure they are working properly. Unit testing is typically performed by software developers to ensure that the code they have written meets software requirements and behaves as the developer intended. Most programmers are already doing unit testing at some level as they write code. You will write a function or small piece of code, compile it, run it against a couple of cases, and if it works, you consider it correct and move on to the next piece of code. Unit testing is a proactive approach to coding, rather than the normal reactive approach used on many projects. Proactive testing is an integral part of the software process, since you are putting in effort up front to make sure your code is as bug-free as possible, rather than waiting to the end to try and find all of the issues. Fixing issues later on requires more time and cost, and the error may have spread to many other parts of the code, which will also need to be fixed. Now we will discuss unit testing in the context of the VModel. n developing and testing software, a software development lifecycle (SDLC) methodology (which is a process model for software development) is used. Its focus is to provide a roadmap on what development tasks/stages should be done and in the order which they are done. Unit Testing is one such task that can be identified within the methodology. The SDLC methodology that we follow can be represented by the V-Model. The V-Model is a process model/framework used to achieve stage containment through validation/verification activities, and help to reduce riskduring software development. The V-Model is based on proven industry standards for application development and testing. Once the application is built, the project moves through the testing activities on the right-hand side of the V. The V-Model calls for each major deliverable to be verified and validated at all stages of testing. In other words, the testing activities should always be checked against the initially documented requirements to ensure that specifications are fulfilled by the application. This process helps catch problems as early as possible and
ensures that the specifications adhere to standards, and are complete and correct. Unit test, also known as component test, is the first test activity on the right side of the Vmodel. It is done right after building each component. Please remember, unit test is an activity in build phase and should be finished before build phase closure. On the next slide we'll discuss the purpose of unit testing. A unit test is used to validate that code is consistent with the detailed design, and that it executes as intended. The goal of unit testing is to isolate each part of the program and show that the individual parts are correct. By implementing unit testing, we can find bugs as early as possible and prepare for further tests. Generally, the earlier a bug is found, the less it costs to fix. Ideally, a unit test should cover all of the code, including branches, paths and cycles. Now that we know the purpose of unit testing, we will discuss the scope of unit testing. The objective of a unit test is to test the whole program and each of its function modules. For new programs, all units or components that form the code would comprise the test scope. For any changes made to the existing code, all the components or modules impacted by the change would also be included in the scope, along with the code that was changed. Often, a unit test is a “white box” test. This is when the internal working operation is well understood. Internal action is tested to validate whether the processes align with the detailed design documents. Black-Box testing can also be used in unit tests. Also BlackBox testing is easier and more efficient to implement in some cases. The test environment should mimic the real running environment as closely as possible. This will help to discover any errors or inconsistencies that are due to differences in the environment. However, since unit testing is done at a very early stage in product development, the development environment itself is used for testing as well. Black Box Testing is also known as functional testing. Black Box Testing focuses solely on the outputs generated in response to selected inputs and execution conditions. When black box testing, the internal working logic of the item being tested may not known by the tester. For example, in a black box test on a software design the tester only knows the inputs and what the expected outcomes should be and not how the program arrived at those outputs. The tester does not examine the programming code and does not need any knowledge of the program other than its specifications. Now, let’s see what white box testing looks like. White box testing is also called structural or open box testing. It takes into account the internal mechanism of a system or component. In white box testing, the tester has to deal with the code and hence needs to possess knowledge of the code and logic, i.e., the internal workings of the code. A white box test also needs the tester to look into the code and find out which unit/statement/chunk of the code is malfunctioning So, the white box tester should have the programming skills and should be able to understand the detail design and code. Next, we will introduce the deliverables of a Unit Test. The unit testing deliverables may include the following documents: Test Conditions and Expected Results (TCER) document, Test Cycle Control Sheet (TCCS), Test Data document, Driver Programs/Media Programs document, and Test Environment document. Test Conditions and Expected Results is a detailed description of a low-level test. A test condition, also known as a test case, describes what to input to cause the condition to occur and the expected result that will be validated against the actual result generated
execution of the test. A group of test conditions defines a list of items that must be tested together to fully execute a test scenario. The content of the test conditions is determined by the test stage they are in. In the unit test stage, unit test conditions will test the application logic (boundary conditions, error processing, field validation, etc.) A logical grouping of related test conditions can be call a test cycle, which can be use to facilitate the test script creation and efficient test execution. Test Cycle Control Sheet is created in the planning of a test stage. It documents a high-level definition of each test cycle. This deliverable will be updated in the preparation of test execution with start and end dates and resources. Test Data document has all the data used for test execution. Test data may be included in to test condition or test script. The Driver Programs/Media Programs document describes the functionality and usage of driver programs used for the unit test. The Test Environment document describes the information of hardware, architecture, and software which needed in the test execution. The test environment has its own requirements for different test. Notice that, based on the client’s demand, document type and quantity may differ. Generally, these points are stipulated in the budget contract during the project’s initial phase On a simple level, a unit test consists of providing input and verifying that the expected output is produced. Let’s first consider what the code depicted in box on the left does. It accepts two numbers and returns the sum. Now, if we were to write a sample unit test case for this code, it might look like the code in the right box. By passing the numbers three and five to the “add two numbers” method, a value of eight should be returned. If we do not get a return value of 8, our unit test has failed and we need to review the code to find the error. The test may look something like what is shown in Fig 2. For the above function, we would have to test every possible combination of input to be 100% certain that it works in all cases, but this is impractical. You should run enough test cases to account for all of the possible errors and boundary cases (such as negative numbers, large numbers, etc.), and from that, you can conclude that the code works for similar input. This will be discussed in more detail in the "Unit Test Conditions" module. Now that we know what a unit test looks like, it’s time to go over unit testing benefits. Unit test is mandatory in the build phase and provides many benefits. 1. Finds and removes defects injected as earlier as possible We know that most of the defects are injected during build phase. And the earlier a bug found, the less it costs to fix. Unit testing is a basic and important activity to find the bugs injected during the build phase. 2.Unit testing facilitates change. Unit testing allows programmers to refractor code at a later date, and make sure the module still works correctly (i.e., regression testing). 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. Readily available unit tests make it easy for programmers to check whether a piece of code is still working properly. Good unit test design produces test cases that cover all paths through the unit with attention paid to loop conditions. 3. Simplifies integration Unit testing helps to eliminate uncertainty in the units themselves, and can be used in a bottom-up testing style approach. By testing the parts of a program first, and then testing the sum of its parts, integration testing becomes much easier. 4. Documentation Unit testing provides a sort of living documentation of the system. Developers looking to learn what functionality is provided by a unit and how to use it can look at a unit test to gain a basic understanding of the unit’s application programming interface (API). Unit test cases embody
characteristics critical to the success of the unit. These characteristics can indicate appropriate/inappropriate use of a unit as well as negative behaviors that are to be trapped by the unit. 5. Design When software is developed using a test-driven approach, the unit test may take the place of formal design. Each unit test can be seen as a design element specifying classes, methods and observable behavior. While unit testing has many benefits, it has limitations too. 20 he Unit testing activity occurs after the Detailed Design and Build phases. A lot of preparation work needs to be finished during the Detailed Design and Build phases to get ready for the Unit Test. To illustrate the process, we will explain what occurs in each of these three phases, from Detailed Design to Build, then finally to the Unit Test. As we can see in the table, most of the test documents need to be created during the Detailed Design phase first. They are then updated during the Coding phase with any new information. Finally, in the Unit Test phase, the documents are further updated with any information, and then the test results document is created to record the results of the unit test. In Accenture Delivery Methodology (ADM), all three of these phases - Detailed Design, Build and Unit Test - belong to the “Build Application” phase. Here, we talk about them separately so as to describe the activities of Unit Test in detail. We will go on to see what these documents contain and who creates them. The first step in the Detailed Design phase related to unit testing documentation is to create the TCER. It’s purpose is to outline the test conditions according to the function of the program and clearly state the expected results. The next step is to create the Test Cycle Control Sheet. This groups all of the test conditions to a test cycle. Each cycle can then be given constraints on when and whom should run that particular test cycle. The Test Data Document is created by the detailed designer. It shows the test cycle requirements and all of the data used in each test condition. The test data can also come from common test data or client-provided business data. The next slide will cover two more documents. In some cases the unit test conditions might be dependant on another program, component or environment. In such cases, the Detail Designer will create a Test Driver/Media Program Document to make the unit test execution possible. Ideally, it is recommended to avoid this as much as possible as it defeats the definition of unit testing. If there are dependencies, the test condition qualifies as an assembly test condition rather than a unit test condition. If you remember from our earlier definition of unit testing in Module 1, we noted that unit test cases need to be free of any dependencies as much as possible. The test environment document keeps track of any differences between the test environment and the development environment. Normally, unit testing is performed in the development environment or a independent unit test environment. This document makes sure the unit test environment is verified and managed. An environment close to the user acceptance test or production test is recommended. At the end of the Detailed Design phase, the Detail Designer and Reviewer should do content verification. This step will help ensure that all the required documents are there and that anything inconsistent with the design standards can be fixed. If you want to learn more about the deliverables,
please use the link in the bottom to go to ADM work products. Now, Let’s see what we should do during the Coding phase. Coding Phase Activities: Coding starts after the Detailed Design phase and after the Unit Test design is confirmed. When developers are coding according to Detailed Design, they may find bugs in the Detailed Design document. So when changes are made to the Detailed Design documents, corresponding changes need to be made to the TCER (Test conditions and Expected Results) document as well. Also during coding, code verification is performed by Developers and Reviewers to ensure that the code meets the Detailed Design requirements and fixes are made even before unit testing is done. Let’s move on to the Unit Test phase. Unit Test Phase Activities: The majority of work done during the unit test phase is to execute the unit test. All the unit test conditions and cycles created during the Detailed Design phases should be executed. Generally, the Developer executes the unit test, although it is recommended that a fellow peer from the development team does it. Any difference with the expected results needs to be recorded and verified again. If confirmed, a defect needs to be logged. After all the test conditions and cycles are executed, the unit test results should be verified. This is done by the Detail Designer and Developer together. The test results are analyzed to make sure all cycles executed normally, and that the results match the expected results in the TCER document. We have just shown you the sequence of activities. Now we will have a knowledge check about this module, please finish the following test questions on the next page. 28 The General Principle for test condition design is to achieve the code coverage and logic coverage target. Code coverage is a way to measure the level of testing you've performed on the software. Coverage is a metric that evaluates how much code you did— or more importantly, did not—execute. Code coverage is a basic requirement in unit testing. A unit tests tells you whether your code performed as expected, and code coverage tells you what remains to be tested. Most developers understand the value of this process, and often target 100% coverage. But, for practical reasons, 100% coverage is difficult to achieve. Even with 100% code coverage, critical bugs may still be present in the logic of your code. So, the logic coverage target also needs to be achieved. To achieve the full unit test coverage target, we need make sure every executable statement is executed at least once and meanwhile, make sure all functional logic is covered. We will introduce different types of coverage that can be use to achieve our coverage goal in the coming slides. To achieve our coverage target, we should consider different kinds of coverage of code logic for unit testing. Here, we summarize the coverage types as: Branch Coverage, Condition Coverage, Loop Coverage, Interface Coverage, Logic Path Coverage and some other types. Let’s see what these are like one-by-one. A branch is the outcome of a decision. Branch coverage measures the percentage of branches that has been reached during testing. We design test conditions to cover all logical branches in a function unit to ensure full branch coverage can be reached. For every condition statement both the true and the false branch should be tested. The table on this slide shows some branch statements. For IF - ELSE - ENDIF statements, we
need to design conditions for the IF part as well as the ELSE part. For CASE - WHEN ENDCASE statements, we should design conditions for each CASE. For WHILE ENDWHILE condition statements, we need to cover the WHILE part as well as the NOT WHILE part. Let’s take a look at some sample code. Condition coverage is similar to branch coverage. The difference is condition coverage is used to check if both the true and the false condition of every sub-condition within an expression have been tested at least once. We should develop enough test conditions to ensure every judgment is executed at least once when running the test conditions. Take a look at the pseudo code example in the table on this slide. It is a very simple single condition. By analyzing the condition “X greater than or equal to 5”, we can design three test conditions to cover all of the branches. Here, the first condition X equals 4 covers the ELSE branch - PERFORM ERROR, and the second and third conditions X equals 5 and X equals 6 covers the IF branch PERFORM SUCCESS. Let’s see another example of condition coverage. Condition coverage is similar to branch coverage. The difference is condition coverage is used to check if both the true and the false condition of every sub-condition within an expression have been tested at least once. We should develop enough test conditions to ensure every judgment is executed at least once when running the test conditions. 40 Loops provide a simple mechanism for repeating a block of code a fixed number of times or for iterating through a set of values. Loop coverage techniques focus on determining what percentage of the source code enters a loop in a program and has been cycled completely. There are several loop constructs in programming languages like DO, FOR, WHILE, and UNTIL. Regardless of the loop construct, the objective of loop testing is to design enough test conditions to cover all the conditions in a loop execution. A loop test should cover one, multiple and the maximum number of loop executions, to validate the expected loop response. Loop testing should also cover no loop executions and n+1 loop executions, which test for unexpected and inappropriate looping conditions. Besides these two points, loop testing should also cover all of the possible outputs and break conditions based on the loop execution. Now, let’s take look at an example of loop and possible test conditions. Interface testing is in the scope of unit test and is the basis for assembly test. Other tests are meaningful only if the data can flow throughout the modules correctly. The purpose of interface testing is to ensure that the interface of the individual program unit works correctly when the units are integrated into a bigger module. Here some factors are listed that need to be considered when testing the interface. Please read and use them in your tests. If tjhemodule includes import and export operations File properties Open/close Comments Buffersize and data record length
If the file is open before using it Whether the end of file is handled properly Expecption handling Spelleing mistakes in output 44 Logic Path Coverage is a basic consideration for all tests. In a Unit test, we should test the logical path of the current program unit. When designing test conditions, we should list all of the paths in the program unit first. Then, based on the path, we can figure out what kind of test data should be used for the test. In this example, we can easily find four paths in the flowchart. They are abd, acd, ace, and abe. In order to cover all of these paths, we design 4 sets of test data: set 1: x=4、y=6、z=5 ; set 2: x=4、y=5、z=15; set 3: x=2、y=6、z=15; set 4: x=5、y=5、and z=5 If the logic path is too long, we can divide the long path into several short paths to reduce the number of tests. Let’s see other types of coverage that would be useful to write test conditions for. Exception Handling Test: During unit testing, we not only cover all of the normal conditions, but also all of the exception conditions. Database Exceptions can be caused by database connection problems or data manipulation errors. This means that all database runtime exceptions, results from incorrect SQL statements, duplicate primary keys, tables locked when updating records, invalid data formats, exceeded field size limitations, need to be handled. Other exceptions like Buffer Overflows, I/O exceptions, network connection exceptions also need to be handled and are good candidates for writing test conditions. Argument Type Cast test: We need to pay attention to the parameter type before it is cast, if the actual parameter type is different with the formal parameter, we need to cast the actual parameter and check the exception of this cast. We also need to consider the new variable’s precision or data length. Comparison Computing test: Whenever we do data comparison for different types of data, we may use the logical operator incorrectly or misunderstand the priority of the operator. It is useful to write test conditions around them. Local Variable Usage Test: It is important to check that Local variables are set or used correctly. Misuse of a local variables is usually the root cause of a defect, and hence, we can design our test conditions around these. Incorrect/incompatible data type declaration, Variables defined without initial value, Incorrect initialization used for variables, Incorrect variable names, Using a global variable instead of declaring a new variable are all good test conditions to explore. We also need to check the effective scope of local variables, as well as the impact of global variables on the tested module during the Unit Test. For Database operations, we can generate unit test conditions from these three perspectives. First, we can generate test condition from the “WHERE clause”. The “WHERE clause” is often used to filter the results of an SQL statement i.e. select, insert, update, or delete statements. Secondly, when there is data definition and manipulation statements, such as CREATE, UPDATE, and DELETE, we should also design test condition to verify the correctness of the SQL statement. Thirdly, stored procedures and self defined functions should be tested during the unit test phase. A store procedure is a group of SQL statements compiled into a single execution plan. It can assist in achieving
a consistent implementation of logic across applications and improve performance. Let’s look at the WHERE clause first. he WHERE clause may include AND/OR, IN, Group By, Order By, and Having operations. Let’s see how to design the test conditions for multiple AND/OR operators in a WHERE clause. If more than one AND operation exists, usually we need to cover the 2 points below. (1)The case that all conditions are TRUE, and (2)The case where one condition is FALSE. For example, in the following WHERE clause: WHERE CONDITION1 AND CONDITION2 AND CONDITION3 AND CONDITION4. We need to design at least 5 test conditions: 1. CONDITION1 through CONDITION4 are all TRUE2. CONDITION1 is FALSE 3. CONDITION2 is FALSE 4. CONDITION3 is FALSE 5. CONDITION4 is FALSE Lets look at the WHERE clause coupled with OR operators. f more than one OR operation exists, usually we need to cover these 2 points: (1)The case of all FALSE conditions (2)The case where one condition is TRUE For example, check the following WHERE clause: WHERE CONDITION1 OR CONDITION2 OR CONDITION3 OR CONDITION4 Usually we test the following cases: 1. CONDITION1 through CONDITION4 are all FALSE 2. Just CONDITION1 is TRUE 3. Just CONDITION2 is TRUE 4. Just CONDITION3 is TRUE 5. Just CONDITION4 is TRUE Note, you also need to consider the case where the condition is NULL. If a “IN” operation exists, we need to cover every item in the IN operator. For example, in the following IN operation, we need to test the cases where the test value does not equal ‘A’, ‘B’ or ‘C’. Also we need to cover cases where the test value equals ‘A’, and equals ‘B’ and equals ‘C’ each in separate test cases. Update Database operations includes: Update, Insert and Delete. When we do a test, we should cover the following points: For the UPDATE operation, we need to design test conditions to test if the items are updated correctly. Also we need to make sure the data records in scope are updated correctly, and the data records out of scope are not affected. For the INSERT operation, we should check that all data is inserted successfully. For the DELETE operation, we need to make sure the targeted row is row that was deleted, and make sure any related table and data are considered when DELETE is executed. Sometimes the table that will be deleted has a constraint within another table. You need to check if it should be deleted, to ensure data integrity. Let’s talk about the Stored Procedures and Functions next. Stored procedures and functions may be used to implement complex operations. For stored procedures and function testing, all of the general principles can be used to create test conditions. Almost all applications are GUI based. GUI, or Graphical User Interface, is a basic feature of a system and an important feature as well. To support business development, the client often expects to get a powerful supporting system with a complex GUI. At the same time, there is often little detailed GUI requirements and acceptance criteria. Here is a simple GUI. Although this is a simple GUI page, there are many points that need to be tested. For example, the individual validation logic used by the GUI, or the components that are used to pull data that populate a drop down list can themselves be individual units subject to a unit test. GUI testing in the overall context may be a good
candidate for assembly or system test. When we develop a system, GUI testing is not simple work. GUI testing can often be a big part of the testing effort, and hence the cost. We need to design thousands of test conditions to verify GUI objects, and sometimes, we can not judge whether the test condition has passed or not In this module, we will focus on introducing the points that need be covered in GUI testing. Automated testing technology for GUI testing will not be introduced in this course. Let’s move on to see what we should pay attention to for GUI testing. During GUI testing, we should pay attention to the following two points: First, Ensure the display of pages and elements are the same as those defined in the requirement. Secondly, Ensure all of the validation or logic control functions for some elements are correct and fully implemented. Let’s look back to the GUI. n this sample GUI, we want to ensure the display of the pages and elements are consistent with those described in the requirements. For example we may need to check if the name and position of every field and button is right. We also may need to check the color, font, etc. If we want to ensure all of the validation or logic control functions are correct, then we need to check these elements are correct and fully implemented. Let’s take a look at GUI testing in more detail. Here is a list of points that need to check when we test the display of a GUI page. You can take these points as a checklist and refer to it when you doing testing. Please spend several minutes to read them one by one. Each page title is correct Each component color follow the design Tab follow the correct sequence left to right top to biottom Password input box is masked The formatted data follow the predefined format Drop down list box allow selection Multi choice box allow multiple selection Date input box accept the correct date input Single choice box allow 1 selection After clicking the cancel check if the data is cleared or processed as specified After clickindg submit check if data is submit or processed as specified Check if the exceptions are pro[perly described 60 To test these validations and logic control functions, we need to design test conditions for the validations and logic control functions one-by-one according to the detail design. It is the same with the function tests. For example: in BROWSER development, most validation and logic control are implemented by Java Script, Applet, and Java classes. We need to test these validations page-by-page and ensure the validation criteria are consistent. In some cases, you also need to consider platform adaptability. A Report is also a GUI, but Reports include data computation logic and possibly involve many data sources. So, report testing is also more complex than GUI tests. We need to test the report’s GUI display as well as data correctness. Besides the points we mentioned in the GUI test, there are some additional factors that need to be considered when designing test conditions for report testing. You can take it as a checklist. Please use several minutes to read it one by one. Report generation function work correctly
Title section display Whole page display Data format Summary part Text display correctly when max rows and columns Margin are presented properly Multi paging view Printed report is same as displayed Paragraph format are correct Properly aligned Page no and total page nos are correct If it contain sub report link is correct Sum calculation is correct Everything is grouped ordered correctly 65 The Regression test is when all the test conditions are re-executed for function modules influenced by a change or bug fix to the program. It is very important to ensure that the change or bug fix does not introduce any unwanted impact to the current system. During this test, the test data is the same as before so that it can be compared to the pre-defined expected test result. When there is a change during the software development life cycle, regression testing is needed. This test also happens during the Unit Test phase. We need select test condition to verify the change to a program unit not inject a new defect. Now let’s talk about performance testing. The Performance test checks the performance capability of software or a system. Software cannot be considered ‘good’ when its performance is poor, even if it fulfills the required functionality. Therefore, it is necessary to conduct performance tests. To build a high performance software system, the performance should be considered at earlier phase, e.g., technical design phase. And the performance of every program unit is the basis of the whole software system. A formal Performance Test should be conducted after the Assembly Test. However, it is also sometimes necessary to check the performance during the Unit Test phase. It is usually better to identify and solve performance bottlenecks early in the process. Performance test could be considered in the following cases in Unit test phase: First of all, when you need to access LARGE amounts of data. During a Unit Test, take all SQL statements and execute them in a Database with lots of data. Also, if there are many conditions in a WHERE clause all combinations of conditions should be tested with a large amount of data. Secondly, when there are Mutex and synchronization considerations. When programming serverside applications, there may be multiple processes/threads accessing shared resources. It may lead to mutex and synchronization problems. In a Unit Test, mutex and synchronization issues should be simulated to test performance. Finally, In the case of multiple loops: Multiple loops should be avoided in the first place. However, if there are multiple loops, we should test the maximum number of loop executions to test its performance. High performance programs are the result of performance optimization activities in all phases, from the Architectural design phase to the Performance Test phase.
In this training course, we introduced the basic concepts of unit testing, unit testing activities and their sequence. We also spent much time discussing how to develop test conditions. We discussed how to design test conditions based on different coverage types, like Branch Coverage, Condition Coverage, Loop Coverage, Interface Coverage, Logic Path Coverage, etc. All of these can be used in function testing and GUI testing. We then went through specific scenarios of Database operation testing, GUI testing and report testing. We also introduced the need for regression and performance tests. These two kinds of test should also be considered in the Unit Test phase. Unit testing is the first test phase in the V-model. The next test phase is the assembly test. There are other reference materials on Accenture’s website and the myLearning system. Let’s introduce some of them.