我有以下的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<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();
}
}
********************
上面的代码有效,很好。
现在我被告知为插入方法和获取方法编写Nunit测试方法。
如何在以下条件下为Insert编写NUnit测试方法: -
1)如何确保将所提供的值提供到数据库中。不应该进行手动验证。它应该是Nunit测试的一部分。
2)如果表中引入了新列。
在Employee模型中,City属性已添加,City param作为参数传递。
让我们说新列City Nullable列添加到表和Insert存储过程中,开发人员没有在insert语句中添加新列,但是City过程中添加了City param。
在上面的场景中,Nunit测试将如何识别此错误(即City未插入表中?
如何编写Nunit测试方法以测试上述条件?
答案 0 :(得分:0)
首先,您需要使用存储库模式,以便在代码中执行单元测试。
创建一个接口IEmployeeRepository
,它将定义您要执行的与Employees相关的操作:
public interface IEmployeeRepository {
void Insert(Employee employee);
List<Employee> GetAll();
}
现在创建必须从该接口继承的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.
}
}
因此,通过这种方式,您可以在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;
}
}
单元测试
如何确保插入的所有值 数据库。不应该是手动验证。它应该是一部分 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<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);
}
}
}
答案 1 :(得分:0)
Employee
类与实现问题紧密耦合,因为它直接调用SqlConnection
和相关实现。
之前的回答提出了存储库模式,这是处理此问题的标准方法。
但根据您对该答案的评论。
我们的应用程序是使用这样的设计开发的,模型应该具有插入,更新和删除方法。这些方法是Employee类的一部分。我们现在无法重新设计它。
我得到的印象是,您无法根据要求更改为更灵活的设计。这并不意味着您仍然无法保持当前结构并仍然使代码可测试。但是,这需要重构Employee
类依赖于抽象并将其关注点分开。
创建数据访问的抽象。
public interface IEmployeeRepository {
void Add(Employee model);
List<Employee> 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<Employee> GetAllEmployees() {
return repository.GetAll();
}
public void SetRepository(IEmployeeRepository repository) {
this.repository = repository;
}
}
请注意,此类的先前公开的API没有更改,但是通过包含抽象,该类的职责已被颠倒。
鉴于您使用的是Active Record Pattern,它非常有利于封装,以至于没有数据库的测试非常困难。因此,比隔离单元测试更有利于集成测试。
由于构造函数注入不适合您当前的设计,我建议公开一种方法,该方法允许将依赖项注入记录中。
由于对课程的规定限制,这只是建议。它违反了封装,因为它隐藏了正确使用的先决条件。
有了这个,现在可以单独测试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();
}
还可以通过不依赖于实施问题来分离关注点来改进存储库的生产实现。
以下是可以使用的接口和支持类的示例。
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;
}
}