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

我有以下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; } 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 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 employeeList = new List(); 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 empList = newEmp.GetAllEmployees(); } } ******************** 

上面的代码工作,很好。

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

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

在Employee模型中,City属性已添加,City param作为参数传递。

让我们说新的列City Nullable列添加到表和Insert存储过程中,开发人员没有在insert语句中添加新列,但City param添加在Procedure中。

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

如何用上述条件编写Nunit测试方法进行测试?

首先,您需要使用存储库模式,以便在代码中执行unit testing。

创建一个接口IEmployeeRepository ,它将定义您要执行的与Employees相关的操作:

 public interface IEmployeeRepository { void Insert(Employee employee); List GetAll(); } 

现在创建必须从该接口inheritance的EmployeeRepository类,并实现您明确定义的函数:

 public class EmployeeRepository : IEmployeeRepository { public void Insert(Employee employee){ // Your code to insert an employee from the db. } public List GetAll(){ // Your code to get all the employees from the db. } } 

因此,通过这种方式,您可以在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

如何确保所提供的值被插入到数据库中。不应该是手动validation。它应该是Nunit测试的一部分。

你真的不想打数据库并执行任何操作。 所以,你应该模拟对数据库的调用。

 using System.Collections.Generic; using NUnit.Framework; using Moq; namespace UnitTestProject1 { [TestFixture] public class EmployeeRepositoryTests { [Test] public void GetAll() { // Arrange var repositoryMock = new Mock(); var employees = new List { 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类与实现问题紧密耦合,因为它直接调用SqlConnection和相关实现。

之前的回答提出了存储库模式 ,这将是处理此问题的标准方法。

但根据你对该答案的评论。

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

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

创建数据访问的抽象。

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

这将在Employee类中用于调用持久性,如前所述,但具有能够使用不同实现的灵活性。

在应用关注点分离后,这是重构的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 GetAllEmployees() { return repository.GetAll(); } public void SetRepository(IEmployeeRepository repository) { this.repository = repository; } } 

请注意,此类的先前公开的API没有更改,但是通过包含抽象来反转类的职责。

鉴于您正在使用看起来像Active Record Pattern的东西 ,它非常有利于封装,以至于在没有数据库的情况下进行测试非常困难。 因此,比隔离unit testing更有利于集成测试。

由于构造函数注入不适合您当前的设计,我建议公开一种方法,该方法允许将依赖项注入记录中。

由于对课程的规定限制,这只是建议。 它违反了封装,因为它隐藏了正确使用的先决条件。

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

 [Test] public void InsertEmployee_Should_Add_Record() { //Arrange var employees = new List(); var repositoryMock = new Mock(); repositoryMock .Setup(_ => _.Add(It.IsAny())) .Callback(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() { new Employee("Ram", "Prem", 30, 90000007), new Employee("Pam", "Rem", 31, 90000008) }; var repositoryMock = new Mock(); 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(); } 

通过不依赖于实现问题来分离关注点,也可以改进存储库的生产实现。

以下是可以使用的接口和支持类的示例。

 public interface IDbConnectionFactory { /// /// Creates a connection based on the given connection string. /// 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 GetAll() { var employeeList = new List(); 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; } }