我的情况是,我被告知我的工作是为以下代码编写单元测试。就是这样 - 使用所有模拟测试方法。周期。
我担心这是写逆逻辑的练习。此外,如果此代码中存在一个微妙的错误,我将为该错误编写测试,不是吗?
public EntityLogin GetAccount(string email)
{
EntityLogin account = null;
try
{
var accounts = _appointmentRepository.GetEntityLogin(_clientContext.selectedSiteId, email);
if (accounts != null && accounts.Any())
{
account = accounts.OrderByDescending(x => x.dtInsertDate).FirstOrDefault();
}
}
catch (Exception ex)
{
Logger.ErrorFormat("Error in method '{0}': {1}", MethodBase.GetCurrentMethod().Name, ex.GetFullMessage());
throw;
}
return account;
}
这是我提出的测试。
[TestMethod]
public void GetAccountTest()
{
// Arrange
const string email1 = "a@b.com";
const string email2 = "c@d.com";
const string email3 = "e@f.com";
const string email4 = "g@h.com";
var expected1 = new EntityLogin {iEntityLoginId = 1234, dtInsertDate = DateTime.Today.AddDays(-10)};
var expected2 = new EntityLogin {iEntityLoginId = 5678, dtInsertDate = DateTime.Today.AddDays(-5)};
_mockAppointmentRepository
.Setup(c => c.GetEntityLogin(_siteId, email1))
.Returns(new[] {expected1})
.Verifiable();
_mockAppointmentRepository
.Setup(c => c.GetEntityLogin(_siteId, email2))
.Returns((EntityLogin[])null)
.Verifiable();
_mockAppointmentRepository
.Setup(c => c.GetEntityLogin(_siteId, email3))
.Returns(new EntityLogin[] {})
.Verifiable();
_mockAppointmentRepository
.Setup(c => c.GetEntityLogin(_siteId, email4))
.Returns(new[] {expected1, expected2})
.Verifiable();
var target = GetAppointmentService();
// Act
var actual1 = target.GetAccount(email1);
var actual2 = target.GetAccount(email2);
var actual3 = target.GetAccount(email3);
var actual4 = target.GetAccount(email4);
// Assert
Assert.AreEqual(expected1, actual1); // returns single result
Assert.IsNull(actual2); // returns null on null result
Assert.IsNull(actual3); // returns null on empty collection result
Assert.AreEqual(actual4, expected2); // sorts desc by insert date on multiple results
_mockAppointmentRepository.Verify();
}
我的问题是,我在这里做得好吗?
答案 0 :(得分:1)
为什么在一个单元测试中测试几个案例?将它拆分为四个单元测试:
[TestMethod]
public void GetAccount_WhenEmailIsNull_ReturnsNull()
{ .. test that is responsible for testing is email null }
[TestMethod]
public void GetAccount_WhenEmailIsValid_ReturnsAccountsSortedByInsertDate() { .. }
// I'm not sure is "SortedByInsertDate" test should be splitted to another test though
等。当你保持良好的命名(Method_WhatIsProvided_WhatShouldBeDoneInSuchCase) - 或smth。就像你的测试值会增加。单元测试应该测试一件事 - 当你破坏你的代码时 - 你应该知道你已经破坏了什么。除了单元测试之外,我不确定是通过" null"来控制流量。好主意(并且对于应该返回集合的东西检查null永远不是好主意 - 你应该期待空集合。)
答案 1 :(得分:1)
代码的内容在_appointmentRepository
。特别是IQueryable<Account>
返回的GetEntityLogin
(我假设您返回IQueryable,因为您应该使用数据库进行过滤,而不是应用程序)。
如果你没有使用IQueryable
,那么你的SUT
和repo
之间会有一个紧密耦合的设计,他们的行为会捆绑在一起,两者之间没有明显的契约。也就是说它们必须一起使用,它们必须一起改变,所以实际上它们是一个单独的类(或单个可变代码单元)。
在自动化测试中,您可以使用一些测试原型。
我会忽略生产代码,因为它显然是什么,专注于其他代码。
假冒是合同的实施,100%合作,但由于某种原因不适合生产。在这种情况下,我建议使用SQL CE或LocalDB伪造数据库,因为它允许您重用最大量的生产代码。您不能使用生产数据库进行单元测试的原因是它由生产共享(因此会影响生产)。它也不容易被重置(因此每个测试不是孤立的)。 LocalDB / SQL CE不适合生产,因为无法从多个位置/应用程序服务器访问它。这正是它将测试与其他开发人员隔离的原因。它们还可以处理本地文件,这使您可以轻松地隔离测试用例。
Stub是一个不实现合同的类。它们作为一种MacGuffin用于单元测试。它唯一的价值在于它存在并且闪亮。
Mock是一个实现脚本而不是契约的类。你告诉它如何回应每个动作,它就是这样做的。在我看来,模拟测试在所有形式的测试中价值最低。您正在测试行为而不是功能,因此它很脆弱。但是如果你在这里看看你的SUT,那么它的行为很少。你应该知道它应该在_appointmentRepository
中调用一个方法,这似乎是这种方法逻辑的核心所在。
最后,我建议您先创建一个代码DbContext
。创建一个带有连接字符串和IDbInitializer
的构造函数。然后在每个测试开始时,首先传入一个新的随机文件位置以获取代码,以实现新的数据库文件。使用IDbInitializer为数据设定种子。然后在清理时删除随机文件。
答案 2 :(得分:1)
不要像你只是为预先存在的代码编写测试而考虑这种情况,而是考虑到你已经完成了识别SUT职责的任务。如果从这个角度编写测试,你应该能够避免意外编写细微错误的情况。
例如,以下是SUT的职责:
GetAccount
会返回null
GetAccount
会返回该帐户GetAccount
应返回最近创建的帐户每个场景都可以有自己的单元测试来验证预期结果。实际上,您在问题中提供的测试基本上可以分解为这些单独的场景。
旁注:您并不需要Verify
模拟对象的行为 - 通过检查返回值已经隐式发生了此验证。 (例如,如果模拟存储库与预期的参数不匹配,那么SUT如何返回expected1
?)