Ensuring Software Excellence with Testing Your Code

Admir Mujkic
8 min readMar 19, 2024

In the field of software engineering, the term testing code holds diverse implications. It may refer to assessing how well an application functions under intense workload (load testing), confirming individual segments perform as anticipated (unit tests), or validating diverse segments collaborate seamlessly (integration testing), among other concepts.

Developers must comprehend these varied testing approach Cycle (SDLC). Each testing style fulfills a unique purpose but all are critical in guaranteeing an application remains stable, dependable, and well-documented.

Ensuring software quality necessitates orchestrating testing strategies fitting changing needs. With care and diligence throughout the SDLC, programmers can help deliver solutions serving users as intended.

Why do we write tests?

The answer to this question is as straightforward as that. Even though some believe that testing starts with the developers, I am confident that it should begin with management.

If developers suggest that tests are the absolute requirement when writing software, and management thinks that tests are the loss of time, it is the proper time to update the resume. Additionally, management, and some developers, have different opinions about the reliability of their software and the impact this has on the timetable.

In this context, the reason for tests is obvious — developers can somehow confirm that the code they write works.

However, even more, tests are testing the one simple way that they can use to interact with the one simple way. The test tells others and the author of the software for that matter exactly what the guarantees.

Unit tests

For C# developers looking to understand unit testing in a straightforward manner, here’s a simpler explanation of the provided code and concepts.

Unit testing is about writing small tests to check if specific parts of your code work as expected. These parts usually are individual methods. When you write a unit test, you’re typically checking:

  • The normal working condition of a method (often called the “happy path”).
  • What happens when something goes wrong (the “unhappy path”).
  • The behavior of the method when it encounters different conditions or inputs.
  • How the method handles incorrect inputs or unexpected situations.

In larger software projects, it’s common to have hundreds of these tests, as they help ensure that all the small parts of your system work correctly.

Here’s a straightforward example of a unit test in C#:

[TestMethod]
public void FormattedDateTimeWithTimeTest()
{
// Arrange
var startDate = new DateTime(2024, 3, 18, 14, 0, 0);
const string expected = @"March 18<sup>th</sup>, 2024 at 2:00pm";

// Act
var actual = startDate.ToFormattedDateTime();

// Assert
Assert.AreEqual(expected, actual);
}

This example focuses on a single method that formats a date and time into a string, suitable for displaying in HTML. The test checks if this method returns the correct string format.

The test uses the AAA pattern, which stands for Arrange, Act, and Assert:

  • Arrange: Set up any data needed for the test. Here, we create a specific date and time and define the expected result.
  • Act: Call the method being tested with the arranged conditions. We format our date and time.
  • Assert: Check if the method’s actual output matches our expected result. This is where we confirm the method works correctly.

After you’re comfortable with unit tests, you’ll likely move on to integration tests, which test how different parts of your application work together.

Integration tests

On the other hand, Integration tests check how multiple components of your application interact. You can imagine it as you check whether the cake will be well-made and baked correctly if all the mix’s components are mixed and baked together, rather than one by one. Unit tests are the opposite of integration tests, as they concern unit in the title. They evaluate isolated components or units of the source code using dummy databases or network calls.

When Does a Test Become an Integration Test?

A test evolves into an integration test when it begins interacting with external resources. For instance, if your test communicates with a database, reads from a disk, or makes network requests, it’s venturing beyond the scope of a unit test into integration test territory.

Testing Database Connectivity with C# Example

Now, let’s dive into a straightforward example where we set up an integration test in C#. Our objective is to test the connection to a local database and ensure it successfully returns a “Students” object.

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

[TestClass]
public class DatabaseConnectionTests
{
private FacultyDbContext _context;
[TestInitialize]
public void InitializeDatabaseConnection()
{
var options = new DbContextOptionsBuilder<FacultyDbContext>()
.UseSqlServer("Server=localhost;Database=StudentsDb;Integrated Security=True;").Options;
_context = new FacultyDbContext(options);
}

[TestMethod]
[TestCategory("Integration")]
public void TestDatabaseConnection()
{
// Arrange
var service = new StudentService(_context);

// Act
var result = service.GetStudents();

// Assert
Assert.IsNotNull(result);
Assert.IsTrue(result.Any());
}
}

In this example, the following steps are executed.

  • Initialization — At the start of our test, we establish a connection to a local database called StudentsDb. We create a DbContextOptions object configured for SQL Server and use it to initialize our FacultyDbContext.
  • Test method — It involves instantiating an instance of StudentService with our context. Therefore, we call GetStudents to ascertain that the database connection works as intended.
  • Verify — That the result is not null and that it has data, which is a sign of a successful connection to the database as follows.

This example illustrates how one could set up an integration test to ascertain database connectivity and put emphasis on practical steps of arrange, act, and assert when it comes to tests with external resources.

Note that the real-world approach to integration testing can vary greatly.

Some testing units might use mocked databases or APIs, whereas others can utilize duplicated environments, or utilize a dedicated test server. The point is that all components of your application should interact properly under conditions that can be controlled.

Regression tests

The regression tests checks tests we’ve already done, functional and nonfunctional, and have passed. In other words, those are the old tests which we want to rerun to make sure updates or new features didn’t break anything that used to work just fine. These are a certain type of unit tests, which test individual pieces of the program, and integration tests, which check how different pieces work together.

Load testing

Adding load testing to your CI/CD pipeline is another way to carry out such maintenance. Load testing is a way to rigorously verify the website’s performance by generating a load on it. It offers significantly a lot more realistic usage than the 1 or two developers that worked on the site making use of the site in question simultaneously may.

It focuses on thoroughly establishing whether or not the web page can take care of much more users than usual at a time.

Ongoing collaboration in maintaining your website in good condition is key. Load testing allows you to measure your goal and improve over time.

End-to-End (E2E) or System testing

In system testing, our team collaborates to ensure everything in our software works well together. We have two main activities:

  1. Testing New Features: When we add new features, some team members and system users test these features to make sure they work as expected.
  2. Regression Testing: Meanwhile, other team members check the existing features to ensure that the new changes haven’t accidentally broken something that was working fine before.

For example, in a website scenario:

  1. Log in.
  2. Add an item to the shopping cart.
  3. Proceed to checkout.
  4. Confirm the purchase and receive an order number.

If all flows as it should in those scenarios, we put a green checkmark over the entire scenario. If something goes wrong at any point, we mark that step with a red X and describe what went wrong. This issue is then open in our backlog, where a developer can later work on it.

While it is critical for us to manually go through these scenarios and observe the use case bugging, there is a thought to automate these tests. It can potentially save time and be more feasible if we have the manpower and good scenarios to write those tests for. This way, we make sure we keep implementing a high-quality software product since new things work as they should and old things don’t break when we update our software.

UI testing

Another type of testing form is visually done using software tools such as Selenium or Cypress and it’s called UI Testing. End user clicking around or customer navigating the website is automated based on a given scenario. The tester needs to know how to use the UI testing software.

This includes knowing how to access page elements, knowing how to place values into those elements, and knowing how to activate related events such as click and blur to simulate an end user clicking a button.

Example using C#

Below is a simplified example of a UI test in C# using Selenium WebDriver. This example demonstrates how to test a login feature on a web application. It assumes you have Selenium WebDriver and a driver for the browser (e.g., ChromeDriver) installed and configured in your project.

First, you’ll need to install the Selenium WebDriver and a driver for your browser (e.g., ChromeDriver) via NuGet in your C# project.

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using System;
using Xunit;

namespace UITests
{
public class LoginTest
{
[Fact]
public void SuccessfulLoginTest()
{
// Initialize the ChromeDriver (make sure the chromedriver.exe is in your PATH or specify its location)
using (IWebDriver driver = new ChromeDriver())
{
// Navigate to the login page
driver.Navigate().GoToUrl("http://example.com/login");
// Find the username field and fill it out
IWebElement usernameField = driver.FindElement(By.Id("username"));
usernameField.SendKeys("testuser");
// Find the password field and fill it out
IWebElement passwordField = driver.FindElement(By.Id("password"));
passwordField.SendKeys("password123");
// Find the login button and click it
IWebElement loginButton = driver.FindElement(By.Id("loginButton"));
loginButton.Click();
// Wait for redirection and assert the URL to verify successful login
// Note: In a real test, consider using WebDriverWait for better stability
System.Threading.Thread.Sleep(1000); // Simple wait, not recommended for real use cases
Assert.Equal("http://example.com/dashboard", driver.Url); // Verify that the user is redirected to the dashboard
}
}
}
}

This test does the following:

  • Opens a Chrome browser and navigates to a login page.
  • Finds the username and password input fields by their IDs and enters credentials.
  • Finds and clicks the login button.
  • Waits briefly for the page to redirect (in real tests, you should use WebDriverWait for more reliable synchronization).
  • Asserts that the current URL is the expected one after a successful login, indicating the test passed.

Remember, this is a basic example.

Real-world UI testing would involve more sophisticated error handling, use of WebDriverWait for better synchronization, possibly navigating complex user flows, and cleaning up resources properly. Additionally, managing browser drivers and integrating with test frameworks would be part of a complete setup.

Final words

In conclusion, software testing is about far more than merely spotting and fixing errors. It professes full-spectrum tactic aimed at the product’s robustness, performance, and user satisfaction.

From component to integration testing, from load to UI approaches, a plethora of methodologies exists at each critical milestone of this voyage. Some are quick and unpretentious, while others are deep and convoluted but sum up to a rich matrix of QA.

This is possible through the future-proofed endeavors of management and developers as it teaches an essential lesson for all involved parties.

It highlights the fact that software that meets consumers’ needs must evolve and withstand the changing landscape of future user demands and available tools. Let us bear in mind that testing is your beacon in navigating the enigmatic waters of software development. It is through testing that we uncover who our users are today and for the coming periods.

--

--

Admir Mujkic

Admir combined engineering expertise with business acumen to make a positive impact & share knowledge. Dedicated to educating the next generation of leaders.