Development Testing

Development Testing

Development testing includes all testing activities that are carried out by the team developing the system. The tester of the software is usually the programmer who developed that software, although this is not always the case.

For critical systems, a more formal process may be used, with a separate testing group within the development team. They are responsible for developing tests and maintaining detailed records of test results. During development, testing may be carried out at three levels of granularity:

  1. Unit testing:- where individual program units or object classes are tested. Unit testing should focus on testing the functionality of objects or methods.

  2. Component testing:- where several individual units are integrated to create composite components. Component testing should focus on testing component interfaces.

  3. System testing:- where some or all of the components in a system are integrated and the system is tested as a whole. System testing should focus on testing component interactions.

Development testing is primarily a defect testing process, where the aim of testing is to discover bugs in the software. It is therefore usually interleaved with debugging— the process of locating problems with the code and changing the program to fix these problems.

Now let us understand each of these in detail.-

Unit testing:-

Unit testing is a crucial process in software development, involving the testing of program components such as methods or object classes to ensure their proper functioning.

When testing individual functions or methods, it's essential to design tests that involve calling routines with different input parameters, utilizing approaches discussed in test case design.

On the other hand, when testing object classes, thorough tests should cover all features, including testing all operations associated with the object, setting and checking the value of all attributes, and simulating all possible states and state changes.

In cases of inheritance, testing becomes more complex as inherited operations may rely on assumptions not valid in subclasses, necessitating testing in all relevant contexts.

Unit testing further involves of:-

  1. State testing.

  2. Automated testing.

Now let us understand each of them in detail.

  1. State testing involves using a state model to identify and test state transition sequences, ensuring comprehensive coverage of the object's behaviour.

  2. Automating unit testing using frameworks, the process allowing for quick execution of tests after program changes. Automated tests typically comprise setup, call, and assertion parts, facilitating efficient testing of the system's functionality.

An automated test has three parts:

  1. A setup part, where you initialize the system with the test case, namely the inputs and expected outputs.

  2. A call part is where you call the object or method to be tested.

  3. An assertion part where you compare the result of the call with the expected result. If the assertion evaluates to true, the test has been successful; if false, then it has failed.

When testing an object, there may be dependencies on external objects that are either unavailable or could slow down the testing process, such as when the object interacts with a database, necessitating time-consuming setup procedures. To address this, mock objects can be employed.

Mock objects mimic the behaviour of external dependencies, offering a lightweight alternative. For instance, a mock object emulating a database might contain a minimal dataset organized in an array, enabling swift access without the overhead of disk operations. Similarly, mock objects can simulate uncommon scenarios, like specific times of day, bypassing the need for real-time data, and thereby enhancing testing efficiency and flexibility.

Now let us learn about the choosing unit test cases-

Testing is expensive and time-consuming, so it is important that you choose effective unit test cases. Effectiveness, in this case, means two things:

  1. The test cases should show that, when used as expected, the component that you are testing does what it is supposed to do.

  2. If there are defects in the component, these should be revealed by test cases.

You should therefore write two kinds of test cases. The first of these should reflect the normal operation of a program and should show that the component works. For example, if you are testing a component that creates and initializes a new patient record, then your test case should show that the record exists in a database and that its fields have been set as specified.

The other kind of test case should be based on testing experience of where common problems arise. It should use abnormal inputs to check that these are properly processed and do not crash the component.

I discuss two possible strategies here that can be effective in helping you choose test cases. These are:

  1. Partition testing, where you identify groups of inputs that have common characteristics and should be processed in the same way. You should choose tests from within each of these groups.

  2. Guideline-based testing, where you use testing guidelines to choose test cases. These guidelines reflect previous experience with the kinds of errors that programmers often make when developing components.

  • Equivalence partitioning, also known as equivalence classes or domains, is a systematic approach to test case design based on grouping input and output data into classes with similar characteristics. Programs typically behave similarly for all members of a class, so test cases are designed to cover these partitions. Input and output equivalence partitions are identified, with the program expected to handle all members of an input partition consistently.

  • Test cases are selected from these partitions, focusing on boundary cases and values close to the partition midpoint. Failure to consider boundary values may lead to program failures. Partitions are identified using program specifications, user documentation, and experience.

  • Black-box testing, which relies solely on system specifications, is complemented by white-box testing, which involves examining the program's code for additional tests, such as exception handling. This holistic testing approach ensures comprehensive coverage of various scenarios, improving the reliability of the software.

  • Equivalence partitioning is an effective approach to testing because it helps account for errors that programmers often make when processing inputs at the edges of partitions. You can also use testing guidelines to help choose test cases.

Guidelines encapsulate knowledge of what kinds of test cases are effective for discovering errors. For example, when you are testing programs with sequences, arrays, or lists, guidelines that could help reveal defects include:

  1. Test software with sequences that have only a single value. Programmers naturally think of sequences as made up of several values and sometimes they embed this assumption in their programs. Consequently, if presented with a single value sequence, a program may not work properly.

  2. Use different sequences of different sizes in different tests. This decreases the chances that a program with defects will accidentally produce a correct output because of some accidental characteristics of the input.

  3. Derive tests so that the first, middle, and last elements of the sequence are accessed. This approach reveals problems at partition boundaries.

We will learn about component testing and system testing in our next blogs.