简体   繁体   English

如何在 c# 中进行单元测试

[英]How do I make unit tests in c#

I have come code here that writes, modifies, and deletes records from an SQL database using a Car class.我在这里的代码使用汽车 class 从 SQL 数据库中写入、修改和删除记录。 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)?我如何获取这段代码及其各种功能并为这段特定的代码实现单元测试(特别是在 Mac 上的 VS Code 中)? The code is written in C# and the SQL database has 9 different columns which are the attributes of the Car class代码写在 C# 和 SQL 数据库中有 9 个不同的列,它们是 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");
        
        
        }    
    }
}

}    

There are two main design choices that make your code not easily unit-testable:有两个主要的设计选择使您的代码不容易进行单元测试:

  1. 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).为了使其可单元测试,您可以将每个“选择”的逻辑移动到单独的函数中,为每个函数单独添加单元测试,并添加一个“处理程序”方法,该方法将“选择”作为输入并路由到正确的 function (并为该处理程序添加测试)。

  2. 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.最后,我假设这是一个“学习”项目,但我建议您养成使用 SQL 参数而不是连接字符串的习惯。 Concatenating strings opens you up to SQL injection attacks and other vulnerabilities (escaping string values, etc.).连接字符串会使您面临 SQL 注入攻击和其他漏洞(转义字符串值等)。 Getting into this habit now will make you a better software engineer going forward.现在养成这种习惯将使您成为更好的软件工程师。

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.无论哪种方式,将一个新项目添加到您的解决方案和 select“NUnit 测试项目”模板。 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.有关如何实现此目的的示例,请参阅https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-nunit (创建第一个测试)。

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.如果您 go 在 Visual Studio 工具栏中“测试”和 select“分析所有测试的代码覆盖率”,它将显示您错过的代码中的路径。 Do note that this overview isn't a golden rule for everything you must test.请注意,此概述并不是您必须测试的所有内容的黄金法则。

Follow @DStanley's advice, his observations are spot on.遵循@Dstanley 的建议,他的观察是正确的。 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这是一个全部列出的示例: 来自 memory 的存储库模式和单元测试

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM