与单元测试相比,这些测试可以还有更多,不是根据执行的实际代码或触发的过程,而是根据经过测试的操作。一个这样的假设检验将是:
(演员1)登记支持票;
(演员1)带有初始数据的注释;
(演员2)回复请求更多信息
(演员1)上传更多信息
(演员2)评论问题已解决
(演员1)关闭问题
所有这些操作在测试过程中得到验证,如果失败,则应用程序数据不一致。
是否有更好的方法使端到端测试自包含并具有幂等性? />
#1 楼
最好的选择是进行幂等的测试。我们使用一个称为nasedjango的工具来运行所有处理数据库,缓存等的测试。但是,这仅在使用django时有效。我敢肯定,无论您使用什么工具,都有相同的工具。#2 楼
我个人认为有一些关键原则。测试应该尽可能地假设它们以前已经运行并且失败了。
一些测试将要验证功能。 ,而其他人则只需要在测试其他方法的过程中“通过”一项功能即可。
我可以扩展并使用许多计算机,以便可以并行运行很多测试,而我却没有在运行中受时间限制。
考虑到这些,我将在可能的情况下:
让每个测试自己生成不依赖的唯一数据之前的测试通过了
将执行逻辑与验证代码分开
,因此对于您的示例,我将为您的每个操作创建并执行六个单独的“对象”:注册支持票,添加评论,回复更多信息的请求,上传其他信息并关闭故障单。
然后我将在以下六个测试用例中将它们组合在一起: br />注册r支持票证并验证
注册支持票证,然后添加评论并确认
注册支持票证,然后添加评论,回复并询问更多信息并验证
注册支持票证,然后添加评论,回复请求更多信息,上传更多信息并验证
注册支持票,然后添加评论,回复请求更多信息,上传更多信息已修复问题的验证
/>注册支持票,然后添加评论,回复请求更多信息,上传更多信息,该问题已解决的评论,关闭并验证
当我测试注册6次时,我会建立一个测试数据对象,并且每次都使用不同的测试数据,因此每次注册时我也可以测试不同的场景。
因此,主要的“成本”是执行时间,因为我执行6个长期测试而不是一次(但是使用开源工具意味着我可以轻松扩展)。对象的使用意味着我到处都没有大量重复的代码。这也意味着,如果更改应用程序的票证部分,则我只会在一个地方更改一个对象,并且所有测试都将继续运行。
这是针对单个“对象”的这种方法使用“ Parkcalc”。
[TestMethod]
public void EconomyLessThanOneHour()
{
Parking.CalculateAndVerify(ParkingType.EconomyParking, "10:00", "AM", "today", "10:59", "AM", "today");
}
[TestMethod]
public void EconomyExactlyOneHour()
{
Parking.CalculateAndVerify(ParkingType.EconomyParking, "10:00", "AM", "today", "11:00", "AM", "today");
}
[TestMethod]
public void EconomyMoreThanOneHour()
{
Parking.CalculateAndVerify(ParkingType.EconomyParking, "10:00", "AM", "today", "11:01", "AM", "today");
}
[TestMethod]
public void EconomyJustLessThanOneDay()
{
Parking.CalculateAndVerify(ParkingType.EconomyParking, "10:00", "AM", "today", "9:59", "AM", "today+1");
}
public void EconomyExactlyOneDay()
{
Parking.CalculateAndVerify(ParkingType.EconomyParking, "10:00", "AM", "today", "10:00", "AM", "today+1");
}
[TestMethod]
public void EconomyJustMoreThanOneDay()
{
Parking.CalculateAndVerify(ParkingType.EconomyParking, "10:00", "AM", "today", "10:01", "AM", "today+1");
}
[TestMethod]
public void EconomyJustLessThanOneWeek()
{
Parking.CalculateAndVerify(ParkingType.EconomyParking, "10:00", "AM", "today", "9:59", "AM", "today+7");
}
CalculateAndVerify的实现...
namespace Parkcalc.Logical
{
public class Parking
{
public static void OpenHome()
{
Physical.NavigateTo.Homepage();
}
public static void Calculate(ParkingType parkingType, string inTime, string inAMPM, string inDate, string outTime, string outAMPM, string outDate)
{
inDate = Utility.CalculateDate(inDate);
outDate = Utility.CalculateDate(outDate);
Physical.Parking.Calculate(parkingType, inTime, inAMPM, inDate, outTime, outAMPM, outDate);
}
public static void CalculateAndVerify(ParkingType parkingType, string inTime, string inAMPM, string inDate, string outTime, string outAMPM, string outDate)
{
Calculate(parkingType, inTime, inAMPM, inDate, outTime, outAMPM, outDate);
Verification.Verify.VerifyResult(parkingType, inTime, inAMPM, inDate, outTime, outAMPM, outDate);
}
}
}
,下一层向下
namespace Parkcalc.Physical
{
public static class Parking
{
public static void Calculate(ParkingType parkingType, string inTime, string inAMPM, string inDate, string outTime, string outAMPM, string outDate)
{
NavigateTo.Homepage();
Browser.SetValue(Controls.ParkingCalculator.ddlLot,EnumValues.GetParkingType(parkingType));
if (!string.IsNullOrEmpty(inTime))
{
Browser.SetValue(Controls.ParkingCalculator.txtEntryTime, inTime);
}
if (!string.IsNullOrEmpty(inAMPM))
{
Browser.SetValue(Controls.ParkingCalculator.rdoEntryTimeAMPM, inAMPM);
}
if (!string.IsNullOrEmpty(inDate))
{
Browser.SetValue(Controls.ParkingCalculator.txtEntryDate, inDate);
}
if (!string.IsNullOrEmpty(outTime))
{
Browser.SetValue(Controls.ParkingCalculator.txtExitTime, outTime);
}
if (!string.IsNullOrEmpty(outAMPM))
{
Browser.SetValue(Controls.ParkingCalculator.rdoExitTimeAMPM, outAMPM);
}
if (!string.IsNullOrEmpty(outDate))
{
Browser.SetValue(Controls.ParkingCalculator.txtExitDate, outDate);
}
Browser.Invoke(Controls.ParkingCalculator.btnCalculate);
}
public static string GetResult()
{
return Browser.GetValue(Controls.ParkingCalculator.txtResult);
}
}
}
我这里有使用WatiN的完整的C#示例... http://testingstax.codeplex.com/SourceControl/changeset/view/7938#
对于测试数据,我通常会构建一个数据生成器并添加唯一的随机字符串,以免发生数据冲突。
#3 楼
我看到过这样编写测试,但是如果您的测试套件始终依赖于每个测试的清理工作,那么您的套件将变得脆弱;一个测试中的错误可能会导致后续测试中误报的传播。有时您可以通过划分环境来使测试独立。例如,如果要测试支持多个用户的应用程序,则可能要以不同的用户身份运行每个测试(或每个测试组)。
#4 楼
我一直秉承这样的理念,即在全新安装(带有新的(空/默认填充)数据库)上运行测试时,这些测试将为您提供最佳信息。我什至看到过有关以编程方式从已保存的映像生成新VM的讨论,以确保两次测试运行之间的交互作用最少。考虑一下经典的错误:“如果我添加用户并将其删除,如果我添加一个用户并将其删除1000次,它将失败。”我们都有这样的错误。当我运行测试时,不止一次,我希望得到完全相同的结果。由于运行程序的性质可能并不总是精确的,所以速度执行可能会有一些细微的差别,但是它们越接近精确越好。
这意味着如果失败,我会得到该对象的调试输出失败,并且在一次运行中具有
id = 14
,最好在另一次运行中具有id = 14
。除非决定更改程序,否则任何决定将其设置为14的逻辑都应再次将其设置为14。如果第二次试验结果是28次,我怎么知道第一次试验并不会影响第二次试验?我们生活在每次产品都必须完美运行的边缘。通常,我们在法律/合同上有义务这样做。这意味着我们的测试必须为我们提供尽可能多的信息。
这并不是说再次运行测试可能没有好处。从本质上讲,是的,它应该运行完全相同(显然,它可能具有不同的通用ID,并且存在其他一些细微差别),并且如果您有多余的CPU周期,则可能值得再次这样做。但是我也感觉到,第二个测试所涵盖的任何东西通常都应该是一个测试用例。
; dr我不仅希望重置数据库,还可以在上面安装一个新的应用程序。如果可能,请使用新机器。脚本可以轻松实现自动化。
#5 楼
尽管不能保证测试是100%等幂的,但在执行后进行清理是最好的方法,例如,可能会更改缓存,更改寄存器值等。#6 楼
在类似的情况下,我们设计并实现了E2E测试,该测试创建并使用自己的数据。即使测试很长,但它们绝对是独立的,没有任何副作用。
在最坏的情况下,测试可能由于任何原因而失败不会以任何方式影响套件中的任何其他单个测试。这确保了我们在两次测试之间没有任何级联效应。
评论
您需要这样做还是可以在最后重置数据库?我可以想象在测试结束时要比较数据库并将其重置为从已知状态开始之前的情况。我曾考虑过进行类似的测试,但这并不总是可行的。我不需要或不想这样做,但是我目前没有重置数据库的方法。基本上,这也意味着重新启动应用程序,因为缓存和会话也需要无效,并且很快就会变得混乱。
我看到的唯一问题是,如果由于某种原因测试失败,则可能会留下不一致的数据,无法轻松清除。但是,如果您可以这样做,那么我会尽力而为,或者确保您拥有做到这一点的工具。