0% found this document useful (0 votes)
40 views132 pages

ASM Final APDP QuangBinhQuan BH00999

This final report for the Pearson BTEC Level 5 Higher National Diploma in Computing covers Unit 20: Applied Programming and Design Principles, detailing the importance of design principles and testing methodologies in software development. It includes sections on object-oriented programming, clean coding techniques, and various testing types, emphasizing how adherence to SOLID principles enhances system quality. The report aims to provide a comprehensive understanding of how these concepts contribute to the successful development and deployment of software systems.

Uploaded by

trieunt6
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
40 views132 pages

ASM Final APDP QuangBinhQuan BH00999

This final report for the Pearson BTEC Level 5 Higher National Diploma in Computing covers Unit 20: Applied Programming and Design Principles, detailing the importance of design principles and testing methodologies in software development. It includes sections on object-oriented programming, clean coding techniques, and various testing types, emphasizing how adherence to SOLID principles enhances system quality. The report aims to provide a comprehensive understanding of how these concepts contribute to the successful development and deployment of software systems.

Uploaded by

trieunt6
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 132

ASSIGNMENT FINAL REPORT

Qualification Pearson BTEC Level 5 Higher National Diploma in Computing

Unit number and title Unit 20: Applied Programming and Design Principles

Submission date 14/8/2024 Date Received 1st Submission

Re-submission Date Date Received 2nd Submission

Student Name Quang Binh Quan Student ID BH00999

Class SE06205 Assessor name Nguyen Thanh Trieu

Plagiarism
Plagiarism is a particular form of cheating. Plagiarism must be avoided at all costs and students who break the rules, however innocently, may be
penalised. It is your responsibility to ensure that you understand correct referencing practices. As a university level student, you are expected to use
appropriate references throughout and keep carefully detailed notes of all your sources of materials for material you have used in your work,
including any material downloaded from the Internet. Please consult the relevant unit lecturer or your course tutor if you need any further advice.

Student Declaration
I certify that the assignment submission is entirely my own work and I fully understand the consequences of plagiarism. I declare that the work
submitted for assessment has been carried out without assistance other than that which is acceptable according to the rules of the specification. I
certify I have clearly referenced any sources and any artificial intelligence (AI) tools used in the work. I understand that making a false declaration is
a form of malpractice.

Student’s signature Quan


Grading grid

P1 P2 P3 P4 M1 M2 D1
ASSIGNMENT GROUP WORK
Qualification Pearson BTEC Level 5 Higher National Diploma in Computing

Unit number and title Unit 20: Applied Programming and Design Principles

Submission date Date Received 1st submission

Re-submission Date Date Received 2nd submission

Student names & codes Final scores Signatures

1. Nguyen Chi Thanh – BH00887 Thanh

2. Le Thanh Dat – BH01028 Dat


Group number:
3. Quang Binh Quan – BH00999 Quan

4. Tran Van Vu – BH01025 Vu

Class SE06205 Assessor name Nguyen Thanh Trieu

Plagiarism
Plagiarism is a particular form of cheating. Plagiarism must be avoided at all costs and students who break the rules, however innocently, may be penalised. It is your
responsibility to ensure that you understand correct referencing practices. As a university level student, you are expected to use appropriate references throughout and
keep carefully detailed notes of all your sources of materials for material you have used in your work, including any material downloaded from the Internet. Please
consult the relevant unit lecturer or your course tutor if you need any further advice.

Student Declaration
I certify that the assignment submission is entirely my own work and I fully understand the consequences of plagiarism. I declare that the work submitted for
assessment has been carried out without assistance other than that which is acceptable according to the rules of the specification. I certify I have clearly referenced any
sources and any artificial intelligence (AI) tools used in the work. I understand that making a false declaration is a form of malpractice.

P5 P6 P7 M3 M4 D2
OBSERVATION RECORD
Student

Description of activity undertaken

Assessment & grading criteria

How the activity meets the requirements of the criteria

Student
Date:
signature:

Assessor
Date:
signature:

Assessor
name:
 Summative Feedback:  Resubmission Feedback:

Grade: Assessor Signature: Date:


Internal Verifier’s Comments:

Signature & Date:


Contents
I. INTRODUCTION ......................................................................................................................................... 12
REPORT PART 1 ............................................................................................................................................. 13
Task 1. How Good Design Principles Enhance System Quality. ............................................................... 13
1. Investigate Object-Oriented Paradigm............................................................................................. 13
2. Class Relationships. .......................................................................................................................... 29
3. SOLID Principles. ............................................................................................................................... 33
Task 2. Clean Coding Techniques and Their Impact on Data Structures and Algorithms. ....................... 48
1. Clean Coding Techniques. ................................................................................................................ 48
2. Impact on Data Structures and Operations...................................................................................... 54
Task 3. Designing the Architecture and Testing Regime for SIMS. .......................................................... 63
1. Use case diagram .............................................................................................................................. 63
2. Class diagram .................................................................................................................................... 65
3. Package diagram............................................................................................................................... 66
4. How SOLID principles have been applied in design. ........................................................................ 67
5. Test Plan for Student Information Management System (SIMS) ..................................................... 69
Task 4. Analyze how creational, structural, and behavioural design patterns are used in practices or in
your team proposed solution. .................................................................................................................. 71
Task 5. Analyze the SIMS application and evaluate how adhering to SOLID principles has influenced the
design and development. ......................................................................................................................... 76
REPORT PART 2 ............................................................................................................................................. 85
Task 6: ...................................................................................................................................................... 85
1. What is Automation Testing? ......................................................................................................... 85
2. What is unit testing? ....................................................................................................................... 91
3. What is Integration Testing? .......................................................................................................... 95
4. What is End-to-End testing? .......................................................................................................... 99
5. Differences between test types .................................................................................................... 101
Task 7: .................................................................................................................................................... 103
1. Function ......................................................................................................................................... 103
2. Test ................................................................................................................................................. 108
Task 8: .................................................................................................................................................... 118
1. Assess effectiveness of using SOLID principles, clean coding techniques on the application
developed .......................................................................................................................................... 118
2. Analyse the benefits and drawbacks of different forms of automatic testing ......................... 124
III. CONCLUSION ......................................................................................................................................... 131
IV. REFERENCES .......................................................................................................................................... 132
Figure 1 OOP example .................................................................................................................................. 14
Figure 2 Inheritance in C# example .............................................................................................................. 15
Figure 3 Example: Class Inheritance in C# .................................................................................................... 15
Figure 4 Example: Inherited Members ......................................................................................................... 16
Figure 5 Example: Constructors in Inheritance ............................................................................................. 17
Figure 6 Example: Object Creation ............................................................................................................... 18
Figure 7 Single Inheritance ........................................................................................................................... 19
Figure 8 Multi-level Inheritance.................................................................................................................... 19
Figure 9 Hierarchical Inheritance ................................................................................................................. 20
Figure 10 Hybrid Inheritance ........................................................................................................................ 20
Figure 11 Multiple Inheritance ..................................................................................................................... 21
Figure 12 Example: Student class encapsulation ......................................................................................... 22
Figure 13 Example: Encapsulation................................................................................................................ 23
Figure 14 Example: Abstract class ................................................................................................................ 25
Figure 15 Example: Interface ........................................................................................................................ 26
Figure 16 Example: Method Overloading ..................................................................................................... 27
Figure 17 Example: Method Overriding ....................................................................................................... 28
Figure 18 Example: Polymorphism code....................................................................................................... 28
Figure 19 UML class diagrams ...................................................................................................................... 29
Figure 20 Example: Inheritance .................................................................................................................... 30
Figure 21 Example: Implementation ............................................................................................................ 30
Figure 22 Example: Composition .................................................................................................................. 31
Figure 23 Example: Aggregation .................................................................................................................. 31
Figure 24 Example: Association .................................................................................................................... 32
Figure 25 Example: Dependency .................................................................................................................. 32
Figure 26 Class Diagram Example: GUI ........................................................................................................ 33
Figure 27 Example: SRP ................................................................................................................................ 34
Figure 28 Example: SRP Solution .................................................................................................................. 35
Figure 29 Example: OCP................................................................................................................................ 36
Figure 30 Example: OCP Solution ................................................................................................................. 38
Figure 31 Example: LSP 1 .............................................................................................................................. 39
Figure 32 Example: LSP 2 .............................................................................................................................. 40
Figure 33 Example: LSP Solution 1 ................................................................................................................ 40
Figure 34 Example: LSP Solution 2 ................................................................................................................ 41
Figure 35 Example: ISP 1 .............................................................................................................................. 42
Figure 36 Example: ISP 2 .............................................................................................................................. 44
Figure 37 Example: DIP ................................................................................................................................. 45
Figure 38 Example: DIP Solution ................................................................................................................... 46
Figure 39 C# Queue Example ........................................................................................................................ 54
Figure 40 Use Case diagram ......................................................................................................................... 63
Figure 41 Class diagram ............................................................................................................................... 65
Figure 42 Package diagram .......................................................................................................................... 66
Figure 43 Example illustrate: Design Patterns ............................................................................................. 71
Figure 44 Singleton Pattern applied in example .......................................................................................... 72
Figure 45 Adapter Pattern applied in example 1.......................................................................................... 73
Figure 46 Adapter Pattern applied in example 2.......................................................................................... 73
Figure 47 Adapter Pattern applied in example 3.......................................................................................... 73
Figure 48 Adapter Pattern applied in example 4.......................................................................................... 74
Figure 49 Strategy Pattern applied in example 1 ......................................................................................... 74
Figure 50 Strategy Pattern applied in example 2 ......................................................................................... 75
Figure 51 Strategy Pattern applied in example 3 ......................................................................................... 75
Figure 52 Strategy Pattern applied in example 4 ......................................................................................... 76
Figure 53 Output Strategy Pattern applied in example 4.5 .......................................................................... 76
Figure 54 SRP example 1 .............................................................................................................................. 77
Figure 55 SRP example 2 .............................................................................................................................. 78
Figure 56 SRP example 3 .............................................................................................................................. 78
Figure 57 SRP example 4 .............................................................................................................................. 79
Figure 58 SRP example 5 .............................................................................................................................. 79
Figure 59 OCP example 1 .............................................................................................................................. 80
Figure 60 OCP example 2 .............................................................................................................................. 80
Figure 61 LSP example 1 ............................................................................................................................... 81
Figure 62 LSP example 2 ............................................................................................................................... 82
Figure 63 LSP example 3 ............................................................................................................................... 82
Figure 64 LSP example 4 ............................................................................................................................... 83
Figure 65 ISP example................................................................................................................................... 83
Figure 66 DIP example 1 ............................................................................................................................... 84
Figure 67 DIP example 2 ............................................................................................................................... 84
Figure 68 Software testing life cycle ............................................................................................................ 87
Figure 69 Challenges of Automation Testing ............................................................................................... 90
Figure 70 Unit testing example .................................................................................................................... 91
Figure 71 Integration Testing ....................................................................................................................... 95
Figure 72 End-to-End testing ........................................................................................................................ 99
Figure 73 Login form................................................................................................................................... 103
Figure 74 Register form .............................................................................................................................. 104
Figure 75 Create Student form ................................................................................................................... 105
Figure 76 Student view list ......................................................................................................................... 105
Figure 77 Edit student form ....................................................................................................................... 106
Figure 78 Student list edit function ............................................................................................................ 106
Figure 79 Student list function delete ........................................................................................................ 107
Figure 80 SRP Example code 1.................................................................................................................... 118
Figure 81 SRP Example code 2.................................................................................................................... 119
Figure 82 SRP Example code 3.................................................................................................................... 119
Figure 83 OCP Example code ...................................................................................................................... 120
Figure 84 ISP Example code ........................................................................................................................ 121
Figure 85 LSP Example code ....................................................................................................................... 122
Figure 86 DIP Example code ....................................................................................................................... 124
I. INTRODUCTION
In the rapidly evolving field of software development, adhering to robust design principles and employing
effective testing methodologies is crucial for producing high-quality systems. This report delves into
several key aspects of software design and testing, emphasizing how these concepts contribute to the
overall quality and maintainability of software systems. The report is structured into two parts.

The first part explores the application of good design principles, with a focus on the object-oriented
paradigm, class relationships, and SOLID principles. It also examines clean coding techniques and their
significant impact on data structures, algorithms, and system architecture, specifically within the context
of a Student Information Management System (SIMS). Furthermore, it analyzes the use of creational,
structural, and behavioral design patterns, and evaluates how the adherence to SOLID principles
influences the development process.

The second part of the report transitions into testing methodologies, discussing various types of testing,
including automation, unit testing, integration testing, and end-to-end testing. It also investigates the
effectiveness of SOLID principles and clean coding techniques in the development of applications and
evaluates the benefits and drawbacks of different forms of automated testing. Through this
comprehensive analysis, the report aims to provide a deep understanding of how design principles and
testing strategies play an integral role in the successful development and deployment of software
systems.
REPORT PART 1
Task 1. How Good Design Principles Enhance System Quality.
1. Investigate Object-Oriented Paradigm.
Object-Oriented Programming (OOP) is a programming paradigm that uses objects and classes to design
and develop software applications. It is based on the concept of objects, which can contain data in the
form of fields (attributes or properties) and code in the form of procedures (methods or functions).

Structure of object-oriented programming

Object-oriented programming contains various structures, known as the building blocks of OOP. These
structures include:

• Class: A class is a data type that provides a framework for creating objects. You can define a class
to create multiple objects without writing additional code.

• Object: In OOP, an object represents an instance, or creation, of a class. Objects define specific
data, such as properties and behaviors, to implement code.

• Method: A method is a function that performs a task or action. For example, a method may
return information about an object's data.

• Attribute: This structure stores information about an object and defines its state. You can define
an attribute as part of the class.

Object-Oriented Programming offers several benefits, including:

• Modularity: OOP promotes modularity by breaking down complex systems into smaller,
manageable parts (objects). This makes it easier to maintain and update the code.

• Reusability: OOP allows you to reuse existing code by creating new objects based on existing
ones. This saves time and effort in developing new applications.

• Flexibility: OOP provides flexibility in designing and implementing software systems. You can
easily modify and extend the functionality of objects without affecting other parts of the system.

• Scalability: OOP supports scalability by allowing you to add new objects and classes as the system
grows. This makes it easier to accommodate changes and enhancements in the software.

As you can see, OOP offers several advantages that makes it a popular choice for developing software
applications. Let's explore the four fundamental pillars of OOP in more detail.

Four Principles of OOP


The four pillars of object-oriented programming are:

• Inheritance: child classes inherit data and behaviors from the parent class

• Encapsulation: containing information in an object, exposing only selected information

• Abstraction: only exposing high-level public methods for accessing an object

• Polymorphism: many methods can do the same task

Figure 1 OOP example

Inheritance in C#

In object-oriented programming, inheritance is another type of relationship between classes. Inheritance


is a mechanism of reusing the functionalities of one class into another related class.

Inheritance is referred to as "is a" relationship. In the real world example, a customer is a person. In the
same way, a student is a person and an employee is also a person. They all have some common things, for
example, they all have a first name, middle name, and last name. So to translate this into object-oriented
programming, we can create the Person class with first name, middle name, and last name properties and
inherit the Customer, Student, and Employee classes from the Person class. That way we don't need to
create the same properties in all classes and avoid the violation of the DRY (Do not Repeat Yourself)
principle.
Figure 2 Inheritance in C# example

In C#, use the : symbol to inherit a class from another class. For example, the following Employee class
inherits from the Person class in C#.

Figure 3 Example: Class Inheritance in C#

In the above example, the Person class is called the base class or the parent class, and the Employee class
is called the derived class or the child class.

The Employee class inherits from the Person class and so it automatically acquires all the public members
of the Person class. It means even if the Employee class does not include FirstName, LastName properties
and GetFullName() method, an object of the Employee class will have all the properties and methods of
the Person class along with its own members.
Figure 4 Example: Inherited Members

Note that C# does not allow a class to inherit multiple classes. A class can only achieve multiple inheritances
through interfaces.

Role of Access Modifiers in Inheritance

Access modifiers play an important role in inheritance. Access modifiers of each member in the base class
impact their accessibility in the derived class.

MODIFIERS OUTSIDE ASSEMBLY DERIVED CLASS

PRIVATE No No

PUBLIC Yes Yes

PROTECTED No No

INTERNAL Yes ( this assembly only) Yes ( this assembly only)

INTERNAL PROTECTED Yes ( this assembly only) Yes

Constructors

Creating an object of the derived class will first call the constructor of the base class and then the derived
class. If there are multiple levels of inheritance then the constructor of the first base class will be called
and then the second base class and so on.
Figure 5 Example: Constructors in Inheritance

Object Initialization

You can create an instance of the derived class and assign it to a variable of the base class or derived
class. The instance's properties and methods are depending on the type of variable it is assigned to. Here,
a type can be a class or an interface, or an abstract class.

The following table list supported members based on a variable type and instance type.

Instance variable Instance Type Instance Members of


Base type Base type Base type

Base type Derived type Base type

Derived type Derived type Base and derived type

The following program demonstrates supported members based on the variable type:
Figure 6 Example: Object Creation

In the above example, the type of per2 is Person, so it will only expose public properties of the Person type
even if an object type is the Employee. However, the type of emp is Employee and so it exposes all the
public properties of both classes. Note that the base type object cannot be assigned to the derived type
variable.

Types of Inheritance

There are different types of inheritance supported in C# based on how the classes are inherited.

Single Inheritance

In a single inheritance, only one derived class inherits a single base class.
Figure 7 Single Inheritance

Multi-level Inheritance

In multi-level inheritance, a derived class inherits from a base class and then the same derived class
becomes a base class for another derived class. Practically, there are no limits on the level of inheritance,
but you should avoid it.

Figure 8 Multi-level Inheritance

Hierarchical Inheritance

In hierarchical inheritance, multiple derived classes inherit from a single base class.
Figure 9 Hierarchical Inheritance

Hybrid Inheritance

Hybrid inheritance is a combination of multi-level and hierarchical inheritance.

Figure 10 Hybrid Inheritance

Multiple Inheritance

In multiple inheritance, a class inherits from multiple interfaces. Note that C# does not support deriving
multiple base classes. Use interfaces for multiple inheritance.
Figure 11 Multiple Inheritance

Important Points:

• In C#, three types can participate in inheritance: Class, Struct, and Interface.

• A class can inherit a single class only. It cannot inherit from multiple classes.

• A class cannot inherit from a struct.

• A class can inherit (implement) one or more interfaces.

• A Struct can inherit from one or more interfaces. However, it cannot inherit from another struct
or class.

• An interface can inherit from one or more interfaces but cannot inherit from a class or a struct.

• Constructors or destructors cannot be inherited.

Encapsulation

Encapsulation is a technique to implement abstraction in code. Create classes and their members with
appropriate access modifiers to show or hide details and complexity.

Encapsulation hides the data and implementation details show only the required members within a class,
thus hiding complexity from other code. No other code needs to know about implementation detail and
also can’t modify the code of the class’s data and methods.

Most object-oriented programming languages allow you to create classes and their properties and
methods along with the access modifiers such as public, private, protected, and internal to show or hide
data members and implementation details. Interfaces and abstract classes can also be used for
encapsulation.
For example, the Student class has the following members:

Figure 12 Example: Student class encapsulation

As you can see, the FirstName, MiddleName, LastName, and FullName are data members
and Save(), Subscribe(), GetSubscribedCourses() are methods.

In C#, we can implement encapsulation mostly using class, interface, abstract class, property, method,
struct, enum, and access modifiers. For the above Student entity, we can create the Student class. Use
properties for the data members and methods for the actions.

The following example demonstrates encapsulation.


Figure 13 Example: Encapsulation

Above, private fields such as _firstName, _middleName, and _lastName store the data privately to hide it
from the external code so that they cannot modify it with invalid values. FirstName, MiddleName,
and LastName properties use these fields in the getters and setters to return and set values to these fields.
These are public properties so that they are visible and accessible to outside code via getters and setters.
The FullName property internally uses private variables to return the full name of the student.

In the same way, the public Enroll() method is visible but it hides the implementation detail by internally
calling the private Subscribe() method. External code cannot know and access the Subscribe() method
because it is a private method.

Advantages of Encapsulation:

• Hides data and complexities.

• Restrict unauthorized access of data by allowing authorization before data access.

• Allow validation before setting data.

• Only the author of the class needs to understand the implementation, not others.

• Makes applications easy to maintain.

Abstraction in Object-oriented Programming

Abstraction is a key concept in object-oriented programming that allows you to create a blueprint for a
class with some abstract methods that must be implemented by the derived classes. It enables you to
define the structure of a class without providing the implementation details.

In C#, Abstraction can be achieved using abstract classes and interfaces. Let's explore both concepts:

Abstract Classes

An abstract class is a class that cannot be instantiated and can contain both abstract and non-abstract
methods. An abstract method is a method without a body that must be implemented by the derived
classes.

Here's an example of an abstract class in C#:


Figure 14 Example: Abstract class

In this example, the Animal class is an abstract class with an abstract method Speak.The Dog and Cat classes
inherit from the Animal class and provide specific implementations for the Speak method. This is an
example of Abstraction using abstract classes in C#.

Interfaces

An interface is a reference type in C# that defines a contract for classes to implement. It contains only the
declaration of the methods, properties, events, or indexers, without providing the implementation.

Here's an example of an interface in C#:


Figure 15 Example: Interface

In this example, the IFlyable interface defines a contract with a method Fly. The Bird and Airplane classes
implement the IFlyable interface and provide specific implementations for the Fly method. This is an
example of Abstraction using interfaces in C#.

Polymorphism

Polymorphism is a core concept in object-oriented programming that allows objects of different classes to
be treated as objects of a common superclass. It provides a single interface to represent multiple
underlying forms (classes) and enables objects to be processed in a generic manner.

In C#, there are two types of Polymorphism:

1. Compile-time Polymorphism (Method Overloading)

2. Run-time Polymorphism (Method Overriding)

Compile-time Polymorphism (Method Overloading)

Compile-time Polymorphism, also known as Method Overloading, allows a class to have multiple methods
with the same name but different parameters. The compiler determines which method to invoke based on
the number and types of arguments.

Here's an example of Method Overloading in C#:


Figure 16 Example: Method Overloading

In this example, the Printer class has three Print methods with the same name but different parameters.
This is an example of Method Overloading in C#.

Run-time Polymorphism (Method Overriding)

Run-time Polymorphism, also known as Method Overriding, allows a subclass to provide a specific
implementation of a method that is already provided by its superclass.

Here's an example of Method Overriding in C#:


Figure 17 Example: Method Overriding

In this example, the MusicPlayer class has a virtual method Play. The Mp3Player and WavPlayer classes
override the Play method with specific implementations for playing MP3 and WAV music, respectively.
This is an example of Method Overriding in C#.

Let's see how Polymorphism can be used in a program:

Figure 18 Example: Polymorphism code

In this code snippet, we created an object of the Mp3Player class and assigned it to a variable of
type MusicPlayer. We then called the Play method on the player object, which invokes the
overridden Play method in the Mp3Player class. We then created an object of the WavPlayer class and
assigned it to the player variable. When we call the Play method again, it invokes the
overridden Play method in the WavPlayer class.
2. Class Relationships.
There are six main types of relationships between classes: inheritance , realization / implementation ,
composition , aggregation , association, and dependency . The arrows for the six relationships are as
follows:

Then we come to understand the specific content of the class relationship.

Figure 19 UML class diagrams

Six types of relationships

In the six types of relationships, the code structure of the three types of relationships such as composition,
aggregation , and association is the same as using attributes to store the references of another class.
Therefore, they must be distinguished by the relationship between the contents.

Inheritance

Inheritance is also called generalization and is used to describe the relationship between parent and
child classes. A parent class is also called a base class, and a subclass is also called a derived class.

In the inheritance relationship, the subclass inherits all the functions of the parent class, and the parent
class has all the attributes, methods, and subclasses. Subclasses contain additional information in
addition to the same information as the parent class.

For example: buses, taxis, and cars are cars, they all have names, and they can all be on the road.
Figure 20 Example: Inheritance

Realization / Implementation

Implementation (Implementation) is mainly used to specify the relationship between interfaces and
implementation classes .

An interface (including an abstract class ) is a collection of methods. In an implementation relationship, a


class implements an interface, and methods in the class implement all methods of the interface
declaration.

For example: cars and ships are vehicles, and the vehicle is just an abstract concept of a mobile tool, and
the ship and the vehicle realize the specific mobile functions.

Figure 21 Example: Implementation

Composition relationship

Composition: The relationship between the whole and the part, but the whole and the part cannot be
separated .

The combination relationship represents the relationship between the whole and part of the class, and
the overall and part have a consistent lifetime. Once the overall object does not exist, some of the
objects will not exist, and they will all die in the same life.For example, a person is composed of a head
and a body. The two are inseparable and coexist.

Figure 22 Example: Composition

Aggregation Relationship

Aggregation: The relationship between the whole and part, and the whole and part can be separated.

Aggregate relations also represent the relationship between the whole and part of the class, member
objects are part of the overall object, but the member object can exist independently from the overall
object.

For example, bus drivers and work clothes and hats are part of the overall relationship, but they can be
separated. Work clothes and hats can be worn on other drivers. Bus drivers can also wear other work
clothes and hats.

Figure 23 Example: Aggregation

Association Relationships

Association: Indicates that a property of a class holds a reference to an instance (or instances) of
another class .

Association is the most commonly used relationship between a class and a class, which means that there
is a connection between one type of object and another type of object. Combinations and aggregations
also belong to associative relations , but relations between classes of affiliations are weaker than the
other two.

There are four kinds of associations : two-way associations , one-way associations , self-association ,
and multiple-number associations .
For example: cars and drivers, one car corresponds to a particular driver, and one driver can drive
multiple cars.

Figure 24 Example: Association

In UML diagrams, bidirectional associations can have two arrows or no arrows , and one-way
associations or self-associations have an arrow .

In a multiplicity relationship, you can add a number directly to the associated line to indicate the number
of objects in the corresponding class.

• 1..1: Only one

• 0..*: Zero or more

• 1..*:one or more

• 0..1: No or only one

• m..n: at least m, at most n (m<=n)

Dependencies

Dependence: Assume that a change in class A causes a change in class B, then say that class B depends on
class A.

In most cases, dependencies are reflected in methods of a class that use another class’s object as a
parameter .

A dependency relationship is a “use” relationship. A change in a particular thing may affect other things
that use it, and use a dependency when it is necessary to indicate that one thing uses another.For
example: The car relies on gasoline. If there is no gasoline, the car will not be able to drive.

Figure 25 Example: Dependency

Class Diagram Example: GUI


A class diagram may also have notes attached to classes or relationships.

Figure 26 Class Diagram Example: GUI

Among the six types of relationships, the code structure of combination, aggregation, and association is
the same, and it can be understood from the strength of the relationship. The order from strong to weak
is: inheritance → implementation → composition → aggregation → association → dependency . The
following is a complete UML diagram.

3. SOLID Principles.
Why should we use the SOLID design principles?

SOLID principles serve as foundational guidelines in software development, aiming to create robust and
adaptable systems. By adhering to SOLID principles, developers foster:

• Code maintainability: Promote clean, organized, and maintainable codebases. By adhering to


these principles, developers ensure that each component of their code has a clear purpose and
well-defined responsibilities. This clarity simplifies maintenance tasks, making it easier to
understand, update, and modify code without causing unintended side effects elsewhere.

• Future scalability: Allows for more adaptable and extensible software systems. Developers should
design code that can be added to but not modified, allowing others to introduce new features or
functionalities without altering existing code. This scalability is essential for accommodating
future changes and expanding the system’s capabilities without compromising stability.

• Flexibility in development: Encourages flexibility in development by providing a framework that


supports iterative and incremental changes. This enables developers to introduce new
functionalities, fix issues, or refactor codebases more efficiently, minimizing risks associated with
unintended consequences or regressions.
• Improved collaboration: Encourages a modular and structured approach to development. This
organization enhances collaboration among team members as it leads to well-defined interfaces
and clear responsibilities, making it easier for different team members to work on distinct parts of
the codebase simultaneously.

Incorporating the SOLID design principles in software development leads to a more robust, adaptable,
and maintainable codebase. Let’s discuss each principle with code examples.

Single Responsibility Principle (SRP)

The Single Responsibility Principle (SRP) promotes clean, maintainable, and scalable software design. It
states that a class should change for only one reason, meaning it should have a single responsibility.

Let’s consider a user creation process that involves validating and saving user data to a database.

Refer to the following code example to understand how SRP can be violated through combined handling
validation and persistence.

Figure 27 Example: SRP

Issue

In the previous code, the UserCreator class violates the SRP by combining multiple responsibilities, such
as validation and database persistence. This can lead to a tightly coupled class, making it difficult to test
and prone to unnecessary modifications.

Solution
To address this issue, we can apply SRP by refactoring the code to separate these responsibilities into
individual classes.

Refer to the following refactored code example.

Figure 28 Example: SRP Solution

After refactoring, the code demonstrates the implementation of SRP through the separation of
responsibilities into three classes:

• UserValidator: Validates the user’s email format.

• UserRepository: Handles saving the user’s data to the database.

• UserCreator: Coordinates the user creation process, leveraging the validator and repository
classes for their specific responsibilities.

Benefits
By separating the concerns, we achieve a more maintainable and testable codebase. Each class has a
single responsibility, allowing for more straightforward modification and extension in the future.

Open/Closed Principle (OCP)

The Open-Closed Principle (OCP) says that software entities should be open for extension but closed for
modification. It allows for adding new functionality without modifying existing code.

Let’s consider a scenario where a file-exporting service initially supports exporting data to CSV files.

Refer to the following code example to understand how OCP can be violated and how to correct it using
C#.

Figure 29 Example: OCP

Issue

In this example, the FileExporter class directly implements the functionality for exporting data to CSV
files. However, if we later want to support exporting data to other file formats like Excel or JSON,
modifying the FileExporter class would violate the OCP.

Solution

To use the OCP, we must design our file-exporting service domain to be open for extension.

Refer to the following refactored code example.


Figure 30 Example: OCP Solution

In the improved implementation, we introduce an abstract FileExporter class that defines the common
behavior for all file export operations. Each specific file exporter (CsvFileExporter, ExcelFileExporter,
and JsonFileExporter) inherits from the FileExporter class and implements the Export method according
to the particular file format export logic.

Applying the OCP allows for adding new file exporters without modifying old ones, making it easier to
add new features by introducing subclasses of the FileExporter base class.

Benefits

This approach enhances code flexibility, reusability, and maintainability. Your code can seamlessly handle
new requirements and changes without introducing bugs or disrupting the existing functionality.

Liskov Substitution Principle (LSP)

The Liskov Substitution Principle (LSP) is a concept that guarantees the smooth substitution of objects of
derived classes for objects of their base classes. Its fundamental rule asserts that objects of the base class
must be interchangeable with objects of any of its derived classes, without impacting the accuracy of the
program.

Refer to the following code example to understand how LSP can be violated and how to correct it using
C#.
Figure 31 Example: LSP 1

Issue

In this example, we have a Vehicle class that represents a generic vehicle. It has abstract
methods, StartEngine() and StopEngine(), for starting and stopping the engine. We also have a Car class
that inherits from Vehicle and provides the necessary implementation for the engine-related methods.

However, when we introduce a new type of vehicle, such as an ElectricCar, which doesn’t have an
engine, we encounter a violation of the LSP. In this case, attempting to call
the StartEngine() or StopEngine() methods on an ElectricCar object would result in exceptions because
electric cars do not have engines.
Figure 32 Example: LSP 2

Solution

To address this violation, we need to ensure the correct substitution of objects. One approach is to
introduce an interface called IEnginePowered that represents vehicles with engines.

Refer to the following refactored code example.

Figure 33 Example: LSP Solution 1

In this corrected design, the Car class implements the IEnginePowered interface along with
the Vehicle class. The Vehicle class will include common vehicle properties and behavior for both. This
design provides the necessary implementation for the engine-related methods. Also, the ElectricCar class
does not implement the IEnginePowered interface because it does not have an engine.

Figure 34 Example: LSP Solution 2

We can substitute objects of the Car or ElectricCar class where instances of the IEnginePowered are
expected. The ElectricCar class does not need to implement engine-related methods.

Benefits

Using the LSP, we ensured that the program remained accurate and consistent when substituting objects
of derived classes for objects of their base class.

Interface Segregation Principle (ISP)

The Interface Segregation Principle (ISP) says to create smaller, specialized interfaces that cater to clients’
specific needs. It discourages large interfaces that include unnecessary methods, so that clients are not
burdened with functionality they don’t require.

Refer to the following example to understand how ISP can be violated and how to correct it using C#.
Figure 35 Example: ISP 1

Issue

In the previous example, we have an IOrder interface that contains methods for placing an order,
canceling an order, updating an order, calculating the total, generating an invoice, sending a confirmation
email, and printing a label.

However, not all client classes implementing this interface require or use all these methods. This violates
ISP, since clients are forced to depend on methods they don’t need.

By following the ISP, we can refactor the code by segregating the interface into smaller, more focused
interfaces.
Figure 36 Example: ISP 2

Solution

By segregating the interfaces, we now have smaller, more focused interfaces that clients can choose to
implement based on their specific needs. This approach eliminates unnecessary dependencies and allows
for better extensibility and maintainability. Clients can implement only the interfaces they require,
resulting in cleaner code that is easier to understand, test, and modify.

Benefits

Using the ISP in C# enables us to create interfaces tailored to specific client requirements. By avoiding the
violation of ISP, we can build more flexible, modular, and maintainable code. Breaking down large
interfaces into smaller, cohesive ones reduces coupling and improves code organization.

Dependency Inversion Principle (DIP)

The Dependency Inversion Principle (DIP) focuses on decoupling high-level modules from low-level
modules by introducing an abstraction layer, with the use of interfaces or abstract classes and reducing
direct dependencies between classes.

Refer to the following example where a UserController class depends directly on a Database class for
data storage.

Figure 37 Example: DIP

Issue

In the previous example, the UserController tightly couples with the concrete Database class, creating a
direct dependency. If we decide to alter the database implementation or introduce a new storage
mechanism, we will need to modify the UserController class, which violates the Open-Closed Principle.

Solution

To address this issue and adhere to the DIP, we must invert the dependencies by introducing an
abstraction that both high-level and low-level modules depend on. Typically, this abstraction is defined
using an interface or an abstract class.

Let’s modify the previous example to align with the DIP.

Refer to the following refactored code example.


Figure 38 Example: DIP Solution

In this updated version, we introduce the IDataStorage interface that defines the contract for data
storage operations. The Database class implements this interface, providing a concrete implementation.
Consequently, the UserController class now relies on the IDataStorage interface rather than the
concrete Database class, resulting in it being decoupled from specific storage mechanisms.
This inversion of dependencies facilitates easier extensibility and maintenance. We can introduce new
storage implementations, such as a file system or cloud storage, by simply creating new classes that
implement the IDataStorage interface, without modifying the UserController or any other high-level
modules.

Benefits

By applying the DIP, we achieve a more flexible and modular design, enabling us to evolve and adapt our
systems more easily over time.

Advantages of using the SOLID principles

SOLID principles offer a set of guidelines that significantly impact the quality, maintainability, and
scalability of software systems. Embracing these principles brings several key advantages to software
development:

• Enhanced maintainability: Following SOLID principles results in code that is easier to understand,
update, and maintain. The principles encourage clear code organization, making it simpler to
identify and modify specific functionalities without impacting the rest of the system. This
ultimately reduces the chances of introducing bugs during maintenance.

• Improved extensibility: By adhering to SOLID principles, code becomes more flexible and open
for extension without requiring modifications to existing, functional code. This extensibility is
particularly beneficial when adding new features or accommodating changing requirements, as it
minimizes the risk of unintentionally breaking existing functionalities.

• Facilitates reusability: SOLID principles promote the creation of modular, loosely coupled
components. This modularity allows for greater code reuse across different parts of the system or
in entirely new projects, leading to more efficient development processes.

• Supports testability: Codes that follow SOLID principles tend to be more testable. The principles
advocate for smaller, more focused units of code with clear responsibilities, making it easier to
write unit tests and ensure proper functionality.

• Reduces technical debt: Implementing SOLID principles from the start reduces the accumulation
of technical debt. It encourages clean code practices, preventing issues such as code smells, tight
coupling, and unnecessary complexity. Consequently, it saves time and effort that might
otherwise be spent refactoring or fixing problematic code later in the development lifecycle.
Task 2. Clean Coding Techniques and Their Impact on Data
Structures and Algorithms.
1. Clean Coding Techniques.
Tips for Writing Clean Code

Writing clean and high-quality code is crucial to maintaining the readability, efficiency, and scalability of
our applications in C# .NET. By following the principles of Clean Code, we can improve the understanding
and maintainability of our code. In this article, we will share 10 practical tips for writing clean code in C#
.NET. From meaningful names to applying SOLID principles, these tips will help you elevate the quality of
your code and foster a more professional software development culture.

1. Use meaningful names for variables and functions

Using descriptive and meaningful names is essential for clean code. Avoid confusing abbreviations and
choose names that clearly reflect the intention of the variable or function. Here's an example:

2. Keep functions and methods short and concise

Long and complex functions can make code reading and maintenance challenging. It is preferable to
break down functions into smaller and self-descriptive units. This improves readability and facilitates
code reuse. Here's an example:
3. Avoid code duplication

Code duplication can increase the likelihood of errors and make maintenance difficult. If you find similar
code blocks, it is recommended to extract them into separate methods or functions to promote reuse.
Here's an example:
4. Keep functions and methods small

Using small functions and methods improves code readability and understanding. Here's an example:
5. Apply the DRY principle

Avoid code duplication by reusing functions or creating proper abstractions. Here's an example:
2. Impact on Data Structures and Operations.
C# Queue
A linear data structure that follows the First In First Out (FIFO) principle. Elements are added at the end
(enqueue) and removed from the front (dequeue).

Figure 39 C# Queue Example

Example:
Output

Here, fruits is a queue that contains string elements ("Apple" and "Orange").

C# Queue Methods

C# provides 3 major Queue<T> methods. These methods are:

• Enqueue() - adds an element to the end of the queue

• Dequeue() - removes and returns an element from the beginning of the queue

• Peek() - returns an element from the beginning of the queue without removing

Let's learn each method in detail.

Queue Enqueue() Method

To add an element to the end of the queue, we use the Enqueue() method. For example,

Output
In the above example, we have created Queue class named numbers.

Then we added elements to the queue using the Enqueue() method.

• numbers.Enqueue(65) - adds 65 to the queue

• numbers.Enqueue(17) - adds 17 to the queue

We then printed those elements using a foreach loop.

Since the queue follows FIFO principle, the element added at the first (65) is displayed at the first in the
output.

Queue Dequeue() Method

To remove an element from the beginning of the queue, we use the Dequeue() method. For example,

Output
In the above example, we have used the Dequeue() method to remove an element from
the colors queue.

The method removed and returned "Red" from the beginning of the queue.

Queue Peek() Method

The Peek() method returns the element from the beginning of the queue without removing it. For
example,

Output

Here, we have displayed the element present at the beginning of the planet queue using
the Peek() method.

Check Whether an Element is Present Inside a Queue

We can use the Contains() method to check whether an element is present inside the queue or not.

The method returns True if a specified element exists in the queue. If not it returns False. For example,
Output

C# ArrayList: A resizable array implementation that allows for dynamic addition and removal of elements.
Provides indexed access to elements.

Basic Operations on C# ArrayList


In C#, we can perform different operations on arraylists. We will look at some commonly used arraylist
operations in this tutorial:

• Add Elements

• Access Elements

• Change Elements

• Remove Elements

Let's see how we can perform these operations in detail!

Add Elements in ArrayList

C# provides a method Add() using which we can add elements in ArrayList. For example,
In the above example, we have created an ArrayList named student.

Then we added "Tina" and 5 to the ArrayList using the Add() method.

Access ArrayList Elements

We use indexes to access elements in ArrayList. The indexing starts from 0. For example,
Output

Since the index of the ArrayList starts from 0:

• schoolDetails[0] - accesses the first element

• schoolDetails[1] - accesses the second element

Iterate ArrayList

In C#, we can also loop through each element of ArrayList using a for loop. For example,

Output

In the above example, we have looped through myList using a for loop.

Here, myList.Count gives the number of elements in myList.


Change ArrayList Elements

We can change the value of elements in ArrayList as:

Output

Here, we have changed the value of the second element in myList.

Remove ArrayList Elements

C# provides methods like Remove(), RemoveAt(), RemoveRange() to remove elements from ArrayList.

We will see an example below using Remove() to a remove element:


Output

In the above example, we have removed "Jack" from myList using the Remove() method.
Task 3. Designing the Architecture and Testing Regime for SIMS.
1. Use case diagram

Figure 40 Use Case diagram

Explaination

Actors:

1. Admin

• Responsible for managing users (Faculty and Students) and Courses.


• Has the capability to add, update, and remove faculties, students, and courses.
2. Student

• Can enroll in courses.


• Can view personal information and grades.
3. Faculty

• Can enter grades for students.


• Can manage courses they are assigned to.
Use Cases:
1. Admin:

• Add Faculty: Admin can add new faculty members to the system.
• Update Faculty: Admin can update details of existing faculty members.
• Remove Faculty: Admin can remove faculty members from the system.
• Add Student: Admin can add new students to the system.
• Update Student: Admin can update details of existing students.
• Remove Student: Admin can remove students from the system.
• Add Course: Admin can add new courses to the system.
• Update Course: Admin can update details of existing courses.
• Remove Course: Admin can remove courses from the system.
2. Student:

• Enroll in Courses: Students can enroll in courses offered by the university.


• View Personal Information: Students can view their personal information stored in the
system.
• View Grades: Students can view their grades for the enrolled courses.
3. Faculty:

• Enter Grades: Faculty can enter grades for students enrolled in their courses.
• Manage Courses: Faculty can manage courses they are responsible for, including updating
course details.
2. Class diagram

Figure 41 Class diagram

Explanation

• Student Registration:

o Admin class methods (addStudent, removeStudent, updateStudent) cover the registration


functionality.

o Student class properties (studentID, courses, inherited name, email, etc.) ensure capturing
and storing essential information.

• Course Management:
o Admin class methods (addCourse, removeCourse, updateCourse) enable course
management.

o Student class method (enrollInCourse) and Course property (enrolledStudents) facilitate


assigning students to courses.

• User Authentication and Authorization:

o User class methods (login, logout) handle authentication.

o Inheritance from User (Student, Faculty, Admin) implements role-based access control by
segregating functionalities based on user roles.

3. Package diagram

Figure 42 Package diagram

Explanation

Packages:

1. Controllers:

o AdminController, StudentController, FacultyController, CourseController

o These classes handle HTTP requests, interact with services, and return appropriate HTTP
responses.
2. Services:

o IStudentService, IFacultyService, IAdminService

o StudentService, FacultyService, AdminService

o Service classes contain business logic and interact with repositories to retrieve or update
data.

3. Repositories:

o IUserRepository, IStudentRepository, IFacultyRepository

o UserRepository, StudentRepository, FacultyRepository, AdminRepository

o Repository classes handle data access operations and interact with the data source (e.g., a
CSV file).

4. Models:

o User, Student, Faculty, Admin, Course

o Model classes represent the data structure of entities in the system.

4. How SOLID principles have been applied in design.


The class diagram adheres to several SOLID principles, which are fundamental to creating a maintainable
and scalable system. Here’s an explanation of how each SOLID principle is applied in the class diagram for
the Student Information Management System (SIMS):

Single Responsibility Principle (SRP)

• Definition: A class should have only one reason to change, meaning it should have only one job or
responsibility.

• Application:

o User: Handles authentication and general user properties.

o Student: Manages student-specific properties and behaviors like viewing personal


information, enrolling in courses, and viewing grades.

o Faculty: Manages faculty-specific properties and behaviors like managing courses and
entering grades.

o Admin: Manages administrative tasks like adding, removing, and updating students,
courses, and faculty.
o Course: Manages course-specific information and enrolled students.

o Grade: Manages grade-specific information.

Each class has a single responsibility, ensuring they are easier to understand, maintain, and extend.

Open/Closed Principle (OCP)

• Definition: Software entities should be open for extension but closed for modification.

• Application:

o Inheritance: The User class is extended by Student, Faculty, and Admin. New
functionalities can be added to these subclasses without modifying the User class.

o Methods: Additional functionalities like enrollInCourse or manageCourse can be added to


the respective classes without altering the existing class definitions.

Liskov Substitution Principle (LSP)

• Definition: Subtypes must be substitutable for their base types without altering the correctness of
the program.

• Application:

o Inheritance: Student, Faculty, and Admin can be used wherever User is expected. This
ensures that derived classes can be substituted for base classes without affecting the
application’s functionality.

Interface Segregation Principle (ISP)

• Definition: Clients should not be forced to depend on interfaces they do not use.

• Application:

o Interfaces in Services and Repositories: The package diagram includes IStudentService,


IFacultyService, IAdminService, and similar repository interfaces. Each service and
repository interface is designed to meet specific needs, ensuring that classes depend only
on the interfaces that are relevant to them.

Dependency Inversion Principle (DIP)

• Definition: High-level modules should not depend on low-level modules; both should depend on
abstractions.

• Application:

o Service and Repository Layers: The AdminController, StudentController, FacultyController,


and CourseController depend on service interfaces (IStudentService, IFacultyService,
IAdminService) rather than concrete implementations. Similarly, services depend on
repository interfaces (IUserRepository, IStudentRepository, IFacultyRepository,
IAdminRepository).

5. Test Plan for Student Information Management System (SIMS)


Unit Testing

Test Description Preconditions Test Steps Expected Result


Case
ID
1 Add User None 1. Create a new User object. The user is
2. Call the AddUser method of added to the
the UserRepository. repository.

3. Verify that the user is added


to the list.
2 Enroll Student The student 1. Create a Student and a The student is
in Course and course Course object. successfully
exist 2. Call the EnrollStudent enrolled in the
method of the StudentService. course.

3. Verify that the student is


enrolled.

Integration Testing

Test Description Preconditions Test Steps Expected Result


Case
ID
1 Add Student None 1. Mock the IStudentService. The student is
via Controller 2. Call the AddStudent method added via the
of the StudentController. controller.

3. Verify that the student


service's AddStudent method
was called.
2 Assign Course The student 1. Mock the ICourseService. The course is
to Student and course 2. Call the AssignCourse assigned to the
exist method of the student.
CourseController.
3. Verify that the course
service's AssignCourse method
was called.
System Testing

Test Description Preconditions Test Steps Expected Result


Case
ID
1 Register New None 1. Navigate to the student The student is
Student registration page. successfully
2. Fill in the student details. registered and
appears in the
3. Submit the registration system.
form. 4. Verify that the
student is added to the
system.
2 Add Course None 1. Navigate to the course The course is
and Enroll management page. successfully
Student 2. Add a new course. added, and the
student is
3. Navigate to the student enrolled in the
enrollment page. course.
4. Enroll a student in the
newly added course.
5. Verify that the student is
enrolled in the course.
Task 4. Analyze how creational, structural, and behavioural design
patterns are used in practices or in your team proposed solution.

Figure 43 Example illustrate: Design Patterns

1. Creational Design Patterns

Purpose: Creational patterns deal with object creation mechanisms, trying to create objects in a manner
suitable to the situation. They help in managing object creation by abstracting the instantiation process.

Pattern Examples:

• Singleton Pattern: Ensures that a class has only one instance and provides a global point of access
to it.

Example:
In the context of SIMS, a UserRepository class might use the Singleton pattern if you want to ensure that
only one instance of the repository is used throughout the application. This can be beneficial to avoid
duplicate data sources or conflicting state.

Code Example:

Figure 44 Singleton Pattern applied in example

2. Structural Design Patterns

Purpose: Structural patterns deal with object composition or the structure of classes. They help in
creating a structure where classes and objects can work together.

Pattern Examples:

• Adapter Pattern: Allows incompatible interfaces to work together. It acts as a bridge between
two incompatible interfaces.

Example:
If the SIMS application needs to interact with an external system or API that provides user data in a
different format, an Adapter pattern could be used to translate between the external data format and
the internal data structures.

Code Example:

Figure 45 Adapter Pattern applied in example 1

Figure 46 Adapter Pattern applied in example 2

Figure 47 Adapter Pattern applied in example 3


Figure 48 Adapter Pattern applied in example 4

3. Behavioral Design Patterns

Purpose: Behavioral patterns focus on communication between objects, what goes on between objects
and how responsibilities are distributed.

Pattern Examples:

• Strategy Pattern: Defines a family of algorithms, encapsulates each one, and makes them
interchangeable. It lets the algorithm vary independently from clients that use it.

Example:

In SIMS, to provide more different ways to calculate student grades (e.g., by average, by highest grade),
we could use the Strategy pattern to encapsulate these different strategies.

Code Example:

Figure 49 Strategy Pattern applied in example 1


Figure 50 Strategy Pattern applied in example 2

Figure 51 Strategy Pattern applied in example 3


Figure 52 Strategy Pattern applied in example 4

Output:

Figure 53 Output Strategy Pattern applied in example 4.5

By applying these patterns, the SIMS application achieves better modularity, flexibility, and
maintainability. Each pattern addresses specific design challenges and helps in creating a well-structured
and scalable system.

Task 5. Analyze the SIMS application and evaluate how adhering to


SOLID principles has influenced the design and development.
1. Single Responsibility Principle (SRP)
Application: Each class in our system has a single responsibility.

Why Applied: To ensure that each class has only one reason to change, making the system easier to
maintain and understand.

Effectiveness and Benefits:

• Improved maintainability: Changes in user-related logic do not affect student-related logic.

• Enhanced readability: Each class has a clear and focused purpose.

Example in Code:

• User.cs: Manages user-related properties and actions.

• Student.cs: Manages student-specific actions and properties.

• UserRepository.cs: Manages user data storage and retrieval.

Code Example:

Figure 54 SRP example 1


Figure 55 SRP example 2

Figure 56 SRP example 3


Figure 57 SRP example 4

Figure 58 SRP example 5

2. Open/Closed Principle (OCP)

Application: Classes are open for extension but closed for modification.

Why Applied: To allow the system to be extended with new functionality without modifying existing
code, thus reducing the risk of introducing bugs.
Effectiveness and Benefits:

• Enhanced flexibility: New features can be added by extending existing classes.

• Reduced risk: Existing code remains untouched, minimizing the chance of introducing errors.

Example in Code:

• UserRepository.cs: Can be extended to use different data sources without changing its existing
code.

Code Example:

Figure 59 OCP example 1

Figure 60 OCP example 2

3. Liskov Substitution Principle (LSP)

Application: Subtypes must be substitutable for their base types.

Why Applied: To ensure that a derived class can be used interchangeably with its base class without
altering the correctness of the program.
Effectiveness and Benefits:

• Promotes polymorphism: Allows for more flexible and reusable code.

• Ensures consistency: Subtypes behave as expected when used in place of base types.

Example in Code:

• Student.cs: Inherits from User and can be used wherever User is expected.

Code Example:

Figure 61 LSP example 1


Figure 62 LSP example 2

Figure 63 LSP example 3


Figure 64 LSP example 4

4. Interface Segregation Principle (ISP)

Application: Clients should not be forced to depend on interfaces they do not use.

Why Applied: To prevent clients from being forced to implement methods they do not use, ensuring a
more decoupled and cohesive design.

Effectiveness and Benefits:

• More modular interfaces: Each interface is focused and easier to implement.

• Improved decoupling: Reduces the impact of changes, making the system easier to maintain.

Example in Code:

• IUserRepository.cs: Defines only user-related operations.

Code Example:

Figure 65 ISP example

5. Dependency Inversion Principle (DIP)

Application: High-level modules should not depend on low-level modules but on abstractions.
Why Applied: To reduce the coupling between high-level and low-level modules, making the system
more flexible and easier to change.

Effectiveness and Benefits:

• Enhanced flexibility: Allows for easy substitution of different implementations.

• Improved maintainability: High-level logic remains unchanged when low-level details are
modified.

Example in Code:

• UserRepository.cs: Implements IUserRepository, allowing the high-level logic to depend on the


interface rather than the implementation.

Code Example:

Figure 66 DIP example 1

Figure 67 DIP example 2


REPORT PART 2
Task 6:
1. What is Automation Testing?
Automation testing is the practice of automating the execution of test cases through scripts and
specialized tools. Instead of having a human tester manually executing test cases, these scripts and tools
directly command the Application Under Test (AUT) to perform all actions needed. It frees up bandwidth
for testers to focus more on more strategic activities.

Note that the concept of Automation Testing is not strictly limited to the Execution phase, although it is
generally understood and accepted that automation testing means “automatically executing a test case”.
Automation testing can actually be an umbrella term referring to the “automation of any testing activity
across the testing life cycle”.

Benefits of Automation Testing

Automation testing is the best way to enhance effectiveness, broaden test coverage, and improve
execution speed in software testing. There are several reasons why Automation Testing is crucial:

• Improved Accuracy: Automation testing reduces the likelihood of human errors as automated
tests are designed to strictly follow a set of predefined steps. Automated tests eliminate the
chances of human testers introducing errors like forgetting a certain step while executing the
tests.

• Increased Speed: Automated tests can run continuously, in parallel, 24/7, without the need for
human intervention, further increasing the speed of test execution and reducing the overall
testing time.

• Consistency: A lack of testing consistency and standardization can result in missing important
issues. In continuous testing scenarios, such as triggering regular regression test runs, automation
suites are indispensable. With automation testing, test cases are executed in exactly the same
way every time they are run. They can also be run multiple times a day, ensuring that new issues
are quickly identified and resolved, leading to improved confidence in the software quality.

• Cost Savings: Projects dealing with more advanced infrastructures or a variety of application
types see the highest automation ROI. The costs of time, technologies and human resources are
often the biggest blockers to automation adoption. Not only pertaining to software test
automation, setting up and standardizing automated workflows don’t happen overnight.
However, the long-term ROI in accuracy, speed, and consistency is guaranteed.

• Enhanced Test Coverage: Automated test suites can be reused to run against multiple browsers,
devices and operating systems combinations. Instead of performing the exact test steps over and
over again, simply lock down the most common environment your users are accessing and press
run. Using cloud environments is also an effective practice to test on older versions of browsers,
devices and operating systems (e.g., iOS 13).

• Improved Test Reusability: Once automated tests are created, they can be stored and reused
across multiple systems with the click of a button, and testers do not need to spend time re-
creating and executing tests for each testing cycle.

• Continuous Testing: Automated tests can be run frequently and at any stage of the development
process, whether it's during the development phase, integration phase, or after deployment.
They can even be integrated into the development pipeline, so that they are run automatically
every time new changes are made to the software.

If you're a software business or an individual working in the digital industry, investing in automation
testing will surely bring immense benefits to your product development process.

Which Test Cases To Automate?

The decision to automate a test case should be based on a careful consideration of the potential benefits
and costs of its automation. Following test cases are ideal candidates for test automation:

1. Tests prone to failure due to human error

2. Monotonous and repetitive tests

3. Extensive tests utilizing multiple data sets

4. Tests not feasible for manual execution

5. Manually intensive tests taking significant time

6. Tests with high potential risk

7. Tests requiring execution on multiple hardware and software platforms

It is also important to note that not all test cases are suitable for automation. Test cases for which the
requirements are frequently changing and test cases executed on an ad-hoc basis should not be
automated due to their unpredictable nature.

Automation testing is extremely versatile with a wide multitude of use cases:

• Industries: automated testing is widely used in many industries, most commonly being IT,
eCommerce, Banking & Finance, Insurance, Telecommunications, Gaming, and even Education.
Any industry with business models revolving around high functionality, stability, digital presence,
or top-notch user experience can benefit tremendously by adopting automation testing.

• Application Under Tests: Automation testing can be used to test various quality aspects of
websites, mobile applications, desktop applications, and API. For example, tests to verify the
functionality, performance, security and usability of a website can easily be automated. Mobile
applications can be tested for compatibility with different operating systems, devices, and screen
sizes.

• Testing types: Testers can automate a wide range of testing types, including regression testing,
acceptance testing, unit testing, or integration testing, to name a few. Here are 15 types of QA
testing you should know.

• Testing environment: Automation testing can be used across different operating systems,
browsers, and devices.

How To Do Automation Testing?

Figure 68 Software testing life cycle

Choose your approach

Similar to any other testing type, you first need to decide on the approach for your automation testing
project. There are usually 2 main approaches:

1. Use a test automation framework

2. Use a test automation tool

Here is a quick table comparing the 2 approaches:


Aspect Test Automation Tool Test Automation Framework

Requires a certain level of


Offers a higher level of abstraction, allowing users
Level of programming expertise and
to automate tests without extensive
Abstraction understanding of coding
programming knowledge.
principles.

May have a steeper learning


Typically has a lower learning curve, making it curve, as it requires a deeper
Learning Curve accessible to individuals with limited understanding of automation
programming skills. principles and programming
concepts.

Provides limited customization options, as users


Offers extensive customization
are bound by the features provided by the tool.
and flexibility, allowing users
Customization However, many tools do offer Scripting mode
to tailor the framework to
along with low-code mode to increase
specific project needs.
customization level

May involve a more complex


Generally easier to set up, with user-friendly
Ease of Initial initial setup, requiring
interfaces and wizards guiding users through the
Setup expertise in designing and
configuration process.
structuring the framework.

Tends to be more resource-efficient, making it May be resource-intensive,


Resource
suitable for smaller projects with limited requiring time, effort, and
Efficiency
resources. expertise.

Easier to maintain, as updates and changes are


Maintenance Requires regular maintenance
typically managed by the tool provider.
Facilitates seamless
Often provides built-in integration capabilities
Integration integration with CI/CD systems
with Continuous Integration/Continuous
with CI/CD but may require additional
Deployment (CI/CD) systems.
configurations and setup.

Frameworks usually have a


Depending on the popularity of the tool, there
Community robust community, offering
may be a supportive community providing
Support resources, forums, and support
resources and assistance.
for users.

In other words:

• To best utilize a framework, you must have extensive coding knowledge yourself, and the effort
to maintain test scripts is significant, but you can freely customize the framework to fit your
specific testing needs

• A testing tool doesn’t require coding so you can easily create tests much faster. You can switch to
coding mode whenever you want (if the tool offers coding mode). Test maintenance is usually
taken care of, which reduces the workload. However, you are bound by the features offered by
the tool, so it is recommended to list out the features you want from a tool to select the one that
best fits your demands.

Challenges of Automation Testing

Of course, automation testing is not without its challenges. There is usually some compromise to be
made when you want to increase testing speed. What’s important is whether that compromise is worth it
or not.

According to the State of Quality Report 2024, the most prevalent challenge to automation testing is the
team's lack of skills and experience in test automation, with up to 45% of respondents agreeing, followed
by 38% thinking that requirements change too often, and another 26% claiming that test maintenance is
costly.
Figure 69 Challenges of Automation Testing

Choose the right automation testing tools

The tool should be able to support the application being tested and the testing requirements. Selecting
an inappropriate tool can result in test automation failure as well as inefficient use of testing budget.
Some criteria for you to consider:

• Compatibility: this automation testing tool should be compatible with your software
development environment, including your operating system, programming language, and any
other tools you are using.

• Functionality: the tool should have the necessary functionalities to create, run, report and debug
tests. Additionally, assess whether the tool’s strength (e.g., web UI testing) matches with your
testing needs most.

• Scalability: it should be scalable to meet the demands of your testing needs, both now and in the
future, as your software evolves and grows.
• Integration: it should also be able to integrate with other tools you are using, such as your bug
tracking system or continuous integration platform, to help streamline your testing process.

• Support: there should be good customer support and a vibrant community, with resources such
as forums, online tutorials, and knowledge bases

• Security: the tool should have adequate security measures in place to protect your data and
ensure that your tests are performed securely.

A good choice is adopting software quality management platforms that integrate multiple testing
functionalities (web, API, mobile) into a unified solution.

These platforms offer end-to-end capabilities from test planning to reporting, including features like
requirements mapping and automation script management. By consolidating various testing tools into
one platform, they streamline testing workflows and eliminate the need for managing multiple tools
separately.

2. What is unit testing?


Unit testing is the process where you test the smallest functional unit of code. Software testing helps
ensure code quality, and it's an integral part of software development. It's a software development best
practice to write software as small, functional units then write a unit test for each code unit. You can first
write unit tests as code. Then, run that test code automatically every time you make changes in the
software code. This way, if a test fails, you can quickly isolate the area of the code that has the bug or
error. Unit testing enforces modular thinking paradigms and improves test coverage and quality.
Automated unit testing helps ensure you or your developers have more time to concentrate on coding

What is a unit test?

Figure 70 Unit testing example

A unit test is a block of code that verifies the accuracy of a smaller, isolated block of application code,
typically a function or method. The unit test is designed to check that the block of code runs as expected,
according to the developer’s theoretical logic behind it. The unit test is only capable of interacting with
the block of code via inputs and captured asserted (true or false) output.

A single block of code may also have a set of unit tests, known as test cases. A complete set of test cases
cover the full expected behavior of the code block, but it’s not always necessary to define the full set of
test cases.

When a block of code requires other parts of the system to run, you can’t use a unit test with that
external data. The unit test needs to run in isolation. Other system data, such as databases, objects, or
network communication, might be required for the code’s functionality. If that's the case, you should use
data stubs instead. It’s easiest to write unit tests for small and logically simple blocks of code.

Unit testing strategies

To create unit tests, you can follow some basic techniques to ensure coverage of all test cases.

Logic checks

Does the system perform the right calculations and follow the right path through the code given a
correct, expected input? Are all paths through the code covered by the given inputs?

Boundary checks

For the given inputs, how does the system respond? How does it respond to typical inputs, edge cases, or
invalid inputs?

Let’s say you expect an integer input between 3 and 7. How does the system respond when you use a 5
(typical input), a 3 (edge case), or a 9 (invalid input)?

Error handling

When there are errors in inputs, how does the system respond? Is the user prompted for another input?
Does the software crash?

Object-oriented checks

If the state of any persistent objects is changed by running the code, is the object updated correctly?

What are the benefits of unit testing?


Unit testing benefits software development projects in many ways.

Efficient bug discovery

If there are any input, output, or logic-based errors within a code block, your unit tests help you catch
them before the bugs reach production. When code changes, you run the same set of unit tests—
alongside other tests such as integration tests—and expect the same results. If tests fail (also
called broken tests) it indicates regression-based bugs.
Unit testing also helps finds bugs faster in code. Your developers don’t spend a large amount of time on
debugging activities. They can quickly pinpoint the exact part of the code that has an error.

Documentation

It's important to document code to know exactly what that code is supposed to be doing. That said, unit
tests also act as a form of documentation.

Other developers read the tests to see what behaviors the code is expected to exhibit when it runs. They
use the information to modify or refactor the code. Refactoring code makes it more performant and well-
composed. You can run the unit testing again to check that code works as expected after changes.

How do developers use unit tests?

Developers use unit tests at various stages of the software development lifecycle.

Test-driven development

Test-driven development (TDD) is when developers build tests to check the functional requirements of a
piece of software before they build the full code itself. By writing the tests first, the code is instantly
verifiable against the requirements once the coding is done and the tests are run.

After completing a block of code

Once a block of code is considered complete, unit tests should be developed if they have not been
already thanks to TDD. Then, you can immediately run unit tests to verify the results. Unit tests are also
run as part of the full suite of other software tests during system testing. They're typically the first set of
tests that run during full system software testing.

DevOps efficiency

One of the core activities in the application of DevOps to software development practices is continuous
integration and continuous delivery (CI/CD). Any changes to the code are automatically integrated into
the wider codebase, run through automated testing, and then deployed if the tests pass.

Unit tests make up part of the test suite alongside integration testing. They run automatically in the
CI/CD pipeline to ensure code quality as it is upgraded and changed over time.

When is unit testing less beneficial?


Unit testing isn’t always required for every single test case in every single block of code in every single
project. Here are some examples of when unit testing could potentially be omitted.

When time is constrained


Even with generative unit testing frameworks, writing new unit tests takes a significant amount of your
developers' time. While input and output-based unit tests may be easy to generate, logic-based checks
are more difficult.

Once your developers start writing tests, they also see refactoring opportunities in the block of code and
get distracted from completing them. This can lead to extended development timelines and budget
issues.

UI/UX applications

When the main system is concerned with look and feel rather than logic, there may not be many unit
tests to run. Other types of testing, such as manual testing, are a better strategy than unit testing in
these cases.

Legacy codebases

Writing tests to wrap around existing legacy code can prove to be near impossible, depending on the
style of the written code. Because unit tests require dummy data, it can also be too time-intensive to
write unit tests for highly interconnected systems with a lot of data parsing.

Rapidly evolving requirements

Depending on the project, the software can grow, change directions, or have whole parts scrapped
altogether in any given work sprint. If requirements are likely to change often, there's not much reason
to write unit tests each time a block of code is developed.

What are unit testing best practices?

We give some unit testing best practices to get the most out of your process.

Use a unit test framework

It wastes time to write explicit, fully customized unit tests for every single block of code. There are
automated testing frameworks for every popular programming language.

For instance, Python has pytest and unittest as two different frameworks for unit testing. Testing
frameworks are used extensively throughout software development projects of all sizes.

Automate unit testing

Unit testing should be triggered on different events within software development. For example, you can
use them before you push changes to a branch using version control software or before you deploy a
software update.

Unit testing may also run on a complete project, set on a timed schedule. Automated unit testing ensures
tests run in all appropriate events and cases throughout the development lifecycle.

Assert once
For each unit test, there should only be one true or false outcome. Make sure that there is only one
assert statement within your test. A failed assert statement in a block of multiple ones can cause
confusion on which one produced the issue.

Implement unit testing

Unit testing is an important part of building software, but many projects don’t dedicate resources to it.
When projects start as prototypes, are small community-based efforts, or are simply coded quickly, unit
testing can be left out due to time constraints.

However, when you build projects with unit testing as a standard practice from the beginning, the
process becomes far easier to follow and repeat.

3. What is Integration Testing?


Integration testing is a type of software testing where components of the software are gradually
integrated and then tested as a whole. Usually, these components are already working well individually.
However, they may break when integrated with other components. Integration testing aims to pinpoint
those communication issues.

Why is Integration Testing Important?

Figure 71 Integration Testing

Software is often built from many individual software components or modules. Issues between those
modules can always happen for many reasons:

• Inconsistent code logic: They are coded by different programmers whose logic and approach to
development differ from each other, so when integrated, the modules cause functional or
usability issues. Integration testing ensures that the code behind these components is aligned,
resulting in a working application.
• Shifting requirements: Clients change their requirements frequently. Modifying the code of 1
module to adapt to new requirements sometimes means changing its code logic entirely, which
affects the entire application. These changes are not always reflected in unit testing, hence the
need for integration testing to uncover the missing defects.

• Erroneous Data: Data can change when transferred across modules. If not properly formatted
when transferring, the data can’t be read and processed, resulting in bugs. Integration testing is
required to pinpoint where the issue lies for troubleshooting.

• Third-party services and API integrations: Since data can change when transferred, API and third-
party services may receive false input and generate false responses. Integration testing ensures
that these integrations can communicate well with each other.

• Inadequate exception handling: Developers usually account for exceptions in their code, but
sometimes they can’t fully see all of the exception scenarios until the modules are pieced
together. Integration testing allows them to recognize those missing exception scenarios and
make revisions.

• External Hardware Interfaces: Bugs can also arise when there is software-hardware
incompatibility, which can easily be found with proper integration testing.

Integration testing protects you from those downfalls.

Types Of Integration Testing

There are several strategies to perform integration testing, each of which has its own advantages and
disadvantages, with the 2 most common approaches being:

• Big Bang Approach

• Incremental Approach

Incremental Approach is further divided into 3 different methods:

• Bottom-up approach

• Top-down approach

• Sandwich approach

1. Big Bang Integration Testing

Big Bang Integration testing is an integration testing approach in which all modules are integrated and
tested at once, as a singular entity. It is essentially “testing in a Big Bang fashion”.

The Big Bang integration testing process is not carried out until all components have been successfully
unit tested.
Advantages:

• Suitable for simple and small-sized systems with low level of dependency among software
components

• Little to no planning beforehand required

• Easy to set up since all modules are integrated simultaneously

• Management and coordination efforts are minimized since there is only one major testing phase

Disadvantages:

• Costly and time-consuming for large systems with a huge number of units as testers have to wait
until all modules have been developed to start testing

• Waiting for all modules to be developed before testing also means late defect detection

• Hard to isolate and pinpoint bugs in specific modules

• Hard to debug due to the complexity of multiple integrated modules

Best Practices when using Big Bang testing:

• Clearly define the interactions between each unit/function before testing to minimize missing
defects

• Perform extensive logging for more accurate fault localization

• Perform Big Bang testing for simple applications

2. Incremental Integration Testing

Incremental integration testing is an approach in which 2 or more modules with closely related logic and
functionality are grouped and tested first, then gradually move on to other groups of modules, instead of
testing everything at once. The process ends when all modules have been integrated and tested.

Incremental integration testing is more strategic than Big Bang testing. It requires substantial planning
beforehand.

Advantages:

• Earlier defect detection compared to Big Bang testing since the modules are integrated and
tested as soon as they are developed. QA teams don't have to wait until all modules are available
to begin testing.

• Easier fault localization since the modules are tested in relatively small groups.
• The strategic nature of incremental integration testing can be leveraged in project management.
For example, QA managers can choose which module to test first based on urgency, priority, or
resource availability.

• The risk of encountering catastrophic failures is also significantly reduced since issues are
addressed early on from the root.

Disadvantages:

• In earlier stages of the project, certain system functionalities may not yet be available, leading to
a dependence on stubs and drivers (which are essentially mock components that will be used as
substitutes for actual components).

• The total number of tests to perform can be huge depending on the scale of the project, requiring
significant organizational resources

• Coordinating a large integration testing project with this approach may be complex

• Require a complete definition and logic of the system before it can be broken down into small
units

• The lack of system functionalities in earlier stages, if not carefully documented, may even lead to
system “blindspots” later down the road

Incremental integration testing can be further divided into 3 smaller approaches, each also comes with
its own advantages and disadvantages that QA teams need to carefully consider for their projects. These
approaches are named based on the level of impact of the software components being integrated have
on the overall system, including:

• Bottom-up approach: perform testing for low-level components first, then gradually move to
higher-level components.

• Top-down approach: perform testing for high-level components first, then gradually move to
lower-level components.

• Hybrid approach: combining the two former approaches


4. What is End-to-End testing?

Figure 72 End-to-End testing

End-to-end testing (E2E testing) checks an entire software application from beginning to end, mimicking
real user interactions and data. Its goal is to find bugs that appear when all parts of the system work
together, ensuring the application performs as expected in real-world scenarios.

End-to-end testing is also referred to as E2E testing. Thanks to end-to-end testing, testers gain insights
into how the application functions from the end user’s perspective, giving them a more comprehensive
understanding the software quality before release.

Importance of End-to-end Testing

End-to-end testing is essential as modern software has grown to be intricate with dozens of systems
interacting with each other simultaneously.

Even if these components function perfectly fine individually, they may still fail when integrated due to
the miscommunication between components. End to end testing is there to verify the flow of
information through the AUT, including all possible paths and dependencies. If there is an issue in any
“touchpoint” between software components and/or subsystems, testers can easily locate the root cause
and troubleshoot immediately.

Testers would want to achieve the highest level of coverage with E2E testing. All subsystems or
components of the application, such as the user interface, application server, database, as well as any
external systems that the application may interact with, should undergo E2E testing.
Advantages

1. Comprehensive Coverage:

o E2E testing covers the entire application workflow, ensuring that all components and their
interactions are working correctly. This helps catch integration issues that unit or
integration tests might miss.

2. Realistic Scenarios:

o E2E tests simulate real user scenarios, which helps identify issues that actual users might
face. This increases the likelihood of catching bugs that would affect user experience.

3. Confidence in System Integrity:

o By testing the entire system, E2E tests provide high confidence that the application will
function correctly in a production environment. This includes interactions with databases,
network, and other external services.

4. Reduced Manual Testing:

o Automating E2E tests can reduce the need for extensive manual testing, saving time and
effort. It also allows for more consistent and repeatable testing.

5. Early Detection of Bugs:

o E2E testing can identify issues early in the development cycle, especially those related to
system integration and data flow. This helps in addressing problems before they escalate.

Disadvantages

1. High Maintenance Cost:

o E2E tests are typically more complex and time-consuming to write and maintain compared
to unit or integration tests. Changes in the application's UI or workflow can break E2E
tests, requiring frequent updates.

2. Slower Execution:

o E2E tests are slower to run because they cover the entire application and its
dependencies. This can slow down the development and continuous
integration/continuous deployment (CI/CD) processes.

3. Complex Test Environment Setup:

o Setting up the test environment for E2E testing can be challenging. It often requires
replicating the production environment, including databases, network configurations, and
external services.
4. Debugging Difficulties:

o When an E2E test fails, it can be difficult to pinpoint the exact cause of the failure. The
issue could be anywhere in the end-to-end workflow, making debugging time-consuming.

5. False Positives/Negatives:

o E2E tests can sometimes produce false positives or negatives due to the complexity of the
system and external dependencies. This can lead to mistrust in the test results and
additional effort to verify the outcomes.

5. Differences between test types


Unit Testing Integration Testing End-to-End Testing

Scope of Testing Focuses on individual Focuses on the Focuses on the entire


units or components of interaction between application from start to
the software. A unit is integrated units or finish. It tests the
the smallest testable components. The goal is complete application
part, such as a function, to identify issues that flow, including
method, or class. arise when units are interactions with
combined. external systems and
services.

Purpose Validates that each unit Ensures that different Validates the end-to-
functions correctly in modules or services end functionality and
isolation. It ensures work together correctly. performance of the
that individual It identifies defects in system. It ensures that
components perform the interactions the complete system
as expected. between integrated behaves as expected
units. under real-world
scenarios.

Test Environment Typically conducted in Conducted in a test Conducted in an


a development environment that environment that
environment with a mimics production to closely resembles the
focus on isolated some extent. It involves production
testing. Mock objects real or simulated environment. It involves
and stubs may be used interactions between testing the entire
to simulate modules. system, including
dependencies. databases, network, and
external services.

Level of Detail Highly detailed and Less detailed than unit Broad and
granular. Tests are tests but more focused comprehensive. Tests
specific to individual on the interactions the overall functionality,
units and their internal between units. Tests performance, and
logic. the communication and behavior of the entire
data exchange between system, covering all
modules. integrated components
and their interactions.

Testing Tools and Uses unit testing May use integration Uses a variety of testing
Techniques frameworks such as testing tools and tools and techniques,
Xunit, JUnit, NUnit, etc. frameworks, and often including automated
Focuses on automated involves combining testing frameworks,
tests. automated and manual functional testing,
testing. performance testing,
and user acceptance
testing.

Responsibility Primarily the Often a shared Primarily the


responsibility of responsibility between responsibility of QA
developers. They write developers and testers. testers. They perform
and maintain unit tests Requires collaboration comprehensive testing
as they develop the to ensure that modules to validate the entire
code. integrate correctly. system and its end-to-
end workflows.
Task 7:
1. Function

Figure 73 Login form


Figure 74 Register form

• This is the login registration function. When we register with different permissions, different
interfaces will be displayed.
Figure 75 Create Student form

Figure 76 Student view list

• This is the function to add a new student. When you enter all the information and click Add, you
will jump to the interface displaying the list of students just added.
Figure 77 Edit student form

Figure 78 Student list edit function

• This is the editing function. When you click on the edit button, it will jump to the editing interface.
We can change the data fields as desired. Once entered, click save and the changes will
automatically return to the list display interface.
Figure 79 Student list function delete

• The last function is to delete. When you click delete, it will be deleted and updated immediately
in the list display interface

• In addition to the student management page, there are also course management, teacher
management, and classroom management pages with add, edit, and delete functions similar to
student management.
2. Test
a) TestLoadCourseFromFile

Detailed Breakdown

1. Arrange

In the arrange section, we set up everything needed for the test:

• Initialize the Controller: Create an instance of the CourseController class.

Define File Name: Specify the name of the JSON file that will be used.

Create JSON Data: Define a JSON string representing an array of courses. This JSON string contains two
course objects.

Write JSON Data to File: Write the JSON string to a file with the specified file name.
2. Act

In the act section, we perform the action that we are testing:

• Call the Method Under Test: Invoke the LoadCourseFromFile method with the file name.

3. Assert

In the assert section, we verify that the method's behavior matches our expectations:

Check Result is Not Null: Ensure that the result of the method call is not null.

Check Number of Courses: Verify that the result contains exactly 2 courses.

Detailed Assertions for Each Course

First Course Assertions:

Check Id: Ensure the first course's Id is 1.

Check Name: Ensure the first course's Name is "Design".

Check Class: Ensure the first course's Class is "SE06205".

Check Major: Ensure the first course's Major is "IT".

Check Lecturer: Ensure the first course's Lecturer is "test".

Check Status: Ensure the first course's Status is "Active".

Check Teacher is Null: Ensure the first course's Teacher is null.

Second Course Assertions:


Check Id: Ensure the second course's Id is 2.

Check Name: Ensure the second course's Name is "Computing".

Check Class: Ensure the second course's Class is "SE06204".

Check Major: Ensure the second course's Major is "IT".

Check Lecturer: Ensure the second course's Lecturer is "Nguyen Thanh Trieu".

Check Status: Ensure the second course's Status is "Active".

Check Teacher is Null: Ensure the second course's Teacher is null.

Conclusion

The TestLoadCourseFromFile method is a comprehensive test that ensures the LoadCourseFromFile


function in the CourseController class works as expected. It checks that the method can read a JSON file,
parse the data correctly, and return a list of courses with the correct attributes.

This test is crucial for validating the data loading functionality, ensuring that any changes to the
LoadCourseFromFile method do not break its expected behavior.

Scenarios That Could Cause the Test to Fail

Scenario 1: Incorrect JSON Format

If the JSON format in the file is incorrect or malformed, the test will fail. This could happen if there are
syntax errors in the JSON string.

Example:

Let's say the JSON string is missing a closing brace or contains an extra comma:
Result:

In this case, the LoadCourseFromFile method might throw an exception while trying to parse the JSON,
resulting in a test failure.

Scenario 2: Incorrect Data Types

If the JSON data contains incorrect data types for the fields, the test will fail during deserialization or
assertion.

Example:

Let's say the Id field is a string instead of an integer:

Result:
The test will fail because the Id field is expected to be an integer, and the deserialization process will not
be able to parse it correctly.

b) TestCreateCourse

Detailed Breakdown

1. Arrange

Set up the necessary objects and state for the test.

• Controller Instance:

• Course to Add:

• List of Classes:
2. Act

Invoke the method under test.

• Create Course and Capture Result:

3. Assert

Verify the expected outcomes.

• Result is Not Null:

• Redirect to "ManageCourse":

Scenarios That Could Cause the Test to Fail

Scenario 1: CreateCourse Method Does Not Redirect Correctly

If the CreateCourse method does not redirect to "ManageCourse", the test will fail.

This will fail the assertion:


Result:

Scenario 2: CreateCourse Method Returns Null

If the CreateCourse method returns null, the test will fail.

Example:

If there is an error in the method causing it to return null:

Result:
c) TestDeleteCourse

1. Arrange

Set up the necessary objects and state for the test:

• Instantiate the Controller:

• Define the ID of the Course to Delete:

• Specify the File Name:


• Create Initial Course Data: Define a list of initial courses and write it to the JSON file.

2. Act

Invoke the method under test:


• Call the Delete Method and Capture the Result:

3. Assert

Verify the expected outcomes:

• Check Result is Not Null:


• Verify Redirect Action: Ensure the action name in the result is "ManageCourse".
• Verify the Course was Actually Deleted: Read the file, deserialize the contents, and check the
remaining courses.

Examples of Scenarios That Could Cause the Test to Fail

Scenario 1: File Not Found or Unreadable

If the JSON file cannot be found or read, the method might fail to load the courses, causing the test to
fail.

Example:

If the file path is incorrect or the file is missing:

Result:

The test will fail at the file reading and deserialization steps:

Because the file does not exist.


Scenario 2: JSON Deserialization Error

If there is an error in the JSON format or deserialization process, the test might fail.

Example:

If the JSON data is malformed:

Result:

The test will fail at the deserialization step:

Because the JSON is not properly formatted.


Task 8:
1. Assess effectiveness of using SOLID principles, clean coding techniques on the
application developed
a) Single Responsibility Principle (SRP)

Figure 80 SRP Example code 1


Figure 81 SRP Example code 2

Figure 82 SRP Example code 3

• How to apply:

o StudentService is only responsible for CRUD (Create, Read, Update, Delete) operations related
to Student.

o Methods like GetAllStudents(), GetStudentById(), AddStudent(), UpdateStudent(), and


DeleteStudent() all focus on handling Student data.
• Specific effects:

o Easy to maintain:

➢ When you need to change the Student processing logic, you only need to focus on the
StudentService class.

➢ For example: If you want to add a log feature every time there is an operation with
Student, you only need to make changes in StudentService, without affecting
StudentController or other parts.

o Reduce risk when modifying:

➢ Changes in StudentService are less likely to affect other parts of the application.

➢ For example: If you change the way of storing data from JSON to database, just make
changes in StudentService, StudentController will still work normally.

o Easy to reuse:

➢ StudentService can be used in other controllers or other parts of the application


without modification.

b) Open/Closed Principle (OCP):

Figure 83 OCP Example code

• How to apply:

o Define the `IStudentService` interface with the necessary methods.

o `StudentService` implements this interface.

o `StudentController` uses `IStudentService` instead of using `StudentService` directly.

• Specific effects:

o Easy expansion:

➢ If in the future you want to add a new way of handling Student (e.g. storage in the
cloud), you can create a new class implementing `IstudentService` without modifying
`StudentController`.

o Flexibility in testing:
➢ You can easily create a mock object for `IstudentService` to test `StudentController`
without needing a real implementation.

c) Interface Segregation Principle (ISP):

Figure 84 ISP Example code

• How to apply:

o `IStudentService` only contains methods related to Student management.

o Do not add extraneous or unnecessary methods to the interface.

• Specific effects:

o Avoid unnecessary dependencies:

➢ `StudentController` only depends on the methods it actually needs from


`IStudentService`.

➢ If in the future you add new methods to `IStudentService`, `StudentController` will not be
affected.

o Easy to implement and maintain:

➢ When creating a new implementation of `IStudentService`, you only need to implement


the necessary methods.

➢ For example, if you create a `ReadOnlyStudentService`, you only need to implement


methods that read data.
d) Liskov Substitution Principle (LSP)

Figure 85 LSP Example code

How to Apply:

1. Design with Abstraction:

o We've used interfaces (ICourseRepository, ITeacherRepository, IClassRepository) to define


common behavior for handling courses, teachers, and classes.

o The CourseController depends on these interfaces rather than concrete implementations,


allowing for flexibility in swapping out different implementations.

2. Ensure Behavioral Consistency:

o Each implementation of ICourseRepository, such as CourseRepository or a hypothetical


DatabaseCourseRepository, should provide the same functionality and adhere to the
expected behavior defined by ICourseRepository.

o If a method is expected to return a list of courses or delete a course, the behavior should
be consistent across all implementations.
3. Use Dependency Injection:

o In the CourseController constructor, dependencies are injected via interfaces. This allows
you to easily swap different implementations of the repositories without altering the
controller's code.

4. Test Substitutability:

o Ensure that all implementations of ICourseRepository can replace each other in the
CourseController without causing any errors or unexpected behavior.

Specific Effects:

1. Improved Flexibility:

o You can switch from a CourseRepository that stores courses in a file to a


DatabaseCourseRepository that stores courses in a database. The controller
(CourseController) remains unchanged because it interacts with the ICourseRepository
interface, not the specific implementation.

2. Enhanced Reusability:

o The ICourseRepository interface can be implemented in different ways depending on the


storage mechanism or business requirements, and each implementation can be reused
across different parts of the application.

3. Simplified Testing and Maintenance:

o By adhering to LSP, you can use mock implementations of ICourseRepository to unit test
the CourseController. This isolates the controller's logic from the underlying data storage
mechanism, simplifying testing.

4. Reduced Risk of Bugs:

o If a new implementation of ICourseRepository is created, it must adhere to the contract


defined by the interface, reducing the likelihood of introducing bugs due to unexpected
behavior.

5. Easier to Extend:

o Adding new functionalities, like logging or caching, can be done by creating new
implementations of ICourseRepository without modifying existing classes. This adheres to
both the Open/Closed Principle and LSP.
e) Dependency Inversion Principle (DIP):

Figure 86 DIP Example code

• How to apply:

o Use dependency injection to inject `IstudentService` into `StudentController`.

o Do not instantiate StudentService directly in `StudentController`.

• Specific effects:

o Easy to change implementation:

➢ You can change how StudentService works without modifying StudentController.

➢ For example: Switching from JSON storage to SQL database only requires changes in
StudentService or creating a new implementation, StudentController still works normally.

o Advantages for unit testing:

➢ You can easily create mock objects for IStudentService and IEmailValidator to test
StudentController independently.

o Reduce coupling:

➢ StudentController does not need to know about the implementation details of


StudentService, helping to reduce dependencies between modules.

2. Analyse the benefits and drawbacks of different forms of automatic testing


Unit Testing

Advantages

• Simplicity: Easy to write and maintain.

• Speed: Tests execute very quickly.

• Early Bug Detection: Helps find bugs early in the development process.

• Isolated Testing: Tests individual units in isolation, which makes debugging easier.

• Encourages Better Design: Writing tests often leads to more modular and cleaner code.
Disadvantages

• Limited Scope: Only tests individual units, missing integration issues.

• Overhead: Writing and maintaining unit tests can be time-consuming.

• False Sense of Security: Passing unit tests doesn’t guarantee the overall system’s correctness.

Benefits

• High Code Quality: Ensures individual components work as expected.

• Fast Feedback: Provides immediate feedback to developers, leading to quicker iterations.

Challenges

• Maintaining Test Suite: Requires regular updates as the codebase evolves.

• Mocking Dependencies: Complex dependencies can be hard to mock correctly.

Integration Testing

Advantages

• Catches Integration Issues: Identifies problems in interactions between modules.

• Broader Scope: Tests multiple units/modules together.

• Improved System Reliability: Ensures that integrated components work well together.

Disadvantages

• Complexity: More complex to set up and maintain than unit tests.

• Execution Time: Slower than unit tests as it tests combined units/modules.

• Resource Intensive: Requires more resources and infrastructure to test integrated systems.

Benefits

• Increased Confidence: Provides greater confidence in the system’s functionality.

• Detects Interface Issues: Finds issues at the interfaces between modules.

Challenges

• Test Environment Setup: Requires a realistic environment that mimics production.

• Dependency Management: Managing dependencies and their states can be challenging.

End-to-End Testing
Advantages

• Comprehensive Testing: Tests the entire application, including front-end and back-end.

• Real-world Scenarios: Validates the application under real user workflows.

• High Bug Detection: Detects issues that affect the entire system, including UI, database, and
network interactions.

Disadvantages

• High Complexity: Most complex to write and maintain.

• Slow Execution: Takes the longest time to run.

• High Cost: Requires significant resources and setup.

• Fragility: Tests can be brittle and fail due to minor changes in the application.

Benefits

• User Experience Validation: Ensures that the application works as expected from a user’s
perspective.

• Comprehensive Coverage: Provides coverage for all parts of the system, reducing the risk of
undetected bugs.

Challenges

• Maintenance Overhead: Requires constant updates to keep tests aligned with the application
changes.

• Test Environment Consistency: Maintaining a consistent test environment similar to production


can be difficult.

• Complex Setup: Setting up and managing test data, state, and configurations is challenging.

Criteria Unit Testing Integration Testing End-to-End Testing

Purpose Verify individual units Verify interactions Verify entire system


of code (e.g., functions, between integrated functionality from start
methods) units/modules to finish

Scope Single units of code Groups of modules The entire system


working together including UI and
backend components
Complexity Simple, as it tests Moderate, involves High complexity, tests
isolated units testing interactions full user workflows and
between modules system behavior

Execution Speed Very fast, typically Moderate speed, Slowest, can take
milliseconds per test typically seconds to several minutes to run
minutes per test full system tests

Cost Low, minimal resources Moderate, more High, significant


required resources needed for resources and setup
module integration required
tests

Bug Detection Detects bugs early in Detects issues in Detects comprehensive


development, focuses module interfaces and system-wide issues
on individual logic interactions including UI and
errors backend

Maintenance Easy to maintain, Moderate High maintenance, must


update tests as maintenance, updates update tests for any
individual units change needed when module changes in user
interactions change workflows or system
logic

Automation Easily automated, often Automatable but Difficult to automate,


part of CI/CD pipelines requires more setup requires robust tools
and setup

Examples Testing a function that Testing if the login Testing a user’s journey
calculates the sum of module correctly from logging in to
two numbers interacts with the user making a purchase
database

Environment Setup Minimal setup, often Requires setup of Requires a full system
uses mocks/stubs multiple modules and environment that
their interactions mimics production

Testing Tools JUnit, pytest, NUnit Postman, TestNG, JUnit Selenium, Cypress,
with integration Puppeteer
libraries

Test Coverage Focus Code correctness of Interaction correctness Overall system behavior
individual units between units and user experience
Feedback Speed Immediate feedback Moderate feedback Slower feedback,
for developers during speed usually part of the final
development testing phase

Example Languages Python, Java, C# Java, Python, JavaScript Any language that
interacts with the entire
system

Development Strongly integrated, Moderately integrated, Integrated but typically


Integration often written alongside usually follows unit follows integration
code testing testing

Reliability High for individual High for module High for overall system
units, low for system interactions, moderate reliability and user
reliability for overall system satisfaction

Detailed Analysis of Each Criterion

Purpose

• Unit Testing focuses on ensuring that individual functions or methods work correctly in isolation.

• Integration Testing ensures that different modules or components work together as expected.

• End-to-End Testing verifies that the entire system works correctly from the user's perspective.

Scope

• Unit Testing is limited to single units of code.

• Integration Testing encompasses multiple modules or components.

• End-to-End Testing covers the entire application, including user interface and backend.

Complexity

• Unit Testing is the simplest because it tests isolated units of code.

• Integration Testing is more complex due to the interactions between different modules.

• End-to-End Testing is the most complex, testing complete user workflows and system behavior.

Execution Speed

• Unit Testing is very fast, as it only tests small, isolated pieces of code.
• Integration Testing is moderately fast, but slower than unit testing due to the additional
complexity.

• End-to-End Testing is the slowest, as it tests the entire system and mimics real user interactions.

Cost

• Unit Testing is low-cost due to its simplicity and minimal resource requirements.

• Integration Testing has a moderate cost, requiring more resources to test module interactions.

• End-to-End Testing is high-cost due to the need for extensive resources and complex setup.

Bug Detection

• Unit Testing is effective for catching simple bugs early in the development process.

• Integration Testing is good for identifying issues in module interfaces and interactions.

• End-to-End Testing is comprehensive, catching system-wide issues, including user interface and
backend interactions.

Maintenance

• Unit Testing is easy to maintain, with updates needed only when individual units change.

• Integration Testing requires moderate maintenance as module interactions evolve.

• End-to-End Testing is challenging to maintain, needing updates for any changes in user workflows
or system logic.

Automation

• Unit Testing is easily automated and typically integrated into CI/CD pipelines.

• Integration Testing can be automated but requires more setup.

• End-to-End Testing is the most difficult to automate, needing robust tools and comprehensive
setup.

Environment Setup

• Unit Testing requires minimal setup, often using mocks and stubs.

• Integration Testing needs a setup that includes multiple modules and their interactions.

• End-to-End Testing demands a full system environment that closely resembles production.

Testing Tools
• Unit Testing tools include JUnit (Java), pytest (Python), and NUnit (C#).

• Integration Testing tools include Postman for API testing, TestNG, and JUnit with integration
libraries.

• End-to-End Testing tools include Selenium, Cypress, and Puppeteer.

Test Coverage Focus

• Unit Testing focuses on the correctness of individual units.

• Integration Testing focuses on the correctness of interactions between units.

• End-to-End Testing focuses on overall system behavior and user experience.

Feedback Speed

• Unit Testing provides immediate feedback for developers.

• Integration Testing offers moderate feedback speed.

• End-to-End Testing provides slower feedback, usually as part of the final testing phase.

Example Languages

• Unit Testing can be done in any programming language, like Python, Java, or C#.

• Integration Testing is commonly done in languages used for the development of interacting
modules, like Java, Python, or JavaScript.

• End-to-End Testing is done in languages that can interact with the entire system.

Development Integration

• Unit Testing is strongly integrated into the development process, often written alongside the
code.

• Integration Testing is moderately integrated, typically following unit testing.

• End-to-End Testing is integrated but generally follows integration testing.

Reliability

• Unit Testing provides high reliability for individual units but does not guarantee overall system
reliability.

• Integration Testing ensures high reliability for module interactions and moderate reliability for
the overall system.

• End-to-End Testing ensures high reliability for the entire system and user satisfaction.
III. CONCLUSION
This report has provided a thorough exploration of essential software design principles and testing
methodologies, demonstrating their critical importance in the development of high-quality software
systems. By investigating the object-oriented paradigm, class relationships, and SOLID principles, as well
as clean coding techniques, the first part of the report underscores how these concepts enhance system
quality and maintainability, particularly through their application in the design and development of the
SIMS architecture.

The second part of the report highlights the various forms of testing—automation, unit, integration, and
end-to-end—and their roles in ensuring software reliability and performance. By evaluating the
effectiveness of SOLID principles and clean coding techniques, along with the advantages and limitations
of automated testing, the report offers valuable insights into the best practices for software
development and testing.

In conclusion, the integration of sound design principles with effective testing strategies is not just a
theoretical exercise but a practical necessity for producing resilient, efficient, and scalable software
systems. The findings and analyses presented in this report emphasize the importance of continuous
learning and adherence to established design and testing methodologies, which ultimately contribute to
the successful realization of robust and reliable software solutions.
IV. REFERENCES
[1] Admin (2023) What are the six types of relationships in UML class diagrams?, Visual Paradigm Blog.
Available at: https://blog.visual-paradigm.com/what-are-the-six-types-of-relationships-in-uml-class-
diagrams/ (Accessed: 22 July 2024).

[2] Malshika, A.Y. (2024) Mastering solid principles in C#: A practical guide, Syncfusion. Available at:
https://www.syncfusion.com/blogs/post/mastering-solid-principles-csharp (Accessed: 22 July 2024).

[3] Object Oriented Programming in C# (no date) TutorialsTeacher. Available at:


https://www.tutorialsteacher.com/csharp/object-oriented-programming (Accessed: 22 July 2024).

[4] Opoku, I.C. (2024) How to use object-oriented programming in C# – explained with examples,
freeCodeCamp.org. Available at: https://www.freecodecamp.org/news/how-to-use-oop-in-c-
sharp/#polymorphism (Accessed: 22 July 2024).

[5] C# queue (2024) Programiz. Available at: https://www.programiz.com/csharp-programming/queue


(Accessed: 22 July 2024).

[6] Parewa Labs (2024) C# arraylist, Programiz. Available at: https://www.programiz.com/csharp-


programming/arraylist (Accessed: 22 July 2024).

[7] AWS (2024) What is unit testing? - unit testing explained - AWS. Available at:
https://aws.amazon.com/what-is/unit-testing/ (Accessed: 06 August 2024).

[8] Katalon (2023) What is integration testing? definition, examples, how-to, katalon.com. Available at:
https://katalon.com/resources-center/blog/integration-testing (Accessed: 07 August 2024).

[9] Team, C. (2023) What is automation testing? Ultimate Guide & Best Practices, katalon.com. Available
at: https://katalon.com/resources-center/blog/what-is-automation-testing (Accessed: 07 August 2024).

My Project:

https://github.com/quanla001/APDP_ASM2

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy