My TestManager
class serves as a centralized manager for handling test runs, test cases, and logging in an automated testing environment. Below are some key points that describe what I am doing and why a class like this would be beneficial:
Singleton Pattern
The Singleton pattern is used to ensure that only one instance of TestManager
exists throughout the application. This is useful for maintaining a single point of control and ensuring that resources like database connections are not duplicated.
Database Integration
The class integrates with an SQLite database to persistently store test runs, test cases, and logs. This is beneficial for tracking test history and for generating reports. SQL Server or Oracle databases or even a file could be used instead of SQLite. I am using SQLite for learning purposes.
Test Management
The class provides methods for creating new test runs (CreateNewTestRun
) and adding individual test cases (AreEqual
, IsTrue
, Fail
, NotImplemented
). This allows me to encapsulate the logic for test management, making it easier to maintain and extend. The individual test cases (AreEqual
, IsTrue
, Fail
, NotImplemented
) are designed to mimic and take the place of the Assert class in Microsoft.VisualStudio.TestTools.UnitTesting namespace.
Logging
The class also provides a logging mechanism (AddLog
, AddLogEntry
) that allows me to add log entries associated with a test run. This is useful for debugging and for providing additional information about the test run while developing the tests when you want the tests to keep running instead of throwing exceptions.
IDisposable Interface
The class implements the IDisposable
interface to manage resources like database connections effectively. This ensures that resources are released when they are no longer needed.
Reusability
The class is designed to be reusable across different test suites and scenarios. This saves time and effort in the long run as I don’t have to rewrite the same logic for each new test suite.
In summary, the TestManager
class serves as a comprehensive utility for managing automated tests, from creating test runs to logging results and storing them in a database. It is a good example of how to organize and manage automated testing in a clean, efficient, and reusable manner.
Here is the class. Feel free to let me know where I can improve.
public class TestManager : IDisposable
{
private static TestManager _instance;
private readonly string _connectionString;
private SQLiteConnection _connection;
TestRun _currentTestRun;
private bool _disposed = false;
private TestManager()
{
string dbFileName = "SeleniumTest.db";
string projectPath = AppDomain.CurrentDomain.BaseDirectory;
string dbFilePath = Path.Combine(projectPath, dbFileName);
string connectionString = $"Data Source={dbFilePath};Version=3;";
_connection = new SQLiteConnection(connectionString);
OpenConnection();
CreateNewTestRun();
}
public static TestManager Instance
{
get
{
if (_instance == null)
{
_instance = new TestManager();
}
return _instance;
}
}
public List<LogEntry> CurrentRunLogEntries
{
get
{
return _currentTestRun.LogEntries;
}
}
private bool OpenConnection()
{
try
{
_connection.Open();
return true;
}
catch (Exception e)
{
throw new Exception("Cannot open connection.", e);
}
}
public void CloseConnection()
{
try
{
if (_connection != null && _connection.State != System.Data.ConnectionState.Closed)
{
_connection.Close();
}
}
catch (Exception e)
{
// Handle the exception if needed
}
}
private TestRun CreateNewTestRun()
{
if (_connection == null || _connection.State != System.Data.ConnectionState.Open)
{
OpenConnection();
}
_currentTestRun = new TestRun();
_currentTestRun.RunDateTime = DateTime.Now;
using (var insertCommand = new SQLiteCommand("INSERT INTO TestRun (RunDateTime) VALUES (@RunDateTime); SELECT last_insert_rowid();", _connection))
{
insertCommand.Parameters.AddWithValue("@RunDateTime", _currentTestRun.RunDateTime.ToString());
_currentTestRun.Id = (int)(long)insertCommand.ExecuteScalar();
}
return _currentTestRun;
}
/// <summary>
/// Compares the string representations of expected and actual values.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expectedValue"></param>
/// <param name="actualValue"></param>
/// <returns></returns>
public bool AreEqual<T>(T expectedValue, T actualValue, string note = "")
{
bool result = true;
// Create a new Test instance
Test test = new Test
{
TestRunId = _currentTestRun.Id,
ExpectedValue = expectedValue.ToString(),
ActualValue = actualValue.ToString(),
Note = note,
};
if (test.ExpectedValue == test.ActualValue)
test.Result = TestResult.Pass;
else
{
test.Result = TestResult.Fail;
result = false;
}
//Add test to TestManager list and database.
AddTest(test);
return result;
}
public bool IsTrue(bool value, string note = "")
{
bool result = true;
// Create a new Test instance
Test test = new Test
{
TestRunId = _currentTestRun.Id,
ExpectedValue = true.ToString(),
ActualValue = value.ToString(),
Note = note,
};
if (value == true)
test.Result = TestResult.Pass;
else
{
test.Result = TestResult.Fail;
result |= false;
}
AddTest(test);
return result;
}
public void Fail(string message, string note = "")
{
// Create a new Test instance
Test test = new Test
{
TestRunId = _currentTestRun.Id,
ExpectedValue = "Fail",
ActualValue = message,
Result = TestResult.Fail,
Note = note,
};
AddTest(test);
}
public void NotImplemented(string message, string note = "")
{
// Create a new Test instance
Test test = new Test
{
TestRunId = _currentTestRun.Id,
ExpectedValue = "Not Implemented",
ActualValue = message,
Result = TestResult.NotImplemented,
Note = note,
};
AddTest(test);
}
private void AddTest(Test test)
{
// Add the test to the Tests list
_currentTestRun.Tests.Add(test);
// Perform database insert or other operations related to the test
using (var insertCommand = new SQLiteCommand("INSERT INTO Test (TestRunId, ExpectedValue, ActualValue, Result, Note) VALUES (@TestRunId, @ExpectedValue, @ActualValue, @Result, @Note); SELECT last_insert_rowid();", _connection))
{
insertCommand.Parameters.AddWithValue("@TestRunId", test.TestRunId);
insertCommand.Parameters.AddWithValue("@ExpectedValue", test.ExpectedValue);
insertCommand.Parameters.AddWithValue("@ActualValue", test.ActualValue);
insertCommand.Parameters.AddWithValue("@Result", (int)test.Result);
insertCommand.Parameters.AddWithValue("@Note", test.Note);
test.Id = (int)(long)insertCommand.ExecuteScalar();
}
}
/// <summary>
/// Add a log entry to the test run
/// </summary>
/// <param name="logEntry"></param>
///
public void AddLog(string description, string note = "")
{
LogEntry logEntry = new LogEntry
{
TestRunId = _currentTestRun.Id,
Description = description,
Note = note,
};
//Add log entry to database
AddLogEntry(logEntry);
}
private void AddLogEntry(LogEntry logEntry)
{
// Add the test to the log list
_currentTestRun.LogEntries.Add(logEntry);
// Perform database insert or other operations related to the test
using (var insertCommand = new SQLiteCommand("INSERT INTO Log (TestRunId, Description, Note) VALUES (@TestRunId, @Description, @Note); SELECT last_insert_rowid();", _connection))
{
insertCommand.Parameters.AddWithValue("@TestRunId", logEntry.TestRunId);
insertCommand.Parameters.AddWithValue("@Description", logEntry.Description);
insertCommand.Parameters.AddWithValue("@Note", logEntry.Note);
logEntry.Id = (int)(long)insertCommand.ExecuteScalar();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
CloseConnection(); // Close the database connection
}
_disposed = true;
}
}
~TestManager()
{
Dispose(false);
}
}
One response to “The TestManager Class”
[…] add in the TestManager class from a few posts back. Create a file and add the code to the project. Don’t forget that you will need to set up a […]