由于种种原因,我以前从未写过单元测试。我现在有机会轻松地编写测试,因为我有一个小应用程序可以从头开始。
然而,我有点困惑。该应用程序应该使用带有智能卡读卡器的打印机来编程智能卡上的数据。所以这里是一系列动作:创建设备上下文,设置打印机模式,初始化文档,将卡片送入打印机,用读卡器连接到卡片,写入卡片,移出卡片,结束文件,处理设备背景。
好的,单元测试应该为每个测试测试一个函数,并且每个测试应该独立于其他测试的结果运行。但是让我们看看 - 如果我没有在打印机中正确放置它并且我没有连接它,我就无法测试写入智能卡。我不能用软件来嘲笑这个 - 我只能测试如果真正的卡正确定位并连接到实际写入的话。如果连接到卡将失败,则无法测试写入卡 - 因此测试独立性原则被破坏。
到目前为止,我想出了一个像这样的测试(还有其他测试'适当'并测试其他东西)
[Test]
public void _WriteToSmartCard()
{
//start print job
printer = new DataCardPrinter();
reader = new SCMSmartCardReader();
di = DataCardPrinter.InitializeDI();
printer.CreateHDC();
Assert.AreNotEqual(printer.Hdc, 0, "Creating HDC Failed");
Assert.Greater(di.cbSize, 0);
int res = ICE_API.SetInteractiveMode(printer.Hdc, true);
Assert.Greater(res, 0, "Interactive Mode Failed");
res = ICE_API.StartDoc(printer.Hdc, ref di);
Assert.Greater(res, 0, "Start Document Failed");
res = ICE_API.StartPage(printer.Hdc);
Assert.Greater(res, 0, "Start Page Failed");
res = ICE_API.RotateCardSide(printer.Hdc, 1);
Assert.Greater(res, 0, "RotateCardSide Failed");
res = ICE_API.FeedCard(printer.Hdc, ICE_API.ICE_SMARTCARD_FRONT + ICE_API.ICE_GRAPHICS_FRONT);
Assert.Greater(res, 0, "FeedCard Failed");
bool bRes = reader.EstablishContext();
Assert.True(bRes, "EstablishContext Failed");
bRes = reader.ConnectToCard();
Assert.True(bRes, "Connect Failed");
bRes = reader.WriteToCard("123456");
Assert.True(bRes, "Write To Card Failed");
string read = reader.ReadFromCard();
Assert.AreEqual("123456", read, "Read From Card Failed");
bRes = reader.DisconnectFromCard();
Assert.True(bRes, "Disconnect Failde");
res = ICE_API.SmartCardContinue(printer.Hdc, ICE_API.ICE_SMART_CARD_GOOD);
Assert.Greater(res, 0, "SmartCardContinue Failed");
res = ICE_API.EndPage(printer.Hdc);
Assert.Greater(res, 0, "End Page Failed");
res = ICE_API.EndDoc(printer.Hdc);
Assert.Greater(res, 0, "End Document Failed");
}
测试工作正常,但原则被打破 - 它测试了多个功能,以及很多功能。并且每个后续功能取决于前一个的结果。现在,我们来问一个问题:在这种情况下我应该如何处理单元测试?
答案 0 :(得分:4)
这看起来不像是单元测试。单元测试应该是快速且自信的,即,您不需要检查硬件中是否实际发生了操作。我会将此代码归类为“测试自动化”,因为您需要执行此任务并确保发生了某些事情。
代码也是程序性的,看起来很难测试。在同一测试方法中使用多个断言表明它应该被划分。
我对单元测试的首选参考是Misko Hevery's site。希望它有所帮助!
答案 1 :(得分:4)
您的测试代码通常被称为集成测试。简而言之,集成测试通常被定义为检查系统组件之间集成的测试。然而,正如David Reis所提到的,单元测试通常会测试单个方法。
这两类测试都很有用。像你这样的集成测试,从头到尾运行系统,确保一切都很好地协同工作。但它们很慢并且通常具有外部依赖性(如读卡器)。单元测试更小,更快,高度集中,但如果您只有单元测试,很难看到森林的树木。
将您的单元测试放在与集成测试不同的目录中。使用持续集成。每天只运行几次集成测试,因为它们速度较慢,需要更多的设置/部署。一直运行单元测试。
现在,您如何对方法依赖于其他方法的特定情况进行单元测试? 目前还不清楚您控制的代码数量与库中的代码量有多少,但在您的代码中,请尽可能多地学习使用依赖注入(DI)。
假设您的reader方法看起来像这样(在伪代码中)
boolean WriteToCard(String data){
// do something to data here
return ICE_API.WriteToCard(ICE_API.SOME_FLAG, data)
}
嗯,你应该能够将其改为:
ICE_API api = null
ICE_API setApi(ICE_API api) {
this.api = api
}
ICE_API getApi() {
if (api == null) {
api = new ICE_API()
}
}
boolean WriteToCard(String data){
// do something to data here
return getApi().WriteToCard(ICE_API.SOME_FLAG, data)
}
然后在设置中进行WriteToCard测试
void setup()
_mockAPI = new Mock(ICE_API)
reader.setApi(_mockAPI)
void testWriteToCard()
reader.writeToCard("12345")
// assert _mockAPI.writeToCard was called with expected data and flags.
答案 2 :(得分:3)
对于一系列依赖于彼此的测试,本身没有任何错误,除了如果多个事情被破坏你将无法获得完整的失败列表,因为第一次测试是失败将是报告的那个。
您可以解决此问题的一种方法是创建一个测试初始化例程(使用[SetUp]
类中的[TestFixture]
属性),在执行单个测试之前将系统置于已知状态。
另请注意,此方案并不完全适合单元测试,因为它需要潜在的手动步骤以外的软件。单元测试本质上更适合于测试不与任何不可重现的东西交互的软件模块。您可能希望在阅读器API摘要上进行操作(通过为您需要的操作创建接口,以及将这些调用传递给实际API的类),然后您可以使用模拟对象假装为读者,这样你就可以测试你的类的主要逻辑,而不必依赖硬件。
然后,您可以将实际真实API的测试实现为单元测试,或者实现需要进行最少人工交互的其他事情...基本上您将在测试过程中封装人类;)