如何从 VS2013 为基于 ADO.Net 的存储库编写单元测试代码
How to write unit test code for my ADO.Net based repository from VS2013
我是单元测试领域的新手。所以请指导我如何在我的控制器中为基于 ADO.Net 的存储库和操作方法编写单元测试?
我将使用 VS 自己的单元测试框架。
所以请看代码告诉我有哪些类和函数需要通过单元测试?
如果有人告诉我哪些区域需要进行单元测试,哪些区域不需要进行单元测试,那将非常有帮助?
如果可能,请写一些单元测试代码,告诉我如何为操作、学生存储库和学生视图模型编写单元测试。
我是否需要使用 mock 或 vs 自己的单元测试 是否足以测试我的存储库功能?
这是我的基本存储库
namespace DataLayer.Repository
{
public abstract class AdoRepository<T> where T : class
{
private SqlConnection _connection;
public virtual void Status(bool IsError, string strErrMsg)
{
}
public AdoRepository(string connectionString)
{
_connection = new SqlConnection(connectionString);
}
public virtual T PopulateRecord(SqlDataReader reader)
{
return null;
}
public virtual void GetDataCount(int count)
{
}
protected IEnumerable<T> GetRecords(SqlCommand command)
{
var reader = (SqlDataReader) null;
var list = new List<T>();
try
{
command.Connection = _connection;
_connection.Open();
reader = command.ExecuteReader();
while (reader.Read())
{
list.Add(PopulateRecord(reader));
}
reader.NextResult();
if (reader.HasRows)
{
while (reader.Read())
{
GetDataCount(Convert.ToInt32(reader["Count"].ToString()));
}
}
Status(false, "");
}
catch (Exception ex)
{
Status(true, ex.Message);
}
finally
{
// Always call Close when done reading.
reader.Close();
_connection.Close();
_connection.Dispose();
}
return list;
}
protected T GetRecord(SqlCommand command)
{
var reader = (SqlDataReader)null;
T record = null;
try
{
command.Connection = _connection;
_connection.Open();
reader = command.ExecuteReader();
while (reader.Read())
{
record = PopulateRecord(reader);
Status(false, "");
break;
}
}
catch (Exception ex)
{
Status(true, ex.Message);
}
finally
{
reader.Close();
_connection.Close();
_connection.Dispose();
}
return record;
}
protected IEnumerable<T> ExecuteStoredProc(SqlCommand command, string CountColName="TotalCount")
{
var reader = (SqlDataReader)null;
var list = new List<T>();
try
{
command.Connection = _connection;
command.CommandType = CommandType.StoredProcedure;
_connection.Open();
reader = command.ExecuteReader();
while (reader.Read())
{
var record = PopulateRecord(reader);
if (record != null) list.Add(record);
}
reader.NextResult();
if (reader.HasRows)
{
while (reader.Read())
{
GetDataCount(Convert.ToInt32(reader[CountColName].ToString()));
}
}
}
finally
{
// Always call Close when done reading.
reader.Close();
_connection.Close();
_connection.Dispose();
}
return list;
}
}
}
这是我的学生资料库
public class StudentRepository : AdoRepository<Student>
{
public int DataCounter { get; set; }
public bool hasError { get; set; }
public string ErrorMessage { get; set; }
public StudentRepository(string connectionString)
: base(connectionString)
{
}
public IEnumerable<Student> GetAll()
{
// DBAs across the country are having strokes
// over this next command!
using (var command = new SqlCommand("SELECT ID, FirstName,LastName,IsActive,StateName,CityName FROM vwListStudents"))
{
return GetRecords(command);
}
}
public Student GetById(string id)
{
// PARAMETERIZED QUERIES!
using (var command = new SqlCommand("SELECT ID, FirstName,LastName,IsActive,StateName,CityName FROM vwListStudents WHERE Id = @id"))
{
command.Parameters.Add(new ObjectParameter("id", id));
return GetRecord(command);
}
}
public IEnumerable<Student> SaveXML(string strXML, int PageNo,int PageSize,string SortCol,string SortOrder)
{
if (PageNo <= 0) PageNo = 1;
using (var command = new SqlCommand("USP_SaveStudent"))
{
command.Parameters.Add("@Data", SqlDbType.VarChar,-1).Value = strXML;
command.Parameters.Add("@PageNbr", SqlDbType.Int).Value = PageNo;
command.Parameters.Add("@PageSize", SqlDbType.Int).Value = PageSize;
command.Parameters.Add("@SortColumn", SqlDbType.VarChar, 20).Value = SortCol;
command.Parameters.Add("@SortOrder", SqlDbType.VarChar, 4).Value = SortOrder;
return ExecuteStoredProc(command);
}
}
public IEnumerable<Student> Delete(int id, int PageNo, int PageSize, string SortCol, string SortOrder)
{
if (PageNo <= 0) PageNo = 1;
using (var command = new SqlCommand("USP_DeleteStudent"))
{
command.Parameters.Add("@ID", SqlDbType.VarChar, -1).Value = id;
command.Parameters.Add("@PageNbr", SqlDbType.Int).Value = PageNo;
command.Parameters.Add("@PageSize", SqlDbType.Int).Value = PageSize;
command.Parameters.Add("@SortColumn", SqlDbType.VarChar, 20).Value = SortCol;
command.Parameters.Add("@SortOrder", SqlDbType.VarChar, 4).Value = SortOrder;
return ExecuteStoredProc(command);
}
}
public IEnumerable<Student> GetStudents(int PageNo, int PageSize, string SortCol, string SortOrder)
{
//string strSQL = "SELECT * FROM vwListStudents WHERE ID >=" + StartIndex + " AND ID <=" + EndIndex;
//strSQL += " ORDER BY " + sortCol + " " + sortOrder;
//strSQL += ";SELECT COUNT(*) AS Count FROM vwListStudents";
//var command = new SqlCommand(strSQL);
//return GetRecords(command);
if (SortOrder == "Ascending")
SortOrder = "ASC";
else if (SortOrder == "Descending")
SortOrder = "DESC";
if (PageNo <= 0) PageNo = 1;
using (var command = new SqlCommand("USP_GetStudentData"))
{
command.Parameters.Add("@PageNbr", SqlDbType.Int).Value = PageNo;
command.Parameters.Add("@PageSize",SqlDbType.Int).Value= PageSize;
command.Parameters.Add("@SortColumn", SqlDbType.VarChar, 20).Value = SortCol;
command.Parameters.Add("@SortOrder", SqlDbType.VarChar, 4).Value = SortOrder;
return ExecuteStoredProc(command);
}
}
public override Student PopulateRecord(SqlDataReader reader)
{
return new Student
{
ID = Convert.ToInt32(reader["ID"].ToString()),
FirstName = reader["FirstName"].ToString(),
LastName = reader["LastName"].ToString(),
IsActive = Convert.ToBoolean(reader["IsActive"]),
StateID = Convert.ToInt32(reader["StateID"].ToString()),
StateName = reader["StateName"].ToString(),
CityID = Convert.ToInt32(reader["CityID"].ToString()),
CityName = reader["CityName"].ToString()
};
}
public override void GetDataCount(int count)
{
DataCounter = count;
}
public override void Status(bool IsError, string strErrMsg)
{
hasError = IsError;
ErrorMessage = strErrMsg;
}
}
Student Model
---------------
public class Student
{
public int ID { get; set; }
[Required(ErrorMessage = "First Name Required")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last Name Required")]
public string LastName { get; set; }
public bool IsActive { get; set; }
public int StateID { get; set; }
public string StateName { get; set; }
public int CityID { get; set; }
public string CityName { get; set; }
}
Student View Model
------------------
public class StudentListViewModel
{
public int StartIndex { get; set; }
public int EndIndex { get; set; }
public int page { get; set; }
public int RowCount { get; set; }
public int PageSize { get; set; }
public int CurrentPage { get; set; }
public string sort { get; set; }
public string sortdir { get; set; }
public IList<Student> Students { get; set; }
[Required(ErrorMessage = "State Required")]
public int SelectedStateId { set; get; }
public IList<State> States { get; set; }
[Required(ErrorMessage = "City Required")]
public int SelectedCityId { set; get; }
public IList<City> Cities { get; set; }
public StudentListViewModel()
{
PageSize = 5;
sort = "ID";
sortdir = "ASC";
CurrentPage = 1;
}
public void SetUpParams(StudentListViewModel oSVm)
{
if (oSVm.page == 0)
oSVm.page = 1;
StartIndex = ((oSVm.page * oSVm.PageSize) - oSVm.PageSize) + 1;
EndIndex = (oSVm.page * oSVm.PageSize);
CurrentPage = (StartIndex - 1) / oSVm.PageSize;
if (string.IsNullOrEmpty(oSVm.sort))
oSVm.sort = "ID";
if (string.IsNullOrEmpty(oSVm.sortdir))
oSVm.sortdir = "ASC";
}
}
Here is few action which i need to test
---------------------------------------
public ActionResult List(StudentListViewModel oSVm)
{
if (Request.IsAjaxRequest())
System.Threading.Thread.Sleep(1000); // just simulate delay of one second
StudentListViewModel SVm = new StudentListViewModel();
SVm.SetUpParams(oSVm);
SVm.Students = _Studentdata.GetStudents(oSVm.page, oSVm.PageSize, oSVm.sort, oSVm.sortdir).ToList();
SVm.States = _Statedata.GetAll().ToList();
SVm.Cities = _Citydata.GetAll().ToList();
SVm.RowCount = _Studentdata.DataCounter;
return View("ListStudents",SVm);
}
[HttpPost]
public ActionResult UpdateStudents(StudentListViewModel oSVm, string Action)
{
if (Request.IsAjaxRequest())
System.Threading.Thread.Sleep(1000); // just simulate delay of one second
StudentListViewModel SVm = new StudentListViewModel();
SVm.SetUpParams(oSVm);
if (Action == "UPDATE")
{
SVm.Students = _Studentdata.SaveXML(new List<Student>(oSVm.Students).ToXml("Students"),
oSVm.page, oSVm.PageSize, oSVm.sort, oSVm.sortdir).ToList();
}
else if (Action == "DELETE")
{
SVm.Students = _Studentdata.Delete(oSVm.Students[0].ID,
oSVm.page, oSVm.PageSize, oSVm.sort, oSVm.sortdir).ToList();
}
SVm.States = _Statedata.GetAll().ToList();
SVm.Cities = _Citydata.GetAll().ToList();
SVm.RowCount = _Studentdata.DataCounter;
return PartialView("_StudentGrid", SVm);
}
[HttpPost]
public ActionResult RefreshStudents(StudentListViewModel oSVm)
{
if (Request.IsAjaxRequest())
System.Threading.Thread.Sleep(1000); // just simulate delay of one second
StudentListViewModel SVm = new StudentListViewModel();
SVm.SetUpParams(oSVm);
SVm.Students = _Studentdata.GetStudents(oSVm.page, oSVm.PageSize, oSVm.sort, oSVm.sortdir).ToList();
SVm.States = _Statedata.GetAll().ToList();
SVm.Cities = _Citydata.GetAll().ToList();
SVm.RowCount = _Studentdata.DataCounter;
return PartialView("_StudentGrid", SVm);
}
[HttpGet]
public JsonResult GetCityName(int StateID)
{
if (Request.IsAjaxRequest())
System.Threading.Thread.Sleep(1000); // just simulate delay of one second
return Json(new {CityList =_Citydata.GetCityByStateId(StateID)} , JsonRequestBehavior.AllowGet);
}
谢谢
一般准则:
如果您对超出内存块的方法进行单元测试(例如,连接到数据库或写入磁盘),您不想对它们进行单元测试。
这是有道理的。例如,如果您正在测试是否可以在您的 people table 中保存另一个 Person 对象,但测试失败了,这是哪里出了问题?我是数据库连接吗?数据库是否已满?也许它因为一些奇怪的原因而停止了?还是您的代码中存在错误? - 问题是,你不能确定,单元测试是为了让你确定哪里出了问题。
所以:策略 1:
停止编写在写入磁盘或数据库的同时执行任何形式的 IF 测试的代码。这些 classes 应该只使用契约,这样你就可以完全模拟数据库层。例如:
public class PeopleManager{
private IDataWriter<Person> _personWriter;
public PeopleManager(IDataWriter<Person> personWriter){
_personWriter = personWriter;
}
public void ImportantMethodToTest(Person person){
if(..important conditionsTested...){
_personWriter.Update(person);
}
}
}
上面的 class 是 unit-testable,因为它不依赖于 IDataWriter 的契约以外的任何东西,你可以在 class 中实现你的写作到数据库,和零逻辑。它只是写。
测试现在超快且超简单:
[TestMethod]
public void ImportantMethod_PersonIsNull_NothingIsWrittenToDatabase(){
// Arrange
var writerMock = new Mock<IDataWriter<Person>>();
var peopleManager = new PeopleManager(writerMock.Object);
// Act
peopleManager.ImportantMethodToTest(null);
// Assert
writerMock.Verify( writer => writer.Update(It.IsAny<Person>), Times.Never());
}
希望你能看到图片。需要搜索以了解更多信息的是 SOLID 原则,我在这里使用的部分是单一责任原则和依赖倒置原则。这些原则使您的代码可以进行单元测试
[TestMethod]
public void List_StudentListViewModelIsValid_ViewModelIsPopulated()
{
// Arrange
var students = Builder<Student>().CreateListOfSize(10).Build();
var repositoryMock = new Mock<StudentRepository>();
repositoryMock.Setup(r => r.GetAll()).Returns(students);
var testInstance = new StudentListViewModel(repositoryMock.Object); // DIP
// Act
testInstance.List()
// ASsert
// Check your values etc
}
这里要注意的重要一点是我对存储库所做的事情。我没有在 List 方法中使用 NEW,而是在 viewModel 构造函数中注入了存储库。你不应该在这样的函数内部使用 "new" 。相反,使用它们通过 class 的构造函数传递这些对象。这就是众所周知的 依赖倒置原则 并且是使您的代码可测试的面向对象编程的 SOLID 原则之一。
因此您必须重写您的视图模型以将存储库作为构造函数参数。这样,当你进行单元测试时,你可以给 viewmodel 一个 mock 作为存储库,而不必使用 new。
同样,您的抽象存储库 class 可以像任何接口一样被模拟。
编辑:
Builder 代码来自一个名为 NBuilder 的 nuget 包,每当我只需要来自模拟的一些数据时,我通常会使用它。当我在测试代码中需要它们时,它可以让我构建各种大小的列表:)
我是单元测试领域的新手。所以请指导我如何在我的控制器中为基于 ADO.Net 的存储库和操作方法编写单元测试?
我将使用 VS 自己的单元测试框架。
所以请看代码告诉我有哪些类和函数需要通过单元测试?
如果有人告诉我哪些区域需要进行单元测试,哪些区域不需要进行单元测试,那将非常有帮助?
如果可能,请写一些单元测试代码,告诉我如何为操作、学生存储库和学生视图模型编写单元测试。
我是否需要使用 mock 或 vs 自己的单元测试 是否足以测试我的存储库功能?
这是我的基本存储库
namespace DataLayer.Repository
{
public abstract class AdoRepository<T> where T : class
{
private SqlConnection _connection;
public virtual void Status(bool IsError, string strErrMsg)
{
}
public AdoRepository(string connectionString)
{
_connection = new SqlConnection(connectionString);
}
public virtual T PopulateRecord(SqlDataReader reader)
{
return null;
}
public virtual void GetDataCount(int count)
{
}
protected IEnumerable<T> GetRecords(SqlCommand command)
{
var reader = (SqlDataReader) null;
var list = new List<T>();
try
{
command.Connection = _connection;
_connection.Open();
reader = command.ExecuteReader();
while (reader.Read())
{
list.Add(PopulateRecord(reader));
}
reader.NextResult();
if (reader.HasRows)
{
while (reader.Read())
{
GetDataCount(Convert.ToInt32(reader["Count"].ToString()));
}
}
Status(false, "");
}
catch (Exception ex)
{
Status(true, ex.Message);
}
finally
{
// Always call Close when done reading.
reader.Close();
_connection.Close();
_connection.Dispose();
}
return list;
}
protected T GetRecord(SqlCommand command)
{
var reader = (SqlDataReader)null;
T record = null;
try
{
command.Connection = _connection;
_connection.Open();
reader = command.ExecuteReader();
while (reader.Read())
{
record = PopulateRecord(reader);
Status(false, "");
break;
}
}
catch (Exception ex)
{
Status(true, ex.Message);
}
finally
{
reader.Close();
_connection.Close();
_connection.Dispose();
}
return record;
}
protected IEnumerable<T> ExecuteStoredProc(SqlCommand command, string CountColName="TotalCount")
{
var reader = (SqlDataReader)null;
var list = new List<T>();
try
{
command.Connection = _connection;
command.CommandType = CommandType.StoredProcedure;
_connection.Open();
reader = command.ExecuteReader();
while (reader.Read())
{
var record = PopulateRecord(reader);
if (record != null) list.Add(record);
}
reader.NextResult();
if (reader.HasRows)
{
while (reader.Read())
{
GetDataCount(Convert.ToInt32(reader[CountColName].ToString()));
}
}
}
finally
{
// Always call Close when done reading.
reader.Close();
_connection.Close();
_connection.Dispose();
}
return list;
}
}
}
这是我的学生资料库
public class StudentRepository : AdoRepository<Student>
{
public int DataCounter { get; set; }
public bool hasError { get; set; }
public string ErrorMessage { get; set; }
public StudentRepository(string connectionString)
: base(connectionString)
{
}
public IEnumerable<Student> GetAll()
{
// DBAs across the country are having strokes
// over this next command!
using (var command = new SqlCommand("SELECT ID, FirstName,LastName,IsActive,StateName,CityName FROM vwListStudents"))
{
return GetRecords(command);
}
}
public Student GetById(string id)
{
// PARAMETERIZED QUERIES!
using (var command = new SqlCommand("SELECT ID, FirstName,LastName,IsActive,StateName,CityName FROM vwListStudents WHERE Id = @id"))
{
command.Parameters.Add(new ObjectParameter("id", id));
return GetRecord(command);
}
}
public IEnumerable<Student> SaveXML(string strXML, int PageNo,int PageSize,string SortCol,string SortOrder)
{
if (PageNo <= 0) PageNo = 1;
using (var command = new SqlCommand("USP_SaveStudent"))
{
command.Parameters.Add("@Data", SqlDbType.VarChar,-1).Value = strXML;
command.Parameters.Add("@PageNbr", SqlDbType.Int).Value = PageNo;
command.Parameters.Add("@PageSize", SqlDbType.Int).Value = PageSize;
command.Parameters.Add("@SortColumn", SqlDbType.VarChar, 20).Value = SortCol;
command.Parameters.Add("@SortOrder", SqlDbType.VarChar, 4).Value = SortOrder;
return ExecuteStoredProc(command);
}
}
public IEnumerable<Student> Delete(int id, int PageNo, int PageSize, string SortCol, string SortOrder)
{
if (PageNo <= 0) PageNo = 1;
using (var command = new SqlCommand("USP_DeleteStudent"))
{
command.Parameters.Add("@ID", SqlDbType.VarChar, -1).Value = id;
command.Parameters.Add("@PageNbr", SqlDbType.Int).Value = PageNo;
command.Parameters.Add("@PageSize", SqlDbType.Int).Value = PageSize;
command.Parameters.Add("@SortColumn", SqlDbType.VarChar, 20).Value = SortCol;
command.Parameters.Add("@SortOrder", SqlDbType.VarChar, 4).Value = SortOrder;
return ExecuteStoredProc(command);
}
}
public IEnumerable<Student> GetStudents(int PageNo, int PageSize, string SortCol, string SortOrder)
{
//string strSQL = "SELECT * FROM vwListStudents WHERE ID >=" + StartIndex + " AND ID <=" + EndIndex;
//strSQL += " ORDER BY " + sortCol + " " + sortOrder;
//strSQL += ";SELECT COUNT(*) AS Count FROM vwListStudents";
//var command = new SqlCommand(strSQL);
//return GetRecords(command);
if (SortOrder == "Ascending")
SortOrder = "ASC";
else if (SortOrder == "Descending")
SortOrder = "DESC";
if (PageNo <= 0) PageNo = 1;
using (var command = new SqlCommand("USP_GetStudentData"))
{
command.Parameters.Add("@PageNbr", SqlDbType.Int).Value = PageNo;
command.Parameters.Add("@PageSize",SqlDbType.Int).Value= PageSize;
command.Parameters.Add("@SortColumn", SqlDbType.VarChar, 20).Value = SortCol;
command.Parameters.Add("@SortOrder", SqlDbType.VarChar, 4).Value = SortOrder;
return ExecuteStoredProc(command);
}
}
public override Student PopulateRecord(SqlDataReader reader)
{
return new Student
{
ID = Convert.ToInt32(reader["ID"].ToString()),
FirstName = reader["FirstName"].ToString(),
LastName = reader["LastName"].ToString(),
IsActive = Convert.ToBoolean(reader["IsActive"]),
StateID = Convert.ToInt32(reader["StateID"].ToString()),
StateName = reader["StateName"].ToString(),
CityID = Convert.ToInt32(reader["CityID"].ToString()),
CityName = reader["CityName"].ToString()
};
}
public override void GetDataCount(int count)
{
DataCounter = count;
}
public override void Status(bool IsError, string strErrMsg)
{
hasError = IsError;
ErrorMessage = strErrMsg;
}
}
Student Model
---------------
public class Student
{
public int ID { get; set; }
[Required(ErrorMessage = "First Name Required")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last Name Required")]
public string LastName { get; set; }
public bool IsActive { get; set; }
public int StateID { get; set; }
public string StateName { get; set; }
public int CityID { get; set; }
public string CityName { get; set; }
}
Student View Model
------------------
public class StudentListViewModel
{
public int StartIndex { get; set; }
public int EndIndex { get; set; }
public int page { get; set; }
public int RowCount { get; set; }
public int PageSize { get; set; }
public int CurrentPage { get; set; }
public string sort { get; set; }
public string sortdir { get; set; }
public IList<Student> Students { get; set; }
[Required(ErrorMessage = "State Required")]
public int SelectedStateId { set; get; }
public IList<State> States { get; set; }
[Required(ErrorMessage = "City Required")]
public int SelectedCityId { set; get; }
public IList<City> Cities { get; set; }
public StudentListViewModel()
{
PageSize = 5;
sort = "ID";
sortdir = "ASC";
CurrentPage = 1;
}
public void SetUpParams(StudentListViewModel oSVm)
{
if (oSVm.page == 0)
oSVm.page = 1;
StartIndex = ((oSVm.page * oSVm.PageSize) - oSVm.PageSize) + 1;
EndIndex = (oSVm.page * oSVm.PageSize);
CurrentPage = (StartIndex - 1) / oSVm.PageSize;
if (string.IsNullOrEmpty(oSVm.sort))
oSVm.sort = "ID";
if (string.IsNullOrEmpty(oSVm.sortdir))
oSVm.sortdir = "ASC";
}
}
Here is few action which i need to test
---------------------------------------
public ActionResult List(StudentListViewModel oSVm)
{
if (Request.IsAjaxRequest())
System.Threading.Thread.Sleep(1000); // just simulate delay of one second
StudentListViewModel SVm = new StudentListViewModel();
SVm.SetUpParams(oSVm);
SVm.Students = _Studentdata.GetStudents(oSVm.page, oSVm.PageSize, oSVm.sort, oSVm.sortdir).ToList();
SVm.States = _Statedata.GetAll().ToList();
SVm.Cities = _Citydata.GetAll().ToList();
SVm.RowCount = _Studentdata.DataCounter;
return View("ListStudents",SVm);
}
[HttpPost]
public ActionResult UpdateStudents(StudentListViewModel oSVm, string Action)
{
if (Request.IsAjaxRequest())
System.Threading.Thread.Sleep(1000); // just simulate delay of one second
StudentListViewModel SVm = new StudentListViewModel();
SVm.SetUpParams(oSVm);
if (Action == "UPDATE")
{
SVm.Students = _Studentdata.SaveXML(new List<Student>(oSVm.Students).ToXml("Students"),
oSVm.page, oSVm.PageSize, oSVm.sort, oSVm.sortdir).ToList();
}
else if (Action == "DELETE")
{
SVm.Students = _Studentdata.Delete(oSVm.Students[0].ID,
oSVm.page, oSVm.PageSize, oSVm.sort, oSVm.sortdir).ToList();
}
SVm.States = _Statedata.GetAll().ToList();
SVm.Cities = _Citydata.GetAll().ToList();
SVm.RowCount = _Studentdata.DataCounter;
return PartialView("_StudentGrid", SVm);
}
[HttpPost]
public ActionResult RefreshStudents(StudentListViewModel oSVm)
{
if (Request.IsAjaxRequest())
System.Threading.Thread.Sleep(1000); // just simulate delay of one second
StudentListViewModel SVm = new StudentListViewModel();
SVm.SetUpParams(oSVm);
SVm.Students = _Studentdata.GetStudents(oSVm.page, oSVm.PageSize, oSVm.sort, oSVm.sortdir).ToList();
SVm.States = _Statedata.GetAll().ToList();
SVm.Cities = _Citydata.GetAll().ToList();
SVm.RowCount = _Studentdata.DataCounter;
return PartialView("_StudentGrid", SVm);
}
[HttpGet]
public JsonResult GetCityName(int StateID)
{
if (Request.IsAjaxRequest())
System.Threading.Thread.Sleep(1000); // just simulate delay of one second
return Json(new {CityList =_Citydata.GetCityByStateId(StateID)} , JsonRequestBehavior.AllowGet);
}
谢谢
一般准则:
如果您对超出内存块的方法进行单元测试(例如,连接到数据库或写入磁盘),您不想对它们进行单元测试。
这是有道理的。例如,如果您正在测试是否可以在您的 people table 中保存另一个 Person 对象,但测试失败了,这是哪里出了问题?我是数据库连接吗?数据库是否已满?也许它因为一些奇怪的原因而停止了?还是您的代码中存在错误? - 问题是,你不能确定,单元测试是为了让你确定哪里出了问题。
所以:策略 1: 停止编写在写入磁盘或数据库的同时执行任何形式的 IF 测试的代码。这些 classes 应该只使用契约,这样你就可以完全模拟数据库层。例如:
public class PeopleManager{
private IDataWriter<Person> _personWriter;
public PeopleManager(IDataWriter<Person> personWriter){
_personWriter = personWriter;
}
public void ImportantMethodToTest(Person person){
if(..important conditionsTested...){
_personWriter.Update(person);
}
}
}
上面的 class 是 unit-testable,因为它不依赖于 IDataWriter 的契约以外的任何东西,你可以在 class 中实现你的写作到数据库,和零逻辑。它只是写。
测试现在超快且超简单:
[TestMethod]
public void ImportantMethod_PersonIsNull_NothingIsWrittenToDatabase(){
// Arrange
var writerMock = new Mock<IDataWriter<Person>>();
var peopleManager = new PeopleManager(writerMock.Object);
// Act
peopleManager.ImportantMethodToTest(null);
// Assert
writerMock.Verify( writer => writer.Update(It.IsAny<Person>), Times.Never());
}
希望你能看到图片。需要搜索以了解更多信息的是 SOLID 原则,我在这里使用的部分是单一责任原则和依赖倒置原则。这些原则使您的代码可以进行单元测试
[TestMethod]
public void List_StudentListViewModelIsValid_ViewModelIsPopulated()
{
// Arrange
var students = Builder<Student>().CreateListOfSize(10).Build();
var repositoryMock = new Mock<StudentRepository>();
repositoryMock.Setup(r => r.GetAll()).Returns(students);
var testInstance = new StudentListViewModel(repositoryMock.Object); // DIP
// Act
testInstance.List()
// ASsert
// Check your values etc
}
这里要注意的重要一点是我对存储库所做的事情。我没有在 List 方法中使用 NEW,而是在 viewModel 构造函数中注入了存储库。你不应该在这样的函数内部使用 "new" 。相反,使用它们通过 class 的构造函数传递这些对象。这就是众所周知的 依赖倒置原则 并且是使您的代码可测试的面向对象编程的 SOLID 原则之一。
因此您必须重写您的视图模型以将存储库作为构造函数参数。这样,当你进行单元测试时,你可以给 viewmodel 一个 mock 作为存储库,而不必使用 new。
同样,您的抽象存储库 class 可以像任何接口一样被模拟。
编辑: Builder 代码来自一个名为 NBuilder 的 nuget 包,每当我只需要来自模拟的一些数据时,我通常会使用它。当我在测试代码中需要它们时,它可以让我构建各种大小的列表:)