Page 1 of 12
.NET Framework
- CLR: Common Language Runtime
- Execution environment that converts programs into native code.
- FCL: Framework Class Library
- Contains a large number of class libraries.
.NET supports over 60 languages, including:
- C#
- F#
- etc.
Key Components
- ASP.NET: Developing web applications and services.
- ADO.NET: Applications and data sources connection.
- LINQ: Query language used to make queries for data sources with C#.
- Entity Framework: ORM-based framework to deal with databases using .NET objects.
Note:
Source code is compiled into Intermediate Language (IL) that conforms to the CLI (Common Language Infrastructure) specification.
Page 2 of 12
C# vs .NET
- C# is a programming language, while .NET is a framework.
- C# has many implementations:
- .NET Framework: Libraries for Windows.
- .NET Core: Libraries for Linux, Mac, and Windows.
- .NET Standard: Common libraries used in both .NET and .NET Core.
C# Data Types
-
Value Data Types:
- Predefined:
int,float. - User-defined:
struct,enum.
- Predefined:
-
Pointer Data Types.
-
Reference Data Types:
- Predefined:
objects,strings. - User-defined:
classes,interfaces.
- Predefined:
ref Type and out Type
-
ref Type:
- Pass by reference.
- Specify
refin both call and definition.
-
out Type:
- Pass by reference and specify which one.
- Specify
outin both call and definition.
Page 3 of 12
Unit Testing in C#
Automated Testing
Test code and running tests in an automated fashion.
Types of Tests
- Unit Test: Test a unit of an application without external dependencies.
- Integration Test: Tests the application with external dependencies (e.g., databases, clients, etc.).
- End-to-End Test: Drives an application through its UI.
graph TD
Unit --> Integration
Integration --> E2E
Frameworks / Tooling
- NUnit, MSTest, xUnit.
Library + Test Runner
Convention (TestMethod Naming):
- Method under test.
- Scenario under testing.
- Expected behavior.
Inside Test Method:
- Arrange: Prepare objects.
- Act: Call method.
- Assert: Verify results.
Page 4 of 12
Test Driven Development (TDD)
- Steps:
- Write a failing test.
- Write the simplest code to make the test pass.
- Refactor if necessary.
graph TD
Write_Failing_Test --> Simplest_Code --> Refactor
Fundamentals of Unit Testing
Characteristics:
- Clean, readable, and maintainable.
- No logic.
- Isolated and not too specific.
Frameworks:
- NUnit or xUnit.
- Setup object and teardown object are there.
- Testcase object for repeated objects.
Techniques:
- Testing method that returns a value:
- Go for the sweet spot in both being too specific or too general.
Page 5 of 12
Unit Testing Best Practices
Key Points:
-
Avoid Private Method Testing
- Testing private methods directly is discouraged.
-
Test Methods as Black-Box
- Don’t depend on the implementation for test cases (TCs).
Breaking External Dependency
classDiagram
Class --> FileReader : External Dependency
Class --> DB : External Dependency
-
Explanation: External dependencies impact unit tests, making them take significantly longer. Example: Microsoft - 2 months.
-
Solution: Use Test Double or Fake Object.
Dependency Injection
-
Purpose: Provides an object of the Reader class to both the test and the real implementation.
-
Implementation: Done using:
- Parameters in the method.
- Property in the class and initializing in the constructor.
> Page 6 of 12
Code Example
public class Video {
private IFileReader _fileReader;
public Video() {
_fileReader = new FileReader();
}
public Video(IFileReader fileReader) {
_fileReader = fileReader;
}
}
// OR
public Video(IFileReader fileReader = null) {
_fileReader = fileReader ?? new FileReader();
}
- Dependency Injection Frameworks:
- NInject
- StructureMap
- Etc.
Page 7 of 12
Unit Tests with Mock Frameworks
- Approach:
Either create different Fake Classes for
FileReaderor use a framework that dynamically creates implementations such as:- Moq
- NSubstitute
- Etc.
Moq Framework
Code Example:
var fileReader = new Mock<IFileReader>();
fileReader.Setup(fr => fr.Read("video.txt"))
.Returns("C#");
For Databases
- Design tests in a way that interaction with storage objects is isolated.
- Use Moq Verify for validation.
mockObject.Verify(s => s.method(), Times.Once);
Page 8 of 12
Notes:
-
Use Moqs for External Resources:
- Otherwise, you may end up with multiple interfaces.
-
Moqs for Class Interactions:
- When two class objects with many paths interact, Moqs simplify testing.
classDiagram
classA --> classB : Interaction
classA --> Moq : Correct Approach
Why Use Mock Objects?
- Improve test execution speed.
- Support parallel development streams.
- Improve test scalability.
- Reduce development/testing costs.
- Facilitate testing for non-deterministic dependencies.
Page 9 of 12
Test Doubles
- Definition: Test double is a generic term for any case where you replace a production object for testing purposes.
- Examples:
- Fakes:
- Working implementation of dependency
- Not suitable for production
- Dummies:
- Passed around
- Never used/accessed
- Stubs:
- Provides answers to calls
- Property gets
- Method return values
- Mocks:
- Detect/verify calls
- Properties
- Call method
- Fakes:
- Examples:
Code Snippet
Mock<T> obj = new Mock<T>();
obj.setup( ... ).returns( ... ); // Configures methods of the Mock
Mock Behaviors
- Strict: Throws exception if method called but not setup.
- Loose
- Default
Page 10 of 12
SOLID Principles
Single Responsibility Principle
- Definition: Each software module should have one and only one reason to change.
- Kind of close-coupling:
- Offers a modular way to track which details are involved in a particular operation.
- Well responsibility:
- Directly proportional to Testability.
- Examples:
- Logging
- Persistence
- Validation
- Guidelines:
- Keep classes small, focused, and testable.
Open/Closed Principle
- Definition: Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
- Typical approaches to extension:
- Parameter-Based Extension
- Inheritance-Based Extension
- Composition/Injection Extension
Page 11 of 12
Liskov Substitution Principle
- Definition: Subtypes must be substitutable for their base types.
- Guidelines:
- Tell, Don't Ask:
- Avoid if-checks.
- Assign responsibilities to a different class.
- Look for:
- Type checking
- Null checking
- Not implemented exception
- Tell, Don't Ask:
Interface Segregation Principle
- Definition: Clients should not be forced to depend on methods they do not use.
- Guidelines:
- Avoid large interfaces because more dependencies mean:
- More coupling
- More difficult testing
- Avoid large interfaces because more dependencies mean:
Page 12 of 12
Example: Interface Segregation Principle
// Example
INotificationService {
sendEmail();
sendText();
}
IEmailNotificationService {
sendEmail();
}
ITextNotificationService {
sendText();
}
public interface NotificationService extends IEmail, IText {
// Can be extended further
}
Diagram: SOLID Principles Relationships
graph TD
A[Single Responsibility] --> B[Add functionality by adding more classes]
B --> C[Open/Closed]
C --> D[Depend on interfaces]
D --> E[Liskov Substitution]
D --> F[Interface Segregation]
F --> G[Interfaces must be fully implemented]
A --> H[Change behavior by passing in different dependencies]
H --> D[Dependency Inversion]
References & Related Topics
- References:
- Dependency Injection in C#
- Unit Testing Strategies for Software Development
- “Clean Code” by Robert C. Martin
- “Design Patterns: Elements of Reusable Object-Oriented Software”
- Related Topics:
- Dependency Injection
- Design Patterns
- Agile Testing