Unit testing 如何最好地为解析器编写单元测试用例?
我正在编写一个解析器,为每个命令生成32位操作码。例如,对于以下语句:Unit testing 如何最好地为解析器编写单元测试用例?,unit-testing,tdd,testcase,test-suite,Unit Testing,Tdd,Testcase,Test Suite,我正在编写一个解析器,为每个命令生成32位操作码。例如,对于以下语句: set lcl_var = 2 我的解析器生成以下操作码: // load immdshort 2 (loads the value 2) 0x10000010 // strlocal lclvar (lcl_var is converted to an index to identify the var) 0x01000002 请注意,lcl\u var可以是任何变量,也就是说,可以给出任何变量。我如何为这个编写单元测
set lcl_var = 2
我的解析器生成以下操作码:
// load immdshort 2 (loads the value 2)
0x10000010
// strlocal lclvar (lcl_var is converted to an index to identify the var)
0x01000002
请注意,
lcl\u var
可以是任何变量,也就是说,可以给出任何变量。我如何为这个编写单元测试用例?我们能避免硬编码值吗?有没有一种方法可以使其通用化?您没有指定编写解析器所用的语言,因此为了论证起见,我假设您使用的是面向对象的语言
如果是这种情况,那么依赖注入可以帮助您解决这个问题。如果发出的操作码的目标是类的实例(例如,像File),请尝试为发射器类提供一个构造函数,该构造函数使用该类型的对象作为发出代码的目标。然后,通过单元测试,您可以传入一个模拟对象,它是目标类的子类的实例,捕获特定语句发出的操作码,并断言它们是正确的
如果您的目标类不容易扩展,您可能希望基于它创建一个接口,目标类和模拟类都可以实现。据我所知,您应该首先为您的特定示例编写一个测试,即解析器的输入是:
set lcl_var = 2
输出为:
0x10000010 // load immdshort 2
0x01000002 // strlocal lclvar
当您实现生产代码以通过该测试并对其进行重构后,如果您不满意,它可以处理任何局部变量,使用不同的局部变量编写另一个测试,并查看它是否通过。e、 g.具有输入的新测试:
set lcl_var2 = 2
并编写新的测试,以获得所需的不同输出。继续这样做,直到您确信您的产品代码足够健壮为止。这取决于您如何构造解析器。单元测试测试单个单元 因此,如果您想将整个解析器作为一个单元进行测试,可以给它一个命令列表,并验证它是否生成了正确的操作码(在编写测试时手动检查)。您可以为每个命令编写测试,并测试正常使用情况、边缘情况使用情况和边缘情况使用情况。例如,测试: 设置lcl_var=2 结果: 0x1000010 0x01000002 对于0,-1,MAX_INT-1,MAX_INT+1 您知道这些值的正确结果。不同的变量也是如此。如果您的问题是“如何使用不同的输入和期望值运行相同的测试,而不为每个输入-输出组合编写一个xUnit测试?”
int[] opcodes = Parser.GetOpcodes("set lcl_var = 2");
Assert.AreEqual(2, opcodes.Length);
Assert.AreEqual(0x10000010, opcodes[0]);
Assert.AreEqual(0x01000002, opcodes[1]);
那么答案就是使用类似RowTest NUnit的扩展。我最近在我的博客上写了一篇文章。
这方面的一个例子是
[TestFixture]
public class TestExpression
{
[RowTest]
[Row(" 2 + 3 ", "2 3 +")]
[Row(" 2 + (30 + 50 ) ", "2 30 50 + +")]
[Row(" ( (10+20) + 30 ) * 20-8/4 ", "10 20 + 30 + 20 * 8 4 / -")]
[Row("0-12000-(16*4)-20", "0 12000 - 16 4 * - 20 -")]
public void TestConvertInfixToPostfix(string sInfixExpr, string sExpectedPostfixExpr)
{
Expression converter = new Expression();
List<object> postfixExpr = converter.ConvertInfixToPostfix(sInfixExpr);
StringBuilder sb = new StringBuilder();
foreach(object term in postfixExpr)
{
sb.AppendFormat("{0} ", term.ToString());
}
Assert.AreEqual(sExpectedPostfixExpr, sb.ToString().Trim());
}
[TestFixture]
公共类TestExpression
{
[行测试]
[第二行(“2+3”、“2+3+”)]
[第2行(“2+(30+50)”,“23050+”]
[世界其他地区(((10+20)+30)*20-8/4”,“1020+30+20*8/-”)
[世界其他地区(“0-12000-(16*4)-20”,“0-12000-16*4*-20-”)
public void TestConvertInfextoPostFix(字符串sInfixExpr、字符串sExpectedPostfixExpr)
{
表达式转换器=新表达式();
List postfix expr=converter.ConvertInfixToPostfix(sInfixExpr);
StringBuilder sb=新的StringBuilder();
foreach(postfixExpr中的对象术语)
{
sb.AppendFormat(“{0}”,term.ToString());
}
Assert.AreEqual(sExpectedPostfixExpr,sb.ToString().Trim());
}
不清楚您是否正在寻找用于测试的方法或特定技术
就方法论而言,也许你不想做大量的单元测试。也许更好的方法是用你的领域特定语言编写一些程序,然后执行操作码来产生结果。然后测试程序会检查这个结果。这样你可以练习一堆代码,但在e从简单的开始清除明显的bug,然后转移到更难的bug,而不是每次都检查生成的操作码
另一种方法是自动生成领域特定语言的程序以及预期的操作码。这可以非常简单,就像编写一个perl脚本生成一组程序,如:
设置lcl_var=2
设置lcl_var=3
一旦您的语言中有了一套具有正确输出的测试程序,您就可以返回并生成检查每个操作码的单元测试。因为您已经有了操作码,所以检查解析器输出的正确性就成了一件事;检查其代码
虽然我没有使用cppunit,但我使用了一个非常类似于cppunit的内部工具。使用cppunit实现单元测试很容易。您想测试什么?您想知道是否使用了正确的“存储”指令是否被创建?是否选取了正确的变量?下定决心想知道什么,测试就会很明显。只要你不知道你想实现什么,你就不知道如何测试未知 同时,只需写一个简单的测试。明天或晚些时候,你会再次来到这个地方,因为有些东西坏了。那时,你会对你想做的事情有更多的了解,设计一个测试可能会更简单
今天,不要试图成为明天的你。我用C++硬编码编写解析器是最好的,单元测试应该非常明确地告诉你错误在代码库中的位置。如果是泛型错误,则错误可能在“有效代码列表”中,而不是在解析器中。