测试和调试
与经典编程一样,能够检查量子程序如预期的那样工作并能够诊断不正确的量子程序是至关重要的。 在本节中,我们将介绍Q#提供的用于测试和调试量子程序的工具。
单元测试
测试传统程序的一种常见方法是编写称为单元测试的小程序,它在库中运行代码并将其输出与某些预期输出进行比较。 例如,我们可能想要确保Square(2)
返回4
,因为我们知道22=4
的先验 。
Q#支持为量子程序创建单元测试,并且可以在xUnit单元测试框架内执行测试。
创建一个测试项目
打开Visual Studio 2017.转到File
菜单并选择New
> Project...
在项目模板资源管理Installed
> Visual C#
,选择Q# Test Project
模板。
无论哪种情况,您的新项目都会打开两个文件。 第一个文件Tests.qs
提供了一个方便的地方来定义新的Q#单元测试。 最初,该文件包含一个样本单元测试AllocateQubitTest
,它检查新分配的量子位处于 ket0
状态并打印一条消息:
operation AllocateQubitTest () : ()
{
body
{
using (qs = Qubit[1]) {
Assert([PauliZ], [qs[0]], Zero, "Newly allocated qubit must be in |0> state");
}
Message("Test passed");
}
}
任何带有签名() => ()
或带有签名() -> ()
函数的Q#操作都可以作为单元测试执行。
第二个文件TestSuiteRunner.cs
包含一个发现并运行Q#单元测试的方法。 这是用OperationDriver
属性注释的方法TestTarget
。 OperationDriver
属性是Xunit扩展库Microsoft.Quantum.Simulation.Xunit的一部分。 单元测试框架为它发现的每个Q#单元测试调用TestTarget
方法。 框架通过op
参数将单元测试描述传递给方法。 以下代码行:
op.TestOperationRunner(sim);
在QuantumSimulator
上执行单元测试。
默认情况下,单元测试发现机制会查找所有具有签名() => ()
或() -> ()
Q#函数或操作,以满足以下属性:
- 位于与用
OperationDriver
属性注释的方法相同的程序集中。 - 与用
OperationDriver
属性注释的方法位于相同的名称空间中。 - 有一个以
Test
结尾的名字。
可以使用OperationDriver
属性的可选参数来设置程序集,命名空间和单元测试函数和操作的后缀:
-
AssemblyName
参数设置正在搜索测试的程序集的名称。 -
TestNamespace
参数设置正在搜索测试的名称空间的名称。 -
Suffix
设置被认为是单元测试的操作或函数名称的后缀。
另外, TestCasePrefix
可选参数允许您为测试用例的名称设置前缀。 操作名称前面的前缀将出现在测试用例列表中。 例如, TestCasePrefix = "QSim:"
将导致AllocateQubitTest
在QSim:AllocateQubitTest
中显示在找到的测试列表中。 例如,这可以用于指示使用哪个模拟器来运行测试。
运行Q#单元测试
作为一次性的每个解决方案设置,请转到Test
菜单并选择Test Settings
> Default Processor Architecture
> X64
。
Tip
Visual Studio的默认处理器体系结构设置存储在每个解决方案的解决方案选项( .suo
)文件中。 如果删除此文件,则需要再次选择X64
作为处理器架构。
生成项目,进入Test
菜单并选择Windows
> Test Explorer
。 AllocateQubitTest
将显示在Not Run Tests
组中的测试列表中。 选择Run All
或运行这个单独的测试,它应该通过!
记录和断言
Q#中的函数没有副作用这一事实的一个重要结果是,执行输出类型为空元组()
的函数的任何影响都无法从Q#程序中观察到。 也就是说,目标机器可以选择不执行任何返回()
函数,并保证这种省略不会修改任何后续Q#代码的行为。 这使函数返回()
一个有用的工具,用于将断言和调试逻辑嵌入到Q#程序中。
记录
原始函数Message的类型为String -> ()
并允许创建诊断消息。
QuantumSimulator
的onLog
操作可用于定义Q#代码调用Message
时执行的操作。 默认情况下,记录的消息被打印到标准输出。
定义一个单元测试套件时,记录的消息可以被定向到测试输出。 当从Q#Test Project模板创建项目时,此重定向是为套件预先配置的,默认情况下创建如下:
using (var sim = new QuantumSimulator())
{
// OnLog defines action(s) performed when Q# test calls operation Message
sim.OnLog += (msg) => { output.WriteLine(msg); };
op.TestOperationRunner(sim);
}
在测试资源管理器中执行测试并单击测试后,将出现一个面板,其中包含有关测试执行的信息:通过/失败状态,已用时间和“输出”链接。 如果点击“输出”链接,测试输出将在新窗口中打开。
断言
相同的逻辑可以应用于实现断言。 我们来看一个简单的例子:
function AssertPositive(value : Double) : () {
if (value <= 0) {
fail "Expected a positive number.";
}
}
在这里,关键字fail
表示计算不应该继续,引发运行Q#程序的目标机器中的异常。 根据定义,在Q#中不能观察到这种类型的失败,因为在达到fail
语句后没有进一步运行Q#代码。 因此,如果我们继续调用AssertPositive
,我们可以确信它的输入是正面的。
基于这些想法, 前奏提供了两个特别有用的断言, Assert和AssertProb都被建模为()
操作。 这些断言分别采用一个泡利算子描述一个特定的兴趣度量,一个要进行度量的量子寄存器和一个假设的结果。 在通过仿真工作的目标机器上,我们不受无克隆定理的约束,并且可以在不干扰传递给这些断言的寄存器的情况下执行这样的测量。 然后,仿真器可以类似于上面的AssertPositive
函数,如果在实践中不会观察到假设结果,则中止计算:
using (register = Qubit[1]) {
H(register[0]);
Assert([PauliX], register, Zero);
// Even though we do not have access to states in Q#,
// we know by the anthropic principle that the state
// of register at this point is |+〉.
}
在物理量子硬件上,无克隆定理阻止量子态的检查, Assert
和AssertProb
操作只返回()
而没有其他影响。
Microsoft.Quantum.Canon命名空间提供了Assert
系列的更多功能,可以让我们检查更高级的条件。 它们在Q#标准库中详细介绍:测试和调试部分。
调试
Q#支持标准Visual Studio调试功能的一个子集: 设置行断点 , 使用F10逐步执行代码以及在模拟器上执行代码时检查经典变量的值 。 在Visual Studio代码中调试尚不支持。