I have come code here that writes, modifies, and deletes records from an SQL database using a Car class. The code runs fine but it produces warnings. How do I take this code and its various functions and implement unit tests for this particular piece of code (particularly in VS Code on a Mac)? The code is written in C# and the SQL database has 9 different columns which are the attributes of the Car class
using System;
using Xunit;
using System.Data;
using Microsoft.Data.Sqlite;
using System.Threading.Tasks;
namespace sqlite_app
{
public class Car{
public string _id;
public string _year;
public string _make;
public string _model;
public string _mileage;
public string _is_in_service;
public string _rental_rate;
public string _color;
public string _location;
public Car(string id, string year, string make, string model, string mileage, string is_in_service, string rental_rate, string color, string location){
_id=id;
_year=year;
_make=make;
_model=model;
_mileage=mileage;
_is_in_service=is_in_service;
_rental_rate=rental_rate;
_color=color;
_location=location;
}
public class car_record{
public static void Main(String[] args){
bool is_true=false;
string choice="0";
while(is_true==false){
System.Console.WriteLine("What would you like to do?");
System.Console.WriteLine("Enter the following numbers for each choice:");
System.Console.WriteLine("1. Enter a record");
System.Console.WriteLine("2. delete a record");
System.Console.WriteLine("3. modify a record");
choice = Console.ReadLine();
if(choice=="1"||choice =="2" ||choice=="3"){
is_true=true;
}
else{
Console.WriteLine("Not a valid choice. Enter 1, 2 or 3");
}
}
if(choice=="1"){
string id, year, make, model, mileage, is_in_service, rental_rate, color, location;
System.Console.WriteLine("Assign the car an id number");
id=Console.ReadLine();
System.Console.WriteLine("What is the car's year?");
year=Console.ReadLine();
System.Console.WriteLine("What is the make?");
make=Console.ReadLine();
System.Console.WriteLine("What is the model?");
model=Console.ReadLine();
System.Console.WriteLine("What is the current mileage");
mileage=Console.ReadLine();
System.Console.WriteLine("Is the car currently in service (type yes or no)?");
is_in_service=Console.ReadLine();
System.Console.WriteLine("What is the car's per day rental rate");
rental_rate=Console.ReadLine();
System.Console.WriteLine("What is the car's color");
color=Console.ReadLine();
System.Console.WriteLine("What location is the car at?");
location=Console.ReadLine();
Car car1 = new Car(id, year, make, model, mileage, is_in_service, rental_rate, color, location);
insertdata(car1);
}
if(choice=="2"){
string id_str;
System.Console.WriteLine("What is the record id?");
id_str=Console.ReadLine();
int flag =0;
deleteRecord(id_str,flag);
}
if(choice=="3"){
string id_string;
Console.WriteLine("What is the id of the record you would like to update?");
id_string=Console.ReadLine();
modifyRecord(id_string);
}
}
public static void insertdata(Car car1)
{
var connectionStringBuilder = new SqliteConnectionStringBuilder();
//Use DB in project directory. If it does not exist, create it:
connectionStringBuilder.DataSource = "carRecords.db";
using (var connection = new SqliteConnection(connectionStringBuilder.ConnectionString))
{
connection.Open();
//Create a table (drop if already exists first):
//Seed some data:
using (var transaction = connection.BeginTransaction())
{
var insertCmd = connection.CreateCommand();
insertCmd.CommandText = "INSERT INTO carRecords VALUES ('" car1._id "','" car1._year "','" car1._make "','" car1._model "','" car1._mileage "','" car1._is_in_service "','" car1._rental_rate "','" car1._color "','" car1._location "')";
insertCmd.ExecuteNonQuery();
transaction.Commit();
}
Console.WriteLine("Record Entered");
}
}
public static int deleteRecord(string id_num, int flag)
{
var connectionStringBuilder = new SqliteConnectionStringBuilder();
//Use DB in project directory. If it does not exist, create it:
connectionStringBuilder.DataSource = "carRecords.db";
using (var connection = new SqliteConnection(connectionStringBuilder.ConnectionString))
{
connection.Open();
//Create a table (drop if already exists first):
//Seed some data:
using (var transaction = connection.BeginTransaction())
{
var insertCmd = connection.CreateCommand();
insertCmd.CommandText = "DELETE FROM carRecords WHERE id=" id_num "";
insertCmd.ExecuteNonQuery();
transaction.Commit();
}
Console.WriteLine("Record Deleted");
flag=1;
return flag;
}
return flag;
}
private static void modifyRecord(string id_string)
{
string choose = "0";
bool iss_true=false;
while(iss_true==false){
System.Console.WriteLine("What would you like to do?");
System.Console.WriteLine("Enter the following numbers for each choice:");
System.Console.WriteLine("1. Update a car's mileage");
System.Console.WriteLine("2. Update a car's rental cost per day.");
System.Console.WriteLine("3. Whether a car is avaiable or not.");
System.Console.WriteLine("4. Update a car's location.");
choose = Console.ReadLine();
if(choose=="1"||choose =="2" ||choose=="3" || choose=="4"){
iss_true=true;
}
else{
Console.WriteLine("Not a valid choice. Enter 1, 2 or 3");
}
}
var connectionStringBuilder = new SqliteConnectionStringBuilder();
//Use DB in project directory. If it does not exist, create it:
connectionStringBuilder.DataSource = "carRecords.db";
if(choose=="1"){
Console.WriteLine("What is the new mileage");
string Mileage=Console.ReadLine();
using (var connection = new SqliteConnection(connectionStringBuilder.ConnectionString))
{
connection.Open();
//Create a table (drop if already exists first):
//Seed some data:
using (var transaction = connection.BeginTransaction())
{
var insertCmd = connection.CreateCommand();
insertCmd.CommandText = "UPDATE carRecords SET mileage=" Mileage " WHERE id =" id_string "";
insertCmd.ExecuteNonQuery();
transaction.Commit();
}
}
}
if(choose=="2"){
Console.WriteLine("What is the new cost for rental");
string Cost=Console.ReadLine();
using (var connection = new SqliteConnection(connectionStringBuilder.ConnectionString))
{
connection.Open();
//Create a table (drop if already exists first):
//Seed some data:
using (var transaction = connection.BeginTransaction())
{
var insertCmd = connection.CreateCommand();
insertCmd.CommandText = "UPDATE carRecords SET cost=" Cost " WHERE id =" id_string "";
insertCmd.ExecuteNonQuery();
transaction.Commit();
}
}
}
if(choose=="3"){
Console.WriteLine("is the car avaiable yes or no");
string is_in_service=Console.ReadLine();
using (var connection = new SqliteConnection(connectionStringBuilder.ConnectionString))
{
connection.Open();
//Create a table (drop if already exists first):
//Seed some data:
using (var transaction = connection.BeginTransaction())
{
var insertCmd = connection.CreateCommand();
insertCmd.CommandText = "UPDATE carRecords SET avaiable='" is_in_service "' WHERE id ='" id_string "'";
insertCmd.ExecuteNonQuery();
transaction.Commit();
}
}
}
if(choose=="4"){
Console.WriteLine("Enter the new location of the car");
string location=Console.ReadLine();
using (var connection = new SqliteConnection(connectionStringBuilder.ConnectionString))
{
connection.Open();
//Create a table (drop if already exists first):
//Seed some data:
using (var transaction = connection.BeginTransaction())
{
var insertCmd = connection.CreateCommand();
insertCmd.CommandText = "UPDATE carRecords SET location='" location "' WHERE id ='" id_string "'";
insertCmd.ExecuteNonQuery();
transaction.Commit();
}
}
}
Console.WriteLine("Record Updated");
}
}
}
}
CodePudding user response:
There are two main design choices that make your code not easily unit-testable:
You are getting all input from the command line. To make it unit-testable you could move the logic for each "choice" into separate functions, add unit tests for each of those functions independently, and add a "handler" method that takes a "choice" as an input and routes to the proper function (and add a test for that handler).
You are connecting to the database directly. It's difficult to write unit tests that affect external resources. The common way to do this is to "mock" the database (there are various frameworks to do this) and add methods to the mock that verify that the changes that you make in your unit test are preserved. This is NOT a simple process as you need to decouple your app from the database and build the mocks to verify the changes.
Finally, I'm assuming this is a "learning" project but I'd suggest that you get in the habit of using SQL parameters instead of concatenating strings. Concatenating strings opens you up to SQL injection attacks and other vulnerabilities (escaping string values, etc.). Getting into this habit now will make you a better software engineer going forward.
CodePudding user response:
What you are looking for isn't a Unit test, but is called an "Integration Test". Since it acts on more than a single "Unit" (especially since there is a Database involved, which may cause the tests to fail for reasons not related to the code you are testing.
You can either make unit tests and mock the database connection (via something like dependency injection and interfaces), or make integration tests.
Either way, add a new project to your solution and select the "NUnit Test Project" template. Reference your project that you want to test and write the test cases.
See https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-nunit (Creating the first test) for examples on how to achieve this.
If you go to "Test" in the Visual Studio toolbar and select "Analyze Code Coverage For All Tests", it will show you paths in your code that you missed. Do note that this overview isn't a golden rule for everything you must test.
CodePudding user response:
Follow @DStanley's advice, his observations are spot on. To decouple and test the database interactions, you can use the repository pattern, which is slightly easier to mock than other patterns. Here's an example all laid out: Repository Pattern and unit testing from memory