Junit单元测试(二)

JUnit是一个开源的Java单元测试框架。


1)     为什么要使用JUnit

以前,开发人员写一个方法,如下代码所示:

Java代码:AddAndSub.java

package test.juit;

public class AddAndSub {

    public static int add(int m, int n) {

       int num = m + n;

       return num;

    }

 

    public static int sub(int m, int n) {

       int num = m - n;

       return num;

    }

}

如果要对AddAndSub类的add和sub方法进行测试,通常要在main里编写相应的测试方法,如下代码所示:

Java代码:MathComputer.java

package test.juit;

public class AddAndSub {

    public static int add(int m, int n) {

       int num = m + n;

       return num;

    }

 

    public static int sub(int m, int n) {

       int num = m - n;

       return num;

    }

 

    public static void main(String args[]) {

        if (add (4, 6) == 10){

            System.out.println("Test Ok");

        } else {

            System.out.println("Test Fail");

        }

        if (sub (6, 4) ==2){

            System.out.println("Test Ok");

        } else {

            System.out.println("Test Fail");

        }

    }

}

从上面的测试可以看出,业务代码和测试代码放在一起,对于复杂的业务逻辑,一方面代码量会非常庞大,另一方面测试代码会显得比较凌乱,而JUnit就能改变这样的状况,它提供了更好的方法来进行单元测试。使用JUnit来测试前面代码的示例如下:

Java代码:TestAddAndSub.java

package test.juit;

 

import junit.framework.TestCase;

 

public class TestAddAndSub extends TestCase {

    public void testadd() {

       // 断言计算结果与10是否相等

       assertEquals(10, AddAndSub.add(4, 6));

    }

 

    public void testsub() {

       // 断言计算结果与2是否相等

       assertEquals(2, AddAndSub.sub(6, 4));

    }

 

    public static void main(String args[]) {

       junit.textui.TestRunner.run(TestAddAndSub.class);

    }

 

}

这里先不对JUnit的使用方法进行讲解,从上可以看到,测试代码和业务代码分离开,使得代码比较清晰,如果将JUnit放在Eclipse中,测试起来将会更加方便。


2)建立junit的开发环境
a)     下载JUnit

从http://www.junit.org/可以进入到JUnit的官方网站的首页。

当前JUit的最新稳定版本是4.8.2版本,另外发布了4.9的二个测试版本,本文以4.8.2版本为例进行讲述。

下载地址为:https://github.com/downloads/KentBeck/junit/junit-4.8.2.jar,点击即可进入下载JUnit的画面。下载后解压缩即可。


b)     配置JUnit

下载Junit4.8.2.jar完毕,并解压缩到D盘根目录下后,即可开始配置环境变量。用前面介绍的设定系统变量的方法,增加设定一个ClassPath。

如果是在项目中,直接将这个JAR包添加到项目的WEB-INF/lib目录下即可。

添加完后,查看是否配置好JUnit,在类里添加如下语句:

import junit.framework.TestCase;

如果编译没有错误,则说明配置成功。


3)Junit的使用方法

JUnit的使用非常简单,共有3步:

第一步、编写测试类,使其继承TestCase;

第二步、编写测试方法,使用test+×××的方式来命名测试方法;

第三步、编写断言。如果测试方法有公用的变量等需要初始化和销毁,则可以使用setUp,tearDown方法。


a)     继承TestCase

如果要使用JUnit,则测试类都必须继承TestCase。当然目前的最新版JUnit是不需要继承它的,但并不是说TestCase类就没有用了,它仍然是JUnit工作的基础。这里先讲述继承TestCase类的方式,稍后再介绍不继承的方式。

下面是前面使用JUnit进行测试AddAndSub类的代码,这里进行详细的分析:

Java代码:TestAddAndSub.java

package test.juit;

import junit.framework.TestCase;

public class TestAddAndSub extends TestCase {

    public void testadd() {

       // 断言计算结果与10是否相等

       assertEquals(10, AddAndSub.add(4, 6));

    }

    public void testsub() {

       // 断言计算结果与2是否相等

       assertEquals(2, AddAndSub.sub(6, 4));

    }

    public static void main(String args[]) {

       junit.textui.TestRunner.run(TestAddAndSub.class);

    }

}

代码说明:

— 这里继承TestCase,表示该类是一个测试类。

— 然后使用junit.textui.TestRunner.run方法来执行这个测试类。

这里给出TestCase的源代码:

Java代码:TestCase.java

package junit.framework;

 

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

import java.lang.reflect.Modifier;

 

public abstract class TestCase extends Assert implements Test {

 

    /** 测试案例的名称 */

    private String fName;

 

    /**

     * 构造函数

     */

    public TestCase() {

       fName = null;

    }

 

    /**

     * 带参数的构造函数

     */

    public TestCase(String name) {

       fName = name;

    }

 

    /**

     * 获取被run执行的测试案例的数量

     */

    public int countTestCases() {

       return 1;

    }

 

    /**

     * 创建一个TestResult

     *

     * @see TestResult

     */

    protected TestResult createResult() {

       return new TestResult();

    }

 

    /**

     * 执行run方法,返回TestResult

     *

     * @see TestResult

     */

    public TestResult run() {

       TestResult result = createResult();

       // 下面一段代码描述了JUnit如何实现在执行具体的测试方法前,先执行初始化方法,在执行完具体的测试方法后,再执行销毁方法.

       run(result);

       return result;

 

    }

 

    /**

     * 执行run方法,参数为TestResult

     */

    public void run(TestResult result) {

       result.run(this);

    }

 

    /**

     * 执行测试方法,包括初始化和销毁方法

     *

     * @throws Throwable

     *             if any exception is thrown

     */

    public void runBare() throws Throwable {

       Throwable exception = null;

       setUp();

       try {

           runTest();

       } catch (Throwable running) {

           exception = running;

       }finally {

           try {

              tearDown();

           } catch (Throwable tearingDown) {

              if (exception == null)

                  exception = tearingDown;

           }

       }

       if (exception != null)

           throw exception;

    }

 

    /**

     * 执行测试方法

     *

     * @throws Throwable

     *             if any exception is thrown

     */

    protected void runTest() throws Throwable {

            assertNotNull("TestCase.fName cannot be null", fName); // Some VMs crash when calling getMethod(null,null);

            Method runMethod= null;

            try {

                //利用反射机制

                runMethod= getClass().getMethod(fName, (Class[])null);

            } catch (NoSuchMethodException e) {

                fail("Method \""+fName+"\" not found");

            }

            if (!Modifier.isPublic(runMethod.getModifiers())) {

                fail("Method \""+fName+"\" should be public");

            }

             //利用反射机制

            try {

                runMethod.invoke(this);

            }

            catch (InvocationTargetException e) {

                e.fillInStackTrace();

                throw e.getTargetException();

                //下面一段代码定义了要想实现初始化和销毁方法,需继承这两个方法.       

            }

            catch (IllegalAccessException e) {

                e.fillInStackTrace();

                throw e;

            }

 

        }

   

    /**

     * 测试前的初始化方法

     */

    protected void setUp() throws Exception {

    }

 

    /**

     * 测试后的销毁方法

     */

    protected void tearDown() throws Exception {

    }

 

    /**

     * 返回测试案例的名称

     *

     * @return the name of the TestCase

     */

    public String getName() {

       return fName;

    }

 

    /**

     * 设定测试案例的名称

     *

     * @param name

     *            the name to set

     */

    public void setName(String name) {

       fName = name;

    }

}

代码说明:

— 该类继承了Assert 类,实现了Test接口。

— 可以看出,TestCase类正是通过runBare实现了在测试方法前初始化相关变量和环境,在测试方法后销毁相关变量和环境


b)     编写测试方法

测试方法名要以test+方法名来命名,当然最新版的JUnit支持直接以方法名来命名测试方法。这是通过TestCase类里的runTest方法来实现的,主要利用了Java的反射机制,runTest方法的代码如下:

    protected void runTest() throws Throwable {

       assertNotNull("TestCase.fName cannot be null", fName); // Some VMs crash

                                                        // when calling

                                                        // getMethod(null,null);

       Method runMethod = null;

       try {

           // 获取要测试的方法

           runMethod = getClass().getMethod(fName, (Class[]) null);

       } catch (NoSuchMethodException e) {

           fail("Method \"" + fName + "\" not found");

       }

       // 判断要测试的方法是否为公用方法

       if (!Modifier.isPublic(runMethod.getModifiers())) {

           fail("Method \"" + fName + "\" should be public");

       }

       // Java的反射机制

       try {

           runMethod.invoke(this);

       }

       // 抛出调用异常

       catch (InvocationTargetException e) {

           e.fillInStackTrace();

           throw e.getTargetException();

       } catch (IllegalAccessException e) {

           e.fillInStackTrace();

           throw e;

       }

    }


c)      编写断言

JUnit主要有以下断言:

— assertEquals(期望值,实际值),检查两个值是否相等。

— assertEquals(期望对象,实际对象),检查两个对象是否相等,利用对象的equals()方法进行判断。

— assertSame(期望对象,实际对象),检查具有相同内存地址的两个对象是否相等,利用内存地址进行判断,注意和上面assertEquals方法的区别。

— assertNotSame(期望对象,实际对象),检查两个对象是否不相等。

— assertNull(对象1,对象2),检查一个对象是否为空。

— assertNotNull(对象1,对象2),检查一个对象是否不为空。

— assertTrue(布尔条件),检查布尔条件是否为真。

— assertFalse(布尔条件),检查布尔条件是否为假。

这些断言主要定义在JUnit的Assert类里,Assert类的示例代码如下:

Java代码:Assert.java

package junit.framework;

 

public class Assert {

    /**

     * 构造函数

     */

    protected Assert() {

 

    }

    /**

     * 断言是否为真,带消息

     */

    static public void assertTrue(String message, boolean condition) {

       if (!condition)

           fail(message);

    }

 

    /**

     * 断言是否为真

     */

    static public void assertTrue(boolean condition) {

       assertTrue(null, condition);

    }

 

    /**

     * 断言是否为假,带消息

     */

    static public void assertFalse(String message, boolean condition) {

       assertTrue(message, !condition);

    }

 

    /**

     * 断言是否为假

     */

    static public void assertFalse(boolean condition) {

       assertFalse(null, condition);

    }

 

    // 下面一段代码描述了如何在JUnit中实现判断是否相等的方法,这些方法要实现的内容相同,只是参数不同,从而实现了可以针对不同类型的数据来判断是否相等的功能.

    /**

     * 断言是否为失败

     */

    static public void fail(String message) {

       throw new AssertionFailedError(message);

    }

 

    /**

     * 断言是否为失败

     */

    static public void fail() {

       fail(null);

    }

 

    /**

     * 是否相等的断言,带消息Object

     */

    static public void assertEquals(String message, Object expected,

           Object actual) {

       if (expected == null && actual == null)

           return;

       if (expected != null && expected.equals(actual))

           return;

       failNotEquals(message, expected, actual);

    }

 

    /**

     * 是否相等的断言,Object

     */

    static public void assertEquals(Object expected, Object actual) {

       assertEquals(null, expected, actual);

    }

 

    /**

     * 是否相等的断言,带消息String

     */

    static public void assertEquals(String message, String expected,

           String actual) {

 

       if (expected == null && actual == null)

 

           return;

 

       if (expected != null && expected.equals(actual))

 

           return;

 

       throw new ComparisonFailure(message, expected, actual);

 

    }

 

    /**

     * 是否相等的断言,String

     */

 

    static public void assertEquals(String expected, String actual) {

 

       assertEquals(null, expected, actual);

 

    }

 

    /**

     * 是否相等的断言,带消息double

     */

 

    static public void assertEquals(String message, double expected,

           double actual, double delta) {

 

       if (Double.compare(expected, actual) == 0)

 

           return;

 

       if (!(Math.abs(expected - actual) <= delta))

 

           failNotEquals(message, new Double(expected), new Double(actual));

 

    }

 

    /**

     * 是否相等的断言,double

     */

 

    static public void assertEquals(double expected, double actual, double delta) {

 

       assertEquals(null, expected, actual, delta);

 

    }

 

    /**

     * 是否相等的断言,带消息float

     */

 

    static public void assertEquals(String message, float expected,

           float actual, float delta) {

       if (Float.compare(expected, actual) == 0)

           return;

       if (!(Math.abs(expected - actual) <= delta))

           failNotEquals(message, new Float(expected), new Float(actual));

    }

 

    /**

     * 是否相等的断言, float

     */

    static public void assertEquals(float expected, float actual, float delta) {

       assertEquals(null, expected, actual, delta);

    }

 

    /**

     * 是否相等的断言,带消息long

     */

    static public void assertEquals(String message, long expected, long actual) {

       assertEquals(message, new Long(expected), new Long(actual));

    }

 

    /**

     * 是否相等的断言, long

     */

    static public void assertEquals(long expected, long actual) {

       assertEquals(null, expected, actual);

    }

 

    /**

     * 是否相等的断言,带消息boolean

     */

    static public void assertEquals(String message, boolean expected,

           boolean actual) {

       assertEquals(message, Boolean.valueOf(expected), Boolean

              .valueOf(actual));

    }

 

    /**

     * 是否相等的断言,boolean

     */

    static public void assertEquals(boolean expected, boolean actual) {

       assertEquals(null, expected, actual);

    }

 

    /**

     * 是否相等的断言,带消息byte

     */

    static public void assertEquals(String message, byte expected, byte actual) {

       assertEquals(message, new Byte(expected), new Byte(actual));

    }

 

    /**

     * 是否相等的断言, byte

     */

    static public void assertEquals(byte expected, byte actual) {

       assertEquals(null, expected, actual);

    }

 

    /**

     * 是否相等的断言,带消息char

     */

    static public void assertEquals(String message, char expected, char actual) {

       assertEquals(message, new Character(expected), new Character(actual));

    }

 

    /**

     * 是否相等的断言,char

     */

    static public void assertEquals(char expected, char actual) {

       assertEquals(null, expected, actual);

    }

 

    /**

     * 是否相等的断言,带消息short

     */

    static public void assertEquals(String message, short expected, short actual) {

       assertEquals(message, new Short(expected), new Short(actual));

    }

 

    /**

     * 是否相等的断言,short

     *

     * static public void assertEquals(short expected, short actual) {

     *

     * assertEquals(null, expected, actual);

     *

     * }

     *

     * /**是否相等的断言,带消息int

     */

 

    static public void assertEquals(String message, int expected, int actual) {

       assertEquals(message, new Integer(expected), new Integer(actual));

    }

 

    /**

     * 是否相等的断言,int

     */

    static public void assertEquals(int expected, int actual) {

       assertEquals(null, expected, actual);

    }

 

    // 下面一段代码描述了JUnit中如何实现判断是否为null的方法,这些方法的功能相同,只是一个带消息,一个不带消息.

    /**

     * 是否不为null的断言 Object

     */

    static public void assertNotNull(Object object) {

       assertNotNull(null, object);

    }

 

    /**

     * 是否不为null的断言,带消息Object

     */

    static public void assertNotNull(String message, Object object) {

       assertTrue(message, object != null);

    }

 

    /**

     * 是否为null的断言Object

     */

    // 下面一段代码描述了JUnit中如何实现判断是否相同的方法,这些方法要实现的内容相同,只是参数不同.

    static public void assertNull(Object object) {

       assertNull(null, object);

    }

 

    /**

     * 是否为null的断言,带消息Object

     */

    static public void assertNull(String message, Object object) {

       assertTrue(message, object == null);

    }

 

    /** 是否相同的断言,带消息 */

    static public void assertSame(String message, Object expected, Object actual) {

       if (expected == actual)

           return;

       failNotSame(message, expected, actual);

    }

 

    /**

     * 是否相同的断言,Object

     */

    static public void assertSame(Object expected, Object actual) {

       assertSame(null, expected, actual);

    }

 

    /**

     * 是否不相同的断言,带消息

     */

    static public void assertNotSame(String message, Object expected,

           Object actual) {

       if (expected == actual)

           failSame(message);

    }

 

    /**

     * 是否不相同的断言Object

     */

    static public void assertNotSame(Object expected, Object actual) {

       assertNotSame(null, expected, actual);

    }

 

    /**

     * 相同时失败

     */

    static public void failSame(String message) {

       String formatted = "";

       if (message != null)

           formatted = message + " ";

       fail(formatted + "expected not same");

    }

 

    /**

     * 不相同时失败

     */

    static public void failNotSame(String message, Object expected,

           Object actual) {

       String formatted = "";

       if (message != null)

           formatted = message + " ";

       fail(formatted + "expected same:<" + expected + "> was not:<" + actual

              + ">");

    }

 

    /**

     * 不相等时失败

     */

    static public void failNotEquals(String message, Object expected,

           Object actual) {

       fail(format(message, expected, actual));

    }

 

    /**

     * 格式化消息

     */

    public static String format(String message, Object expected, Object actual) {

       String formatted = "";

       if (message != null)

           formatted = message + " ";

       return formatted + "expected:<" + expected + "> but was:<" + actual

              + ">";

    }

 

}

从上述代码中,读者可以研读JUnit中有关断言的实现方式,其实,最终都是使用后面的几个static方法来实现的。

Java 5的发布为JUnit带来了新的特性。自JUnit 4.0之后,JUnit大量使用了annotations特性,使编写单元测试变得更加简单。


4)Junit4.0新变化
a)     改变测试方法的命名方式

前面讲过,使用JUnit 4.0以上版本可以不用遵循以前JUnit约定的测试方法命名方法,以前命名方法的示例代码如下:

Java代码:TestAddAndSub.java

package test.juit;

import junit.framework.TestCase;

public class TestAddAndSub extends TestCase {

    public void testadd() {

       // 断言计算结果与10是否相等

       assertEquals(10, AddAndSub.add(4, 6));

    }

    public void testsub() {

       // 断言计算结果与2是否相等

       assertEquals(2, AddAndSub.sub(6, 4));

    }

    public static void main(String args[]) {

       junit.textui.TestRunner.run(TestAddAndSub.class);

    }

}

JUnit 4.0以上版本的命名方式,是在测试方法前使用@Test注释,示例代码如下:

Java代码:TestAddAndSub.java

package test.juit;

 

import junit.framework.TestCase;

import org.junit.Test;

 

public class TestAddAndSub extends TestCase {

    @Test

    public void add() {

       // 断言计算结果与10是否相等

       assertEquals(10, AddAndSub.add(4, 6));

    }

    @Test

    public void sub() {

       // 断言计算结果与2是否相等

       assertEquals(2, AddAndSub.sub(6, 4));

    }

    public static void main(String args[]) {

       junit.textui.TestRunner.run(TestAddAndSub.class);

    }

}

这个时候,测试方法的命名将不再重要,开发人员可以按照自己的命名方式来命名。


b)     不再继承TestCase

新版本的JUnit将不再强制继承TestCase,但需要import org.junit.Assert来实现断言,示例代码如下:

Java代码:TestAddAndSub.java

package test.juit;

 

import static org.junit.Assert.assertEquals;

import org.junit.Test;

 

public class TestAddAndSub {

 

    @Test

    public void add() {

       // 断言计算结果与10是否相等

       assertEquals(10, AddAndSub.add(4, 6));

    }

 

    @Test

    public void sub() {

       // 断言计算结果与2是否相等

       assertEquals(2, AddAndSub.sub(6, 4));

    }

 

}


c)      改变初始化和销毁方式

以前,JUnit使用SetUp和TearDown方法来进行初始化和销毁动作,JUnit 4.0以上版本将不再强制使用SetUp和TearDown方法来进行初始化和销毁,原来使用SetUp和TearDown方法的示例代码如下:

Java代码:TestAddAndSub.java

package test.juit;

 

import junit.framework.TestCase;

 

public class TestAddAndSub extends TestCase {

 

    private int m = 0;

    private int n = 0;

 

    // 初始化

    protected void setUp() {

       m = 4;

       n = 6;

    }

    public void testadd() {

       // 断言计算结果与10是否相等

       assertEquals(10, AddAndSub.add(m, n));

    }

    public void testsub() {

       // 断言计算结果与2是否相等

       assertEquals(2, AddAndSub.sub(n, m));

    }

    // 销毁

    protected void tearDown() {

       m = 0;

       n = 0;

    }

}

不使用SetUp和TearDown方法的示例代码如下:

Java代码:TestAddAndSub.java

package test.juit;

 

import org.junit.After;

import org.junit.Before;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

 

public class TestAddAndSub{

 

    protected int m = 0;

    protected int n = 0;

    //初始化

    @Before

    public void init() {

        m = 4;

        n = 6;

    }

    @Test

    public void add() {

        //断言计算结果与10是否相等

        assertEquals(10, AddAndSub.add(m, n));

    }

    @Test

    public void sub() {

        //断言计算结果与2是否相等

        assertEquals(2, AddAndSub.sub(n, m));

    }

    //销毁

    @After

    public void destory() {

        m = 0;

        n = 0;

    }

}

上面示例中的初始化和销毁都是针对一个方法来说的,每个方法执行前都要进行初始化,执行完毕都要进行销毁。而JUnit的最新版本则提供了新的特性,针对类进行初始化和销毁。也就是说,该类中的方法只进行一次初始化和销毁,方法就是使用@BeforeClass和@AfterClass,示例代码如下:

Java代码:TestAddAndSub.java

package test.juit;

 

import static org.junit.Assert.assertEquals;

 

import org.junit.AfterClass;

import org.junit.BeforeClass;

import org.junit.Test;

 

public class TestAddAndSub{

 

    protected int m = 0;

    protected int n = 0;

    //初始化

    @BeforeClass

    public void init() {

        m = 4;

        n = 6;

    }

    @Test public void add() {

        //断言计算结果与10是否相等

        assertEquals(10, AddAndSub.add(m, n));

    }

    @Test public void sub() {

        //断言计算结果与2是否相等

        assertEquals(2, AddAndSub.sub(n, m));

    }

    //销毁

    @AfterClass

    public void destory() {

        m = 0;

        n = 0;

    }

 

}

上述初始化和销毁动作,只执行一次即可。


d)     改变异常处理的方式

以前,使用JUnit进行单元测试时,如果遇到异常情况,需要使用try…catch的形式来捕捉异常,示例代码如下:

Java代码:TestAddAndSub.java

package test.juit;

 

import junit.framework.TestCase;

 

public class TestAddAndSub extends TestCase{

 

    private int m = 0;

    private int n = 0;

    //初始化

    protected void setUp() {

        m = 4;

        n = 6;

    }

    public void testadd() {

        //断言计算结果与10是否相等

        assertEquals(10, AddAndSub.add(m, n));

    }

    public void testsub() {

        //断言计算结果与2是否相等

        assertEquals(2, AddAndSub.sub(n, m));

    }

    public void testdiv() {

        //断言除数为0

        try {

              int n = 2 / 0;

              fail("Divided by zero!");

         }

         catch (ArithmeticException success) {

              assertNotNull(success.getMessage());

         }

    }

    //销毁

    protected void tearDown() {

        m = 0;

        n = 0;

    }

 

}

JUnit4.0以后的版本,将不再使用try…catch的方式来捕捉异常,示例代码如下:

Java代码:TestAddAndSub.java

package test.juit;

 

import static org.junit.Assert.assertEquals;

 

import org.junit.After;

import org.junit.Before;

import org.junit.Test;

 

public class TestAddAndSub{

 

    protected int m = 0;

    protected int n = 0;

    //初始化

    @Before

    public void init() {

        m = 4;

        n = 6;

    }

    @Test

    public void add() {

        //断言计算结果与10是否相等

        assertEquals(10, AddAndSub.add(m, n));

    }

    @Test

    public void sub() {

        //断言计算结果与2是否相等

        assertEquals(2, AddAndSub.sub(n, m));

    }

    @Test(expected=ArithmeticException.class)

    public void div() {

        //断言除数是否为0

        int n = 2 / 0;

    }

    //销毁

    @After

    public void destory() {

        m = 0;

        n = 0;

    }

}

当然,JUnit还有许多新的特性,限于篇幅原因,这里只对比较重要的特性进行讲解,其余将不再多讲,想要了解的读者可以到JUnit的相关网站进行学习。


5)小结

本节首先讲述了JUnit的下载和安装,接着又讲解了JUnit的相关知识,最后讲解了JUnit的新特性。

JUnit对开发人员进行大规模的单元测试来说,是非常有用的,但对于大量的代码如何来管理就只有靠CVS了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值