L16 Unit Testing

Phil Koopman
16 Jul 202117:48

Summary

TLDRThis tutorial by Phil Copeman delves into the intricacies of unit testing, emphasizing its importance in validating software's detailed design and implementation. It distinguishes between white box and black box testing, highlighting their respective advantages and limitations. The script underscores the significance of coverage metrics like statement, branch, and MCDC coverage, and the necessity of boundary and exceptional value testing. It also advises on best practices, including the use of a unit testing framework and the importance of thorough testing to ensure robust software development.

Takeaways

  • πŸ” Unit testing is a method to verify that the detailed design and implementation of a software meet the expected outcomes by testing individual components of code.
  • πŸ“ A unit test checks various input combinations against an expected output, using an 'oracle' to determine correctness.
  • πŸ”‘ Unit tests should cover both structural (white box) and functional (black box) aspects of code to ensure comprehensive testing.
  • πŸ› Catching boundary-based bugs and slight implementation imperfections is more feasible in unit testing than at the system testing level.
  • 🚫 Three anti-patterns in unit testing include not performing unit tests, only testing 'happy paths', and forgetting to test missing code.
  • πŸ“¦ Black box testing focuses on the software's behavior without knowledge of its implementation, which helps avoid bias but may miss internal code paths.
  • πŸ” White box testing, also known as structural testing, involves examining the code to ensure all paths and conditions are tested, which can help achieve high code coverage.
  • πŸ“ˆ Coverage metrics are crucial for assessing the thoroughness of testing; they range from function coverage to more granular metrics like statement, branch, and MCDC coverage.
  • πŸ›  Achieving 100% code coverage can be challenging, especially when dealing with error handlers for unlikely events or unused code that should be removed.
  • πŸ“Š MCDC (Modified Condition/Decision Coverage) is a sophisticated coverage metric used in safety-critical applications to ensure all possible execution paths are tested.
  • πŸ“ Boundary testing is essential for probing the limits of software behavior, including minimum and maximum values, and exceptional time-related events.
  • πŸ›‘ Exceptional value testing is vital for ensuring robust systems can handle unexpected inputs such as null pointers, undefined inputs, or unusual events like leap years.

Q & A

  • What is the primary purpose of unit testing?

    -Unit testing is a method to ensure that the detailed design and implementation of a software module meet the expected outcomes by testing individual subroutines, procedures, or methods.

  • What are the two main types of unit tests mentioned in the script?

    -The two main types of unit tests are structure or white box tests, which examine the internal workings of the code, and functionality or black box tests, which focus on the output without considering the internal implementation.

  • Why is it important to test both 'happy paths' and edge cases in unit testing?

    -Testing both 'happy paths' and edge cases is crucial because it helps uncover bugs that may arise due to slight imperfections in the code implementation, which might not be evident during regular operation.

  • What are the three anti-patterns for unit testing mentioned in the script?

    -The three anti-patterns for unit testing are: 1) Not performing unit tests at all, relying only on system-level testing, 2) Testing only the 'happy paths' and not exploring all possible scenarios, and 3) Forgetting to test missing code, such as exception handlers or special conditions that are not present in the code.

  • What is the difference between black box testing and white box testing?

    -Black box testing focuses on the software's behavior without knowledge of the internal implementation, whereas white box testing, also known as structural testing, involves knowledge of the software's actual code structure and is used to ensure all paths and conditions are tested.

  • Why is coverage a key concept in unit testing?

    -Coverage is a metric that measures the thoroughness of testing. High coverage helps ensure that all parts of the code are executed and tested, increasing the likelihood of catching bugs and ensuring the software behaves as expected under various conditions.

  • What is the significance of boundary testing in unit testing?

    -Boundary testing is important because it checks the software's behavior at the limits of its input ranges, such as minimum and maximum values, to ensure there are no unexpected results or errors at these critical points.

  • What is an assertion in the context of unit testing?

    -An assertion is a statement used within a test case to determine whether the test has passed or failed. It checks if the actual output of the tested code matches the expected outcome.

  • Why is it recommended to use a unit testing framework for unit testing?

    -Using a unit testing framework simplifies the process of creating, organizing, and running test cases. It also provides built-in functionalities for assertions and reporting, making it easier to manage and understand the results of the tests.

  • What are some best practices for writing effective unit tests?

    -Best practices for unit testing include testing every module, using a combination of white box and black box testing with granular coverage metrics, breaking tests into small, focused chunks, and ensuring both control flow and data value coverage, including boundary and exceptional value testing.

  • What are some pitfalls to avoid when performing unit testing?

    -Pitfalls to avoid in unit testing include not creating test cases, using a debugger instead of a testing framework, testing large code blobs instead of individual units, relying solely on white box testing, and neglecting peer reviews and static analysis as part of the development process.

Outlines

00:00

πŸ” Introduction to Unit Testing

Phil Copeman introduces the concept of unit testing, emphasizing its importance in verifying the detailed design and implementation of software. Unit testing focuses on individual subroutines, procedures, or methods, using a low-level interface to assess their functionality. The tutorial outlines the process of testing various input combinations against an 'oracle' that defines expected outcomes. It also highlights the significance of both structure and functionality in unit tests, the potential for catching boundary-based bugs, and the three common anti-patterns to avoid in unit testing: not testing at all, only testing 'happy paths', and forgetting to test missing code. The paragraph concludes with an introduction to black box and white box testing approaches, explaining their differences and applications.

05:01

πŸ“š Understanding White Box and Black Box Testing

This paragraph delves deeper into the two primary testing approaches: white box and black box testing. White box testing, also known as structural testing, involves examining the code's structure to ensure all paths are tested, including boundary conditions and special cases. The advantage of white box testing is the ability to achieve high structural code coverage, but it may miss testing for missing code that isn't present. On the other hand, black box testing focuses on the software's behavior without knowledge of its internal workings, making it less biased but potentially missing code paths. The importance of using both methods for comprehensive unit testing is stressed, along with the concept of coverage metrics, which measure the thoroughness of testing, from function coverage to more granular metrics like statement, branch, and MC/DC coverage.

10:01

πŸ”¬ Advanced Coverage Metrics and Boundary Testing

The paragraph discusses advanced coverage metrics, such as MC/DC (Modified Condition/Decision Coverage), which is crucial for safety-critical applications. It explains the criteria for MC/DC coverage, which ensures that every decision in the code is tested for all possible outcomes independently. Additionally, the importance of boundary testing is highlighted, which involves checking the limits of a function's behavior, including minimum and maximum values, rollovers, and exceptional time-related events. The paragraph also addresses the handling of exceptional values, such as NaN, infinity, null pointers, and unusual inputs, which are vital for robust system testing. The need for a strategy that encompasses both structural and behavioral coverage, as well as boundary and exception coverage, is emphasized for thorough unit testing.

15:02

πŸ›  Best Practices and Pitfalls in Unit Testing

The final paragraph provides best practices for effective unit testing, advocating for the use of a unit testing framework like CUnit to organize tests into suites and cases. It stresses the importance of high coverage, combining both white box and black box testing, and focusing on granular metrics like MC/DC or branch coverage. The paragraph also advises on the significance of data value coverage, including validating lookup tables and checking for divide by zeros. It warns against common pitfalls, such as creating test cases with bugs, using ineffective testing strategies, and relying solely on white box testing or unit testing frameworks without considering other quality assurance methods like peer reviews and static analysis.

Mindmap

Keywords

πŸ’‘Unit Testing

Unit testing is a method of validating that individual components or units of a software function as intended. It's a fundamental aspect of the video's theme, emphasizing the importance of ensuring each part of the code works correctly before integration. The script mentions that unit testing involves testing a single subroutine or method and checking the results against expected values to confirm correctness.

πŸ’‘Subroutine

A subroutine is a piece of code that performs a specific task and is used in the script to illustrate what a unit test might focus on. It's a standalone section of code that can be tested independently. The script uses the example of a subroutine that takes two inputs and produces one output to demonstrate how unit testing would be applied.

πŸ’‘Oracle

In the context of the video, an oracle is a mechanism for determining the correctness of a program's output. It's a reference point or a set of expected results that a unit test checks the actual output against. The script describes the oracle as something that defines what the output 'should be' for a given function with specific inputs.

πŸ’‘White Box Testing

White box testing, also known as structural testing, is a method where the tester has knowledge of the internal workings of the software. The video emphasizes its importance in unit testing, as it allows for the examination of every line of code and the paths taken within the code. The script contrasts this with black box testing, highlighting its role in achieving high structural code coverage.

πŸ’‘Black Box Testing

Black box testing is an approach where the tester focuses on the software's functionality without knowledge of its internal workings. The script explains that this type of testing is based on the behavior of the software, aiming to verify the 'what' rather than the 'how'. It is used in unit testing to ensure that the software meets the specified requirements.

πŸ’‘Coverage

Coverage in the script refers to the extent to which the software's code has been tested. It's a metric used to measure the thoroughness of testing. The video discusses various levels of coverage, such as function coverage, statement coverage, branch coverage, and MC/DC coverage, illustrating the importance of aiming for high coverage to ensure comprehensive testing.

πŸ’‘Anti-patterns

Anti-patterns in the script are common practices in unit testing that are considered ineffective or counterproductive. The video mentions three anti-patterns: not performing unit testing, only testing 'happy paths', and forgetting to test missing code. These are highlighted as pitfalls to avoid in order to achieve effective unit testing.

πŸ’‘Boundary Testing

Boundary testing is a specific type of testing that focuses on the boundaries of input values to ensure the software behaves correctly at minimum, maximum, and edge cases. The script describes boundary testing as probing the borders of behavioral changes and gives examples such as checking values just below, at, and above a minimum acceptable value.

πŸ’‘Exceptional Values

Exceptional values are unusual or unexpected inputs that a program might encounter. The video script discusses the importance of testing how software handles exceptional values such as null pointers, undefined inputs, or unusual events like leap years. This type of testing ensures the software's robustness and reliability.

πŸ’‘C Unit

C Unit is mentioned in the script as an example of a unit testing framework that can be used to facilitate the creation and management of unit tests. The video describes how C Unit uses a test registry, test suites, and assertions to organize and verify the correctness of tests, emphasizing the practical application of unit testing methodologies.

πŸ’‘Assertions

Assertions are statements used within unit tests to check if a condition is true. If the condition is false, the assertion fails, indicating that the test did not pass. The script uses assertions as a way to determine whether the output of a unit under test matches the expected results, exemplifying their role in verifying the correctness of software behavior.

Highlights

Unit testing is a method to ensure the detailed design and implementation align with expectations.

It involves testing individual code modules like subroutines, procedures, or methods.

Unit tests use a low-level interface to check various input combinations against expected outcomes.

Results are verified against an oracle, which defines the correct output for given inputs.

Unit tests should cover both structure (white box) and functionality (black box).

Unit testing is crucial for catching boundary-based bugs and implementation imperfections.

Three anti-patterns in unit testing include not testing, only testing happy paths, and forgetting to test missing code.

Black box testing focuses on software behavior without knowledge of the detailed implementation.

White box testing, or structural testing, examines the software with knowledge of its codebase.

Coverage metrics are essential for assessing the thoroughness of testing.

Function coverage is a basic metric measuring the number of tested functions.

Statement coverage measures the percentage of executed code lines.

Branch coverage assesses whether all code paths have been tested.

MCDC (Modified Condition/Decision Coverage) is a sophisticated metric used in safety-critical applications.

Boundary testing checks the borders of behavioral changes, such as minimum and maximum values.

Exceptional value testing ensures robustness against unusual or unexpected data inputs.

Unit testing frameworks like CUnit facilitate organized and effective testing practices.

Best practices for unit testing include comprehensive coverage, use of frameworks, and small, focused test cases.

Pitfalls of unit testing include the resource-intensive nature of creating test cases and potential bugs within test cases themselves.

Unit testing should not replace peer reviews and static analysis but complement them in the development process.

Transcripts

play00:00

hi this is phil copeman with a tutorial

play00:03

on unit testing

play00:06

we've covered requirements and design

play00:09

and now we'll be talking about

play00:10

unit test which is a way to check to

play00:13

make sure the detailed design

play00:15

and the implementation turned out as you

play00:17

expected

play00:20

unit testing involves testing a single

play00:22

subroutine

play00:23

procedure method or other standalone

play00:27

chunk of code

play00:28

it uses a low-level interface so you can

play00:31

think of

play00:32

unit as being a code module here's an

play00:35

example of a subroutine that takes two

play00:37

inputs and produces one output

play00:39

a unit test would test several

play00:41

combinations of inputs for example

play00:44

a equals zero and b equals zero is one

play00:46

unit test

play00:47

and a equals negative one b is plus one

play00:50

is a different unit test the results of

play00:53

the unit test

play00:54

are checked against some oracle

play00:56

something that says

play00:57

this is what it should be for this

play00:59

function the two inputs being zero is

play01:01

supposed to result in zero

play01:03

and negative one and plus two are

play01:05

supposed to result in plus one

play01:07

if those results match the expected

play01:09

values then the unit test passes

play01:12

unit tests should include both structure

play01:15

and functionality

play01:16

structure or white box tests

play01:18

functionality of black box tests and

play01:20

we'll talk about both of those

play01:23

unit testing is your best chance to

play01:25

catch boundary based bugs

play01:26

and other bugs due to slight

play01:28

imperfections in how the code was

play01:30

implemented

play01:31

it is going to be much easier to find

play01:33

those sorts of bugs

play01:34

here in unit test than at the system

play01:37

testing level

play01:38

there are three anti-patterns for unit

play01:40

testing

play01:42

one anti-pattern is not doing it some

play01:45

systems are only tested at the system

play01:47

level

play01:47

and that's a lost opportunity to find

play01:49

many detailed bugs that will not

play01:51

be found in most system testing but

play01:53

could have been found in unit testing

play01:56

another anti-pattern is testing only

play01:58

happy paths

play01:59

it's common to see unit tests that just

play02:01

confirm what the programmer

play02:02

expected to see instead of really trying

play02:05

to delve down

play02:06

into all the deep dark corners of the

play02:08

way the code was written

play02:10

a subtle but critical ansi pattern is

play02:12

forgetting to test

play02:13

missing code sometimes there are pieces

play02:16

of code that should be there

play02:17

that aren't and if you look at the code

play02:19

and say well we're going to just test

play02:21

based on what's in the code

play02:22

you won't ever think of those sorts of

play02:24

things such as exception handlers or

play02:26

missing special conditions

play02:28

because they're not in the code and it

play02:30

didn't occur to you to test things that

play02:32

you're not looking at

play02:33

in the code

play02:37

there are two different generic

play02:39

approaches to doing testing

play02:41

one is black box testing black box

play02:44

testing

play02:44

uses test design based on knowledge of

play02:47

the behavior of the software

play02:49

but not the detailed implementation it

play02:51

is often called functional

play02:53

or behavioral testing the idea is to

play02:56

test the

play02:57

what but not the how as an example

play03:00

if you have a automotive cruise control

play03:02

that system might

play03:03

behave differently at different speeds a

play03:06

black box test

play03:07

might try different various speeds and

play03:10

different control combinations

play03:11

but would have no idea if there's

play03:13

special cases inside the code that

play03:15

handle things

play03:16

it might hit them it might not the

play03:18

advantage to a black box test

play03:20

is it can be written only based on the

play03:22

requirements or design

play03:24

you don't need to know what's inside the

play03:26

code you can just say

play03:27

gee does this thing do more or less what

play03:29

it's supposed to

play03:31

another advantage is that sometimes

play03:33

looking at the code makes you think a

play03:34

certain way

play03:35

and black box testing is not subject to

play03:38

that particular bias

play03:40

the disadvantage of black box testing is

play03:43

that it can be difficult to exercise all

play03:45

the code paths

play03:46

because you don't really know what's

play03:47

going on inside the code you just know

play03:49

at the high functional level

play03:50

you seem to be hitting all the high

play03:52

level requirements

play03:54

at the unit testing level black box

play03:57

testing

play03:57

looks like basing tests on the detailed

play04:00

design

play04:01

this means you would look at the

play04:02

flowchart or state chart and design

play04:04

tests

play04:05

just looking at the flowchart and state

play04:06

chart and confirm that the software

play04:08

behaves the way the design says it

play04:12

should

play04:14

the other general approach to testing is

play04:17

white box testing

play04:18

in white box testing the designer has

play04:21

knowledge of how the software is

play04:23

actually written

play04:24

that means you're actually looking at

play04:26

the code when you design the white box

play04:28

tests

play04:30

white box testing is often called

play04:32

structural testing

play04:33

or sometimes glass box or clear box

play04:35

testing

play04:36

but white box testing is the preferred

play04:38

term the idea is to

play04:40

exercise software knowing how it is

play04:42

written

play04:44

back to the cruise control automotive

play04:45

example a white box test would look at

play04:48

all the lines of code and make sure that

play04:50

all the paths in the code are exercised

play04:53

and if there's a lookup table make sure

play04:55

that every entry in the lookup table has

play04:57

been tested

play04:58

knowing exactly where the boundaries are

play05:00

between

play05:01

when each entry in the table would be

play05:03

applied

play05:04

an advantage to white box testing is

play05:07

that it helps get

play05:08

high structural code coverage black box

play05:11

testing

play05:12

struggles to hit all lines of code

play05:14

because it's operating blindfolded

play05:16

with whitebox testing if you miss a line

play05:19

of code you can say oh there's a line of

play05:20

code i need to test

play05:22

let me think about how to get at that

play05:23

line of code

play05:25

the good news is with whitebox testing

play05:27

you can get at all the lines of code

play05:29

because you get to see what's going on

play05:31

an important disadvantage though is that

play05:33

you don't necessarily test code that's

play05:35

missing

play05:36

what do i mean by missing code well

play05:38

let's say there's a special case you

play05:39

should handle

play05:40

if that special case is not in the code

play05:42

at all nothing in the code will tell you

play05:44

you should be testing for it

play05:45

that means that black box and white box

play05:47

testing both have their roles and you

play05:49

should be using both of them for unit

play05:51

testing

play05:55

as with all testing the concept of

play05:57

coverage

play05:58

is key coverage is a metric for how

play06:01

thorough your testing is

play06:03

getting high coverage can be difficult

play06:05

think of it as trying to paint a wall

play06:07

painting the wide open middle pretty

play06:09

straightforward takes a few roller

play06:11

strokes

play06:12

getting into the nooks and crannies in

play06:13

the corners that's a little bit more

play06:15

delicate work

play06:16

takes more time takes more attention

play06:19

coverage

play06:19

metrics tend to reflect this the

play06:22

simplest

play06:22

coarsest metric for unit testing is

play06:24

function coverage

play06:26

how many functions in your system did

play06:27

you test at all

play06:29

if you never even tried to test half the

play06:31

functions well

play06:32

that's probably not very thorough

play06:34

testing is it

play06:36

once you've gotten to the point where

play06:38

most or all of the functions have been

play06:39

tested

play06:40

at least a little a finer grain notion

play06:42

of coverage is statement coverage

play06:44

what fraction of the code statements

play06:46

have been tested

play06:47

did you actually execute every line of

play06:49

code at least once

play06:51

a typical metric here might be that

play06:53

you've tested

play06:54

95 percent of the lines of code well 95

play06:57

sounds good but that means 5 of the

play06:59

codes in your system

play07:00

have never been executed at all and

play07:03

depending on your system that could be a

play07:04

problem

play07:06

once you've executed all the lines of

play07:08

code there are still

play07:09

better types of coverage metrics

play07:10

available the next one is branch

play07:12

coverage

play07:13

also called path coverage in branch

play07:16

coverage you say all right i've executed

play07:18

all the lines of code

play07:19

but did i try each branch both ways this

play07:22

one's a little subtle

play07:23

if you have an if else statement then

play07:25

100 statement coverage means you also

play07:28

got 100

play07:28

branch coverage because you must have

play07:30

done both the if and the else

play07:32

but branch coverage also includes the

play07:35

notion that if you have an if without an

play07:37

else did you do

play07:38

both the if and did you also do the if

play07:40

being false which

play07:41

skips the code if you missed the

play07:44

skipping the code you still might have

play07:45

100

play07:46

statement coverage but you'd be missing

play07:48

that half of the branch coverage for

play07:50

that if statement

play07:52

an even finer grain notion of control

play07:54

flow coverage is mcdc coverage which

play07:56

we'll handle in the next slide

play07:59

getting to 100 coverage can be a bit

play08:01

tricky

play08:02

if you have error handlers for errors

play08:04

that are never supposed to happen

play08:05

you can't get 100 coverage unless you

play08:08

make that error happen

play08:09

now you might say well it's never

play08:10

supposed to happen so what's the big

play08:12

deal

play08:13

well the counter to that is if you have

play08:14

an error handler that you've never

play08:16

actually tested how do you know it's

play08:18

going to work if that error actually

play08:19

occurs

play08:20

you need to find some test method to

play08:23

exercise the handler to make sure it

play08:24

really works

play08:26

another issue with 100 code coverage is

play08:29

dead or unused code and usually there

play08:31

what you should be doing is

play08:33

if that code cannot ever be executed why

play08:36

is it there

play08:36

probably you should take it out

play08:41

an even more sophisticated type of

play08:43

coverage is

play08:44

mcdc coverage for white box testing

play08:48

mcdc is modified condition decision

play08:51

coverage it's used by do178

play08:54

for critical aviation software testing

play08:56

and is commonly used for other safety

play08:58

critical applications where you have to

play09:00

be really sure the code's always going

play09:02

to work

play09:02

and do exactly the right thing or else

play09:04

somebody could die

play09:06

mcdc coverage exercises all the ways to

play09:10

reach

play09:10

all the code the criteria for mcdc

play09:14

coverage are that

play09:15

every entry and exit point is invoked

play09:18

every decision tries every possible

play09:20

outcome

play09:21

each condition in a decision generates

play09:23

all the outcomes

play09:25

and each condition in a decision is

play09:27

shown to independently affect the

play09:29

outcome of the decision

play09:31

that's quite a lot to take in but the

play09:33

idea is

play09:34

in branch coverage you do the if and the

play09:37

else

play09:37

but in mcdc coverage if there's five

play09:40

different ways the if could be true

play09:42

you need to try all five different ways

play09:44

and then also show the else could happen

play09:47

as an example the condition clause if a

play09:50

is equal to 3 or b is equal to 4

play09:54

for branch coverage you'd just need to

play09:56

test that it was true and that it was

play09:58

false

play09:58

for mcdc coverage you have to say well

play10:01

if a is 3 and b is not 4

play10:03

then we know a activates the true

play10:05

statement

play10:07

if a is not equal to 3 and b is 4 then

play10:09

we know that b

play10:10

can activate the true statement and if

play10:13

both a is not equal to 3

play10:14

and b is not equal to 4 we know that the

play10:17

else can be

play10:18

activated even if it's just a fall

play10:19

through now

play10:21

it seems like almost everything has to

play10:22

be tried you do not have to try

play10:25

a is 3 and b is 4 because that's already

play10:28

been covered by the first two cases

play10:31

if you're not quite sure go look at the

play10:33

list at the top

play10:35

of the four decision criteria and

play10:37

convince yourself the first three test

play10:39

cases check all the boxes and the fourth

play10:41

one is not needed

play10:43

for simple ants and ors you generally

play10:45

need one more test

play10:46

than the number of terms in the

play10:48

condition clause for two terms you need

play10:50

three tests

play10:51

for five terms you need six tests and so

play10:54

on

play10:55

if you have more complicated expressions

play10:57

there's a truth table technique that can

play10:58

help and you may need trial and error to

play11:00

find

play11:01

all the different variations the next

play11:04

item in the playlist

play11:05

is a video from another author which

play11:07

works a truth table example for mcdc

play11:12

coverage

play11:14

there are a number of other possible

play11:17

considerations when considering unit

play11:19

testing coverage

play11:21

a common criteria for good unit tests is

play11:23

to exercise

play11:24

boundaries known as boundary testing

play11:27

boundary testing probes the borders of

play11:30

behavioral changes

play11:32

as examples if you have a minimum

play11:34

acceptable value

play11:35

you check one notch below the minimum

play11:38

exactly at the minimum and one notch

play11:40

above the minimum to make sure there's

play11:42

no unexpected results

play11:44

that includes minimum and maximum values

play11:46

things like counter rollovers

play11:48

wraparounds overflows and those sorts of

play11:51

things

play11:52

a special category of boundary tests

play11:54

that is important in practice

play11:56

are time crossings hours crossing

play11:58

midnight

play11:59

days crossing the end of the month leap

play12:01

years leap days

play12:03

years rolling over from 1999 to 2000

play12:06

and so on a different type of unit

play12:09

testing coverage that also matters for

play12:11

robust systems

play12:12

is handling exceptional values every

play12:15

time there's

play12:16

a floating point computation ask

play12:18

yourself what happens

play12:19

if a not a number or an infinity is

play12:21

pulled into the equation

play12:23

for pointers ask what happens if there's

play12:25

a null pointer that's dereferenced

play12:27

for string operations ask what happens

play12:29

if there's a null string or a string

play12:31

with no

play12:31

terminating character other exceptional

play12:34

values include

play12:35

undefined inputs or invalid inputs or

play12:38

unusual events

play12:39

such as leap year or daylight savings

play12:41

time changes

play12:43

in other words exceptional value testing

play12:45

is both control flow but also

play12:47

often more importantly special data

play12:49

values that are not the normal expected

play12:52

values but which can still happen

play12:54

and cause your software to misbehave

play12:56

when you think you're done unit testing

play12:58

you should be able to justify

play13:00

your level of coverage is your software

play13:03

good enough if you only get statement

play13:05

coverage or do you need

play13:06

mcdc coverage and did you cover all the

play13:09

special cases did you cover all the

play13:11

potential data issues

play13:12

did you look for divide by zero and so

play13:15

on

play13:16

it's important to define a strategy not

play13:18

only for structural and behavior

play13:20

coverage

play13:21

but also for boundary and exception

play13:23

coverage to get good

play13:24

thorough unit testing

play13:28

while you can do your own unit testing

play13:30

from the ground up

play13:32

it's smart to use a unit testing

play13:33

framework if one is available in your

play13:35

development environment

play13:37

c unit is a classic example framework

play13:40

although it doesn't really matter which

play13:41

one you use as long as it suits your

play13:43

purposes

play13:45

in c unit there's a test registry which

play13:47

contains a number of test suites

play13:49

and a test suite is a set of related

play13:51

test cases

play13:53

a test case is a procedure that runs one

play13:55

or more executions of a module

play13:57

for the purpose of testing typically

play14:00

it's better if a test case has either

play14:02

one case

play14:02

or a number of variations on a very

play14:05

narrow theme

play14:06

to keep things consistent within the

play14:08

test case

play14:09

an assertion is a statement that

play14:11

determines if a test has passed or fail

play14:13

remember that just exercising the

play14:15

software isn't good enough you have to

play14:17

also know

play14:18

that the test passed by comparing the

play14:19

output values to see if they're what's

play14:21

expected

play14:23

as an example of a test case let's

play14:25

consider you have a function that takes

play14:27

two integers and returns the maximum you

play14:30

would define

play14:30

test maxi with the test underscore part

play14:33

being there to help the framework know

play14:35

that these two routines correspond to

play14:37

each other

play14:38

test maxi is a test case and has three

play14:41

different assertions in it

play14:43

cu standing for c unit assert is true

play14:46

if the logical relation is true and

play14:48

false if it's not

play14:49

the test passes if it's true the test

play14:51

fails if it's false

play14:52

and here we say the max of 0 and 2

play14:55

should be 2

play14:56

the max of 0 negative 2 should be 0 and

play14:59

the max of 2 and 2 should be 2.

play15:02

in practice a c unit test looks like

play15:04

long lists of cu asserts or other

play15:06

similar assertions

play15:07

and inside the assertion is a test that

play15:10

sends inputs to the unit you're trying

play15:12

to test and then checks for the expected

play15:13

output

play15:17

here are some best practices for unit

play15:19

testing

play15:20

you should unit test every module in

play15:23

your system

play15:24

use a high coverage combination of white

play15:26

box and black box testing

play15:28

ideally with a fairly granular coverage

play15:30

metric such as

play15:31

mcdc coverage or branch coverage

play15:35

use a unit testing framework such as c

play15:37

unit and break tests up into fairly

play15:39

small

play15:40

chunks so it's easy to understand what

play15:42

each test does

play15:43

and you can be sure that you did all the

play15:44

things you intended to do

play15:46

in terms of different types of coverage

play15:48

don't forget

play15:49

to not only get control flow coverage

play15:51

which is what those metrics were about

play15:53

but also good data value coverage such

play15:55

as validating all lookup table

play15:57

entries checking for divide by zeros

play16:00

checking for unusual exceptional data

play16:02

values and so on

play16:04

there are a number of pitfalls to unit

play16:06

testing creating test cases is a

play16:08

development effort

play16:10

it takes significant resources and guess

play16:12

what test cases can also have bugs

play16:15

so a failed test case could be a bug in

play16:17

the code but it could also be a bug in

play16:19

the test case

play16:20

nonetheless it's important to do the

play16:22

test cases to make sure that your code

play16:24

is as thoroughly debugged as possible

play16:26

before you begin

play16:28

more integrated system level testing if

play16:31

you've written code that is difficult to

play16:32

test

play16:33

it can lead to dysfunctional unit test

play16:35

strategies

play16:36

for example sometimes people put their

play16:39

system into a breakpoint debugger

play16:41

take the entire source code image and

play16:43

try and do unit testing with a

play16:45

breakpoint

play16:45

debugger that is not an effective unit

play16:48

test strategy

play16:49

if that's the only way to run your

play16:51

functions then you should probably

play16:53

redesign to make unit test more feasible

play16:56

similarly just because you're using c

play16:58

unit doesn't mean you're doing unit

play16:59

testing

play17:00

using c unit to test a hundred thousand

play17:03

line blob of code

play17:04

well that is a sort of testing but

play17:06

you're not going to get very good

play17:07

coverage and it's not really what we

play17:09

meant by unit testing

play17:11

if you use only white box testing it is

play17:14

doomed to succeed

play17:15

and by that i mean there are tools that

play17:16

will automatically generate

play17:18

tests that cover all your lines of code

play17:20

but those tools have no way of knowing

play17:22

what the values are supposed to be

play17:24

unless you feed them additional

play17:25

information and they're not going to

play17:27

generate code you forgot to put in

play17:29

such as missing special cases finally

play17:32

do not substitute unit testing for peer

play17:35

reviews and static analysis

play17:37

you should be doing peer reviews you

play17:39

should be doing static analysis

play17:40

and unit testing should come after the

play17:42

code looks clean from both of those

play17:44

methods

Rate This
β˜…
β˜…
β˜…
β˜…
β˜…

5.0 / 5 (0 votes)

Related Tags
Unit TestingSoftware DesignTest CoverageQuality AssuranceDebugging TipsCode ModulesTest SuitesAssertionsBest PracticesBoundary Testing