需要指导在.NET中为数据插入场景做Nunit测试用例 [英] Need guidance to do Nunit test case for data insert scenario in .NET

查看:37
本文介绍了需要指导在.NET中为数据插入场景做Nunit测试用例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下 Employee 模型类和控制台客户端.

I have the below Employee model class and console client.

员工类:-

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();
    }
}

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

上面的代码工作正常.

现在我被告知要为 Insert 方法和 fetch 方法编写 Nunit 测试方法.

Now I was told to write Nunit test method for Insert method and fetch method.

如何在以下条件下为 Insert 编写 NUnit 测试方法:-
1) 如何确保所提供的值被插入到数据库中.不应该手动验证.它应该是 Nunit 测试的一部分.
2) 如果表中引入了新列.

How can I write NUnit test method for Insert with following conditions:-
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.
2) In case if new column got introduced in the table .

在 Employee 模型中添加了 City 属性,City 参数作为参数传递.

In the Employee model City property got added and City param is passed as parameter.

假设新列 City Nullable 列添加到表中,在 Insert 存储过程中,开发人员没有在 insert 语句中添加新列,但在过程中添加了 City 参数.

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.

在上述场景中,Nunit 测试将如何识别此错误(即 City 未插入表中?

In this above scenario how Nunit test will identify this bug (that is City is not inserted into the table?

如何编写 Nunit 测试方法来测试上述条件?

How to write Nunit test method to test with above conditions?

推荐答案

Employee 类与实现问题的耦合过于紧密,因为它直接调用 SqlConnection 和相关实现.

Employee class is too tightly coupled to implementation concerns as it is directly calling SqlConnection and related implementations.

之前的回答建议使用 存储库模式,这将是处理此问题的标准方法.

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.

我们的应用程序是这样设计的,模型应该有插入、更新和删除方法.这些方法是 Employee 类的一部分.我们现在无法重新设计它.

Our application is developed with such design that model should have Insert,update and delete methods. These methods are part of the Employee class. We can't redesign it now.

我的印象是您无法根据需求更改为更灵活的设计.这并不意味着您不能仍然保持当前结构并仍然使代码可测试.然而,这需要重构 Employee 类以依赖抽象并分离其关注点.

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.

创建数据访问的抽象.

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

这将在 Employee 类中用于调用持久性,就像之前所做的那样,但可以灵活地使用不同的实现.

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.

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;
    }
}

请注意,该类之前公开的 API 没有改变,但该类的职责随着抽象的包含而颠倒了.

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.

鉴于您正在使用看起来像 Active Record Pattern 的东西,它非常有利于封装在没有数据库的情况下进行测试非常困难.因此,与孤立的单元测试相比,更倾向于集成测试.

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. 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.

有了这个,Employee 类现在可以单独测试,使用在安排测试时注入的依赖项的模拟实现.

With that in place the Employee class can now be tested in isolation, using mocked implementations of its dependencies injected when arranging the test.

[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;
    }
}

这篇关于需要指导在.NET中为数据插入场景做Nunit测试用例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆