可插拔黑盒测试工具包

pbbt的Python项目详细描述


Overview

pbbt是黑盒测试的回归测试工具。它是 适用于测试具有明确输入的复杂软件组件 和输出接口。

  input    +----------+   output
o--------> | Software | --------->o
           +----------+

在黑盒测试中,test caseinput和 需要output数据。测试线束使用 给定输入并验证生成的输出与 预期产出。

黑盒测试可以用于许多不同类型的 软件。例如,

  • a数据库系统:输入是一个sql语句,输出是一个集合 记录的数量;
  • web服务:输入是http请求,输出是http 响应;
  • a命令行实用程序:输入是一个命令行序列 参数和stdin,输出是stdout
  • gui应用程序:可以使用不同的方法;例如, 输入可以是一系列用户操作,输出可以是 特定小部件的状态。

pbbt是一个python库和一个应用程序,它允许您:

  • 使用内置测试类型测试命令行脚本和python 代码;
  • 注册自定义测试类型;
  • 以简洁的YAML格式准备测试输入;
  • train模式下,运行测试用例并记录预期输出;
  • check模式下,运行测试用例并验证生成的 输出与预先记录的预期输出一致。

PBBT是麻省理工学院许可下发布的免费软件。PBBT是由 克拉克·C·埃文斯和基里尔·西蒙诺夫来自Prometheus Research, LLC

Using PBBT

要安装pbbt,可以使用pip包管理器:

# pip install pbbt

此命令从下载并安装最新版本的pbbt Python Package Index。成功安装后,您应该 能够导入pbbtpython模块并运行pbbt命令行 公用事业。

要开始使用pbbt,需要创建一个包含输入数据的文件。为了 例如,使用以下内容创建input.yaml

py: |
  print "Hello, World!"

此文件采用YAML格式,这是一种数据序列化语言 类似于JSON,实际上是json的超集。上面的文件 可以用json表示为:

{ "py": "print \"Hello, World!\"\n" }

有关yaml语法和语义的描述,请参见http://yaml.org/

接下来,在training模式下执行pbbt以生成预期的输出数据。 运行:

$ pbbt input.yaml output.yaml --train

并接受新的输出。PBBT将输出数据写入 output.yaml

py: print-hello-world
stdout: |
  Hello, World!

现在您可以在checking模式下启动pbbt,在该模式下执行测试 案例并验证预期和实际输出数据是否一致:

$ pbbt input.yaml output.yaml

要向input.yaml添加更多测试用例,需要将其转换为 测试套件

title: My Tests
tests:
- py: |
    print "Hello, World!"
- sh: echo Hello, World!

该文件现在包含一个测试套件my tests和两个测试用例:一个 和前面的例子一样,还有一个执行shell命令 echo Hello, World!

sh: echo Hello, World!

这个测试用例的输出是由shell生成的stdout。 命令。要记录预期输出,请在训练模式下运行pbbt。 再一次。

Built-in Test Types

开箱即用,PBBT支持一小组预定义的测试类型:

  • 测试python代码;
  • 测试shell命令;
  • 文件操作测试。

还提供特殊测试类型:

  • T型EST套房;
  • 包括;
  • 条件变量。
  • 其他测试系统的网关。

每个测试类型定义输入和输出记录的结构,即 是,强制字段和可选字段的集合以及字段的类型 价值观。在本节中,我们列出所有可用的测试类型并描述 它们的输入字段。

Common Fields

以下可选字段适用于所有测试类型,其中 它们有意义:

skiptruefalse
true,跳过此测试用例。
if:变量、变量列表或python表达式

variable name上,仅当变量是 已定义且不为假。

在变量列表中,仅当至少有一个 变量已定义且不为false。

python表达式上,如果表达式 计算结果为true。您可以在 表达。

unless:变量、变量列表或python表达式

variable name上,如果定义了变量,则跳过此测试用例 不是假的。

在变量列表中,如果至少有一个 变量已定义且不为false。

对于python表达式,如果表达式 计算结果为true。您可以在 表达。

ignoretruefalse或正则表达式
在{tt12}$上,允许实际和预期的输出不相等。 测试用例仍然必须在没有任何错误的情况下执行。

正则表达式上,预处理实际的和预期的 比较前输出:

  1. 对输出数据运行正则表达式并查找 所有匹配项。
  2. 如果正则表达式不包含()子组, 从输出中删除所有匹配项。
  3. 如果正则表达式包含一个或多个()子组, 从输出中删除子组的内容。

正则表达式是用MULTILINEVERBOSE标志。

示例:

title: Integration with MySQL
if: has_mysql
tests:
- set:
    MYSQL_HOST: localhost
    MYSQL_PORT: 3306
  unless: [MYSQL_HOST, MYSQL_PORT]
- read: /etc/mysql/my.cnf
  if: MYSQL_HOST == 'localhost'
- py: test-scalar-types.py
  ignore: |
    ^Today:.(\d\d\d\d)-(\d\d)-(\d\d)$
- py: test-array-types.py
  skip: true    # No array type in MySQL

Test Suite

测试套件是测试用例的集合。

一个套件可以包含其他套件,因此所有测试套件形成一棵树 结构。由套件标识符形成的path可以唯一地定位 任何套房。我们对套件路径使用文件系统表示法(例如 /path/to/suite)。

字段:

title:文本
套房的名称。
suite:标识符(可选)
套件的标识符。如果未设置,则从标题生成。
tests:输入记录列表
套房的内容。
output:路径(可选)
如果设置,则从给定的 文件。

示例:

title: All Tests
suite: all
output: output.yaml
tests:
- py: ./test/core.py
- py: ./test/ext.py
- title: Database Tests
  tests:
  - py: ./test/sqlite.py
  - py: ./test/pgsql.py
  - py: ./test/mysql.py

在本例中,数据库测试套件的路径是 /all/database-tests

Conditional Variables

这个测试用例定义了一个条件变量。

变量可以在ifunless子句中使用 有条件地启用或禁用测试用例。变量也可以是 通过全局字典__pbbt__设置或读取python测试。

也可以使用^{tt34}从命令行设置条件变量$ 选择。

设置条件变量会影响 相同的测试套件。从套件出口处重新设置变量值。

字段:

set:变量或变量字典

variable name上,将给定变量的值设置为 True

dictionary上,设置给定变量的值。

示例:

title: MySQL Tests
tests:
- set: MYSQL
- set:
    MYSQL_HOST: localhost
    MYSQL_PORT: 3306
  unless: [MYSQL_HOST, MYSQL_PORT]
- py: |
    # Determine the version of the MySQL server
    import MySQLdb
    connection = MySQLdb.connect(host=__pbbt__['MYSQL_HOST'],
                                 port=int(__pbbt__['MYSQL_PORT']),
                                 db='mysql')
    cursor = connection.cursor()
    cursor.execute("""SELECT VERSION()""")
    version_string = cursor.fetchone()[0]
    version = tuple(map(int, version_string.split('-')[0].split('.')))
    __pbbt__['MYSQL_VERSION'] = version
- py: test-ddl.py
- py: test-dml.py
- py: test-select.py
- py: test-new-features.py
  if: MYSQL_VERSION >= (5, 5)

Include Test

这个测试用例从一个文件。

字段:

include:路径
要加载的文件。该文件应包含 yaml格式。

示例:

title: All Tests
tests:
- include: test/core.yaml
- include: test/ext.yaml
- include: test/sqlite.yaml
- include: test/pgsql.yaml
- include: test/mysql.yaml

Python Code

这个测试用例执行python代码并生成stdout

字段:

py:路径或python代码

onpython代码,要执行的源代码。

文件名上,包含要执行的源代码的文件。

stdin:文本(可选)
标准输入的内容。
except:异常类型(可选)
如果已设置,则指示代码将引发 给定类型。

示例:

title: Python tests
tests:
- py: hello.py
- py: &sum |
    # Sum of two numbers
    import sys
    a = int(sys.stdin.readline())
    b = int(sys.stdin.readline())
    c = a+b
    sys.stdout.write("%s\n" % c)
  stdin: |
    2
    2
- py: *sum
  stdin: |
    1
    -5
- py: *sum
  stdin: |
    one
    three
  except: ValueError

注意,我们使用yaml锚(由&sum表示)和别名 (用*sum表示)在多个测试中使用同一段代码。

Shell Command

这个测试用例执行一个shell命令并生成stdout

字段:

sh:带有参数列表的命令或可执行文件
要执行的shell命令。
stdin:文本(可选)
标准输入的内容。
cd:路径(可选)
将当前工作目录更改为之前指定的路径 执行命令。
environ:变量字典(可选)
将给定的变量添加到命令环境中。
exit:整数(可选)
< d> >期望的退出代码;默认情况下{tT50}$。< /dd>

示例:

title: Shell tests
tests:
- sh: echo Hello, World!
- sh: cat
  stdin: |
    Hello, World!
- sh: [cat, /etc/shadow]
  exit: 1   # Permission denied

Write to File

此测试用例创建具有给定内容的文件。

字段:

write:路径
要创建的文件。
data:文本
文件内容。

示例:

write: test/tmp/data.txt
data: |
    Hello, World!

Read from File

此测试的输出是文件的内容。

字段:

read:路径
要读取的文件。

示例:

read: test/tmp/data.txt

Remove File

这个测试用例删除一个文件。如果文件没有 存在。

字段:

rm:路径或路径列表
要删除的文件。

示例:

rm: test/tmp/data.txt

Make Directory

这个测试用例创建一个目录。

如果需要,还会创建父目录。如果 目录已经存在。

字段:

mkdir:路径
要创建的目录。

示例:

mkdir: test/tmp

Remove Directory

此测试用例将删除包含其所有内容的目录。

如果目录不存在,则不是错误。

字段:

rmdir:路径
要删除的目录。

示例:

rmdir: test/tmp

Doctest

此测试用例对一组文件执行doctest

字段:

doctest:路径模式
带有doctest会话的文件。

示例:

doctest: test/test_*.rst

Unittest

此测试用例执行unittest测试套件。

字段:

unittest:路径模式
带有unittest测试的文件。

示例:

unittest: test/test_*.py

Pytest

此测试用例执行py.test测试套件。

必须安装来自http://pytest.org/的包pytest

字段:

pytest:路径模式
带有py.test测试的文件。

示例:

pytest: test/test_*.py

Coverage

这个测试用例开始覆盖python代码。

来自http://nedbatchelder.com/code/coverage/的包coverage必须 安装。

字段:

coverage:文件名或None
配置文件的路径。
data_file:文件名
保存覆盖率数据的位置。
timidfalsetrue(可选)
使用更简单的跟踪函数。
branchfalsetrue(可选)
启用分支覆盖范围。
source:包名称的文件路径(可选)
要测量的源文件或包。
include:文件模式
要测量的文件。
omit:文件模式
要忽略的文件。

示例:

coverage:
source: pbbt
branch: true

Coverage check

此测试用例停止覆盖并报告度量摘要。

字段:

coverage-check:浮点
预期覆盖率。

示例:

coverage-check: 99.0

Coverage report

此测试用例停止覆盖并将覆盖率报告保存到文件中。

字段:

coverage-report:目录
报告的保存位置。

示例:

coverage-report: coverage

Custom Test Types

在本节中,我们将讨论如何向pbbt添加自定义测试类型。

假设我们想通过运行一系列sql来测试sql数据库 查询并验证是否获得预期输出。为了实现这个 测试方案,我们需要一种方法:

  • 打开到数据库的连接;
  • 执行SQL语句并生成一系列记录。

输入文件可能如下所示:

title: Database tests
tests:
# Remove the database file left after the last testing session.
- rm: test.sqlite
# Create a new SQLite database.
- connect: test.sqlite
# Run a series of SQL commands.
- sql: |
    SELECT 'Hello, World!';
- sql: |
    CREATE TABLE student (
        id      INTEGER PRIMARY KEY,
        name    TEXT NOT NULL,
        gender  CHAR(1) NOT NULL
                CHECK (gender in ('f', 'i', 'm')),
        dob     DATE NOT NULL
    );
- sql: |
    INSERT INTO student (id, name, gender, dob)
    VALUES (1001, 'Linda Wright', 'f', '1988-10-03'),
           (1002, 'Beth Thompson', 'f', '1988-01-24'),
           (1003, 'Mark Melton', 'm', '1984-06-05'),
           (1004, 'Judy Flynn', 'f', '1986-09-02'),
           (1005, 'Jonathan Bouchard', 'm', '1982-02-12');
- sql: |
    SELECT *
    FROM student
    ORDER BY dob;
- sql: |
    SELECT name
    FROM student
    WHERE id = 1003;

在这个输入文件中,我们使用两种新类型的测试用例:

connect
指定到sqlite数据库的连接。
sql
指定要执行的SQL语句。

我们将编写一个pbbt扩展来实现这些测试类型。

创建包含以下内容的文件sql.py

from pbbt import Test, Field, BaseCase, MatchCase, listof
import sqlite3, traceback, csv, StringIO

@Test
class ConnectCase(BaseCase):

    class Input:
        connect = Field(str)

    def check(self):
        self.state['connect'] = None
        try:
            self.state['connect'] = sqlite3.connect(self.input.connect)
        except:
            self.ui.literal(traceback.format_exc())
            self.ui.warning("exception occurred while"
                            " connecting to the database")
            self.ctl.failed()
        else:
            self.ctl.passed()

@Test
class SQLCase(MatchCase):

    class Input:
        sql = Field(str)

    class Output:
        sql = Field(str)
        rows = Field(listof(listof(object)))

    def render(self, output):
        stream = StringIO.StringIO()
        writer = csv.writer(stream, lineterminator='\n')
        writer.writerows(output.rows)
        return stream.getvalue()

    def run(self):
        connection = self.state.get('connect')
        if not connection:
            self.ui.warning("database connection is not defined")
            return
        rows = []
        try:
            cursor = connection.cursor()
            cursor.execute(self.input.sql)
            for row in cursor.fetchall():
                rows.append(list(row))
        except:
            self.ui.literal(traceback.format_exc())
            self.ui.warning("exception occurred while"
                            " executing a SQL query")
            connection.rollback()
            new_output = None
        else:
            connection.commit()
            new_output = self.Output(sql=self.input.sql, rows=rows)
        return new_output

要使用此扩展,请将参数-E sql.py添加到所有pbbt 召唤。例如:

$ pbbt -E sql.py input.yaml output.yaml --train

现在我们将逐行解释sql.py的内容。

第一行导入我们将使用的一些类和装饰器:

from pbbt import Test, Field, BaseCase, MatchCase

要注册测试类型,请使用@Testdecorator。这里有一个 通用模板:

@Test
class CustomCase(object):

    class Input:
        some_field = Field(...)
        [...]

    class Output:
        some_field = Field(...)
        [...]

    def __init__(self, ctl, input, output):
        self.ctl = ctl
        self.input = input
        self.output = output

    def __call__(self):
        [...]
        return new_output

decorator的参数必须是遵循以下内容的类 规则:

  • 输入和输出记录的结构用嵌套 类InputOutput。记录字段使用 Field描述符。

  • 要创建测试用例,测试工具将生成类的实例。 类构造函数接受三个参数:

    ctl

    测试线束控制器。它用于用户交互, 报告测试成功或失败,并作为 条件变量。

    input

    输入记录。

    output

    预期的输出记录或None

  • 测试用例通过调用其__call__()方法执行。这个 方法必须运行测试用例,生成新的输出记录,并比较 它具有给定的预期输出记录。

    如果预期和实际输出记录不一致:

    • check模式下,该方法必须报告失败。
    • train模式下,该方法可以请求用户允许 更新预期输出。如果要更新预期输出,则 方法应返回新的输出记录。
也提供了两个类,分别是。 这些类实现了 测试类型。

让我们回顾一下connecttest类型,它由 ConnectCase类:

@Test
class ConnectCase(BaseCase):

    class Input:
        connect = Field(str)

    def check(self):
        [...]

ConnectCase继承自BaseCase,它适合于 不产生输出数据并在其一侧执行的测试 影响。嵌套的Input类定义用于声明 输入记录的字段。在这种情况下,输入记录只有一个 文本字段connect,其中包含数据库的名称。

BaseCase继承的测试类型必须重写抽象方法 check()

import sqlite3, traceback
[...]

@Test
class ConnectCase(BaseCase):
    [...]

    def check(self):
        self.state['connect'] = None
        try:
            self.state['connect'] = sqlite3.connect(self.input.connect)
        except:
            self.ui.literal(traceback.format_exc())
            self.ui.warning("exception occurred while"
                            " connecting to the database")
            self.ctl.failed()
        else:
            self.ctl.passed()

此代码试图创建新的数据库连接并将其存储为 条件变量connect。如果尝试失败,它将调用 ui.literal()显示异常回溯和ctl.failed() 报告测试失败。否则,ctl.passed()被调用 表示测试成功。

接下来,让我们回顾一下sql测试类型:

@Test
class SQLCase(MatchCase):

    class Input:
        sql = Field(str)

    class Output:
        sql = Field(str)
        rows = Field(listof(listof(object)))

    def run(self):
        [...]

    def render(self, output):
        [...]

此测试类型同时具有输入和输出记录,如下所述 使用InputOutput嵌套类。输入记录包含 一个字段sql,一个要执行的sql查询。输出记录包含 两个字段:sqlrows。字段sql包含相同的sql 查询并用于将输出记录与 输入记录。字段rows包含生成的输出行列表 通过sql查询。

^ tT115}$继承自^ {TT94} $,这是一个MIXIN类 用于生成文本输出的测试类型。从继承的测试类型 MatchCase必须重写两个方法:

run()
执行测试用例并返回生成的输出记录。
render(output)
将输出记录转换为可打印格式。

SQLCase中,render()通过将输出行转换为 CSV格式:

import csv, StringIO
[...]

@Test
class SQLCase(MatchCase):
    [...]

    def render(self, output):
        stream = StringIO.StringIO()
        writer = csv.writer(stream, lineterminator='\n')
        writer.writerows(output.rows)
        return stream.getvalue()

方法run()的实现如下:

@Test
class SQLCase(MatchCase):
    [...]

    def run(self):
        connection = self.state.get('connect')
        if not connection:
            self.ui.warning("database connection is not defined")
            return
        rows = []
        try:
            cursor = connection.cursor()
            cursor.execute(self.input.sql)
            for row in cursor.fetchall():
                rows.append(list(row))
        except:
            self.ui.literal(traceback.format_exc())
            self.ui.warning("exception occurred while"
                            " executing a SQL query")
            connection.rollback()
            new_output = None
        else:
            connection.commit()
            new_output = self.Output(sql=self.input.sql, rows=rows)
        return new_output

Command-line Interface

用法:

pbbt [<options>] INPUT [OUTPUT]

这里,INPUTOUTPUT是包含输入和输出的文件 分别测试数据。

提供以下选项:

-h--help
显示使用信息并退出.< /dD>
-q--quiet
仅显示警告和错误。
-T--train
在培训模式下运行测试。
-P--purge
清除过时的输出数据。
-M N--max-errors N
N测试失败后停止;0表示“从不”。
-D VAR-D VAR=VALUE--define VAR--define VAR=VALUE
设置条件变量。
-E FILE-E MODULE--extend FILE--extend MODULE
从文件或python模块加载pbbt扩展名。
-S ID--suite ID
运行特定的测试套件。

PBBT还可以从以下文件中读取配置:

setup.cfg
此文件为ini格式,在节中定义了pbbt设置 [pbbt]。可以识别以下参数:extend, ^{TT89}$,^{TT29}$,^{TT152}$,^{TT27}$,^{TT154}$,^{TT155}$, max_errorsquiet
pbbt.yaml
此文件必须是具有以下键的yaml文件:extend, ^{TT89}$,^{TT29}$,^{TT152}$,^{TT27}$,^{TT154}$,^{TT155}$, max-errorsquiet

PBBT也可以作为distutils命令执行:

python setup.py pbbt

在这种情况下,pbbt配置可以在^{tt147}中指定$ 或者通过命令行参数。

API Reference

pbbt.maybe(T)
用于isinstance(X, ...)的伪类型:检查X是否是 T或等于None的实例。
pbbt.oneof(T1, T2, ...)
用于isinstance(X, ...)的伪类型:检查X是否是 T1的实例或T2的实例等。
pbbt.choiceof(values)
用于isinstance(X, ...)的伪类型:检查X是否等于 给定值之一。
pbbt.listof(T, length=None)
用于isinstance(X, ...)的伪类型:检查X是否是 T的元素。
pbbt.tupleof(T1, T2, ...)
用于isinstance(X, ...)的伪类型:检查X是否是元组 包含类型为T1T2等的字段。
pbbt.dictof(T1, T2)
用于isinstance(X, ...)的伪类型:检查X是否是 带键类型T1和值类型T2的字典。
pbbt.raises(E)
使用withwith子句验证嵌套块是否引发 给定类型的异常。
pbbt.raises(E, fn, *args, **kwds)
验证函数调用fn(*args, **kwds)是否引发 给定类型的异常。
pbbt.Test(CaseType)
将给定类注册为测试类型。
pbbt.Field(check=None, default=REQUIRED, order=None, hint=None)

描述输入或输出记录的字段。

check
字段值的类型。
default
如果缺少字段,则为默认值。如果未设置,则 字段是必需的。
order
如果已设置,则允许覆盖默认字段顺序。
hint
字段的简短描述。
pbbt.Record(*p_fields, **kv_fields)

所有输入和输出记录的基类。

Testdecorator转换嵌套的InputOutput 类到Record子类。

可以使用或重写以下方法:

classmethod __recognizes__(keys)

检查密钥集是否与记录类型兼容。

默认实现检查密钥集是否包含 所有必需的记录字段。

classmethod __load__(mapping)
从记录键和 数值。
__dump__()
生成字段键和值的列表。
__complements__(other)
检查两个记录是否是互补的输入和输出记录 对于相同的测试用例。
__clone__(**kv_fields)
用给定字段的新值复制记录。
__str__()
生成可打印的表示。
pbbt.Control(...)

测试线束。

测试线束对象作为第一个对象传递给测试用例 构造函数的参数。以下方法和属性 可由测试用例对象使用。

属性:

ui
提供用户交互服务。
training
如果设置,则线束处于训练模式。
purging
如果设置,则线束处于清除模式。
quiet
如果设置,则只显示警告和错误。
halted
如果设置,则测试过程已停止。
state
条件变量。
selection
选定套房。

方法:

passed(text=None)
证明当前测试用例已通过。
failed(text=None)
证明当前测试用例失败。
updated(text=None)
证明当前测试用例的输出数据 已更新。
halt(text=None)
停止测试过程。
load_input(path)
从给定文件加载输入测试数据。
load_output(path)
从给定文件加载输出测试数据。
dump_output(path, data)
将输出测试数据保存到给定文件。
run(case)
执行测试用例。
__call__(input_path, output_path)
使用给定的输入和输出运行测试过程。
pbbt.locate(record)
查找给定记录的位置。
pbbt.Location(filename, line)
输入或输出记录在yaml文档中的位置。
pbbt.run(input_path, output_path=None, **configuration)

从给定文件加载测试数据并运行测试。

配置:

ui
用户交互控制器。
variables
预定义的条件变量。
targets
选定套房。
training
将线束设置为训练模式。
purging
清除过时的输出数据。
max_errors
在吊带停止之前最大允许的故障数。
quiet
仅显示警告和错误。
pbbt.main()
实现pbbt命令行实用程序。
pbbt.BaseCase

适合大多数测试类型的模板类。

子类需要重写以下方法:

check()
以检查模式运行测试用例。
train()
以列车模式运行测试用例。默认实现 只需调用check()
pbbt.MatchCase

生成输出的测试类型的模板类。

子类需要重写以下方法:

run()
执行案例;返回生成的输出。
render(output)
将输出记录转换为文本。
pbbt.UI

用户交互服务的抽象类。

方法:

part()
开始新的部分。
section()
开始一个小节。
header(text)
显示节标题。
notice(text)
显示通知。
warning(text)
显示警告。
error(text)
显示错误。
literal(text)
显示文本。
choice(text, *choices)
问一个单选题。
pbbt.ConsoleUI(stdin=None, stdout=None, stderr=None)
实现控制台输入/输出的UI
pbbt.SilentUI(backend)
实现与--quiet选项一起使用的UI

欢迎加入QQ群-->: 979659372 Python中文网_新手群

推荐PyPI第三方库


热门话题
swing如何在不使用BorderLayout的情况下将组件居中放置在JPanel中?JAVA   java行话游戏向gridlayout添加输入   如何在Java中检索数组中列表的第一个和第二个元素?   在IDEA 12.1中调试Java Spring项目时,Jetty server为什么会挂起?   java我的循环没有在屏幕上显示任何内容   Protostuff 1.3.8中的java RuntimeSchema   java Android Realm RecyclerView动画不工作?   java在主活动中创建类   java如何在OData 4中实现三段或更多段导航   java TreeCellEditor:即使ShouldSelectCell返回false,也必须选择要编辑的单元格   java Tomcat安全权限错误   java通过split()解析文件名以比较两个常用字段   Java数组排序不起作用   使用Java创建带多个项目的签出会话   java有没有办法在安卓上,在我自己的应用程序中使用本机消息传递应用程序?   java Hibernate多个关系和一个附加表   java格式化TableView列中的ObjectProperty<LocalDateTime>   带有原子替换的java线程安全可序列化集合