openSUSE:WebYaST 测试/模型封装
编写测试时的模型封装
例如,如果您有一个 SoftwareService 资源类,它读取 /etc/zypp/services.d 中的产品文件并将它们作为 RESTful 资源公开,您可以创建一个像这样的索引控制器:
def show
# get all files
Dir.glob("/etc/zypp/services.d/*").each ...
# parse ini file, make a hash object
hash = INI.parse(files)
render hash.to_xml
end
您可以在控制器测试类中通过 stubbing Dir 在调用该目录时返回的内容,以及 File 在调用这些路径时读取的内容来测试该控制器。
Dir.stubs(:glob).with("/etc/zypp/services.d/*).returns(filelist)
# stub File.read to return a hardcoded ini file
...
您看,这变得很复杂了吗?您真的不希望在控制器测试中这样做。因为控制器测试应该专注于工作流程,而不是获取数据。您应该测试控制器如何*响应*某些数据和异常。相反,创建一个 SoftwareService 模型,具有典型的 find、save 等方法,并允许从其属性构造它。
然后在控制器类中,mock
s1 = SoftwareService.new(attrs) s2 = SoftwareService.new(attrs) SoftwareService.stubs(:find_all).returns([s1, s2])
现在您可以测试您的控制器,而无需担心数据是如何实际读取的。您还可以 mock SoftwareService 以在某些值上抛出异常,并查看您的控制器如何响应。
然后您需要为 SoftwareService 类创建一个新的测试。在这个测试中,您可以 mock Dir 类,以便在调用 services.d 路径的 Dir.glob 时返回一个固定的文件名列表。
然后您需要决定是否 mock File 类,以便在用其中一个固定路径构造时返回一些固定的内容。例如,您可以 mock File.new 以返回一个从一些固定的 INI 样式的字符串构造的 StringIO 对象(它也继承 IO,就像一个正常的文件一样),但仅在调用您知道的固定路径时才返回。
Dir.stubs(:glob).with("/etc/zypp/services.d/*").returns([ "/etc/zypp/services.d/service1.service", "/etc/zypp/services.d/service2.service"])
Dir.glob("/etc/zypp/services.d/*")
=> ["/etc/zypp/services.d/service1.service", "/etc/zypp/services.d/service2.service"]
文件不存在,因此任何尝试打开它的操作都将失败。
File.new("/etc/zypp/services.d/service2.service")
Errno::ENOENT: No such file or directory - /etc/zypp/services.d/service2.service
如果知道我们的代码只会执行诸如读取之类的操作,我们可以 stub File.new 以返回基于 String 的 IO 对象。
File.stubs(:new).with("/etc/zypp/services.d/service2.service").returns(StringIO.new("[service]\nattr1=val1\n"))
现在即使文件不存在,您也可以读取该文件,您将处理一个由我们想要测试的固定字符串构造的 StringIO 对象
f = File.new("/etc/zypp/services.d/service2.service")
=> #<StringIO:0x7fa223f10a28>
f.read
=> "[service]\nattr1=val1\n"
但是,然后我们需要为 SoftwareService 读取文件的每个方法 mock File。相反,您可以将读取代码移动到单独的方法 content_for(path),mock 该方法以在测试 SoftwareService 的任何方法时返回一个固定的 INI 字符串,并另外添加一个测试来测试 content_for(path),这将是唯一 mock 文件的测试。您看懂了吗?
- 您有各种需要 mock 复杂内容(如 ComplexClass)才能进行测试的方法
- 您将依赖于 File 的代码移动到单独的方法 read_data(虚构名称),该方法与 ComplexClass 通信
- 您通过 mock read_data 方法以返回一个简单的 String、Array 等来测试所有方法。
- 您为测试用例添加一个额外的测试,以测试新的 read_data 方法,并且只有在这里您才 mock ComplexClass
这样您就可以简化测试用例,而不会降低覆盖率。