简体   繁体   English

需要指导为.NET中的数据插入方案执行Nunit测试用例

[英]Need guidance to do Nunit test case for data insert scenario in .NET

I have the below Employee model class and console client. 我有以下Employee模型类和控制台客户端。

Employee class:- 员工类: -

public class Employee
{
    public int EmployeeId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public int phoneNumber { get; set; }

    public Employee()
    {

    }

    public Employee(string fname,string lname,int age,int phone)
    {            
        this.FirstName = fname;
        this.LastName = lname;
        this.Age = age;
        this.phoneNumber = phone;
    }

    public void InsertEmployee()
    {
        SqlConnection con = new SqlConnection("sqlconnection");
        SqlCommand cmd = new SqlCommand("sp_insert", con);
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("fname", this.FirstName);
        cmd.Parameters.AddWithValue("lname", this.LastName);
        cmd.Parameters.AddWithValue("age", this.Age);
        cmd.Parameters.AddWithValue("phoneno",this.phoneNumber);            
        con.Open();
        cmd.ExecuteNonQuery();            
        con.Close();
    }

    public List<Employee> GetAllEmployees()
    {
        SqlConnection connection = new SqlConnection("sqlconnection");
        SqlCommand cmd = new SqlCommand("GetAllEmployees", connection);
        cmd.CommandType = System.Data.CommandType.StoredProcedure;
        connection.Open();
        SqlDataReader dr = cmd.ExecuteReader();
        List<Employee> employeeList = new List<Employee>();         
        while (dr.Read())
        {
            Employee emp = new Employee();
            emp.EmployeeId = int.Parse(dr["empID"].ToString());
            emp.FirstName = dr["fname"].ToString();
            emp.LastName = dr["lname"].ToString();
            emp.Age= int.Parse(dr["age"].ToString());
            emp.phoneNumber= int.Parse(dr["phone"].ToString());
            employeeList.Add(emp);
        }
        return employeeList;
    }
}

******Client code****

class Program
{
    static void Main(string[] args)
    {
        Employee newEmp = new Employee("Ram", "Prem", 30, 90000007);
        newEmp.InsertEmployee();
        List<Employee> empList = newEmp.GetAllEmployees();
    }
}

********************

The above code works and it is fine. 上面的代码工作,很好。

Now I was told to write Nunit test method for Insert method and fetch method. 现在我被告知要为Insert方法和fetch方法编写Nunit测试方法。

How can I write NUnit test method for Insert with following conditions:- 如何在以下条件下为Insert编写NUnit测试方法: -
1) How to ensure the what ever value supplied got inserted into database.There should not be manual validation.It should be part of Nunit test. 1)如何确保将所提供的值插入到数据库中。不应该进行手动验证。它应该是Nunit测试的一部分。
2) In case if new column got introduced in the table . 2)如果表中引入了新列。

In the Employee model City property got added and City param is passed as parameter. 在Employee模型中,City属性已添加,City param作为参数传递。

Lets say new column City Nullable column added to the table and in the Insert stored procedure the developer didnt add the new column in the insert statement but the City param is added in the Procedure. 让我们说新的列City Nullable列添加到表中,在Insert存储过程中,开发人员没有在insert语句中添加新列,但City param添加在Procedure中。

In this above scenario how Nunit test will identify this bug (that is City is not inserted into the table? 在上面的场景中,Nunit测试将如何识别这个错误(即City未插入表中?

How to write Nunit test method to test with above conditions? 如何用上述条件编写Nunit测试方法进行测试?

First of all, you would need to make use of the Repository Pattern so you can perform Unit Test in your code. 首先,您需要使用存储库模式,以便在代码中执行单元测试。

Create an interface IEmployeeRepository which will define the operations that you want to perform related to the Employees: 创建一个接口IEmployeeRepository ,它将定义您要执行的与Employees相关的操作:

public interface IEmployeeRepository {

  void Insert(Employee employee);
  List<Employee> GetAll();

}

Now create the EmployeeRepository class which must inherit from that interface, and implement the functions that you explicitly defined: 现在创建必须从该接口继承的EmployeeRepository类,并实现您明确定义的函数:

public class EmployeeRepository : IEmployeeRepository {

  public void Insert(Employee employee){
    // Your code to insert an employee from the db.
  }

  public List<Employee> GetAll(){
    // Your code to get all the employees from the db.
  }

}

So in this way, the Employee class is where you just define the properties that match your Employee table in the db and the constructor: 因此,通过这种方式,您可以在Employee类中定义与db和构造函数中的Employee表匹配的属性:

public class Employee {

        public int EmployeeId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
        public int phoneNumber { get; set; }

        public Employee()
        {

        }

        public Employee(string fname,string lname,int age,int phone)
        {            
            this.FirstName = fname;
            this.LastName = lname;
            this.Age = age;
            this.phoneNumber = phone;
        }
}

Unit Testing 单元测试

How to ensure the what ever value supplied got inserted into database.There should not be manual validation.It should be part of Nunit test. 如何确保将所提供的值插入到数据库中。不应该进行手动验证。它应该是Nunit测试的一部分。

You really don't want to hit the database and perform any operations. 你真的不想打数据库并执行任何操作。 So, you should Mock the calls to the database. 所以,你应该模拟对数据库的调用。

using System.Collections.Generic;
using NUnit.Framework;
using Moq;

namespace UnitTestProject1
{
    [TestFixture]
    public class EmployeeRepositoryTests
    {
        [Test]
        public void GetAll()
        {
            // Arrange
            var repositoryMock = new Mock<IEmployeeRepository>();
            var employees = new List<Employee>
            {
                new Employee("John", "Smith", 20, 12345678),
                new Employee("Robert", "Taylor", 20, 12345678)
            };

            // We simulate the DB returns the previous employees when calling the "GetAll()" method 
            repositoryMock.Setup(x => x.GetAll()).Returns(employees);

            // Act
            var result = repositoryMock.Object.GetAll();

            // Assert
            CollectionAssert.AreEquivalent(employees, result);
        }
    }
}

Employee class is too tightly coupled to implementation concerns as it is directly calling SqlConnection and related implementations. Employee类与实现问题紧密耦合,因为它直接调用SqlConnection和相关实现。

A previous answer suggested the Repository Pattern , which would be the standard way of dealing with this issue. 之前的回答提出了存储库模式 ,这将是处理此问题的标准方法。

But based on your comment to that answer. 但根据你对该答案的评论。

Our application is developed with such design that model should have Insert,update and delete methods. 我们的应用程序是使用这样的设计开发的,模型应该具有插入,更新和删除方法。 These methods are part of the Employee class. 这些方法是Employee类的一部分。 We can't redesign it now. 我们现在无法重新设计它。

I get the impression you are unable to change to a more flexible design based on requirements. 我得到的印象是,您无法根据要求更改为更灵活的设计。 That does not mean that you cannot still keep the current structure and still make the code testable. 这并不意味着您仍然无法保持当前结构并仍然使代码可测试。 This would however require refactoring the Employee class to be dependent on abstractions and separate its concerns. 但是,这需要重构Employee类依赖于抽象并将其关注点分开。

Create the abstraction of your data access. 创建数据访问的抽象。

public interface IEmployeeRepository {
    void Add(Employee model);
    List<Employee> GetAll();
}

This will be used in the Employee class to make calls to persistence as was done before but with the flexibility of being able to use different implementations. 这将在Employee类中用于调用持久性,如前所述,但具有能够使用不同实现的灵活性。

Here is the refactored Employee class after applying separation of concerns. 在应用关注点分离后,这是重构的Employee类。

public class Employee {
    IEmployeeRepository repository;

    public int EmployeeId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public int phoneNumber { get; set; }

    public Employee() {

    }

    public Employee(string fname, string lname, int age, int phone) {
        this.FirstName = fname;
        this.LastName = lname;
        this.Age = age;
        this.phoneNumber = phone;
    }

    public void InsertEmployee() {
        repository.Add(this);
    }

    public List<Employee> GetAllEmployees() {
        return repository.GetAll();
    }

    public void SetRepository(IEmployeeRepository repository) {
        this.repository = repository;
    }
}

Note that the previous exposed API for this class has not changed but the responsibilities of the class has be inverted with the inclusion of the abstraction. 请注意,此类的先前公开的API没有更改,但是通过包含抽象来反转类的职责。

Given that you are using what looks like an Active Record Pattern , which heavily favor encapsulation to the point where testing without a database is quite difficult. 鉴于您正在使用看起来像Active Record Pattern的东西 ,它非常有利于封装,以至于没有数据库的测试非常困难。 Thus favoring integration tests more so than isolated unit tests. 因此,比隔离单元测试更有利于集成测试。

Since constructor injections does not fit well into your current design, I suggest exposing a method that would allow for the injection of the dependency into the record. 由于构造函数注入不适合您当前的设计,我建议公开一种方法,允许将依赖项注入记录。

This is only being suggested because of the stated restrictions on the class. 由于对课程的规定限制,这只是建议。 It violates encapsulation as it hides preconditions for proper usage. 它违反了封装,因为它隐藏了正确使用的先决条件。

With that in place the Employee class can now be tested in isolation, using mocked implementations of its dependencies injected when arranging the test. 有了这个,现在可以单独测试Employee类,使用在安排测试时注入的依赖项的模拟实现。

[Test]
public void InsertEmployee_Should_Add_Record() {
    //Arrange
    var employees = new List<Employee>();

    var repositoryMock = new Mock<IEmployeeRepository>();

    repositoryMock
        .Setup(_ => _.Add(It.IsAny<Employee>()))
        .Callback<Employee>(employees.Add)
        .Verifiable();

    var newEmp = new Employee("Ram", "Prem", 30, 90000007);
    newEmp.SetRepository(repositoryMock.Object);

    //Act
    newEmp.InsertEmployee();

    //Assert
    employees.Should()
        .HaveCount(1)
        .And
        .Contain(newEmp);
    repositoryMock.Verify();
}

[Test]
public void GetAllEmployees_Should_GetAll() {
    //Arrange
    var expected = new List<Employee>() {
        new Employee("Ram", "Prem", 30, 90000007),
        new Employee("Pam", "Rem", 31, 90000008)
    };

    var repositoryMock = new Mock<IEmployeeRepository>();

    repositoryMock
        .Setup(_ => _.GetAll())
        .Returns(expected)
        .Verifiable();

    var newEmp = new Employee();
    newEmp.SetRepository(repositoryMock.Object);

    //Act
    var actual = newEmp.GetAllEmployees();

    //Assert
    expected.Should().Equal(actual);
    repositoryMock.Verify();
}

The production implementation of the repository can also be improved through separation of concerns by not depending on implementation concerns. 通过不依赖于实现问题来分离关注点,也可以改进存储库的生产实现。

Here are examples of the interfaces and supporting classes that can be used. 以下是可以使用的接口和支持类的示例。

public interface IDbConnectionFactory {
    ///<summary>
    /// Creates a connection based on the given connection string.
    ///</summary>
    IDbConnection CreateConnection(string nameOrConnectionString);
}

public class SqlConnectionFactory : IDbConnectionFactory {
    public IDbConnection CreateConnection(string nameOrConnectionString) {
        return new SqlConnection(nameOrConnectionString);
    }
}

public static class DbExtension {
    public static IDbDataParameter AddParameterWithValue(this IDbCommand command, string parameterName, object value) { 
        var parameter = command.CreateParameter();
        parameter.ParameterName = parameterName;
        parameter.Value = value;
        command.Parameters.Add(parameter);
        return parameter;
    }

    public static IDbCommand CreateCommand(this IDbConnection connection, string commandText) {
        var command = connection.CreateCommand();
        command.CommandText = commandText;
        return command;
    }
}

public class EmployeeSqlRepository : IEmployeeRepository {
    private IDbConnectionFactory connectionFactory;

    public EmployeeSqlRepository(IDbConnectionFactory connectionFactory) {
        this.connectionFactory = connectionFactory;
    }

    public void Add(Employee model) {
        using (var connection = connectionFactory.CreateConnection("sqlconnection")) {
            using (var command = connection.CreateCommand("sp_insert")) {
                command.CommandType = CommandType.StoredProcedure;
                command.AddParameterWithValue("fname", model.FirstName);
                command.AddParameterWithValue("lname", model.LastName);
                command.AddParameterWithValue("age", model.Age);
                command.AddParameterWithValue("phoneno", model.phoneNumber);
                connection.Open();
                command.ExecuteNonQuery();
                connection.Close();
            }
        }
    }

    public List<Employee> GetAll() {
        var employeeList = new List<Employee>();
        using (var connection = connectionFactory.CreateConnection("sqlconnection")) {
            using (var command = connection.CreateCommand("GetAllEmployees")) {
                command.CommandType = CommandType.StoredProcedure;
                connection.Open();
                using (var reader = command.ExecuteReader()) {
                    while (reader.Read()) {
                        var employee = new Employee();
                        employee.EmployeeId = int.Parse(reader["empID"].ToString());
                        employee.FirstName = reader["fname"].ToString();
                        employee.LastName = reader["lname"].ToString();
                        employee.Age = int.Parse(reader["age"].ToString());
                        employee.phoneNumber = int.Parse(reader["phone"].ToString());

                        employee.SetRepository(this);

                        employeeList.Add(employee);
                    }
                }
            }
        }
        return employeeList;
    }
}

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

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