从JUnit到spec的尝试

本文对比分析了JUnit和Rspec两种测试框架的特点和使用场景。JUnit适用于Java单元测试,强调与开发过程同步进行;Rspec则基于Ruby,采用BDD理念,强调测试先行,推动开发流程。文章详细介绍了Rspec在Rails框架中的应用,包括环境搭建和常用插件。

如今,各种测试框架层出不穷,每一种框架都有其独特性以及各自的优势。本人由于工作的原因,分别先后接触了JUnit以及Respec两套测试框架,虽然研究的不深,但也就这两套框架谈一谈自己的理解。重点主要针对于Rspec框架。

JUnit介绍

众所周知,JUnit是一个用Java编写而成的单元测试框架。利用JUnit,我们可以通过编写简单的测试代码,方便的进行白盒测试,也可以说:在了解了被测代码如何工作的前提下,对其内部结构的正确性进行自动化的测试。在JUnit的官网上,我们可以看到有关JUnit更为正统的释义——JUnit是一个开放源代码的简单框架,用来编写和运行可重复的测试(也被称为回归测试)。它是致力于单元测试框架的XUnit架构的一种实现。其中包含了:

1.用于检测预期结果的Assertions

2.用于共享测试数据的Test Fixtures

3.用于运行测试的Test Runners

在使用JUnit时,其主要投放场景是:a.与开发人员约定好要测的内部方法、或是对外接口(方法名称、参数个数、参数类型等),两边同时开工,开发提测,运行JUnit测试代码。b.根据开发已写好的代码,编写测试代码,校验其正确性。这两种情景都很常见,具体采用哪种,视时间宽松程度而定。

由于JUnit在很多平台工具上的集成(ANT,Maven,Eclipse,IntelliJ IDEA等),以及配有大量的插件(dbUnit,xmlUnit等),以及其易用性,使其仍是当今单元测试框架的首选。

Rspec介绍

前不久,由于需要对Kelude平台建立起一套测试体系,于是便研究起来Respec这一BDD测试框架。说起BDD,我想大家都不陌生,其英文全称为Behaviour Driven Development,即行为驱动开发。Respec就是Ruby社区中最为流行的行为驱动测试框架。关于BDD更详细的介绍,大家可以参考http://qa.taobao.com/?p=11630这篇文章,这里就不再过多介绍。

要想真正用好Respec,就要遵循其背后的测试思想——测试先行。这也是与JUnit等单元测试框架最大的不同之处。所谓测试先行,就是要求我们,在开发人员编码工作之前,先写好测试用例,然后由测试来推动开发工作。通俗解释为:在设计实现一个功能之前,先考虑好如何来测试这个功能,测试的代码完成后,再编写功能实现代码,并且使得该测试用例运行通过,即完成了系统的一个功能模块。这与通常的JUnit使用场景背道而驰。

如果在接触Respec的初期,就要求编写Respec测试用例,和编写功能代码的人员角色分开,我想这对于开发人员来说,应该是非常被动和痛苦的,同时也会很大程度上限制开发人员的创造性。因为功能代码的编写目的由原来的实现功能,变为了如今的通过测试用例,无论如何对于开发人员都是一种挑战。因此,初期尝试Respec阶段,我建议测试用例和功能代码的编写都由一个人来承担。也就是说,自己根据业务需求,先写好Respec测试用例,然后再编写功能代码,而编写功能代码的目的就是为了通过测试。这样做的好处,不仅可以最大程度的理解Resepc背后的思想,同时也会逼迫开发人员为了满足易测性,从而写出简洁高质量的代码。这些优点,只有真正尝试过Respec后,才会深有感触。

Respec在Rails框架中,不仅能针对MVC的Controller层、Model层、View层,进行分层测试,同时也可以针对例如路由等其它层面进行测试,可以说是功能非常全面。

Rspec初体验

最后,针对于Rails应用搭建Respec环境、以及一些常用的插件做一下简短介绍:

1.假设Ruby、Rails环境均已安装完成,安装Rspec插件

gem install rspec

2. 安装测试数据准备工具Factory Girl

gem install factory_girl_rails

3. 安装测试脚本对代码覆盖率的检测工具RCov

gem install rcov

以上三步安装了进行Respec测试最常用的几个工具。

所有测试代码放在spec目录下,并且与功能代码的目录结构相对应。

下面附上一个Rspec代码文件:

如上图:

let 方法主要针对数据准备声明

Factory :XXX 从FactoryGirl 插件中取出预先准备好的数据

describe 对应于一个测试集

it 对应于一个测试用例

每个测试集和测试用例都配有各自的描述信息(测试集名、用例名),这些都是写进测试代码中的,这样做,不仅增加了代码的可读性,同时在执行用例时,一旦用例报错,可以很方便的查看到是对应于哪一部分业务规则的代码出了问题。在测试用例中用通用语言把系统的行为描述出来,把测试代码作为系统的定义文档,将系统的设计和测试用例结合起来,这即是BDD所倡导的。

当想查看测试用例脚本对于功能代码的覆盖情况时,我们可以使用RCov这个工具,通过它,不仅可以通过命令行的形式查看到覆盖率数据,也可以以HTML网页形式查看,非常清晰、具体。

转载于:https://my.oschina.net/haquanwen/blog/57876

D:\JDKandJAVA\bin\java.exe -javaagent:C:\Users\zxcio\AppData\Local\JetBrains\IntelliJIdea2025.2\captureAgent\debugger-agent.jar=file:///C:/Users/zxcio/AppData/Local/Temp/capture17287576565442257411.props -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\IDEA\IntelliJ IDEA 2025.2.4\lib\idea_rt.jar=54595" -Dkotlinx.coroutines.debug.enable.creation.stack.trace=false -Ddebugger.agent.enable.coroutines=true -Dkotlinx.coroutines.debug.enable.flows.stack.trace=true -Dkotlinx.coroutines.debug.enable.mutable.state.flows.stack.trace=true -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath "D:\IDEA\IntelliJ IDEA 2025.2.4\lib\idea_rt.jar;D:\IDEA\IntelliJ IDEA 2025.2.4\plugins\junit\lib\junit6-rt.jar;D:\IDEA\IntelliJ IDEA 2025.2.4\plugins\junit\lib\junit5-rt.jar;D:\IDEA\IntelliJ IDEA 2025.2.4\plugins\junit\lib\junit-rt.jar;C:\Users\zxcio\IdeaProjects\Demo2\target\classes;C:\Users\zxcio\.m2\repository\org\apache\hadoop\hadoop-client\2.10.2\hadoop-client-2.10.2.jar;C:\Users\zxcio\.m2\repository\org\apache\hadoop\hadoop-common\2.10.2\hadoop-common-2.10.2.jar;C:\Users\zxcio\.m2\repository\com\google\guava\guava\11.0.2\guava-11.0.2.jar;C:\Users\zxcio\.m2\repository\commons-cli\commons-cli\1.2\commons-cli-1.2.jar;C:\Users\zxcio\.m2\repository\org\apache\commons\commons-math3\3.1.1\commons-math3-3.1.1.jar;C:\Users\zxcio\.m2\repository\xmlenc\xmlenc\0.52\xmlenc-0.52.jar;C:\Users\zxcio\.m2\repository\org\apache\httpcomponents\httpclient\4.5.13\httpclient-4.5.13.jar;C:\Users\zxcio\.m2\repository\org\apache\httpcomponents\httpcore\4.4.13\httpcore-4.4.13.jar;C:\Users\zxcio\.m2\repository\commons-codec\commons-codec\1.4\commons-codec-1.4.jar;C:\Users\zxcio\.m2\repository\commons-io\commons-io\2.5\commons-io-2.5.jar;C:\Users\zxcio\.m2\repository\commons-net\commons-net\3.1\commons-net-3.1.jar;C:\Users\zxcio\.m2\repository\commons-collections\commons-collections\3.2.2\commons-collections-3.2.2.jar;C:\Users\zxcio\.m2\repository\org\mortbay\jetty\jetty-sslengine\6.1.26\jetty-sslengine-6.1.26.jar;C:\Users\zxcio\.m2\repository\javax\servlet\jsp\jsp-api\2.1\jsp-api-2.1.jar;C:\Users\zxcio\.m2\repository\commons-logging\commons-logging\1.1.3\commons-logging-1.1.3.jar;C:\Users\zxcio\.m2\repository\ch\qos\reload4j\reload4j\1.2.18.3\reload4j-1.2.18.3.jar;C:\Users\zxcio\.m2\repository\commons-lang\commons-lang\2.6\commons-lang-2.6.jar;C:\Users\zxcio\.m2\repository\commons-configuration\commons-configuration\1.6\commons-configuration-1.6.jar;C:\Users\zxcio\.m2\repository\commons-digester\commons-digester\1.8\commons-digester-1.8.jar;C:\Users\zxcio\.m2\repository\commons-beanutils\commons-beanutils\1.9.4\commons-beanutils-1.9.4.jar;C:\Users\zxcio\.m2\repository\org\apache\commons\commons-lang3\3.4\commons-lang3-3.4.jar;C:\Users\zxcio\.m2\repository\org\slf4j\slf4j-reload4j\1.7.36\slf4j-reload4j-1.7.36.jar;C:\Users\zxcio\.m2\repository\org\codehaus\jackson\jackson-core-asl\1.9.13\jackson-core-asl-1.9.13.jar;C:\Users\zxcio\.m2\repository\org\codehaus\jackson\jackson-mapper-asl\1.9.13\jackson-mapper-asl-1.9.13.jar;C:\Users\zxcio\.m2\repository\org\apache\avro\avro\1.7.7\avro-1.7.7.jar;C:\Users\zxcio\.m2\repository\com\thoughtworks\paranamer\paranamer\2.3\paranamer-2.3.jar;C:\Users\zxcio\.m2\repository\org\xerial\snappy\snappy-java\1.0.5\snappy-java-1.0.5.jar;C:\Users\zxcio\.m2\repository\com\google\protobuf\protobuf-java\2.5.0\protobuf-java-2.5.0.jar;C:\Users\zxcio\.m2\repository\com\google\code\gson\gson\2.2.4\gson-2.2.4.jar;C:\Users\zxcio\.m2\repository\org\apache\hadoop\hadoop-auth\2.10.2\hadoop-auth-2.10.2.jar;C:\Users\zxcio\.m2\repository\com\nimbusds\nimbus-jose-jwt\7.9\nimbus-jose-jwt-7.9.jar;C:\Users\zxcio\.m2\repository\com\github\stephenc\jcip\jcip-annotations\1.0-1\jcip-annotations-1.0-1.jar;C:\Users\zxcio\.m2\repository\net\minidev\json-smart\2.3\json-smart-2.3.jar;C:\Users\zxcio\.m2\repository\net\minidev\accessors-smart\1.2\accessors-smart-1.2.jar;C:\Users\zxcio\.m2\repository\org\ow2\asm\asm\5.0.4\asm-5.0.4.jar;C:\Users\zxcio\.m2\repository\org\apache\directory\server\apacheds-kerberos-codec\2.0.0-M15\apacheds-kerberos-codec-2.0.0-M15.jar;C:\Users\zxcio\.m2\repository\org\apache\directory\server\apacheds-i18n\2.0.0-M15\apacheds-i18n-2.0.0-M15.jar;C:\Users\zxcio\.m2\repository\org\apache\directory\api\api-asn1-api\1.0.0-M20\api-asn1-api-1.0.0-M20.jar;C:\Users\zxcio\.m2\repository\org\apache\directory\api\api-util\1.0.0-M20\api-util-1.0.0-M20.jar;C:\Users\zxcio\.m2\repository\org\apache\curator\curator-framework\2.13.0\curator-framework-2.13.0.jar;C:\Users\zxcio\.m2\repository\org\apache\curator\curator-client\2.13.0\curator-client-2.13.0.jar;C:\Users\zxcio\.m2\repository\org\apache\curator\curator-recipes\2.13.0\curator-recipes-2.13.0.jar;C:\Users\zxcio\.m2\repository\com\google\code\findbugs\jsr305\3.0.2\jsr305-3.0.2.jar;C:\Users\zxcio\.m2\repository\org\apache\htrace\htrace-core4\4.1.0-incubating\htrace-core4-4.1.0-incubating.jar;C:\Users\zxcio\.m2\repository\org\apache\zookeeper\zookeeper\3.4.14\zookeeper-3.4.14.jar;C:\Users\zxcio\.m2\repository\com\github\spotbugs\spotbugs-annotations\3.1.9\spotbugs-annotations-3.1.9.jar;C:\Users\zxcio\.m2\repository\org\apache\yetus\audience-annotations\0.5.0\audience-annotations-0.5.0.jar;C:\Users\zxcio\.m2\repository\io\netty\netty\3.10.6.Final\netty-3.10.6.Final.jar;C:\Users\zxcio\.m2\repository\org\apache\commons\commons-compress\1.21\commons-compress-1.21.jar;C:\Users\zxcio\.m2\repository\org\codehaus\woodstox\stax2-api\4.2.1\stax2-api-4.2.1.jar;C:\Users\zxcio\.m2\repository\com\fasterxml\woodstox\woodstox-core\5.3.0\woodstox-core-5.3.0.jar;C:\Users\zxcio\.m2\repository\org\apache\hadoop\hadoop-hdfs-client\2.10.2\hadoop-hdfs-client-2.10.2.jar;C:\Users\zxcio\.m2\repository\com\squareup\okhttp\okhttp\2.7.5\okhttp-2.7.5.jar;C:\Users\zxcio\.m2\repository\com\squareup\okio\okio\1.6.0\okio-1.6.0.jar;C:\Users\zxcio\.m2\repository\org\apache\hadoop\hadoop-mapreduce-client-app\2.10.2\hadoop-mapreduce-client-app-2.10.2.jar;C:\Users\zxcio\.m2\repository\org\apache\hadoop\hadoop-mapreduce-client-common\2.10.2\hadoop-mapreduce-client-common-2.10.2.jar;C:\Users\zxcio\.m2\repository\org\apache\hadoop\hadoop-mapreduce-client-shuffle\2.10.2\hadoop-mapreduce-client-shuffle-2.10.2.jar;C:\Users\zxcio\.m2\repository\org\apache\hadoop\hadoop-yarn-server-common\2.10.2\hadoop-yarn-server-common-2.10.2.jar;C:\Users\zxcio\.m2\repository\org\apache\hadoop\hadoop-yarn-registry\2.10.2\hadoop-yarn-registry-2.10.2.jar;C:\Users\zxcio\.m2\repository\org\apache\geronimo\specs\geronimo-jcache_1.0_spec\1.0-alpha-1\geronimo-jcache_1.0_spec-1.0-alpha-1.jar;C:\Users\zxcio\.m2\repository\org\ehcache\ehcache\3.3.1\ehcache-3.3.1.jar;C:\Users\zxcio\.m2\repository\com\zaxxer\HikariCP-java7\2.4.12\HikariCP-java7-2.4.12.jar;C:\Users\zxcio\.m2\repository\com\microsoft\sqlserver\mssql-jdbc\6.2.1.jre7\mssql-jdbc-6.2.1.jre7.jar;C:\Users\zxcio\.m2\repository\org\fusesource\leveldbjni\leveldbjni-all\1.8\leveldbjni-all-1.8.jar;C:\Users\zxcio\.m2\repository\org\apache\hadoop\hadoop-yarn-api\2.10.2\hadoop-yarn-api-2.10.2.jar;C:\Users\zxcio\.m2\repository\javax\xml\bind\jaxb-api\2.2.2\jaxb-api-2.2.2.jar;C:\Users\zxcio\.m2\repository\javax\xml\stream\stax-api\1.0-2\stax-api-1.0-2.jar;C:\Users\zxcio\.m2\repository\javax\activation\activation\1.1\activation-1.1.jar;C:\Users\zxcio\.m2\repository\org\apache\hadoop\hadoop-mapreduce-client-core\2.10.2\hadoop-mapreduce-client-core-2.10.2.jar;C:\Users\zxcio\.m2\repository\org\apache\hadoop\hadoop-yarn-client\2.10.2\hadoop-yarn-client-2.10.2.jar;C:\Users\zxcio\.m2\repository\org\apache\hadoop\hadoop-yarn-common\2.10.2\hadoop-yarn-common-2.10.2.jar;C:\Users\zxcio\.m2\repository\javax\servlet\servlet-api\2.5\servlet-api-2.5.jar;C:\Users\zxcio\.m2\repository\org\mortbay\jetty\jetty-util\6.1.26\jetty-util-6.1.26.jar;C:\Users\zxcio\.m2\repository\com\sun\jersey\jersey-core\1.9\jersey-core-1.9.jar;C:\Users\zxcio\.m2\repository\com\sun\jersey\jersey-client\1.9\jersey-client-1.9.jar;C:\Users\zxcio\.m2\repository\org\codehaus\jackson\jackson-jaxrs\1.9.13\jackson-jaxrs-1.9.13.jar;C:\Users\zxcio\.m2\repository\org\codehaus\jackson\jackson-xc\1.9.13\jackson-xc-1.9.13.jar;C:\Users\zxcio\.m2\repository\org\apache\hadoop\hadoop-mapreduce-client-jobclient\2.10.2\hadoop-mapreduce-client-jobclient-2.10.2.jar;C:\Users\zxcio\.m2\repository\org\apache\hadoop\hadoop-annotations\2.10.2\hadoop-annotations-2.10.2.jar;C:\Users\zxcio\.m2\repository\junit\junit\4.12\junit-4.12.jar;C:\Users\zxcio\.m2\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;C:\Users\zxcio\.m2\repository\org\slf4j\slf4j-log4j12\1.7.30\slf4j-log4j12-1.7.30.jar;C:\Users\zxcio\.m2\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;C:\Users\zxcio\.m2\repository\log4j\log4j\1.2.17\log4j-1.2.17.jar" com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 org.example.HdfsClient,testmkdir SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/C:/Users/zxcio/.m2/repository/org/slf4j/slf4j-reload4j/1.7.36/slf4j-reload4j-1.7.36.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/C:/Users/zxcio/.m2/repository/org/slf4j/slf4j-log4j12/1.7.30/slf4j-log4j12-1.7.30.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual binding is of type [org.slf4j.impl.Reload4jLoggerFactory] java.lang.UnsupportedOperationException: getSubject is not supported at java.base/javax.security.auth.Subject.getSubject(Subject.java:277) at org.apache.hadoop.security.UserGroupInformation.getCurrentUser(UserGroupInformation.java:700) at org.apache.hadoop.fs.FileSystem$Cache$Key.<init>(FileSystem.java:3445) at org.apache.hadoop.fs.FileSystem$Cache$Key.<init>(FileSystem.java:3435) at org.apache.hadoop.fs.FileSystem$Cache.get(FileSystem.java:3277) at org.apache.hadoop.fs.FileSystem.get(FileSystem.java:475) at org.apache.hadoop.fs.FileSystem$1.run(FileSystem.java:218) at org.apache.hadoop.fs.FileSystem$1.run(FileSystem.java:215) at java.base/jdk.internal.vm.ScopedValueContainer.callWithoutScope(ScopedValueContainer.java:162) at java.base/jdk.internal.vm.ScopedValueContainer.call(ScopedValueContainer.java:147) at java.base/java.lang.ScopedValue$Carrier.call(ScopedValue.java:419) at java.base/javax.security.auth.Subject.callAs(Subject.java:331) at java.base/javax.security.auth.Subject.doAs(Subject.java:440) at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1938) at org.apache.hadoop.fs.FileSystem.get(FileSystem.java:215) at org.example.HdfsClient.testmkdir(HdfsClient.java:17) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) at java.base/java.lang.reflect.Method.invoke(Method.java:565) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58) 还是测试失败怎么办,xml已经像上面一样配置好了
10-31
### 解决SLF4J多个实现依赖问题 可通过查看项目依赖树来确定是否存在多个 SLF4J 实现依赖,使用以下命令: ```bash mvn dependency:tree ``` 若发现存在多个 SLF4J 实现依赖,可在 `pom.xml` 中排除重复依赖。例如,若 `hadoop-client` 传递引入了 `slf4j-reload4j`,可在 `hadoop-client` 依赖中添加排除项: ```xml <dependencies> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>2.10.2</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-reload4j</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.30</version> </dependency> </dependencies> ``` ### 解决 `java.lang.UnsupportedOperationException: getSubject is not supported` 错误 此错误通常表示调用了不支持的操作,一般是因为代码中使用了某个 API 的不支持方法。 - **检查代码**:定位抛出该异常的代码行,查看调用的方法是否确实不支持。例如,在使用某个安全框架时,`getSubject` 方法可能在特定环境或配置下不被支持。 - **查看文档**:查阅相关库的官方文档,确认该方法的使用条件和支持情况。 - **更新库版本**:可能是使用的库版本存在 bug,尝试更新到最新稳定版本。 - **调整配置**:某些情况下,该错误可能是由于配置问题导致的。检查相关配置文件,确保配置正确。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值