first commit
This commit is contained in:
parent
ce87569c70
commit
37c1aedd5e
|
@ -0,0 +1,2 @@
|
|||
# Default ignored files
|
||||
/workspace.xml
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.9" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PackageRequirementsSettings">
|
||||
<option name="requirementsPath" value="" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
<option name="format" value="GOOGLE" />
|
||||
<option name="myDocStringFormat" value="Google" />
|
||||
</component>
|
||||
<component name="TestRunnerService">
|
||||
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
|
||||
</component>
|
||||
</module>
|
|
@ -0,0 +1,28 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<DBN-PSQL>
|
||||
<case-options enabled="true">
|
||||
<option name="KEYWORD_CASE" value="lower" />
|
||||
<option name="FUNCTION_CASE" value="lower" />
|
||||
<option name="PARAMETER_CASE" value="lower" />
|
||||
<option name="DATATYPE_CASE" value="lower" />
|
||||
<option name="OBJECT_CASE" value="preserve" />
|
||||
</case-options>
|
||||
<formatting-settings enabled="false" />
|
||||
</DBN-PSQL>
|
||||
<DBN-SQL>
|
||||
<case-options enabled="true">
|
||||
<option name="KEYWORD_CASE" value="lower" />
|
||||
<option name="FUNCTION_CASE" value="lower" />
|
||||
<option name="PARAMETER_CASE" value="lower" />
|
||||
<option name="DATATYPE_CASE" value="lower" />
|
||||
<option name="OBJECT_CASE" value="preserve" />
|
||||
</case-options>
|
||||
<formatting-settings enabled="false">
|
||||
<option name="STATEMENT_SPACING" value="one_line" />
|
||||
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
|
||||
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
|
||||
</formatting-settings>
|
||||
</DBN-SQL>
|
||||
</code_scheme>
|
||||
</component>
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="dataSourceStorageLocal" created-in="PY-222.3739.56">
|
||||
<data-source name="MySQL - @10.8.203.25" uuid="dd327101-360a-4ce3-a39e-0a30a298d5cf">
|
||||
<database-info product="MySQL" version="8.0.22" jdbc-version="4.2" driver-name="MySQL Connector/J" driver-version="mysql-connector-java-8.0.21 (Revision: 33f65445a1bcc544eb0120491926484da168f199)" dbms="MYSQL" exact-version="8.0.22" exact-driver-version="8.0">
|
||||
<extra-name-characters>#@</extra-name-characters>
|
||||
<identifier-quote-string>`</identifier-quote-string>
|
||||
</database-info>
|
||||
<case-sensitivity plain-identifiers="lower" quoted-identifiers="lower" />
|
||||
<secret-storage>master_key</secret-storage>
|
||||
<user-name>root</user-name>
|
||||
<schema-mapping>
|
||||
<introspection-scope>
|
||||
<node kind="schema" qname="@" />
|
||||
</introspection-scope>
|
||||
</schema-mapping>
|
||||
</data-source>
|
||||
<data-source name="@localhost" uuid="49b6f686-3676-4df5-9645-cd7a2fe91d80">
|
||||
<database-info product="MySQL" version="8.0.26" jdbc-version="4.2" driver-name="MySQL Connector/J" driver-version="mysql-connector-java-8.0.25 (Revision: 08be9e9b4cba6aa115f9b27b215887af40b159e0)" dbms="MYSQL" exact-version="8.0.26" exact-driver-version="8.0">
|
||||
<extra-name-characters>#@</extra-name-characters>
|
||||
<identifier-quote-string>`</identifier-quote-string>
|
||||
</database-info>
|
||||
<case-sensitivity plain-identifiers="lower" quoted-identifiers="lower" />
|
||||
<secret-storage>master_key</secret-storage>
|
||||
<user-name>root</user-name>
|
||||
<schema-mapping>
|
||||
<introspection-scope>
|
||||
<node kind="schema">
|
||||
<name qname="@" />
|
||||
<name qname="do_mysql" />
|
||||
</node>
|
||||
</introspection-scope>
|
||||
</schema-mapping>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="MySQL - @10.8.203.25" uuid="dd327101-360a-4ce3-a39e-0a30a298d5cf">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://10.8.203.25:3306</jdbc-url>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="@localhost" uuid="49b6f686-3676-4df5-9645-cd7a2fe91d80">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://localhost:3306</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
|||
#n:do_mysql
|
Binary file not shown.
|
@ -0,0 +1,2 @@
|
|||
#n:information_schema
|
||||
!<md> [null, 0, null, null, -2147483648, -2147483648]
|
|
@ -0,0 +1,2 @@
|
|||
#n:mysql
|
||||
!<md> [null, 0, null, null, -2147483648, -2147483648]
|
|
@ -0,0 +1,2 @@
|
|||
#n:performance_schema
|
||||
!<md> [null, 0, null, null, -2147483648, -2147483648]
|
|
@ -0,0 +1,2 @@
|
|||
#n:sys
|
||||
!<md> [null, 0, null, null, -2147483648, -2147483648]
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,2 @@
|
|||
#n:information_schema
|
||||
!<md> [null, 0, null, null, -2147483648, -2147483648]
|
|
@ -0,0 +1,2 @@
|
|||
#n:mysql
|
||||
!<md> [null, 0, null, null, -2147483648, -2147483648]
|
|
@ -0,0 +1,2 @@
|
|||
#n:performance_schema
|
||||
!<md> [null, 0, null, null, -2147483648, -2147483648]
|
|
@ -0,0 +1,2 @@
|
|||
#n:sys
|
||||
!<md> [null, 0, null, null, -2147483648, -2147483648]
|
|
@ -0,0 +1,643 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DBNavigator.Project.DataEditorManager">
|
||||
<record-view-column-sorting-type value="BY_INDEX" />
|
||||
<value-preview-text-wrapping value="false" />
|
||||
<value-preview-pinned value="false" />
|
||||
</component>
|
||||
<component name="DBNavigator.Project.DataExportManager">
|
||||
<export-instructions>
|
||||
<create-header value="true" />
|
||||
<friendly-headers value="false" />
|
||||
<quote-values-containing-separator value="true" />
|
||||
<quote-all-values value="false" />
|
||||
<value-separator value="" />
|
||||
<file-name value="" />
|
||||
<file-location value="" />
|
||||
<scope value="GLOBAL" />
|
||||
<destination value="FILE" />
|
||||
<format value="EXCEL" />
|
||||
<charset value="UTF-8" />
|
||||
</export-instructions>
|
||||
</component>
|
||||
<component name="DBNavigator.Project.DatabaseBrowserManager">
|
||||
<autoscroll-to-editor value="false" />
|
||||
<autoscroll-from-editor value="true" />
|
||||
<show-object-properties value="true" />
|
||||
<loaded-nodes />
|
||||
</component>
|
||||
<component name="DBNavigator.Project.DatabaseConsoleManager">
|
||||
<connection id="e6ce7a96-ca6d-4c49-afff-dccc204a27a7">
|
||||
<console name="阿里云测试平台-数据库" type="STANDARD" schema="" session="Main" />
|
||||
</connection>
|
||||
<connection id="15e40db3-456e-4fce-b776-ef2dc81c4b5c">
|
||||
<console name="TEST 环境数据库-主表" type="STANDARD" schema="" session="Main" />
|
||||
</connection>
|
||||
</component>
|
||||
<component name="DBNavigator.Project.DatabaseFileManager">
|
||||
<open-files />
|
||||
</component>
|
||||
<component name="DBNavigator.Project.DatabaseSessionManager">
|
||||
<connection id="e6ce7a96-ca6d-4c49-afff-dccc204a27a7" />
|
||||
<connection id="15e40db3-456e-4fce-b776-ef2dc81c4b5c" />
|
||||
</component>
|
||||
<component name="DBNavigator.Project.EditorStateManager">
|
||||
<last-used-providers />
|
||||
</component>
|
||||
<component name="DBNavigator.Project.ExecutionManager">
|
||||
<retain-sticky-names value="false" />
|
||||
</component>
|
||||
<component name="DBNavigator.Project.MethodExecutionManager">
|
||||
<method-browser />
|
||||
<execution-history>
|
||||
<group-entries value="true" />
|
||||
<execution-inputs />
|
||||
</execution-history>
|
||||
<argument-values-cache />
|
||||
</component>
|
||||
<component name="DBNavigator.Project.ObjectDependencyManager">
|
||||
<last-used-dependency-type value="INCOMING" />
|
||||
</component>
|
||||
<component name="DBNavigator.Project.ObjectQuickFilterManager">
|
||||
<last-used-operator value="EQUAL" />
|
||||
<filters />
|
||||
</component>
|
||||
<component name="DBNavigator.Project.ParserDiagnosticsManager">
|
||||
<diagnostics-history />
|
||||
</component>
|
||||
<component name="DBNavigator.Project.ScriptExecutionManager" clear-outputs="true">
|
||||
<recently-used-interfaces>
|
||||
<mapping database-type="MYSQL" interface-id="DEFAULT" />
|
||||
</recently-used-interfaces>
|
||||
</component>
|
||||
<component name="DBNavigator.Project.Settings">
|
||||
<connections>
|
||||
<connection id="e6ce7a96-ca6d-4c49-afff-dccc204a27a7" active="true" signed="true">
|
||||
<database>
|
||||
<name value="阿里云测试平台-数据库" />
|
||||
<description value="用于部署接口自动化出视平台的数据库" />
|
||||
<database-type value="MYSQL" />
|
||||
<config-type value="BASIC" />
|
||||
<database-version value="5.7" />
|
||||
<driver-source value="BUILTIN" />
|
||||
<driver-library value="" />
|
||||
<driver value="" />
|
||||
<url-type value="DATABASE" />
|
||||
<host value="120.77.93.192" />
|
||||
<port value="3306" />
|
||||
<database value="test_platform" />
|
||||
<type value="USER_PASSWORD" />
|
||||
<user value="root" />
|
||||
<deprecated-pwd value="YW5rYW5pIzEy" />
|
||||
</database>
|
||||
<properties>
|
||||
<auto-commit value="false" />
|
||||
</properties>
|
||||
<ssh-settings>
|
||||
<active value="false" />
|
||||
<proxy-host value="" />
|
||||
<proxy-port value="22" />
|
||||
<proxy-user value="" />
|
||||
<deprecated-proxy-pwd value="" />
|
||||
<auth-type value="PASSWORD" />
|
||||
<key-file value="" />
|
||||
<key-passphrase value="" />
|
||||
</ssh-settings>
|
||||
<ssl-settings>
|
||||
<active value="false" />
|
||||
<certificate-authority-file value="" />
|
||||
<client-certificate-file value="" />
|
||||
<client-key-file value="" />
|
||||
</ssl-settings>
|
||||
<details>
|
||||
<charset value="UTF-8" />
|
||||
<session-management value="true" />
|
||||
<ddl-file-binding value="true" />
|
||||
<database-logging value="false" />
|
||||
<connect-automatically value="false" />
|
||||
<restore-workspace value="true" />
|
||||
<restore-workspace-deep value="true" />
|
||||
<environment-type value="default" />
|
||||
<connectivity-timeout value="5" />
|
||||
<idle-time-to-disconnect value="30" />
|
||||
<idle-time-to-disconnect-pool value="5" />
|
||||
<credential-expiry-time value="10" />
|
||||
<max-connection-pool-size value="7" />
|
||||
<alternative-statement-delimiter value="" />
|
||||
</details>
|
||||
<object-filters hide-empty-schemas="false" hide-pseudo-columns="false">
|
||||
<object-type-filter>
|
||||
<object-type name="SCHEMA" enabled="true" />
|
||||
<object-type name="USER" enabled="true" />
|
||||
<object-type name="ROLE" enabled="true" />
|
||||
<object-type name="PRIVILEGE" enabled="true" />
|
||||
<object-type name="CHARSET" enabled="true" />
|
||||
<object-type name="TABLE" enabled="true" />
|
||||
<object-type name="VIEW" enabled="true" />
|
||||
<object-type name="MATERIALIZED_VIEW" enabled="true" />
|
||||
<object-type name="NESTED_TABLE" enabled="true" />
|
||||
<object-type name="COLUMN" enabled="true" />
|
||||
<object-type name="INDEX" enabled="true" />
|
||||
<object-type name="CONSTRAINT" enabled="true" />
|
||||
<object-type name="DATASET_TRIGGER" enabled="true" />
|
||||
<object-type name="DATABASE_TRIGGER" enabled="true" />
|
||||
<object-type name="SYNONYM" enabled="true" />
|
||||
<object-type name="SEQUENCE" enabled="true" />
|
||||
<object-type name="PROCEDURE" enabled="true" />
|
||||
<object-type name="FUNCTION" enabled="true" />
|
||||
<object-type name="PACKAGE" enabled="true" />
|
||||
<object-type name="TYPE" enabled="true" />
|
||||
<object-type name="TYPE_ATTRIBUTE" enabled="true" />
|
||||
<object-type name="ARGUMENT" enabled="true" />
|
||||
<object-type name="DIMENSION" enabled="true" />
|
||||
<object-type name="CLUSTER" enabled="true" />
|
||||
<object-type name="DBLINK" enabled="true" />
|
||||
</object-type-filter>
|
||||
<object-name-filters />
|
||||
</object-filters>
|
||||
</connection>
|
||||
<connection id="15e40db3-456e-4fce-b776-ef2dc81c4b5c" active="true" signed="true">
|
||||
<database>
|
||||
<name value="TEST 环境数据库-主表" />
|
||||
<description value="TEST 环境数据库-主表" />
|
||||
<database-type value="MYSQL" />
|
||||
<config-type value="BASIC" />
|
||||
<database-version value="8.0" />
|
||||
<driver-source value="BUILTIN" />
|
||||
<driver-library value="" />
|
||||
<driver value="" />
|
||||
<url-type value="DATABASE" />
|
||||
<host value="10.8.203.25" />
|
||||
<port value="3306" />
|
||||
<database value="mysql" />
|
||||
<type value="USER_PASSWORD" />
|
||||
<user value="root" />
|
||||
<deprecated-pwd value="Z2QxMjM0" />
|
||||
</database>
|
||||
<properties>
|
||||
<auto-commit value="false" />
|
||||
</properties>
|
||||
<ssh-settings>
|
||||
<active value="false" />
|
||||
<proxy-host value="" />
|
||||
<proxy-port value="22" />
|
||||
<proxy-user value="" />
|
||||
<deprecated-proxy-pwd value="" />
|
||||
<auth-type value="PASSWORD" />
|
||||
<key-file value="" />
|
||||
<key-passphrase value="" />
|
||||
</ssh-settings>
|
||||
<ssl-settings>
|
||||
<active value="false" />
|
||||
<certificate-authority-file value="" />
|
||||
<client-certificate-file value="" />
|
||||
<client-key-file value="" />
|
||||
</ssl-settings>
|
||||
<details>
|
||||
<charset value="UTF-8" />
|
||||
<session-management value="true" />
|
||||
<ddl-file-binding value="true" />
|
||||
<database-logging value="false" />
|
||||
<connect-automatically value="true" />
|
||||
<restore-workspace value="true" />
|
||||
<restore-workspace-deep value="true" />
|
||||
<environment-type value="default" />
|
||||
<connectivity-timeout value="5" />
|
||||
<idle-time-to-disconnect value="30" />
|
||||
<idle-time-to-disconnect-pool value="5" />
|
||||
<credential-expiry-time value="10" />
|
||||
<max-connection-pool-size value="7" />
|
||||
<alternative-statement-delimiter value="" />
|
||||
</details>
|
||||
<object-filters hide-empty-schemas="false" hide-pseudo-columns="false">
|
||||
<object-type-filter>
|
||||
<object-type name="SCHEMA" enabled="true" />
|
||||
<object-type name="USER" enabled="true" />
|
||||
<object-type name="ROLE" enabled="true" />
|
||||
<object-type name="PRIVILEGE" enabled="true" />
|
||||
<object-type name="CHARSET" enabled="true" />
|
||||
<object-type name="TABLE" enabled="true" />
|
||||
<object-type name="VIEW" enabled="true" />
|
||||
<object-type name="MATERIALIZED_VIEW" enabled="true" />
|
||||
<object-type name="NESTED_TABLE" enabled="true" />
|
||||
<object-type name="COLUMN" enabled="true" />
|
||||
<object-type name="INDEX" enabled="true" />
|
||||
<object-type name="CONSTRAINT" enabled="true" />
|
||||
<object-type name="DATASET_TRIGGER" enabled="true" />
|
||||
<object-type name="DATABASE_TRIGGER" enabled="true" />
|
||||
<object-type name="SYNONYM" enabled="true" />
|
||||
<object-type name="SEQUENCE" enabled="true" />
|
||||
<object-type name="PROCEDURE" enabled="true" />
|
||||
<object-type name="FUNCTION" enabled="true" />
|
||||
<object-type name="PACKAGE" enabled="true" />
|
||||
<object-type name="TYPE" enabled="true" />
|
||||
<object-type name="TYPE_ATTRIBUTE" enabled="true" />
|
||||
<object-type name="ARGUMENT" enabled="true" />
|
||||
<object-type name="DIMENSION" enabled="true" />
|
||||
<object-type name="CLUSTER" enabled="true" />
|
||||
<object-type name="DBLINK" enabled="true" />
|
||||
</object-type-filter>
|
||||
<object-name-filters />
|
||||
</object-filters>
|
||||
</connection>
|
||||
</connections>
|
||||
<browser-settings>
|
||||
<general>
|
||||
<display-mode value="TABBED" />
|
||||
<navigation-history-size value="100" />
|
||||
<show-object-details value="false" />
|
||||
</general>
|
||||
<filters>
|
||||
<object-type-filter>
|
||||
<object-type name="SCHEMA" enabled="true" />
|
||||
<object-type name="USER" enabled="true" />
|
||||
<object-type name="ROLE" enabled="true" />
|
||||
<object-type name="PRIVILEGE" enabled="true" />
|
||||
<object-type name="CHARSET" enabled="true" />
|
||||
<object-type name="TABLE" enabled="true" />
|
||||
<object-type name="VIEW" enabled="true" />
|
||||
<object-type name="MATERIALIZED_VIEW" enabled="true" />
|
||||
<object-type name="NESTED_TABLE" enabled="true" />
|
||||
<object-type name="COLUMN" enabled="true" />
|
||||
<object-type name="INDEX" enabled="true" />
|
||||
<object-type name="CONSTRAINT" enabled="true" />
|
||||
<object-type name="DATASET_TRIGGER" enabled="true" />
|
||||
<object-type name="DATABASE_TRIGGER" enabled="true" />
|
||||
<object-type name="SYNONYM" enabled="true" />
|
||||
<object-type name="SEQUENCE" enabled="true" />
|
||||
<object-type name="PROCEDURE" enabled="true" />
|
||||
<object-type name="FUNCTION" enabled="true" />
|
||||
<object-type name="PACKAGE" enabled="true" />
|
||||
<object-type name="TYPE" enabled="true" />
|
||||
<object-type name="TYPE_ATTRIBUTE" enabled="true" />
|
||||
<object-type name="ARGUMENT" enabled="true" />
|
||||
<object-type name="DIMENSION" enabled="true" />
|
||||
<object-type name="CLUSTER" enabled="true" />
|
||||
<object-type name="DBLINK" enabled="true" />
|
||||
</object-type-filter>
|
||||
</filters>
|
||||
<sorting>
|
||||
<object-type name="COLUMN" sorting-type="NAME" />
|
||||
<object-type name="FUNCTION" sorting-type="NAME" />
|
||||
<object-type name="PROCEDURE" sorting-type="NAME" />
|
||||
<object-type name="ARGUMENT" sorting-type="POSITION" />
|
||||
<object-type name="TYPE ATTRIBUTE" sorting-type="POSITION" />
|
||||
</sorting>
|
||||
<default-editors>
|
||||
<object-type name="VIEW" editor-type="SELECTION" />
|
||||
<object-type name="PACKAGE" editor-type="SELECTION" />
|
||||
<object-type name="TYPE" editor-type="SELECTION" />
|
||||
</default-editors>
|
||||
</browser-settings>
|
||||
<navigation-settings>
|
||||
<lookup-filters>
|
||||
<lookup-objects>
|
||||
<object-type name="SCHEMA" enabled="true" />
|
||||
<object-type name="USER" enabled="false" />
|
||||
<object-type name="ROLE" enabled="false" />
|
||||
<object-type name="PRIVILEGE" enabled="false" />
|
||||
<object-type name="CHARSET" enabled="false" />
|
||||
<object-type name="TABLE" enabled="true" />
|
||||
<object-type name="VIEW" enabled="true" />
|
||||
<object-type name="MATERIALIZED VIEW" enabled="true" />
|
||||
<object-type name="INDEX" enabled="true" />
|
||||
<object-type name="CONSTRAINT" enabled="true" />
|
||||
<object-type name="DATASET TRIGGER" enabled="true" />
|
||||
<object-type name="DATABASE TRIGGER" enabled="true" />
|
||||
<object-type name="SYNONYM" enabled="false" />
|
||||
<object-type name="SEQUENCE" enabled="true" />
|
||||
<object-type name="PROCEDURE" enabled="true" />
|
||||
<object-type name="FUNCTION" enabled="true" />
|
||||
<object-type name="PACKAGE" enabled="true" />
|
||||
<object-type name="TYPE" enabled="true" />
|
||||
<object-type name="DIMENSION" enabled="false" />
|
||||
<object-type name="CLUSTER" enabled="false" />
|
||||
<object-type name="DBLINK" enabled="true" />
|
||||
</lookup-objects>
|
||||
<force-database-load value="false" />
|
||||
<prompt-connection-selection value="true" />
|
||||
<prompt-schema-selection value="true" />
|
||||
</lookup-filters>
|
||||
</navigation-settings>
|
||||
<dataset-grid-settings>
|
||||
<general>
|
||||
<enable-zooming value="true" />
|
||||
<enable-column-tooltip value="true" />
|
||||
</general>
|
||||
<sorting>
|
||||
<nulls-first value="true" />
|
||||
<max-sorting-columns value="4" />
|
||||
</sorting>
|
||||
<audit-columns>
|
||||
<column-names value="" />
|
||||
<visible value="true" />
|
||||
<editable value="false" />
|
||||
</audit-columns>
|
||||
</dataset-grid-settings>
|
||||
<dataset-editor-settings>
|
||||
<text-editor-popup>
|
||||
<active value="false" />
|
||||
<active-if-empty value="false" />
|
||||
<data-length-threshold value="100" />
|
||||
<popup-delay value="1000" />
|
||||
</text-editor-popup>
|
||||
<values-actions-popup>
|
||||
<show-popup-button value="true" />
|
||||
<element-count-threshold value="1000" />
|
||||
<data-length-threshold value="250" />
|
||||
</values-actions-popup>
|
||||
<general>
|
||||
<fetch-block-size value="100" />
|
||||
<fetch-timeout value="30" />
|
||||
<trim-whitespaces value="true" />
|
||||
<convert-empty-strings-to-null value="true" />
|
||||
<select-content-on-cell-edit value="true" />
|
||||
<large-value-preview-active value="true" />
|
||||
</general>
|
||||
<filters>
|
||||
<prompt-filter-dialog value="true" />
|
||||
<default-filter-type value="BASIC" />
|
||||
</filters>
|
||||
<qualified-text-editor text-length-threshold="300">
|
||||
<content-types>
|
||||
<content-type name="Text" enabled="true" />
|
||||
<content-type name="Properties" enabled="true" />
|
||||
<content-type name="XML" enabled="true" />
|
||||
<content-type name="DTD" enabled="true" />
|
||||
<content-type name="HTML" enabled="true" />
|
||||
<content-type name="XHTML" enabled="true" />
|
||||
<content-type name="CSS" enabled="true" />
|
||||
<content-type name="SQL" enabled="true" />
|
||||
<content-type name="PL/SQL" enabled="true" />
|
||||
<content-type name="JavaScript" enabled="true" />
|
||||
<content-type name="JSON" enabled="true" />
|
||||
<content-type name="JSON5" enabled="true" />
|
||||
<content-type name="YAML" enabled="true" />
|
||||
</content-types>
|
||||
</qualified-text-editor>
|
||||
<record-navigation>
|
||||
<navigation-target value="VIEWER" />
|
||||
</record-navigation>
|
||||
</dataset-editor-settings>
|
||||
<code-editor-settings>
|
||||
<general>
|
||||
<show-object-navigation-gutter value="false" />
|
||||
<show-spec-declaration-navigation-gutter value="true" />
|
||||
<enable-spellchecking value="true" />
|
||||
<enable-reference-spellchecking value="false" />
|
||||
</general>
|
||||
<confirmations>
|
||||
<save-changes value="false" />
|
||||
<revert-changes value="true" />
|
||||
</confirmations>
|
||||
</code-editor-settings>
|
||||
<code-completion-settings>
|
||||
<filters>
|
||||
<basic-filter>
|
||||
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
|
||||
<filter-element type="RESERVED_WORD" id="function" selected="true" />
|
||||
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
|
||||
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
|
||||
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
|
||||
<filter-element type="OBJECT" id="schema" selected="true" />
|
||||
<filter-element type="OBJECT" id="role" selected="true" />
|
||||
<filter-element type="OBJECT" id="user" selected="true" />
|
||||
<filter-element type="OBJECT" id="privilege" selected="true" />
|
||||
<user-schema>
|
||||
<filter-element type="OBJECT" id="table" selected="true" />
|
||||
<filter-element type="OBJECT" id="view" selected="true" />
|
||||
<filter-element type="OBJECT" id="materialized view" selected="true" />
|
||||
<filter-element type="OBJECT" id="index" selected="true" />
|
||||
<filter-element type="OBJECT" id="constraint" selected="true" />
|
||||
<filter-element type="OBJECT" id="trigger" selected="true" />
|
||||
<filter-element type="OBJECT" id="synonym" selected="false" />
|
||||
<filter-element type="OBJECT" id="sequence" selected="true" />
|
||||
<filter-element type="OBJECT" id="procedure" selected="true" />
|
||||
<filter-element type="OBJECT" id="function" selected="true" />
|
||||
<filter-element type="OBJECT" id="package" selected="true" />
|
||||
<filter-element type="OBJECT" id="type" selected="true" />
|
||||
<filter-element type="OBJECT" id="dimension" selected="true" />
|
||||
<filter-element type="OBJECT" id="cluster" selected="true" />
|
||||
<filter-element type="OBJECT" id="dblink" selected="true" />
|
||||
</user-schema>
|
||||
<public-schema>
|
||||
<filter-element type="OBJECT" id="table" selected="false" />
|
||||
<filter-element type="OBJECT" id="view" selected="false" />
|
||||
<filter-element type="OBJECT" id="materialized view" selected="false" />
|
||||
<filter-element type="OBJECT" id="index" selected="false" />
|
||||
<filter-element type="OBJECT" id="constraint" selected="false" />
|
||||
<filter-element type="OBJECT" id="trigger" selected="false" />
|
||||
<filter-element type="OBJECT" id="synonym" selected="false" />
|
||||
<filter-element type="OBJECT" id="sequence" selected="false" />
|
||||
<filter-element type="OBJECT" id="procedure" selected="false" />
|
||||
<filter-element type="OBJECT" id="function" selected="false" />
|
||||
<filter-element type="OBJECT" id="package" selected="false" />
|
||||
<filter-element type="OBJECT" id="type" selected="false" />
|
||||
<filter-element type="OBJECT" id="dimension" selected="false" />
|
||||
<filter-element type="OBJECT" id="cluster" selected="false" />
|
||||
<filter-element type="OBJECT" id="dblink" selected="false" />
|
||||
</public-schema>
|
||||
<any-schema>
|
||||
<filter-element type="OBJECT" id="table" selected="true" />
|
||||
<filter-element type="OBJECT" id="view" selected="true" />
|
||||
<filter-element type="OBJECT" id="materialized view" selected="true" />
|
||||
<filter-element type="OBJECT" id="index" selected="true" />
|
||||
<filter-element type="OBJECT" id="constraint" selected="true" />
|
||||
<filter-element type="OBJECT" id="trigger" selected="true" />
|
||||
<filter-element type="OBJECT" id="synonym" selected="true" />
|
||||
<filter-element type="OBJECT" id="sequence" selected="true" />
|
||||
<filter-element type="OBJECT" id="procedure" selected="true" />
|
||||
<filter-element type="OBJECT" id="function" selected="true" />
|
||||
<filter-element type="OBJECT" id="package" selected="true" />
|
||||
<filter-element type="OBJECT" id="type" selected="true" />
|
||||
<filter-element type="OBJECT" id="dimension" selected="true" />
|
||||
<filter-element type="OBJECT" id="cluster" selected="true" />
|
||||
<filter-element type="OBJECT" id="dblink" selected="true" />
|
||||
</any-schema>
|
||||
</basic-filter>
|
||||
<extended-filter>
|
||||
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
|
||||
<filter-element type="RESERVED_WORD" id="function" selected="true" />
|
||||
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
|
||||
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
|
||||
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
|
||||
<filter-element type="OBJECT" id="schema" selected="true" />
|
||||
<filter-element type="OBJECT" id="user" selected="true" />
|
||||
<filter-element type="OBJECT" id="role" selected="true" />
|
||||
<filter-element type="OBJECT" id="privilege" selected="true" />
|
||||
<user-schema>
|
||||
<filter-element type="OBJECT" id="table" selected="true" />
|
||||
<filter-element type="OBJECT" id="view" selected="true" />
|
||||
<filter-element type="OBJECT" id="materialized view" selected="true" />
|
||||
<filter-element type="OBJECT" id="index" selected="true" />
|
||||
<filter-element type="OBJECT" id="constraint" selected="true" />
|
||||
<filter-element type="OBJECT" id="trigger" selected="true" />
|
||||
<filter-element type="OBJECT" id="synonym" selected="true" />
|
||||
<filter-element type="OBJECT" id="sequence" selected="true" />
|
||||
<filter-element type="OBJECT" id="procedure" selected="true" />
|
||||
<filter-element type="OBJECT" id="function" selected="true" />
|
||||
<filter-element type="OBJECT" id="package" selected="true" />
|
||||
<filter-element type="OBJECT" id="type" selected="true" />
|
||||
<filter-element type="OBJECT" id="dimension" selected="true" />
|
||||
<filter-element type="OBJECT" id="cluster" selected="true" />
|
||||
<filter-element type="OBJECT" id="dblink" selected="true" />
|
||||
</user-schema>
|
||||
<public-schema>
|
||||
<filter-element type="OBJECT" id="table" selected="true" />
|
||||
<filter-element type="OBJECT" id="view" selected="true" />
|
||||
<filter-element type="OBJECT" id="materialized view" selected="true" />
|
||||
<filter-element type="OBJECT" id="index" selected="true" />
|
||||
<filter-element type="OBJECT" id="constraint" selected="true" />
|
||||
<filter-element type="OBJECT" id="trigger" selected="true" />
|
||||
<filter-element type="OBJECT" id="synonym" selected="true" />
|
||||
<filter-element type="OBJECT" id="sequence" selected="true" />
|
||||
<filter-element type="OBJECT" id="procedure" selected="true" />
|
||||
<filter-element type="OBJECT" id="function" selected="true" />
|
||||
<filter-element type="OBJECT" id="package" selected="true" />
|
||||
<filter-element type="OBJECT" id="type" selected="true" />
|
||||
<filter-element type="OBJECT" id="dimension" selected="true" />
|
||||
<filter-element type="OBJECT" id="cluster" selected="true" />
|
||||
<filter-element type="OBJECT" id="dblink" selected="true" />
|
||||
</public-schema>
|
||||
<any-schema>
|
||||
<filter-element type="OBJECT" id="table" selected="true" />
|
||||
<filter-element type="OBJECT" id="view" selected="true" />
|
||||
<filter-element type="OBJECT" id="materialized view" selected="true" />
|
||||
<filter-element type="OBJECT" id="index" selected="true" />
|
||||
<filter-element type="OBJECT" id="constraint" selected="true" />
|
||||
<filter-element type="OBJECT" id="trigger" selected="true" />
|
||||
<filter-element type="OBJECT" id="synonym" selected="true" />
|
||||
<filter-element type="OBJECT" id="sequence" selected="true" />
|
||||
<filter-element type="OBJECT" id="procedure" selected="true" />
|
||||
<filter-element type="OBJECT" id="function" selected="true" />
|
||||
<filter-element type="OBJECT" id="package" selected="true" />
|
||||
<filter-element type="OBJECT" id="type" selected="true" />
|
||||
<filter-element type="OBJECT" id="dimension" selected="true" />
|
||||
<filter-element type="OBJECT" id="cluster" selected="true" />
|
||||
<filter-element type="OBJECT" id="dblink" selected="true" />
|
||||
</any-schema>
|
||||
</extended-filter>
|
||||
</filters>
|
||||
<sorting enabled="true">
|
||||
<sorting-element type="RESERVED_WORD" id="keyword" />
|
||||
<sorting-element type="RESERVED_WORD" id="datatype" />
|
||||
<sorting-element type="OBJECT" id="column" />
|
||||
<sorting-element type="OBJECT" id="table" />
|
||||
<sorting-element type="OBJECT" id="view" />
|
||||
<sorting-element type="OBJECT" id="materialized view" />
|
||||
<sorting-element type="OBJECT" id="index" />
|
||||
<sorting-element type="OBJECT" id="constraint" />
|
||||
<sorting-element type="OBJECT" id="trigger" />
|
||||
<sorting-element type="OBJECT" id="synonym" />
|
||||
<sorting-element type="OBJECT" id="sequence" />
|
||||
<sorting-element type="OBJECT" id="procedure" />
|
||||
<sorting-element type="OBJECT" id="function" />
|
||||
<sorting-element type="OBJECT" id="package" />
|
||||
<sorting-element type="OBJECT" id="type" />
|
||||
<sorting-element type="OBJECT" id="dimension" />
|
||||
<sorting-element type="OBJECT" id="cluster" />
|
||||
<sorting-element type="OBJECT" id="dblink" />
|
||||
<sorting-element type="OBJECT" id="schema" />
|
||||
<sorting-element type="OBJECT" id="role" />
|
||||
<sorting-element type="OBJECT" id="user" />
|
||||
<sorting-element type="RESERVED_WORD" id="function" />
|
||||
<sorting-element type="RESERVED_WORD" id="parameter" />
|
||||
</sorting>
|
||||
<format>
|
||||
<enforce-code-style-case value="true" />
|
||||
</format>
|
||||
</code-completion-settings>
|
||||
<execution-engine-settings>
|
||||
<statement-execution>
|
||||
<fetch-block-size value="100" />
|
||||
<execution-timeout value="20" />
|
||||
<debug-execution-timeout value="600" />
|
||||
<focus-result value="false" />
|
||||
<prompt-execution value="false" />
|
||||
</statement-execution>
|
||||
<script-execution>
|
||||
<command-line-interfaces />
|
||||
<execution-timeout value="300" />
|
||||
</script-execution>
|
||||
<method-execution>
|
||||
<execution-timeout value="30" />
|
||||
<debug-execution-timeout value="600" />
|
||||
<parameter-history-size value="10" />
|
||||
</method-execution>
|
||||
</execution-engine-settings>
|
||||
<operation-settings>
|
||||
<transactions>
|
||||
<uncommitted-changes>
|
||||
<on-project-close value="ASK" />
|
||||
<on-disconnect value="ASK" />
|
||||
<on-autocommit-toggle value="ASK" />
|
||||
</uncommitted-changes>
|
||||
<multiple-uncommitted-changes>
|
||||
<on-commit value="ASK" />
|
||||
<on-rollback value="ASK" />
|
||||
</multiple-uncommitted-changes>
|
||||
</transactions>
|
||||
<session-browser>
|
||||
<disconnect-session value="ASK" />
|
||||
<kill-session value="ASK" />
|
||||
<reload-on-filter-change value="false" />
|
||||
</session-browser>
|
||||
<compiler>
|
||||
<compile-type value="KEEP" />
|
||||
<compile-dependencies value="ASK" />
|
||||
<always-show-controls value="false" />
|
||||
</compiler>
|
||||
<debugger>
|
||||
<debugger-type value="JDBC" />
|
||||
<use-generic-runners value="true" />
|
||||
</debugger>
|
||||
</operation-settings>
|
||||
<ddl-file-settings>
|
||||
<extensions>
|
||||
<mapping file-type-id="VIEW" extensions="vw" />
|
||||
<mapping file-type-id="TRIGGER" extensions="trg" />
|
||||
<mapping file-type-id="PROCEDURE" extensions="prc" />
|
||||
<mapping file-type-id="FUNCTION" extensions="fnc" />
|
||||
<mapping file-type-id="PACKAGE" extensions="pkg" />
|
||||
<mapping file-type-id="PACKAGE_SPEC" extensions="pks" />
|
||||
<mapping file-type-id="PACKAGE_BODY" extensions="pkb" />
|
||||
<mapping file-type-id="TYPE" extensions="tpe" />
|
||||
<mapping file-type-id="TYPE_SPEC" extensions="tps" />
|
||||
<mapping file-type-id="TYPE_BODY" extensions="tpb" />
|
||||
</extensions>
|
||||
<general>
|
||||
<lookup-ddl-files value="true" />
|
||||
<create-ddl-files value="false" />
|
||||
<synchronize-ddl-files value="true" />
|
||||
<use-qualified-names value="false" />
|
||||
<make-scripts-rerunnable value="true" />
|
||||
</general>
|
||||
</ddl-file-settings>
|
||||
<general-settings>
|
||||
<regional-settings>
|
||||
<date-format value="MEDIUM" />
|
||||
<number-format value="UNGROUPED" />
|
||||
<locale value="SYSTEM_DEFAULT" />
|
||||
<use-custom-formats value="false" />
|
||||
</regional-settings>
|
||||
<environment>
|
||||
<environment-types>
|
||||
<environment-type id="development" name="Development" description="Development environment" color="-2430209/-12296320" readonly-code="false" readonly-data="false" />
|
||||
<environment-type id="integration" name="Integration" description="Integration environment" color="-2621494/-12163514" readonly-code="true" readonly-data="false" />
|
||||
<environment-type id="production" name="Production" description="Productive environment" color="-11574/-10271420" readonly-code="true" readonly-data="true" />
|
||||
<environment-type id="other" name="Other" description="" color="-1576/-10724543" readonly-code="false" readonly-data="false" />
|
||||
</environment-types>
|
||||
<visibility-settings>
|
||||
<connection-tabs value="true" />
|
||||
<dialog-headers value="true" />
|
||||
<object-editor-tabs value="true" />
|
||||
<script-editor-tabs value="false" />
|
||||
<execution-result-tabs value="true" />
|
||||
</visibility-settings>
|
||||
</environment>
|
||||
</general-settings>
|
||||
</component>
|
||||
<component name="DBNavigator.Project.StatementExecutionManager">
|
||||
<execution-variables />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,3 @@
|
|||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="Administrator" />
|
||||
</component>
|
|
@ -0,0 +1,3 @@
|
|||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="swallow" />
|
||||
</component>
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="" charset="UTF-8" />
|
||||
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/dd327101-360a-4ce3-a39e-0a30a298d5cf/console.sql" charset="US-ASCII" />
|
||||
<file url="file://$PROJECT_DIR$/OutPut/Log/api_2020-05-31.log" charset="GBK" />
|
||||
<file url="file://$PROJECT_DIR$/common/comparator/comparators.py" charset="US-ASCII" />
|
||||
<file url="file://$PROJECT_DIR$/common/comparator/extractor.py" charset="US-ASCII" />
|
||||
<file url="file://$PROJECT_DIR$/common/comparator/loaders.py" charset="US-ASCII" />
|
||||
<file url="file://$PROJECT_DIR$/common/comparator/validator.py" charset="US-ASCII" />
|
||||
<file url="file://$PROJECT_DIR$/config/questionnaire_sql.sql" charset="GBK" />
|
||||
<file url="file://$PROJECT_DIR$/data/booking/json_file/ai/ai_sql.json" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/data/booking/json_file/safe.json" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/data/booking/json_file/wifi/clear_wifi_data.json" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/data/moduleA/db_config/db_config.json" charset="US-ASCII" />
|
||||
<file url="file://$PROJECT_DIR$/data/moduleA/sql_file/safe.sql" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/html_script/javas.js" charset="US-ASCII" />
|
||||
<file url="file://$PROJECT_DIR$/temp/sjk.py" charset="US-ASCII" />
|
||||
<file url="file://$PROJECT_DIR$/test_d/day_four.txt" charset="GBK" />
|
||||
<file url="file://$PROJECT_DIR$/test_d/t" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/test_script/auto_script/login.py" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/test_script/bgy/AI_comparison_data.py" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/test_script/bgy/AI_data_comparison.py" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/test_script/bgy/__init__.py" charset="US-ASCII" />
|
||||
<file url="file://$PROJECT_DIR$/test_script/bgy/get_security_check_type_info.py" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/test_script/bgy/run_sql.py" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JsonSchemaMappingsProjectConfiguration">
|
||||
<state>
|
||||
<map>
|
||||
<entry key="Doki Master Themes">
|
||||
<value>
|
||||
<SchemaInfo>
|
||||
<option name="name" value="Doki Master Themes" />
|
||||
<option name="relativePathToSchema" value="$APPLICATION_PLUGINS_DIR$/doki-theme-jetbrains/lib/doki-theme-jetbrains-18.0.0.jar!/theme-schema/master.theme.schema.json" />
|
||||
<option name="applicationDefined" value="true" />
|
||||
<option name="patterns">
|
||||
<list>
|
||||
<Item>
|
||||
<option name="path" value="data/bgy/json_file/assert_date_upload.json" />
|
||||
</Item>
|
||||
</list>
|
||||
</option>
|
||||
</SchemaInfo>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</state>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9" project-jdk-type="Python SDK" />
|
||||
<component name="PyCharmProfessionalAdvertiser">
|
||||
<option name="shown" value="true" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/api_project.iml" filepath="$PROJECT_DIR$/.idea/api_project.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="mqttClient">
|
||||
<option name="connectionConfigurations">
|
||||
<list>
|
||||
<ConnectionConfiguration>
|
||||
<option name="clientId" value="mqtt_74ghr0tw" />
|
||||
<option name="host" value="ibs-test.bzlrobot.com" />
|
||||
<option name="id" value="ibs-test.bzlrobot.com:1883@mqtt_74ghr0tw" />
|
||||
<option name="lastWillTopic" value="" />
|
||||
<option name="name" value="TV" />
|
||||
<option name="port" value="1883" />
|
||||
<option name="username" value="" />
|
||||
<option name="utf8EncoderData" value="true" />
|
||||
<option name="lastWillPayload" value="" />
|
||||
</ConnectionConfiguration>
|
||||
</list>
|
||||
</option>
|
||||
<option name="subscribeConfigurations">
|
||||
<list>
|
||||
<SubscribeMessage>
|
||||
<option name="alias" value="" />
|
||||
<option name="connectionId" value="ibs-test.bzlrobot.com:1883@mqtt_74ghr0tw" />
|
||||
<option name="topic" value="appTv/210929094850758001/KQ" />
|
||||
</SubscribeMessage>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.5" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PackageRequirementsSettings">
|
||||
<option name="requirementsPath" value="" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
<option name="format" value="GOOGLE" />
|
||||
<option name="myDocStringFormat" value="Google" />
|
||||
<option name="renderExternalDocumentation" value="true" />
|
||||
</component>
|
||||
</module>
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="PROJECT" dialect="MySQL" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,746 @@
|
|||
# coding=utf-8
|
||||
"""
|
||||
A连接信息 TestRunner for use with the Python unit testing framework. It
|
||||
generates a HTML report to show the result at a glance.
|
||||
The simplest way to use this is to invoke its main method. E列表.g.
|
||||
import unittest
|
||||
import HTMLTestRunner
|
||||
... define your tests ...
|
||||
if __name__ == '__main__':
|
||||
HTMLTestRunner.main()
|
||||
For more customization options, instantiates a HTMLTestRunner object.
|
||||
HTMLTestRunner is a counterpart to unittest's TextTestRunner. E列表.g.
|
||||
# output to a file
|
||||
fp = file('my_report.html', 'wb')
|
||||
runner = HTMLTestRunner.HTMLTestRunner(
|
||||
stream=fp,
|
||||
title='My unit test',
|
||||
description='This demonstrates the report output by HTMLTestRunner.'
|
||||
)
|
||||
# Use an external stylesheet.
|
||||
# See the Template_mixin class for more customizable options
|
||||
runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'
|
||||
# run the test
|
||||
runner.run(my_test_suite)
|
||||
------------------------------------------------------------------------
|
||||
Copyright (c) 2004-2007, Wai Yip Tung
|
||||
All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name Wai Yip Tung nor the names of its contributors may be
|
||||
used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A连接信息
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
"""
|
||||
|
||||
# URL: http://tungwaiyip.info/software/HTMLTestRunner.html
|
||||
|
||||
__author__ = "Wai Yip Tung, Findyou"
|
||||
__version__ = "0.8.2.2"
|
||||
|
||||
"""
|
||||
Change History
|
||||
Version 0.8.2.1 -Findyou
|
||||
* 改为支持python3
|
||||
Version 0.8.2.1 -Findyou
|
||||
* 支持中文,汉化
|
||||
* 调整样式,美化(需要连入网络,使用的百度的Bootstrap.js)
|
||||
* 增加 通过分类显示、测试人员、通过率的展示
|
||||
* 优化“详细”与“收起”状态的变换
|
||||
* 增加返回顶部的锚点
|
||||
Version 0.8.2
|
||||
* Show output inline instead of popup window (Viorel Lupu).
|
||||
Version in 0.8.1
|
||||
* Validated XHTML (Wolfgang Borgert).
|
||||
* Added description of test classes and test cases.
|
||||
Version in 0.8.0
|
||||
* Define Template_mixin class for customization.
|
||||
* Workaround a IE 6 bug that it does not treat <script> block as CDATA.
|
||||
Version in 0.7.1
|
||||
* Back port to Python 2.3 (Frank Horowitz).
|
||||
* Fix missing scroll bars in detail log (Podi).
|
||||
"""
|
||||
|
||||
# TODO: color stderr
|
||||
# TODO: simplify javascript using ,ore than 1 class in the class attribute?
|
||||
|
||||
import datetime
|
||||
import io
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
from xml.sax import saxutils
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# The redirectors below are used to capture output during testing. Output
|
||||
# sent to sys.stdout and sys.stderr are automatically captured. However
|
||||
# in some cases sys.stdout is already cached before HTMLTestRunner is
|
||||
# invoked (e.g. calling logging.basicConfig). In order to capture those
|
||||
# output, use the redirectors for the cached stream.
|
||||
#
|
||||
# e.g.
|
||||
# >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
|
||||
# >>>
|
||||
|
||||
class OutputRedirector(object):
|
||||
""" Wrapper to redirect stdout or stderr """
|
||||
|
||||
def __init__(self, fp):
|
||||
self.fp = fp
|
||||
|
||||
def write(self, s):
|
||||
self.fp.write(s)
|
||||
|
||||
def writelines(self, lines):
|
||||
self.fp.writelines(lines)
|
||||
|
||||
def flush(self):
|
||||
self.fp.flush()
|
||||
|
||||
|
||||
stdout_redirector = OutputRedirector(sys.stdout)
|
||||
stderr_redirector = OutputRedirector(sys.stderr)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Template
|
||||
|
||||
class Template_mixin(object):
|
||||
"""
|
||||
Define a HTML template for report customerization and generation.
|
||||
Overall structure of an HTML report
|
||||
HTML
|
||||
+------------------------+
|
||||
|<html> |
|
||||
| <head> |
|
||||
| |
|
||||
| STYLESHEET |
|
||||
| +----------------+ |
|
||||
| | | |
|
||||
| +----------------+ |
|
||||
| |
|
||||
| </head> |
|
||||
| |
|
||||
| <body> |
|
||||
| |
|
||||
| HEADING |
|
||||
| +----------------+ |
|
||||
| | | |
|
||||
| +----------------+ |
|
||||
| |
|
||||
| REPORT |
|
||||
| +----------------+ |
|
||||
| | | |
|
||||
| +----------------+ |
|
||||
| |
|
||||
| ENDING |
|
||||
| +----------------+ |
|
||||
| | | |
|
||||
| +----------------+ |
|
||||
| |
|
||||
| </body> |
|
||||
|</html> |
|
||||
+------------------------+
|
||||
"""
|
||||
|
||||
STATUS = {
|
||||
0: '通过',
|
||||
1: '失败',
|
||||
2: '错误',
|
||||
}
|
||||
|
||||
DEFAULT_TITLE = '接口自动化测试报告'
|
||||
DEFAULT_DESCRIPTION = ''
|
||||
DEFAULT_TESTER = '陈勇志'
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# HTML Template
|
||||
|
||||
HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>%(title)s</title>
|
||||
<meta name="generator" content="%(generator)s"/>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<link href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
|
||||
<script src="http://libs.baidu.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
|
||||
%(stylesheet)s
|
||||
</head>
|
||||
<body >
|
||||
<script language="javascript" type="text/javascript">
|
||||
output_list = Array();
|
||||
/*level 调整增加只显示通过用例的分类 --Findyou
|
||||
0:Summary //all hiddenRow
|
||||
1:Failed //pt hiddenRow, ft none
|
||||
2:Pass //pt none, ft hiddenRow
|
||||
3:All //pt none, ft none
|
||||
*/
|
||||
function showCase(level) {
|
||||
trs = document.getElementsByTagName("tr");
|
||||
for (var i = 0; i < trs.length; i++) {
|
||||
tr = trs[i];
|
||||
id = tr.id;
|
||||
if (id.substr(0,2) == 'ft') {
|
||||
if (level == 2 || level == 0 ) {
|
||||
tr.className = 'hiddenRow';
|
||||
}
|
||||
else {
|
||||
tr.className = '';
|
||||
}
|
||||
}
|
||||
if (id.substr(0,2) == 'pt') {
|
||||
if (level < 2) {
|
||||
tr.className = 'hiddenRow';
|
||||
}
|
||||
else {
|
||||
tr.className = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
//加入【详细】切换文字变化 --Findyou
|
||||
detail_class=document.getElementsByClassName('detail');
|
||||
//console.log(detail_class.length)
|
||||
if (level == 3) {
|
||||
for (var i = 0; i < detail_class.length; i++){
|
||||
detail_class[i].innerHTML="收起"
|
||||
}
|
||||
}
|
||||
else{
|
||||
for (var i = 0; i < detail_class.length; i++){
|
||||
detail_class[i].innerHTML="详细"
|
||||
}
|
||||
}
|
||||
}
|
||||
function showClassDetail(cid, count) {
|
||||
var id_list = Array(count);
|
||||
var toHide = 1;
|
||||
for (var i = 0; i < count; i++) {
|
||||
//ID修改 点 为 下划线 -Findyou
|
||||
tid0 = 't' + cid.substr(1) + '_' + (i+1);
|
||||
tid = 'f' + tid0;
|
||||
tr = document.getElementById(tid);
|
||||
if (!tr) {
|
||||
tid = 'p' + tid0;
|
||||
tr = document.getElementById(tid);
|
||||
}
|
||||
id_list[i] = tid;
|
||||
if (tr.className) {
|
||||
toHide = 0;
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < count; i++) {
|
||||
tid = id_list[i];
|
||||
//修改点击无法收起的BUG,加入【详细】切换文字变化 --Findyou
|
||||
if (toHide) {
|
||||
document.getElementById(tid).className = 'hiddenRow';
|
||||
document.getElementById(cid).innerText = "详细"
|
||||
}
|
||||
else {
|
||||
document.getElementById(tid).className = '';
|
||||
document.getElementById(cid).innerText = "收起"
|
||||
}
|
||||
}
|
||||
}
|
||||
function html_escape(s) {
|
||||
s = s.replace(/&/g,'&');
|
||||
s = s.replace(/</g,'<');
|
||||
s = s.replace(/>/g,'>');
|
||||
return s;
|
||||
}
|
||||
</script>
|
||||
%(heading)s
|
||||
%(report)s
|
||||
%(ending)s
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
# variables: (title, generator, stylesheet, heading, report, ending)
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Stylesheet
|
||||
#
|
||||
# alternatively use a <link> for external style sheet, e.g.
|
||||
# <link rel="stylesheet" href="$url" type="text/css">
|
||||
|
||||
STYLESHEET_TMPL = """
|
||||
<style type="text/css" media="screen">
|
||||
body { font-family: Microsoft YaHei,Tahoma,arial,helvetica,sans-serif;padding: 20px; font-size: 120%; }
|
||||
table { font-size: 100%; }
|
||||
/* -- heading ---------------------------------------------------------------------- */
|
||||
.heading {
|
||||
margin-top: 0ex;
|
||||
margin-bottom: 1ex;
|
||||
}
|
||||
.heading .description {
|
||||
margin-top: 4ex;
|
||||
margin-bottom: 6ex;
|
||||
}
|
||||
/* -- report ------------------------------------------------------------------------ */
|
||||
#total_row { font-weight: bold; }
|
||||
.passCase { color: #5cb85c; }
|
||||
.failCase { color: #d9534f; font-weight: bold; }
|
||||
.errorCase { color: #f0ad4e; font-weight: bold; }
|
||||
.hiddenRow { display: none; }
|
||||
.testcase { margin-left: 2em; }
|
||||
</style>
|
||||
"""
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Heading
|
||||
#
|
||||
|
||||
HEADING_TMPL = """<div class='heading'>
|
||||
<h1 style="font-family: Microsoft YaHei">%(title)s</h1>
|
||||
%(parameters)s
|
||||
<p class='description'>%(description)s</p>
|
||||
</div>
|
||||
""" # variables: (title, parameters, description)
|
||||
|
||||
HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s : </strong> %(value)s</p>
|
||||
""" # variables: (name, value)
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Report
|
||||
#
|
||||
# 汉化,加美化效果 --Findyou
|
||||
REPORT_TMPL = """
|
||||
<p id='show_detail_line'>
|
||||
<a class="btn btn-primary" href='javascript:showCase(0)'>概要{ %(passrate)s }</a>
|
||||
<a class="btn btn-danger" href='javascript:showCase(1)'>失败{ %(fail)s }</a>
|
||||
<a class="btn btn-success" href='javascript:showCase(2)'>通过{ %(Pass)s }</a>
|
||||
<a class="btn btn-info" href='javascript:showCase(3)'>所有{ %(count)s }</a>
|
||||
</p>
|
||||
<table id='result_table' class="table table-condensed table-bordered table-hover">
|
||||
<colgroup>
|
||||
<col align='left' />
|
||||
<col align='right' />
|
||||
<col align='right' />
|
||||
<col align='right' />
|
||||
<col align='right' />
|
||||
<col align='right' />
|
||||
</colgroup>
|
||||
<tr id='header_row' class="text-center success" style="font-weight: bold;font-size: 16px;">
|
||||
<td>用例集/测试用例</td>
|
||||
<td>总计</td>
|
||||
<td>通过</td>
|
||||
<td>失败</td>
|
||||
<td>错误</td>
|
||||
<td>详细</td>
|
||||
</tr>
|
||||
%(test_list)s
|
||||
<tr id='total_row' class="text-center active">
|
||||
<td>总计</td>
|
||||
<td>%(count)s</td>
|
||||
<td>%(Pass)s</td>
|
||||
<td>%(fail)s</td>
|
||||
<td>%(error)s</td>
|
||||
<td>通过率:%(passrate)s</td>
|
||||
</tr>
|
||||
</table>
|
||||
""" # variables: (test_list, count, Pass, fail, error ,passrate)
|
||||
|
||||
REPORT_CLASS_TMPL = r"""
|
||||
<tr class='%(style)s warning'>
|
||||
<td>%(desc)s</td>
|
||||
<td class="text-center">%(count)s</td>
|
||||
<td class="text-center">%(Pass)s</td>
|
||||
<td class="text-center">%(fail)s</td>
|
||||
<td class="text-center">%(error)s</td>
|
||||
<td class="text-center"><a href="javascript:showClassDetail('%(cid)s',%(count)s)" class="detail" id='%(cid)s'>详细</a></td>
|
||||
</tr>
|
||||
""" # variables: (style, desc, count, Pass, fail, error, cid)
|
||||
|
||||
# 失败 的样式,去掉原来JS效果,美化展示效果 -Findyou
|
||||
REPORT_TEST_WITH_OUTPUT_TMPL = r"""
|
||||
<tr id='%(tid)s' class='%(Class)s'>
|
||||
<td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
|
||||
<td colspan='5' align='center'>
|
||||
<!--默认收起错误信息 -Findyou
|
||||
<button id='btn_%(tid)s' type="button" class="btn btn-danger btn-xs collapsed" data-toggle="collapse" data-target='#div_%(tid)s'>%(status)s</button>
|
||||
<div id='div_%(tid)s' class="collapse"> -->
|
||||
<!-- 默认展开错误信息 -Findyou -->
|
||||
<button id='btn_%(tid)s' type="button" class="btn btn btn-info btn-xs" data-toggle="collapse" data-target='#div_%(tid)s'>%(status)s</button>
|
||||
<div id='div_%(tid)s' class="collapse" align="left">
|
||||
<pre>
|
||||
%(script)s
|
||||
</pre>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
""" # variables: (tid, Class, style, desc, status)
|
||||
|
||||
# 通过 的样式,加标签效果 -Findyou
|
||||
REPORT_TEST_NO_OUTPUT_TMPL = r"""
|
||||
<tr id='%(tid)s' class='%(Class)s'>
|
||||
<td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
|
||||
<td colspan='5' align='center'><span class="label label-success success">%(status)s</span></td>
|
||||
</tr>
|
||||
""" # variables: (tid, Class, style, desc, status)
|
||||
|
||||
REPORT_TEST_OUTPUT_TMPL = r"""
|
||||
%(id)s: %(output)s
|
||||
""" # variables: (id, output)
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# ENDING
|
||||
#
|
||||
# 增加返回顶部按钮 --Findyou
|
||||
ENDING_TMPL = """<div id='ending'> </div>
|
||||
<div style=" position:fixed;right:50px; bottom:30px; width:20px; height:20px;cursor:pointer">
|
||||
<a href="#"><span class="glyphicon glyphicon-eject" style = "font-size:30px;" aria-hidden="true">
|
||||
</span></a></div>
|
||||
"""
|
||||
|
||||
|
||||
# -------------------- The end of the Template class -------------------
|
||||
|
||||
|
||||
TestResult = unittest.TestResult
|
||||
|
||||
|
||||
class _TestResult(TestResult):
|
||||
# note: _TestResult is a pure representation of results.
|
||||
# It lacks the output and reporting ability compares to unittest._TextTestResult.
|
||||
|
||||
def __init__(self, verbosity=1):
|
||||
TestResult.__init__(self)
|
||||
self.stdout0 = None
|
||||
self.stderr0 = None
|
||||
self.success_count = 0
|
||||
self.failure_count = 0
|
||||
self.error_count = 0
|
||||
self.verbosity = verbosity
|
||||
|
||||
# result is a list of result in 4 tuple
|
||||
# (
|
||||
# result code (0: success; 1: fail; 2: error),
|
||||
# TestCase object,
|
||||
# Test output (byte string),
|
||||
# stack trace,
|
||||
# )
|
||||
self.result = []
|
||||
# 增加一个测试通过率 --Findyou
|
||||
self.passrate = float(0)
|
||||
|
||||
def startTest(self, test):
|
||||
print("{0} - Start Test:{1}".format(time.asctime(), str(test)))
|
||||
TestResult.startTest(self, test)
|
||||
# just one buffer for both stdout and stderr
|
||||
self.outputBuffer = io.StringIO()
|
||||
stdout_redirector.fp = self.outputBuffer
|
||||
stderr_redirector.fp = self.outputBuffer
|
||||
self.stdout0 = sys.stdout
|
||||
self.stderr0 = sys.stderr
|
||||
sys.stdout = stdout_redirector
|
||||
sys.stderr = stderr_redirector
|
||||
|
||||
def complete_output(self):
|
||||
"""
|
||||
Disconnect output redirection and return buffer.
|
||||
Safe to call multiple times.
|
||||
"""
|
||||
if self.stdout0:
|
||||
sys.stdout = self.stdout0
|
||||
sys.stderr = self.stderr0
|
||||
self.stdout0 = None
|
||||
self.stderr0 = None
|
||||
return self.outputBuffer.getvalue()
|
||||
|
||||
def stopTest(self, test):
|
||||
# Usually one of addSuccess, addError or addFailure would have been called.
|
||||
# But there are some path in unittest that would bypass this.
|
||||
# We must disconnect stdout in stopTest(), which is guaranteed to be called.
|
||||
self.complete_output()
|
||||
|
||||
def addSuccess(self, test):
|
||||
self.success_count += 1
|
||||
TestResult.addSuccess(self, test)
|
||||
output = self.complete_output()
|
||||
self.result.append((0, test, output, ''))
|
||||
if self.verbosity > 1:
|
||||
sys.stderr.write('ok ')
|
||||
sys.stderr.write(str(test))
|
||||
sys.stderr.write('\n')
|
||||
else:
|
||||
sys.stderr.write('.')
|
||||
|
||||
def addError(self, test, err):
|
||||
self.error_count += 1
|
||||
TestResult.addError(self, test, err)
|
||||
_, _exc_str = self.errors[-1]
|
||||
output = self.complete_output()
|
||||
self.result.append((2, test, output, _exc_str))
|
||||
if self.verbosity > 1:
|
||||
sys.stderr.write('E列表 ')
|
||||
sys.stderr.write(str(test))
|
||||
sys.stderr.write('\n')
|
||||
else:
|
||||
sys.stderr.write('E列表')
|
||||
|
||||
def addFailure(self, test, err):
|
||||
self.failure_count += 1
|
||||
TestResult.addFailure(self, test, err)
|
||||
_, _exc_str = self.failures[-1]
|
||||
output = self.complete_output()
|
||||
self.result.append((1, test, output, _exc_str))
|
||||
if self.verbosity > 1:
|
||||
sys.stderr.write('F ')
|
||||
sys.stderr.write(str(test))
|
||||
sys.stderr.write('\n')
|
||||
else:
|
||||
sys.stderr.write('F')
|
||||
|
||||
|
||||
class HTMLTestRunner(Template_mixin):
|
||||
"""
|
||||
"""
|
||||
|
||||
def __init__(self, stream=sys.stdout, verbosity=2, title=None, description=None, tester=None):
|
||||
self.stream = stream
|
||||
self.verbosity = verbosity
|
||||
if title is None:
|
||||
self.title = self.DEFAULT_TITLE
|
||||
else:
|
||||
self.title = title
|
||||
if description is None:
|
||||
self.description = self.DEFAULT_DESCRIPTION
|
||||
else:
|
||||
self.description = description
|
||||
if tester is None:
|
||||
self.tester = self.DEFAULT_TESTER
|
||||
else:
|
||||
self.tester = tester
|
||||
|
||||
self.startTime = datetime.datetime.now()
|
||||
|
||||
def run(self, test):
|
||||
"Run the given test case or test suite."
|
||||
result = _TestResult(self.verbosity)
|
||||
test(result)
|
||||
self.stopTime = datetime.datetime.now()
|
||||
self.generateReport(test, result)
|
||||
print('\nTime Elapsed: %s' % (self.stopTime - self.startTime), file=sys.stderr)
|
||||
return result
|
||||
|
||||
def sortResult(self, result_list):
|
||||
# unittest does not seems to run in any particular order.
|
||||
# Here at least we want to group them together by class.
|
||||
rmap = {}
|
||||
classes = []
|
||||
for n, t, o, e in result_list:
|
||||
cls = t.__class__
|
||||
if cls not in rmap:
|
||||
rmap[cls] = []
|
||||
classes.append(cls)
|
||||
rmap[cls].append((n, t, o, e))
|
||||
r = [(cls, rmap[cls]) for cls in classes]
|
||||
return r
|
||||
|
||||
# 替换测试结果status为通过率 --Findyou
|
||||
def getReportAttributes(self, result):
|
||||
"""
|
||||
Return report attributes as a list of (name, value).
|
||||
Override this to add custom attributes.
|
||||
"""
|
||||
startTime = str(self.startTime)[:19]
|
||||
duration = str(self.stopTime - self.startTime)
|
||||
status = []
|
||||
status.append('共 %s' % (result.success_count + result.failure_count + result.error_count))
|
||||
if result.success_count: status.append('通过 %s' % result.success_count)
|
||||
if result.failure_count: status.append('失败 %s' % result.failure_count)
|
||||
if result.error_count: status.append('错误 %s' % result.error_count)
|
||||
if status:
|
||||
status = ','.join(status)
|
||||
self.passrate = str("%.2f%%" % (float(result.success_count) / float(
|
||||
result.success_count + result.failure_count + result.error_count) * 100))
|
||||
else:
|
||||
status = 'none'
|
||||
return [
|
||||
('测试人员', self.tester),
|
||||
('开始时间', startTime),
|
||||
('结束时间', str(self.stopTime)),
|
||||
('合计耗时', duration),
|
||||
('测试结果', status + ",通过率= " + self.passrate),
|
||||
]
|
||||
|
||||
def generateReport(self, test, result):
|
||||
report_attrs = self.getReportAttributes(result)
|
||||
generator = 'HTMLTestRunner %s' % __version__
|
||||
stylesheet = self._generate_stylesheet()
|
||||
heading = self._generate_heading(report_attrs)
|
||||
report = self._generate_report(result)
|
||||
ending = self._generate_ending()
|
||||
output = self.HTML_TMPL % dict(
|
||||
title=saxutils.escape(self.title),
|
||||
generator=generator,
|
||||
stylesheet=stylesheet,
|
||||
heading=heading,
|
||||
report=report,
|
||||
ending=ending,
|
||||
)
|
||||
self.stream.write(output.encode('utf8'))
|
||||
|
||||
def _generate_stylesheet(self):
|
||||
return self.STYLESHEET_TMPL
|
||||
|
||||
# 增加Tester显示 -Findyou
|
||||
def _generate_heading(self, report_attrs):
|
||||
a_lines = []
|
||||
for name, value in report_attrs:
|
||||
line = self.HEADING_ATTRIBUTE_TMPL % dict(
|
||||
name=saxutils.escape(name),
|
||||
value=saxutils.escape(value),
|
||||
)
|
||||
a_lines.append(line)
|
||||
heading = self.HEADING_TMPL % dict(
|
||||
title=saxutils.escape(self.title),
|
||||
parameters=''.join(a_lines),
|
||||
description=saxutils.escape(self.description),
|
||||
tester=saxutils.escape(self.tester),
|
||||
)
|
||||
return heading
|
||||
|
||||
# 生成报告 --Findyou添加注释
|
||||
def _generate_report(self, result):
|
||||
rows = []
|
||||
sortedResult = self.sortResult(result.result)
|
||||
for cid, (cls, cls_results) in enumerate(sortedResult):
|
||||
# subtotal for a class
|
||||
np = nf = ne = 0
|
||||
for n, t, o, e in cls_results:
|
||||
if n == 0:
|
||||
np += 1
|
||||
elif n == 1:
|
||||
nf += 1
|
||||
else:
|
||||
ne += 1
|
||||
|
||||
# format class description
|
||||
if cls.__module__ == "__main__":
|
||||
name = cls.__name__
|
||||
else:
|
||||
name = "%s.%s" % (cls.__module__, cls.__name__)
|
||||
doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
|
||||
desc = doc and '%s: %s' % (name, doc) or name
|
||||
|
||||
row = self.REPORT_CLASS_TMPL % dict(
|
||||
style=ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
|
||||
desc=desc,
|
||||
count=np + nf + ne,
|
||||
Pass=np,
|
||||
fail=nf,
|
||||
error=ne,
|
||||
cid='c%s' % (cid + 1),
|
||||
)
|
||||
rows.append(row)
|
||||
|
||||
for tid, (n, t, o, e) in enumerate(cls_results):
|
||||
self._generate_report_test(rows, cid, tid, n, t, o, e)
|
||||
|
||||
report = self.REPORT_TMPL % dict(
|
||||
test_list=''.join(rows),
|
||||
count=str(result.success_count + result.failure_count + result.error_count),
|
||||
Pass=str(result.success_count),
|
||||
fail=str(result.failure_count),
|
||||
error=str(result.error_count),
|
||||
passrate=self.passrate,
|
||||
)
|
||||
return report
|
||||
|
||||
def _generate_report_test(self, rows, cid, tid, n, t, o, e):
|
||||
# e.g. 'pt1.1', 'ft1.1', etc
|
||||
has_output = bool(o or e)
|
||||
# ID修改点为下划线,支持Bootstrap折叠展开特效 - Findyou
|
||||
tid = (n == 0 and 'p' or 'f') + 't%s_%s' % (cid + 1, tid + 1)
|
||||
name = t.id().split('.')[-1]
|
||||
doc = t.shortDescription() or ""
|
||||
desc = doc and ('%s: %s' % (name, doc)) or name
|
||||
tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL
|
||||
|
||||
# utf-8 支持中文 - Findyou
|
||||
# o and e should be byte string because they are collected from stdout and stderr?
|
||||
if isinstance(o, str):
|
||||
# TODO: some problem with 'string_escape': it escape \n and mess up formating
|
||||
# uo = unicode(o.encode('string_escape'))
|
||||
# uo = o.decode('latin-1')
|
||||
uo = o
|
||||
else:
|
||||
uo = o
|
||||
if isinstance(e, str):
|
||||
# TODO: some problem with 'string_escape': it escape \n and mess up formating
|
||||
# ue = unicode(e.encode('string_escape'))
|
||||
# ue = e.decode('latin-1')
|
||||
ue = e
|
||||
else:
|
||||
ue = e
|
||||
|
||||
script = self.REPORT_TEST_OUTPUT_TMPL % dict(
|
||||
id=tid,
|
||||
output=saxutils.escape(uo + ue),
|
||||
)
|
||||
|
||||
row = tmpl % dict(
|
||||
tid=tid,
|
||||
Class=(n == 0 and 'hiddenRow' or 'none'),
|
||||
style=n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'passCase'),
|
||||
desc=desc,
|
||||
script=script,
|
||||
status=self.STATUS[n],
|
||||
)
|
||||
rows.append(row)
|
||||
if not has_output:
|
||||
return
|
||||
|
||||
def _generate_ending(self):
|
||||
return self.ENDING_TMPL
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Facilities for running tests from the command line
|
||||
##############################################################################
|
||||
|
||||
# Note: Reuse unittest.TestProgram to launch test. In the future we may
|
||||
# build our own launcher to support more specific command line
|
||||
# parameters like test title, CSS, etc.
|
||||
class TestProgram(unittest.TestProgram):
|
||||
"""
|
||||
A连接信息 variation of the unittest.TestProgram. Please refer to the base
|
||||
class for command line parameters.
|
||||
"""
|
||||
|
||||
def runTests(self):
|
||||
# Pick HTMLTestRunner as the default test runner.
|
||||
# base class's testRunner parameter is not useful because it means
|
||||
# we have to instantiate HTMLTestRunner before we know self.verbosity.
|
||||
if self.testRunner is None:
|
||||
self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
|
||||
unittest.TestProgram.runTests(self)
|
||||
|
||||
|
||||
main = TestProgram
|
||||
|
||||
##############################################################################
|
||||
# Executing this module from the command line
|
||||
##############################################################################
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(module=None)
|
|
@ -0,0 +1,4 @@
|
|||
2023-03-20 15:26:21,276-ERROR-logger.py-[ line:66 ] - 日志信息:None: unsupported operand type(s) for +: 'int' and 'str'
|
||||
2023-03-20 15:26:48,216-ERROR-logger.py-[ line:66 ] - 日志信息:None: exceptions must derive from BaseException
|
||||
2023-03-20 15:27:03,824-ERROR-logger.py-[ line:66 ] - 日志信息:None: No active exception to reraise
|
||||
2023-03-20 15:51:03,455-ERROR-logger.py-[ line:66 ] - 日志信息:读取excel中初始化数据: [Errno 2] No such file or directory: 'D:\\apk_api\\api-test-project\\data\\bgy\\excel_file\\test_api.xlsx'
|
|
@ -0,0 +1,5 @@
|
|||
2023-03-20 15:51:03,454-INFO-logger.py-[ line:68 ] - 日志信息:读取测试用例excel文件:D:\apk_api\api-test-project\data\bgy\excel_file\test_api.xlsx
|
||||
2023-03-20 15:53:11,029-INFO-logger.py-[ line:68 ] - 日志信息:读取测试用例excel文件:D:\apk_api\api-test-project\data\module_1\test_cases\test_api.xlsx
|
||||
2023-03-20 15:53:11,210-INFO-logger.py-[ line:68 ] - 日志信息:如下是初始化得到得数据:{'id': 2, 'environment': 'bsp UAT 环境', 'host': 'https://bimdc.bzlrobot.com', 'path': '/bsp/pre/user/ugs', 'databases': '{\n "host": "10.8.203.157",\n "port": 3306,\n "database": "ibs_ai_iot",\n "user":"root",\n "password":"csbdr2020"\n}', 'sheets': '["人员定位大屏"]', 'initialize_data': '{\n"{{account}}":"luoshunwen01",\n"{{passwd}}":"hkTagcl3KTV3GkOP0YwvTvfcFj5rNtW2pHSQM3hWXbv3EG6Fn+yoD+58eU14Z+PT6AoRfiNljNIUGtA0vvooZQVyJ+EFQB0wtucPuGmNsCqYEaI4IlSxZqBVOg+frHLs8BEhSH4DLhuO3kzOYT8r20MTefRBgo83rBmiM5hVHjc=",\n"{{projectId}}":"103672"\n}', 'run': 'YES'}
|
||||
2023-03-20 15:55:54,590-INFO-logger.py-[ line:65 ] - 日志信息:读取测试用例excel文件:D:\apk_api\api-test-project\data\module_1\test_cases\test_api.xlsx
|
||||
2023-03-20 15:55:54,771-INFO-logger.py-[ line:65 ] - 日志信息:如下是初始化得到得数据:{'id': 2, 'environment': 'bsp UAT 环境', 'host': 'https://bimdc.bzlrobot.com', 'path': '/bsp/pre/user/ugs', 'databases': '{\n "host": "10.8.203.157",\n "port": 3306,\n "database": "ibs_ai_iot",\n "user":"root",\n "password":"csbdr2020"\n}', 'sheets': '["人员定位大屏"]', 'initialize_data': '{\n"{{account}}":"luoshunwen01",\n"{{passwd}}":"hkTagcl3KTV3GkOP0YwvTvfcFj5rNtW2pHSQM3hWXbv3EG6Fn+yoD+58eU14Z+PT6AoRfiNljNIUGtA0vvooZQVyJ+EFQB0wtucPuGmNsCqYEaI4IlSxZqBVOg+frHLs8BEhSH4DLhuO3kzOYT8r20MTefRBgo83rBmiM5hVHjc=",\n"{{projectId}}":"103672"\n}', 'run': 'YES'}
|
|
@ -0,0 +1 @@
|
|||
2023-03-22 15:35:58,590-ERROR-logger.py-[ line:63 ] - 日志信息:Error evaluating expression '$.data[].age[].a': Parse error at 1:7 near token ] (])
|
|
@ -0,0 +1,3 @@
|
|||
2023-03-23 14:52:08,726-ERROR-logger.py-[ line:63 ] - 日志信息:反序列化:['baz', false] 失败, -->错误信息:Expecting value: line 1 column 2 (char 1)
|
||||
2023-03-23 15:03:49,729-ERROR-logger.py-[ line:63 ] - 日志信息:反序列化:['baz', false] 失败, -->错误信息:Expecting value: line 1 column 2 (char 1)
|
||||
2023-03-23 15:04:04,275-ERROR-logger.py-[ line:63 ] - 日志信息:反序列化:['baz', false] 失败, -->错误信息:Expecting value: line 1 column 2 (char 1)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,6 @@
|
|||
2023-03-27 12:01:41,162-ERROR-logger.py-[ line:63 ] - 日志信息:关闭数据库失败: 'DoMysql' object has no attribute 'cur'
|
||||
2023-03-27 17:12:56,494-ERROR-logger.py-[ line:63 ] - 日志信息:读取excel中初始化数据异常: Value does not match pattern ^[$]?([A-Za-z]{1,3})[$]?(\d+)(:[$]?([A-Za-z]{1,3})[$]?(\d+)?)?$|^[A-Za-z]{1,3}:[A-Za-z]{1,3}$
|
||||
2023-03-27 17:13:18,428-ERROR-logger.py-[ line:63 ] - 日志信息:读取excel中初始化数据异常: Value does not match pattern ^[$]?([A-Za-z]{1,3})[$]?(\d+)(:[$]?([A-Za-z]{1,3})[$]?(\d+)?)?$|^[A-Za-z]{1,3}:[A-Za-z]{1,3}$
|
||||
2023-03-27 17:20:34,535-ERROR-logger.py-[ line:63 ] - 日志信息:读取excel中初始化数据异常: Value does not match pattern ^[$]?([A-Za-z]{1,3})[$]?(\d+)(:[$]?([A-Za-z]{1,3})[$]?(\d+)?)?$|^[A-Za-z]{1,3}:[A-Za-z]{1,3}$
|
||||
2023-03-27 17:23:09,571-ERROR-logger.py-[ line:63 ] - 日志信息:读取excel中初始化数据异常: Value does not match pattern ^[$]?([A-Za-z]{1,3})[$]?(\d+)(:[$]?([A-Za-z]{1,3})[$]?(\d+)?)?$|^[A-Za-z]{1,3}:[A-Za-z]{1,3}$
|
||||
2023-03-27 17:31:29,036-ERROR-logger.py-[ line:63 ] - 日志信息:读取excel中初始化数据异常: Value does not match pattern ^[$]?([A-Za-z]{1,3})[$]?(\d+)(:[$]?([A-Za-z]{1,3})[$]?(\d+)?)?$|^[A-Za-z]{1,3}:[A-Za-z]{1,3}$
|
|
@ -0,0 +1,5 @@
|
|||
2023-03-27 17:12:54,345-INFO-logger.py-[ line:65 ] - 日志信息:读取测试用例excel文件:D:\apk_api\api-test-project\data\module_1\test_cases\test_api.xlsx
|
||||
2023-03-27 17:13:16,482-INFO-logger.py-[ line:65 ] - 日志信息:读取测试用例excel文件:D:\apk_api\api-test-project\data\module_1\test_cases\test_api.xlsx
|
||||
2023-03-27 17:20:32,642-INFO-logger.py-[ line:65 ] - 日志信息:读取测试用例excel文件:D:\apk_api\api-test-project\data\module_1\test_cases\test_api.xlsx
|
||||
2023-03-27 17:23:07,522-INFO-logger.py-[ line:65 ] - 日志信息:读取测试用例excel文件:D:\apk_api\api-test-project\data\module_1\test_cases\test_api.xlsx
|
||||
2023-03-27 17:31:27,217-INFO-logger.py-[ line:65 ] - 日志信息:读取测试用例excel文件:D:\apk_api\api-test-project\data\module_1\test_cases\test_api.xlsx
|
|
@ -0,0 +1,205 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>test_api 接口自动化测试报告</title>
|
||||
<meta name="generator" content="HTMLTestRunner 0.8.2.2"/>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<link href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
|
||||
<script src="http://libs.baidu.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
|
||||
|
||||
<style type="text/css" media="screen">
|
||||
body { font-family: Microsoft YaHei,Tahoma,arial,helvetica,sans-serif;padding: 20px; font-size: 120%; }
|
||||
table { font-size: 100%; }
|
||||
/* -- heading ---------------------------------------------------------------------- */
|
||||
.heading {
|
||||
margin-top: 0ex;
|
||||
margin-bottom: 1ex;
|
||||
}
|
||||
.heading .description {
|
||||
margin-top: 4ex;
|
||||
margin-bottom: 6ex;
|
||||
}
|
||||
/* -- report ------------------------------------------------------------------------ */
|
||||
#total_row { font-weight: bold; }
|
||||
.passCase { color: #5cb85c; }
|
||||
.failCase { color: #d9534f; font-weight: bold; }
|
||||
.errorCase { color: #f0ad4e; font-weight: bold; }
|
||||
.hiddenRow { display: none; }
|
||||
.testcase { margin-left: 2em; }
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body >
|
||||
<script language="javascript" type="text/javascript">
|
||||
output_list = Array();
|
||||
/*level 调整增加只显示通过用例的分类 --Findyou
|
||||
0:Summary //all hiddenRow
|
||||
1:Failed //pt hiddenRow, ft none
|
||||
2:Pass //pt none, ft hiddenRow
|
||||
3:All //pt none, ft none
|
||||
*/
|
||||
function showCase(level) {
|
||||
trs = document.getElementsByTagName("tr");
|
||||
for (var i = 0; i < trs.length; i++) {
|
||||
tr = trs[i];
|
||||
id = tr.id;
|
||||
if (id.substr(0,2) == 'ft') {
|
||||
if (level == 2 || level == 0 ) {
|
||||
tr.className = 'hiddenRow';
|
||||
}
|
||||
else {
|
||||
tr.className = '';
|
||||
}
|
||||
}
|
||||
if (id.substr(0,2) == 'pt') {
|
||||
if (level < 2) {
|
||||
tr.className = 'hiddenRow';
|
||||
}
|
||||
else {
|
||||
tr.className = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
//加入【详细】切换文字变化 --Findyou
|
||||
detail_class=document.getElementsByClassName('detail');
|
||||
//console.log(detail_class.length)
|
||||
if (level == 3) {
|
||||
for (var i = 0; i < detail_class.length; i++){
|
||||
detail_class[i].innerHTML="收起"
|
||||
}
|
||||
}
|
||||
else{
|
||||
for (var i = 0; i < detail_class.length; i++){
|
||||
detail_class[i].innerHTML="详细"
|
||||
}
|
||||
}
|
||||
}
|
||||
function showClassDetail(cid, count) {
|
||||
var id_list = Array(count);
|
||||
var toHide = 1;
|
||||
for (var i = 0; i < count; i++) {
|
||||
//ID修改 点 为 下划线 -Findyou
|
||||
tid0 = 't' + cid.substr(1) + '_' + (i+1);
|
||||
tid = 'f' + tid0;
|
||||
tr = document.getElementById(tid);
|
||||
if (!tr) {
|
||||
tid = 'p' + tid0;
|
||||
tr = document.getElementById(tid);
|
||||
}
|
||||
id_list[i] = tid;
|
||||
if (tr.className) {
|
||||
toHide = 0;
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < count; i++) {
|
||||
tid = id_list[i];
|
||||
//修改点击无法收起的BUG,加入【详细】切换文字变化 --Findyou
|
||||
if (toHide) {
|
||||
document.getElementById(tid).className = 'hiddenRow';
|
||||
document.getElementById(cid).innerText = "详细"
|
||||
}
|
||||
else {
|
||||
document.getElementById(tid).className = '';
|
||||
document.getElementById(cid).innerText = "收起"
|
||||
}
|
||||
}
|
||||
}
|
||||
function html_escape(s) {
|
||||
s = s.replace(/&/g,'&');
|
||||
s = s.replace(/</g,'<');
|
||||
s = s.replace(/>/g,'>');
|
||||
return s;
|
||||
}
|
||||
</script>
|
||||
<div class='heading'>
|
||||
<h1 style="font-family: Microsoft YaHei">test_api 接口自动化测试报告</h1>
|
||||
<p class='attribute'><strong>测试人员 : </strong> 陈勇志</p>
|
||||
<p class='attribute'><strong>开始时间 : </strong> 2022-04-21 09:41:10</p>
|
||||
<p class='attribute'><strong>结束时间 : </strong> 2022-04-21 09:41:10.286865</p>
|
||||
<p class='attribute'><strong>合计耗时 : </strong> 0:00:00.001028</p>
|
||||
<p class='attribute'><strong>测试结果 : </strong> 共 1,错误 1,通过率= 0.00%</p>
|
||||
|
||||
<p class='description'>接口自动化测试</p>
|
||||
</div>
|
||||
|
||||
|
||||
<p id='show_detail_line'>
|
||||
<a class="btn btn-primary" href='javascript:showCase(0)'>概要{ 0.00% }</a>
|
||||
<a class="btn btn-danger" href='javascript:showCase(1)'>失败{ 0 }</a>
|
||||
<a class="btn btn-success" href='javascript:showCase(2)'>通过{ 0 }</a>
|
||||
<a class="btn btn-info" href='javascript:showCase(3)'>所有{ 1 }</a>
|
||||
</p>
|
||||
<table id='result_table' class="table table-condensed table-bordered table-hover">
|
||||
<colgroup>
|
||||
<col align='left' />
|
||||
<col align='right' />
|
||||
<col align='right' />
|
||||
<col align='right' />
|
||||
<col align='right' />
|
||||
<col align='right' />
|
||||
</colgroup>
|
||||
<tr id='header_row' class="text-center success" style="font-weight: bold;font-size: 16px;">
|
||||
<td>用例集/测试用例</td>
|
||||
<td>总计</td>
|
||||
<td>通过</td>
|
||||
<td>失败</td>
|
||||
<td>错误</td>
|
||||
<td>详细</td>
|
||||
</tr>
|
||||
|
||||
<tr class='errorClass warning'>
|
||||
<td>unittest.loader._FailedTest</td>
|
||||
<td class="text-center">1</td>
|
||||
<td class="text-center">0</td>
|
||||
<td class="text-center">0</td>
|
||||
<td class="text-center">1</td>
|
||||
<td class="text-center"><a href="javascript:showClassDetail('c1',1)" class="detail" id='c1'>详细</a></td>
|
||||
</tr>
|
||||
|
||||
<tr id='ft1_1' class='none'>
|
||||
<td class='errorCase'><div class='testcase'>test_api</div></td>
|
||||
<td colspan='5' align='center'>
|
||||
<!--默认收起错误信息 -Findyou
|
||||
<button id='btn_ft1_1' type="button" class="btn btn-danger btn-xs collapsed" data-toggle="collapse" data-target='#div_ft1_1'>错误</button>
|
||||
<div id='div_ft1_1' class="collapse"> -->
|
||||
<!-- 默认展开错误信息 -Findyou -->
|
||||
<button id='btn_ft1_1' type="button" class="btn btn btn-info btn-xs" data-toggle="collapse" data-target='#div_ft1_1'>错误</button>
|
||||
<div id='div_ft1_1' class="collapse" align="left">
|
||||
<pre>
|
||||
|
||||
ft1_1: ImportError: Failed to import test module: test_api
|
||||
Traceback (most recent call last):
|
||||
File "D:\Program Files\python39\lib\unittest\loader.py", line 436, in _find_test_path
|
||||
module = cls._get_module_from_name(name)
|
||||
File "D:\Program Files\python39\lib\unittest\loader.py", line 377, in _get_module_from_name
|
||||
__import__(name)
|
||||
File "D:\api-test-project\script\bgy\test_api.py", line 37, in <module>
|
||||
header = login(init_data[0], init_data[1].get('{{account}}'), init_data[1].get('{{passwd}}'))
|
||||
KeyError: 0
|
||||
|
||||
|
||||
|
||||
</pre>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr id='total_row' class="text-center active">
|
||||
<td>总计</td>
|
||||
<td>1</td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
<td>通过率:0.00%</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div id='ending'> </div>
|
||||
<div style=" position:fixed;right:50px; bottom:30px; width:20px; height:20px;cursor:pointer">
|
||||
<a href="#"><span class="glyphicon glyphicon-eject" style = "font-size:30px;" aria-hidden="true">
|
||||
</span></a></div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2019/11/18 10:15
|
||||
# @Author : kira
|
||||
# @Email : 262667641@qq.com
|
||||
# @File : __init__.py.py
|
||||
# @Project : risk_api_project
|
||||
|
||||
|
Binary file not shown.
|
@ -0,0 +1,39 @@
|
|||
# Case
|
||||
|
||||
#### 项目介绍
|
||||
接口自动化测试项目
|
||||
|
||||
#### 软件架构
|
||||
软件架构说明
|
||||
unittest + pymysql + openpyxl + HTMLTestRunner
|
||||
|
||||
#### 安装教程
|
||||
|
||||
1. pip install -r requirement.txt
|
||||
2. excel 按照格式写入测试用例
|
||||
3. 执行.bat 文件
|
||||
|
||||
#### 使用说明
|
||||
|
||||
1. 提取参数方式可以使用正则,jsonpath及json逐层取值
|
||||
2. xxxx
|
||||
3. Personalinformation
|
||||
|
||||
#### 介绍
|
||||
Python随机生成个人信息, 包括姓名、性别、年龄、出生日期、身份证号、银行卡号、电话、手机号、邮箱等信息。
|
||||
|
||||
1、生成随机个人信息
|
||||
```
|
||||
PS D:\api-test-project> python .\main_personal_information.py 海省广州市南
|
||||
请输入你需要生成的数据总数:10
|
||||
+--------+------+------+------------+--------------------+----------------------+--------------+-------------+-------------------------+----------------------------------------+--------------------+ 门特别行政区
|
||||
| 用户名 | 性别 | 年龄 | 生日 | 身份证 | 银行卡或信用卡 | 座机 | 手机号 | 邮箱 | 地址 | 统一社会信用代码 |
|
||||
+--------+------+------+------------+--------------------+----------------------+--------------+-------------+-------------------------+----------------------------------------+--------------------+ 西省张家港市
|
||||
| 刘世 | 男 | 57 | 1965-07-21 | 342422196507219413 | 62263745016607204 | 0744-3715089 | 14705083324 | liushi@tianya.cn | 甘肃省辽阳县沙湾海口街y座 180548 | 713101514054645086 |
|
||||
| 龙清承 | 男 | 56 | 1966-08-21 | 342400196608212132 | 62284150185529051643 | 0893-4423648 | 13487550352 | longqingcheng@tianya.cn | 辽宁省玉珍市黄浦重庆街z座 401247 | 93421303115117882W |
|
||||
| 刘娴 | 女 | 37 | 1985-05-23 | 411023198505239964 | 6213058212589619696 | 0562-0004972 | 17303409666 | liuxian@tianya.cn | 福建省拉萨县朝阳胡街X座 479131 | 92341122605351081N |
|
||||
| 朱晨 | 男 | 55 | 1967-10-08 | 142733196710082991 | 45128971703439970 | 0375-7394954 | 18024463495 | zhuchen@139.com | 宁夏回族自治区秀云县高坪任路Q座 544781 | 955205231604137955 |
|
||||
| 许天有 | 男 | 51 | 1971-10-17 | 330901197110176212 | 62592648755593670 | 0973-8733227 | 13413236004 | xutianyou@wo.cn | 辽宁省天津市南长深圳路f座 952461 | 91120114649961877C |
|
||||
| 陈锦 | 女 | 29 | 1993-02-12 | 452123199302128568 | 62260020819291645 | 0915-7410180 | 17509030433 | chenjin@icloud.com | 重庆市西安市沙湾广州街G座 531384 | 92445302298701343Q |
|
||||
+--------+------+------+------------+--------------------+----------------------+--------------+-------------+-------------------------+----------------------------------------+--------------------+
|
||||
```
|
|
@ -0,0 +1,925 @@
|
|||
|
||||
## 框架介绍
|
||||
|
||||
本框架主要是基于 Python + pytest + allure + log + yaml + mysql + redis + 钉钉通知 + Jenkins 实现的接口自动化框架。
|
||||
|
||||
* git地址: [https://gitee.com/yu_xiao_qi/pytest-auto-api2](https://gitee.com/yu_xiao_qi/pytest-auto-api2)
|
||||
* 项目参与者: 余少琪
|
||||
* 技术支持邮箱: 1603453211@qq.com
|
||||
* 个人博客地址: [https://blog.csdn.net/weixin_43865008](https://blog.csdn.net/weixin_43865008)
|
||||
|
||||
如果对您有帮助,请点亮 小星星 以表支持,谢谢
|
||||
|
||||

|
||||
|
||||
## 前言
|
||||
|
||||
公司突然要求你做自动化,但是没有代码基础不知道怎么做?或者有自动化基础,但是不知道如何系统性的做自动化,
|
||||
放在yaml文件中维护,不知道如何处理多业务依赖的逻辑?
|
||||
|
||||
那么这里 Gitte 中开源的自动化框架,将为你解决这些问题。
|
||||
框架主要使用 python 语言编写,结合 pytest 进行二次开发,用户仅需要在 yaml 文件中编写测试用例,
|
||||
编写成功之后,会自动生成 pytest 的代码,零基础代码小白,也可以操作。
|
||||
|
||||
本框架支持多业务接口依赖,多进程执行,mysql 数据库断言和 接口响应断言,并且用例直接在yaml文件中维护,无需编写业务代码,
|
||||
接口pytest框架生成allure报告,并且发送 企业微信通知/ 钉钉通知/ 邮箱通知/ 飞书通知,灵活配置。
|
||||
|
||||
## 实现功能
|
||||
|
||||
* 测试数据隔离, 实现数据驱动
|
||||
* 支持多接口数据依赖: 如A接口需要同时依赖B、C接口的响应数据作为参数
|
||||
* 数据库断言: 直接在测试用例中写入查询的sql即可断言,无需编写代码
|
||||
* 动态多断言: 如接口需要同时校验响应数据和sql校验,支持多场景断言
|
||||
* 自动生成用例代码: 测试人员在yaml文件中填写好测试用例, 程序可以直接生成用例代码,纯小白也能使用
|
||||
* 代理录制: 支持代理录制,生成yaml格式的测试用例
|
||||
* 统计接口的运行时长: 拓展功能,订制开关,可以决定是否需要使用
|
||||
* 日志模块: 打印每个接口的日志信息,同样订制了开关,可以决定是否需要打印日志
|
||||
* 钉钉、企业微信通知: 支持多种通知场景,执行成功之后,可选择发送钉钉、或者企业微信、邮箱通知
|
||||
* 自定义拓展字段: 如用例中需要生成的随机数据,可直接调用
|
||||
* 多线程执行
|
||||
* 支持swagger接口文档转成yaml用例,节省用例编写时间
|
||||
|
||||
|
||||
## 遇到问题
|
||||
|
||||
* 请仔细阅读文档,文档中几乎可以帮你避免所有的问题
|
||||
* 可以添加微信: being_chaoren, 添加微信会将你拉倒自动化交流群中,群内有很多热心的小伙伴,但是前提是希望你已经阅读了文档中的所有内容
|
||||
* 你也可以请作者为你解答,当然我不是免费的
|
||||
|
||||

|
||||
|
||||
## 目录结构
|
||||
|
||||
|
||||
├── common // 配置
|
||||
│ ├── conf.yaml // 公共配置
|
||||
│ ├── setting.py // 环境路径存放区域
|
||||
├── data // 测试用例数据
|
||||
├── File // 上传文件接口所需的文件存放区域
|
||||
├── logs // 日志层
|
||||
├── report // 测试报告层
|
||||
├── test_case // 测试用例代码
|
||||
├── utils // 工具类
|
||||
│ └── assertion
|
||||
│ └── assert_control.py // 断言
|
||||
│ └── assert_type.py // 断言类型
|
||||
│ └── cache_process // 缓存处理模块
|
||||
│ └── cacheControl.py
|
||||
│ └── redisControl.py
|
||||
│ └── logUtils // 日志处理模块
|
||||
│ └── logControl.py
|
||||
│ └── logDecoratrol.py // 日志装饰器
|
||||
│ └── runTimeDecoratrol.py // 统计用例执行时长装饰器
|
||||
│ └── mysqlUtils // 数据库模块
|
||||
│ └── get_sql_data.py
|
||||
│ └── mysqlControl.py
|
||||
│ └── noticUtils // 通知模块
|
||||
│ └── dingtalkControl.py // 钉钉通知
|
||||
│ └── feishuControl.py // 飞书通知
|
||||
│ └── sendmailControl.py // 邮箱通知
|
||||
│ └── weChatSendControl.py // 企业微信通知
|
||||
│ └── otherUtils // 其他工具类
|
||||
│ └── allureDate // allure封装
|
||||
│ └── allure_report_data.py // allure报告数据清洗
|
||||
│ └── allure_tools.py // allure 方法封装
|
||||
│ └── error_case_excel.py // 收集allure异常用例,生成excel测试报告
|
||||
│ └── localIpControl.py // 获取本地IP
|
||||
│ └── threadControl.py // 定时器类
|
||||
│ └── readFilesUtils // 文件操作
|
||||
│ └── caseAutomaticControl.py // 自动生成测试代码
|
||||
│ └── clean_files.py // 清理文件
|
||||
│ └── excelControl.py // 读写excel
|
||||
│ └── get_all_files_path.py // 获取所有文件路径
|
||||
│ └── get_yaml_data_analysis.py // yaml用例数据清洗
|
||||
│ └── regularControl.py // 正则
|
||||
│ └── yamlControl.py // yaml文件读写
|
||||
│ └── recordingUtils // 代理录制
|
||||
│ └── mitmproxyContorl.py
|
||||
│ └── requestsUtils
|
||||
│ └── dependentCase.py // 数据依赖处理
|
||||
│ └── requestControl.py // 请求封装
|
||||
│ └── timeUtils
|
||||
├── Readme.md // help
|
||||
├── pytest.ini
|
||||
├── run.py // 运行入口
|
||||
|
||||
|
||||
## 依赖库
|
||||
|
||||
|
||||
allure-pytest==2.9.45
|
||||
allure-python-commons==2.9.45
|
||||
atomicwrites==1.4.0
|
||||
attrs==21.2.0
|
||||
certifi==2021.10.8
|
||||
cffi==1.15.0
|
||||
charset-normalizer==2.0.7
|
||||
colorama==0.4.4
|
||||
colorlog==6.6.0
|
||||
cryptography==36.0.0
|
||||
DingtalkChatbot==1.5.3
|
||||
execnet==1.9.0
|
||||
Faker==9.8.3
|
||||
idna==3.3
|
||||
iniconfig==1.1.1
|
||||
jsonpath==0.82
|
||||
packaging==21.3
|
||||
pluggy==1.0.0
|
||||
py==1.11.0
|
||||
pycparser==2.21
|
||||
PyMySQL==1.0.2
|
||||
pyOpenSSL==21.0.0
|
||||
pyparsing==3.0.6
|
||||
pytest==6.2.5
|
||||
pytest-forked==1.3.0
|
||||
pytest-xdist==2.4.0
|
||||
python-dateutil==2.8.2
|
||||
PyYAML==6.0
|
||||
requests==2.26.0
|
||||
six==1.16.0
|
||||
text-unidecode==1.3
|
||||
toml==0.10.2
|
||||
urllib3==1.26.7
|
||||
xlrd==2.0.1
|
||||
xlutils==2.0.0
|
||||
xlwt==1.3.0
|
||||
|
||||
## 安装教程
|
||||
|
||||
首先,执行本框架之后,需要搭建好 python、jdk、 allure环境
|
||||
|
||||
搭建python教程:[http://c.biancheng.net/view/4161.html](http://c.biancheng.net/view/4161.html)
|
||||
|
||||
搭建jdk环境:[https://www.cnblogs.com/zll-wyf/p/15095664.html](https://www.cnblogs.com/zll-wyf/p/15095664.html)
|
||||
|
||||
安装allure:[https://blog.csdn.net/m0_49225959/article/details/117194318](https://blog.csdn.net/m0_49225959/article/details/117194318)
|
||||
|
||||
|
||||
如上环境如都搭建好,则安装本框架的所有第三方库依赖,执行如下命令
|
||||
|
||||
pip3 install -r requirements.txt
|
||||
|
||||
|
||||

|
||||
|
||||
如果在安装过程中出现如下 Could not find a version 类似的异常, 不用担心,可能是因为你安装的python环境
|
||||
版本和我不一致导致的,直接 pip install 库名称,不指定版本安装就可以了。
|
||||
|
||||
如上方截图说没有找到 asgiref==3.5.1,报错的意思是,没有找到3.5.1这个版本,那么直接控制台输入 pip3 install asgiref 进行安装即可
|
||||
|
||||
|
||||
## 接口文档
|
||||
|
||||
这里非常感谢一位安卓的朋友,给我推荐了开源的接口文件,框架中会针对开源接口中的登录、个人信息、收藏(新增、查看、修改、删除)等功能,编写结果自动化案例
|
||||
下方是接口文档地址,大家可以自行查看(因为开源的接口,里面有些逻辑性的功能,如修改被删除的网址接口并没有过多的做判断,
|
||||
因此用例中只写了一些基础的场景,仅供大家参考。)
|
||||
[https://wanandroid.com/blog/show/2](https://wanandroid.com/blog/show/2)
|
||||
|
||||
## 如何创建用例
|
||||
|
||||
### 创建用例步骤
|
||||
1、在data文件夹下方创建相关的yaml用例
|
||||
|
||||
2、写完之后,需要执行 utils\readFilesUtils\caseAutomaticControl.py 这个文件,生成自动化代码
|
||||
|
||||
3、执行caseAutomaticControl.py文件之后,会发现,在test_case层新增该条用例的对应代码,可直接执行该用例调试
|
||||
|
||||
4、注意,如果生成对应的测试代码之后,期间有更改过yaml用例中的内容,需要重新生成代码,必现因为更改yaml用例之后导致运行失败
|
||||
|
||||
5、当所有接口都编写好之后,可以直接运行run.py主程序,执行所有自动化接口
|
||||
|
||||
|
||||
下面我们来看一下,如何创建用例
|
||||
|
||||
### 用例中相关字段的介绍
|
||||
|
||||

|
||||
|
||||
上方截图,就是一个用例中需要维护的相关字段,下面我会对每个字段的作用,做出解释。
|
||||
|
||||

|
||||
|
||||
### 如何发送get请求
|
||||
上方了解了用例的数据结构之后,下面我们开始编写第一个get请求方式的接口。
|
||||
首先,开始编写项目之后,我们在 conf.yaml 中配置项目的域名
|
||||
|
||||

|
||||
|
||||
域名配置好之后,我们来编写测试用例,在 data 文件下面,创建一个名称为
|
||||
collect_tool_list.yaml 的用例文件,请求/lg/collect/usertools/json这个收藏网址列表接口,所有接口的详细信息,可以在接口文档中查看,下方不在做赘述
|
||||
|
||||
接口文档:[https://wanandroid.com/blog/show/2](https://wanandroid.com/blog/show/2)
|
||||
|
||||
# 公共参数
|
||||
case_common:
|
||||
allureEpic: 开发平台接口
|
||||
allureFeature: 收藏模块
|
||||
allureStory: 收藏网址列表接口
|
||||
|
||||
collect_tool_list_01:
|
||||
host: ${{host()}}
|
||||
url: /lg/collect/usertools/json
|
||||
method: GET
|
||||
detail: 查看收藏网址列表接口
|
||||
headers:
|
||||
Content-Type: multipart/form-data;
|
||||
# 这里cookie的值,写的是存入缓存的名称
|
||||
cookie: login_cookie
|
||||
# 请求的数据,是 params 还是 json、或者file、data
|
||||
requestType: data
|
||||
# 是否执行,空或者 true 都会执行
|
||||
is_run:
|
||||
data:
|
||||
pageNum: 1
|
||||
pageSize: 10
|
||||
# 是否有依赖业务,为空或者false则表示没有
|
||||
dependence_case: False
|
||||
# 依赖的数据
|
||||
dependence_case_data:
|
||||
assert:
|
||||
# 断言接口状态码
|
||||
errorCode:
|
||||
jsonpath: $.errorCode
|
||||
type: ==
|
||||
value: 0
|
||||
AssertType:
|
||||
sql:
|
||||
|
||||
get请求我们 requestType 写的是 params ,这样发送请求时,我们会将请求参数拼接中url中,最终像服务端发送请求的地址格式会为:
|
||||
|
||||
如: ${{host()}}/lg/collect/usertools/json?pageNum=1&pageSize=10
|
||||
|
||||
### 如何发送post请求
|
||||
|
||||
# 公共参数
|
||||
case_common:
|
||||
allureEpic: 开发平台接口
|
||||
allureFeature: 收藏模块
|
||||
allureStory: 收藏网址接口
|
||||
|
||||
collect_addtool_01:
|
||||
host: ${{host()}}
|
||||
url: /lg/collect/addtool/json
|
||||
method: POST
|
||||
detail: 新增收藏网址接口
|
||||
headers:
|
||||
Content-Type: multipart/form-data;
|
||||
# 这里cookie的值,写的是存入缓存的名称
|
||||
cookie: login_cookie
|
||||
# 请求的数据,是 params 还是 json、或者file、data
|
||||
requestType: data
|
||||
# 是否执行,空或者 true 都会执行
|
||||
is_run:
|
||||
data:
|
||||
name: 自动化生成收藏网址${{random_int()}}
|
||||
link: https://gitee.com/yu_xiao_qi/pytest-auto-api2
|
||||
# 是否有依赖业务,为空或者false则表示没有
|
||||
dependence_case: False
|
||||
# 依赖的数据
|
||||
dependence_case_data:
|
||||
assert:
|
||||
# 断言接口状态码
|
||||
errorCode:
|
||||
jsonpath: $.errorCode
|
||||
type: ==
|
||||
value: 0
|
||||
AssertType:
|
||||
sql:
|
||||
|
||||
这里post请求,我们需要请求的数据格式是json格式的,那么requestType 则填写为json格式。
|
||||
包括 PUT/DELETE/HEAD 请求的数据格式都是一样的,唯一不同的就是需要配置 reuqestType,
|
||||
如果需要请求的参数是json格式,则requestType我们就填写json,如果是url拼接的形式,我们就填写 params
|
||||
|
||||
### 如何测试上传文件接口
|
||||
|
||||
首先,我们将所有需要测试的文件,全部都放在 files 文件夹中
|
||||

|
||||
|
||||
requestType: file
|
||||
# 是否执行,空或者 true 都会执行
|
||||
is_run:
|
||||
data:
|
||||
file:
|
||||
xxx: 排入水体名.png
|
||||
|
||||
|
||||
在yaml文件中,我们需要注意两个地方,主要是用例中的requestType、和 filename 字段:
|
||||
|
||||
* requestType: 上传文件,我们需要更改成 file
|
||||
* file: 上传文件中,新增一个file关键字,在下方传我们需要的数据
|
||||
* file_name: 首先,这个xxx是我们公司接口定义的上传文件的参数,排入水体名.png 这个是我们放在Files这个文件夹下方的文件名称
|
||||
程序在执行的时候,会判断如果你的requestType为 file的时候,则会去执行file下方的参数,然后取到文件名称直接去执行用例
|
||||
|
||||
### 上传文件接口,即需要上传文件,又需要上传其他参数
|
||||
requestType: file
|
||||
# 是否执行,空或者 true 都会执行
|
||||
is_run:
|
||||
data:
|
||||
file:
|
||||
file_name: 排入水体名.png
|
||||
data:
|
||||
is_upload: 0
|
||||
params:
|
||||
collect: false
|
||||
|
||||
上方的这个案例,请求参数即上传了文件,又上传了其他参数
|
||||
|
||||
* 1、file: 这里下方上传的是文件参数
|
||||
* 2、data: 这个data下方是该接口,除了文件参数,还需要上传其他的参数,这个参数会以json的方式传给服务端(如果没有其他参数,可以不用写这个)
|
||||
* 3、params: 这个是除了文件参数以外的,上传的其他参数,这个参数是拼接在url后方的
|
||||
|
||||

|
||||
|
||||
为了方便大家理解,上方将该参数,以postman的形式上传
|
||||
|
||||
### 多业务逻辑,如何编写测试用例
|
||||
|
||||
多业务这一块,我们拿个简单的例子举例,比如登录场景,在登陆之前,我们需要先获取到验证码。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
首先,我们先创建一个 get_send_sms_code.yaml 的文件,编写一条发送验证码的用例
|
||||
|
||||
# 公共参数
|
||||
case_common:
|
||||
allureEpic: 盲盒APP
|
||||
allureFeature: 登录模块
|
||||
allureStory: 获取登录验证码
|
||||
|
||||
send_sms_code_01:
|
||||
host: ${{host()}}
|
||||
url: /mobile/sendSmsCode
|
||||
method: POST
|
||||
detail: 正常获取登录验证码
|
||||
headers:
|
||||
appId: '23132'
|
||||
masterAppId: masterAppId
|
||||
Content-Type: application/json;charset=UTF-8
|
||||
# 请求的数据,是 params 还是 json、或者file
|
||||
requestType: json
|
||||
# 是否执行,空或者 true 都会执行
|
||||
is_run:
|
||||
data:
|
||||
phoneNumber: "180****9278"
|
||||
# 是否有依赖业务,为空或者false则表示没有
|
||||
dependence_case: False
|
||||
# 依赖的数据
|
||||
dependence_case_data:
|
||||
assert:
|
||||
code:
|
||||
jsonpath: $.code
|
||||
type: ==
|
||||
value: '00000'
|
||||
AssertType:
|
||||
success:
|
||||
jsonpath: $.success
|
||||
type: ==
|
||||
value: true
|
||||
AssertType:
|
||||
|
||||
sql:
|
||||
|
||||
编写好之后,我们在创建一个 login.yaml 文件
|
||||
|
||||
# 公共参数
|
||||
case_common:
|
||||
allureEpic: 盲盒APP
|
||||
allureFeature: 登录模块
|
||||
allureStory: 登录
|
||||
|
||||
login_02:
|
||||
host: ${{host()}}
|
||||
url: /login/phone
|
||||
method: POST
|
||||
detail: 登录输入错误的验证码
|
||||
headers:
|
||||
appId: '23132'
|
||||
masterAppId: masterAppId
|
||||
Content-Type: application/json;charset=UTF-8
|
||||
# 请求的数据,是 params 还是 json、或者file
|
||||
requestType: json
|
||||
# 是否执行,空或者 true 都会执行
|
||||
is_run:
|
||||
data:
|
||||
phoneNumber: 18014909278
|
||||
code: $cache{login_02_v_code}
|
||||
# 是否有依赖业务,为空或者false则表示没有
|
||||
dependence_case: True
|
||||
# 依赖的数据
|
||||
dependence_case_data:
|
||||
- case_id: send_sms_code_01
|
||||
dependent_data:
|
||||
- dependent_type: response
|
||||
jsonpath: $.code
|
||||
set_cache: login_02_v_code
|
||||
|
||||
assert:
|
||||
code:
|
||||
jsonpath: $.code
|
||||
type: ==
|
||||
value: '00000'
|
||||
AssertType:
|
||||
sql:
|
||||
|
||||
其中处理多业务的核心区域,主要在这里:
|
||||
|
||||
dependence_case: True
|
||||
# 依赖的数据
|
||||
dependence_case_data:
|
||||
- case_id: send_sms_code_01
|
||||
dependent_data:
|
||||
- dependent_type: response
|
||||
jsonpath: $.code
|
||||
set_cache: login_02_v_code
|
||||
|
||||
首先,我们 dependence_case 需要设置成 True,并且在下面的 dependence_case_data 中设计相关依赖的数据。
|
||||
|
||||
* case_id:上方场景中,我们登录需要先获取验证码,因此依赖的case_id 就是发送短信验证码的 case_id :send_sms_code_01
|
||||
* dependent_type:我们依赖的是获取短信验证码接口中的响应内容,因此这次填写的是 response, 同样也支持request、sql等方式
|
||||
* jsonpath: 通过jsonpath 提取方式,提取到短信验证码中的验证码内容(jsonpath规格和jmeter中的json在线提取器的规则一致)
|
||||
* set_cache:拿到验证码之后,这里我们可以自定义一个缓存名称 如: login_02_v_code,程序中会将你所提取到的验证码存入缓存中,
|
||||
因此我们在这条用例的 data 中,有个code 的参数,值设置成 $cache{login_02_v_code},程序中会将我们 send_sms_code_01中的验证码给提取出来,
|
||||
通过 $cache{login_02_v_code} 语法获取到。
|
||||
|
||||
* 注意,定义缓存名称,每个公司最好定义一个规范,比如 当前这条 case_id名称 + 缓存自定义名称,如 login_02_v_code, case_id 是唯一的,
|
||||
这样可以避免不同用例之间缓存名称重复的问题,导致无法获取到对应的缓存数据
|
||||
|
||||
### 多业务逻辑,需要依赖同一个接口中的多个数据
|
||||
|
||||
dependence_case_data:
|
||||
- case_id: send_sms_code_01
|
||||
dependent_data:
|
||||
# 提取接口响应的code码
|
||||
- dependent_type: response
|
||||
jsonpath: $.code
|
||||
set_cache: v_code
|
||||
# 提取接口响应的accessToken
|
||||
- dependent_type: response
|
||||
jsonpath: $.data.accessToken
|
||||
# 替换请求头中的accessToken
|
||||
set_cache: accessToken
|
||||
|
||||
|
||||
如上方示例,可以添加多个 dependent_type
|
||||
|
||||
### 多业务逻辑,需要依赖不同接口的数据
|
||||
|
||||
假设我们需要获取 send_sms_code_01、get_code_01两个接口中的数据,用例格式如下
|
||||
|
||||
dependence_case: True
|
||||
# 依赖的数据
|
||||
dependence_case_data:
|
||||
- case_id: send_sms_code_01
|
||||
dependent_data:
|
||||
# 提取接口响应的code码
|
||||
- dependent_type: response
|
||||
jsonpath: $.code
|
||||
set_cache: v_code
|
||||
- case_id: get_code_01
|
||||
dependent_data:
|
||||
# 提取接口响应的code码
|
||||
- dependent_type: response
|
||||
jsonpath: $.code
|
||||
set_cache: v_code2
|
||||
|
||||
### 请求参数为路径参数
|
||||
|
||||
|
||||
collect_delete_tool_01:
|
||||
host: ${{host()}}
|
||||
url: /lg/collect/deletetool/json/$cache{collect_delete_tool_01_id}
|
||||
method: POST
|
||||
detail: 正常删除收藏网站
|
||||
headers:
|
||||
Content-Type: multipart/form-data;
|
||||
# 这里cookie的值,写的是存入缓存的名称
|
||||
cookie: $cache{login_cookie}
|
||||
# 请求的数据,是 params 还是 json、或者file、data
|
||||
requestType: None
|
||||
# 是否执行,空或者 true 都会执行
|
||||
is_run:
|
||||
data:
|
||||
dependence_case: True
|
||||
# 依赖的数据
|
||||
dependence_case_data:
|
||||
- case_id: collect_addtool_01
|
||||
dependent_data:
|
||||
- dependent_type: response
|
||||
jsonpath: $.data.id
|
||||
set_cache: collect_delete_tool_01_id
|
||||
|
||||
以上方实例,我们的参数是在url中的,因此我们可以通过 dependence_case 获取到我们需要依赖的数据,
|
||||
将本条用例需要用到的数据存入缓存,从而在 /lg/collect/deletetool/json/$cache{collect_delete_tool_01_id} 直接调用缓存数据即可
|
||||
|
||||
### 将当前用例的请求值或者响应值存入缓存中
|
||||
|
||||
有些小伙伴之前有反馈过,比如想要做数据库的断言,但是这个字段接口没有返回,我应该怎么去做校验呢?
|
||||
程序中提供了current_request_set_cache这个关键字,可以将当前这条用例的请求数据 或者响应数据 给直接存入缓存中
|
||||
如下案例所示:
|
||||
|
||||
current_request_set_cache:
|
||||
# 1、response 从响应中提取内容 2、request从请求中提取内容
|
||||
- type: response
|
||||
jsonpath: $.data.data.[0].id
|
||||
# 自定义的缓存名称
|
||||
name: test_query_shop_brand_list_02_id
|
||||
|
||||
### 请求用例时参数需要从数据库中提取
|
||||

|
||||
如上图所示,用例中的 dependent_type 需要填写成 sqlData。
|
||||
当你的依赖类型为 sqlData 数据库的数据时,那么下方就需要再加一个 setup_sql 的参数,下方填写需要用到的sql语句
|
||||
|
||||
注意case_id: 因为程序设计原因,通常情况下,我们关联的业务,会发送接口请求,但是如果我们依赖的是sql的话,
|
||||
是不需要发送请求的,因此我们如果是从数据库中提取数据作为参数的话,我们case_id 需要写self ,方便程序中去做区分
|
||||
|
||||
ApplyVerifyCode_01:
|
||||
host: ${{host}}
|
||||
url: /api/v1/merchant/apply/verifyCode
|
||||
method: GET
|
||||
detail: 校验已经审核通过的供应商手机号码
|
||||
headers:
|
||||
Content-Type: application/json;charset=UTF-8
|
||||
# 请求的数据,是 params 还是 json、或者file、data
|
||||
requestType: params
|
||||
# 是否执行,空或者 true 都会执行
|
||||
is_run:
|
||||
data:
|
||||
mobile: 18811111111
|
||||
authCode: 123456
|
||||
name: $cache{username}
|
||||
# 是否有依赖业务,为空或者false则表示没有
|
||||
dependence_case: True
|
||||
# 依赖的数据
|
||||
dependence_case_data:
|
||||
- case_id: self
|
||||
dependent_data:
|
||||
- dependent_type: sqlData
|
||||
jsonpath: $.username
|
||||
set_cache: username
|
||||
|
||||
assert:
|
||||
code:
|
||||
jsonpath: $.code
|
||||
type: ==
|
||||
value: 200
|
||||
AssertType:
|
||||
applyId:
|
||||
jsonpath: $.data[0].applyId
|
||||
type: ==
|
||||
value: $.applyId
|
||||
AssertType: SQL
|
||||
applyStatus:
|
||||
jsonpath: $.data[0].applyStatus
|
||||
type: ==
|
||||
value: $.applyStatus
|
||||
AssertType: SQL
|
||||
|
||||
sql:
|
||||
- select a.apply_id as applyId, a.to_status as applyStatus, a.sub_biz_type as subBizType, a.operator_name as operatorName, a.operator_user_id as operatorUserId, b.apply_type as applyType from test_obp_midware.apply_operate_log as a inner join test_obp_midware.apply as b on a.apply_id = b.id where b.id = $json($.data[0].applyId)$ order by a.id desc limit 1;
|
||||
setup_sql:
|
||||
- SELECT * FROM test_obp_user.user_biz_info where user_id = '300000405'
|
||||
|
||||
|
||||
### 用例中需要依赖登录的token,如何设计
|
||||
|
||||
首先,为了防止重复请求调用登录接口,pytest中的 conftest.py 提供了热加载机制,看上方截图中的代码,我们需要在 conftest.py 提前编写好登录的代码。
|
||||
|
||||
|
||||
如上方代码所示,我们会先去读取login.yaml文件中的用例,然后执行获取到响应中的token,然后 编写 Cache('work_login_init').set_caches(token),将token写入缓存中,其中 work_login_init 是缓存名称。
|
||||
|
||||
编写好之后,我们会在 requestControl.py 文件中,读取缓存中的token,如果该条用例需要依赖token,则直接进行内容替换。
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def work_login_init():
|
||||
"""
|
||||
获取登录的cookie
|
||||
:return:
|
||||
"""
|
||||
url = "https://www.wanandroid.com/user/login"
|
||||
data = {
|
||||
"username": 18800000001,
|
||||
"password": 123456
|
||||
}
|
||||
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
# 请求登录接口
|
||||
res = requests.post(url=url, data=data, verify=True, headers=headers).json()
|
||||
token = res['response']['token']
|
||||
|
||||
CacheHandler.update_cache(cache_name='work_login_init', value=token)
|
||||
|
||||
这里在编写用例的时候,token 填写我们所编写的缓存名称即可。
|
||||

|
||||
|
||||
### 用例中依赖cookie如何设计
|
||||
|
||||

|
||||
|
||||
首先我们在conftest.py中编写获取cookie的方法
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def work_login_init():
|
||||
"""
|
||||
获取登录的cookie
|
||||
:return:
|
||||
"""
|
||||
url = "https://www.wanandroid.com/user/login"
|
||||
data = {
|
||||
"username": 18800000001,
|
||||
"password": 123456
|
||||
}
|
||||
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
# 请求登录接口
|
||||
res = requests.post(url=url, data=data, verify=True, headers=headers)
|
||||
response_cookie = res.cookies
|
||||
|
||||
cookies = ''
|
||||
for k, v in response_cookie.items():
|
||||
_cookie = k + "=" + v + ";"
|
||||
# 拿到登录的cookie内容,cookie拿到的是字典类型,转换成对应的格式
|
||||
cookies += _cookie
|
||||
# 将登录接口中的cookie写入缓存中,其中login_cookie是缓存名称
|
||||
CacheHandler.update_cache(cache_name='login_cookie', value=cookies)
|
||||
|
||||
和token一样,我们如果用例的请求头中依赖cookie, cookie中的值,直接写我们存入缓存中的名称即可
|
||||
|
||||
headers:
|
||||
Content-Type: multipart/form-data;
|
||||
# 这里cookie的值,写的是存入缓存的名称
|
||||
cookie: $cache{login_cookie}
|
||||
|
||||
### 用例中如何生成随机数据
|
||||
|
||||
比如我们有些特殊的场景,可能会涉及到一些定制化的数据,每次执行数据,需要按照指定规则随机生成。
|
||||
|
||||

|
||||
|
||||
如上图所示,我们用例中的 reason 审核原因后方,需要展示审核的当前时间。那么我们首先需要封装一个获取当前时间的方法
|
||||
|
||||

|
||||
|
||||
那么我们就在 regularControl.py 文件中,编写 get_time 的方法。编写好之后,在用例中编写规则如下:
|
||||
|
||||
reason: 审核时间${{get_time()}}
|
||||
使用 " ${{函数名称()}}" 的方法,程序调用时,会生成当前时间。在regularControl.py 文件中,我还封装了一些常用的随机数,
|
||||
如随机生成男生姓名、女生姓名、身份证、邮箱、手机号码之类的,方便大家使用。
|
||||
如,随机生成邮箱,我们在用例中编写的格式为 " ${{get_email()}} " 。
|
||||
|
||||
其他所需随机生成的数据,可在文件中自行添加。
|
||||
|
||||
### 自动化函数传递参数
|
||||
|
||||
首先同样和上方一样,创建一个随机生成的方法,改方法支持接收参数
|
||||
|
||||
@classmethod
|
||||
def random_int(cls, min_num, max_num):
|
||||
"""
|
||||
随机生成指定范围的随机数
|
||||
@param min_num: 最小数字
|
||||
@param max_num: 最大数字
|
||||
@return:
|
||||
"""
|
||||
num = random.randint(int(min_num), int(max_num))
|
||||
return num
|
||||
|
||||
|
||||
在用例中,假设我们需要获取一个 1-10之间的随机数,那么我们直接这样调用该数据即可
|
||||
|
||||
reason: {{random_int(1, 10)}}
|
||||
|
||||
### 断言http响应状态码
|
||||
|
||||
相信有些小伙伴在做接口测试的过程中,有部分接口是没有任何响应的,那么在没有响应数据的情况下
|
||||
我们就只能通过 http的状态码去判断这条用例是否通过,我们可以这样写
|
||||
|
||||
assert:
|
||||
status_code: 200
|
||||
|
||||
我们直接在assert下方添加一个 status_code 参数,状态码我们判断其为 200
|
||||
|
||||
### 用例中添加等待时间
|
||||
|
||||
程序中可以设定接口请求之后,等待时长,假设A接口依赖B接口的业务,A接口请求完时,我们需要让他等待几秒钟
|
||||
再次请求B接口,这样的话,我们可以使用sleep关键字
|
||||
|
||||
sleep: 3
|
||||
|
||||
|
||||
### 断言类型
|
||||
下放截图中,是所有断言支持的类型
|
||||
|
||||

|
||||
|
||||
|
||||
### 用例中如何进行接口断言和数据库断言
|
||||
|
||||
假设现在我需要测试一个报表统计的数据,该接口返回了任务的处理时长 和 处理数量。功能如下截图所示:
|
||||
|
||||

|
||||
|
||||
假设下方是我们拿到接口响应的数据内容:
|
||||
|
||||
{"code": 200, "times": 155.91, "counts": 9}
|
||||
|
||||
这个时候,我们需要判断该接口返回的数据是否正确,就需要编写sql,对响应内容进行校验。
|
||||
|
||||

|
||||
|
||||
因此我们编写了如上sql,查出对应的数据,那么用例中编写规则如下,下方我们分别断言了两个内容,一个是对接口的响应code码进行断言,一个是断言数据库中的数据。
|
||||
|
||||
|
||||
assert:
|
||||
code:
|
||||
jsonpath: $.code
|
||||
type: ==
|
||||
value: 200
|
||||
# 断言接口响应时,可以为空
|
||||
AssertType:
|
||||
do_time:
|
||||
# jsonpath 拿到接口响应的数据
|
||||
jsonpath: $.times
|
||||
type: ==
|
||||
# sql 查出来的数据,是字典类型的,因此这里是从字段中提取查看出来的字段
|
||||
value: $.do_time
|
||||
# 断言sql的时候,AssertType 的值需要填写成 SQL
|
||||
AssertType: SQL
|
||||
question_counts:
|
||||
jsonpath: $.counts
|
||||
type: ==
|
||||
#
|
||||
value: $.question_counts
|
||||
# 断言sql的时候,AssertType 的值需要填写成 SQL
|
||||
AssertType: SQL
|
||||
sql:
|
||||
- select * from test_goods where shop_id = 515
|
||||
|
||||
我们分别对用例的数据进行讲解,首先是响应断言, 编写规则如下
|
||||
|
||||
code:
|
||||
# 通过jsonpath获取接口响应中的code {"code": 200, "times": 155.91, "counts": 9}
|
||||
jsonpath: $.code
|
||||
type: ==
|
||||
value: 200
|
||||
# 断言接口响应时,可以为空
|
||||
AssertType:
|
||||
|
||||
下面是对sql进行断言
|
||||
|
||||
question_counts:
|
||||
# 断言接口响应的问题上报数量counts {"code": 200, "times": 155.91, "counts": 9}
|
||||
jsonpath: $.counts
|
||||
type: ==
|
||||
# 查询sql,我们数据库查到的数据是一个字段,数据是这样的:{question_counts: 13, do_time: 1482.70}, 这里我们通过 jsonpath获取question_counts
|
||||
value: $.question_counts
|
||||
# 断言sql的时候,AssertType 的值需要填写成 SQL
|
||||
AssertType: SQL
|
||||
sql:
|
||||
- SELECT round( sum(( UNIX_TIMESTAMP( filing_time )- UNIX_TIMESTAMP( report_time )) / 60 ) / 60, 2 ) AS do_time, count( id ) AS question_counts FROM fl_report_info WHERE state IN ( 1, 3 )
|
||||
|
||||
有些细心的小伙伴会发现,我们的sql,是列表类型的。这样就意味这,我们的sql可以同时编写多条,这样会对不会编写多表联查的小伙伴比较友好,可以进行单表查询,获取我们需要的数据。
|
||||
|
||||
sql:
|
||||
- select * from users;
|
||||
- select * from goods;
|
||||
|
||||
### 使用teardown功能,做数据清洗
|
||||
|
||||
通常情况下,我们做自动化所有新增的数据,我们测试完成之后,都需要讲这些数据删除,程序中支持两种写法
|
||||
一种是直接调用接口进行数据删除。另外一种是直接删除数据库中的数据,建议使用第一种,直接调用业务接口删除对应的数据
|
||||
|
||||
|
||||
1、下面我们先来看看第一种删除方式,teardown的功能,因为需要兼容较多的场景,因此使用功能上相对也会比较复杂
|
||||
需要小伙伴们一个一个去慢慢的理解。
|
||||
|
||||
下面为了方便大家对于teardown功能的理解,我会针对不同的场景进行举例:
|
||||
|
||||
* 假设现在我们有一个新增接口,写完之后,我们需要先调用查询接口获取到新增接口的ID,然后再进行删除
|
||||
那么此时会设计到两个场景,首先执行新增接口ID,然后再拿到响应(这里有个逻辑上的先后关系,查询接口,是先发送请求,在提取数据)
|
||||
获取到查询的ID之后,我们在执行删除,删除的话,我们是直接发送请求
|
||||
|
||||
那么针对这个场景,我们就需要有个关键字去做区分,什么场景下先发送请求,什么场景下后发送请求,下面我们来看一下案例,方便大家理解
|
||||
|
||||
teardown:
|
||||
# 查看品牌审核列表,获取品牌的apply_id
|
||||
- case_id: query_apply_list_01
|
||||
# 注意这里我们是先发送请求,在拿到自己响应的内容,因此我们这个字段需要写param_prepare
|
||||
param_prepare:
|
||||
# 因为是获取自己的响应内容,我们dependent_type需要写成 self_response
|
||||
- dependent_type: self_response
|
||||
# 通过jsonpath的方法,获取query_apply_list_01这个接口的响应内容
|
||||
jsonpath: $.data.data.[0].applyId
|
||||
# 将内容存入缓存,这个是自定义的缓存名称
|
||||
set_cache: test_brand_apply_initiate_apply_01_applyId
|
||||
|
||||
# 支持同时存多个数据,只会发送一次请求
|
||||
- dependent_type: self_response
|
||||
jsonpath: $.data.data.[0].brandName
|
||||
set_cache: test_brand_apply_initiate_apply_01_brandName
|
||||
|
||||
# 删除
|
||||
- case_id: delete_01
|
||||
# 删除的话,我们是直接发送请求的,因此我们这里写 send_request
|
||||
send_request:
|
||||
# 我们上方已经拿到了ID,并且将ID存入缓存中,因此这里依赖数据的类型为cache,直接从缓存中提取
|
||||
- dependent_type: cache
|
||||
# 这个是缓存名称
|
||||
cache_data: test_brand_apply_initiate_apply_01_applyId
|
||||
# 通过relace_key 去替换 delete_01 中的 applyID参数
|
||||
replace_key: $.data.applyId
|
||||
|
||||
* 那么有些小伙伴会在想,同样我们以上方的接口场景为例,有些小伙伴会说,我公司的新增的接口,有直接返回ID,不需要调用查询接口
|
||||
程序中当然也支持这种场景,我们只需要这么编写
|
||||
|
||||
- case_id: process_apply_01
|
||||
# 同样这么写 send_request
|
||||
send_request:
|
||||
# 这里我们从响应中获取
|
||||
- dependent_type: response
|
||||
# 通过jsonpath的方式,获取响应的内容
|
||||
jsonpath: $.data.id
|
||||
# 使用repalce_key进行替换
|
||||
replace_key: $.data.applyId
|
||||
|
||||
* 程序中也支持从请求里面获取内容,编写规则如下
|
||||
|
||||
- case_id: process_apply_01
|
||||
# 同样这么写 send_request
|
||||
send_request:
|
||||
# 这里我们从响应中获取
|
||||
- dependent_type: request
|
||||
# 通过jsonpath的方式,获取请求的内容
|
||||
jsonpath: $.data.id
|
||||
# 使用repalce_key进行替换
|
||||
replace_key: $.data.applyId
|
||||
|
||||
### 使用 teardown_sql 后置sql删除数据
|
||||
如一些特殊场景,业务上并没有提供删除接口,我们也可以直接通过 sql去讲对应的sql删除
|
||||
teardown_sql:
|
||||
- delete * from xxx
|
||||
- delete * from xxx
|
||||
|
||||
### 自动生成test_case层代码
|
||||
|
||||
小伙伴们在编写好 yaml 用例之后,可以直接执行 caseAutomaticControl.py ,会跟你设计的测试用例,生成对应的代码。
|
||||
|
||||

|
||||
|
||||
### 发送钉钉通知通知
|
||||
|
||||

|
||||
|
||||
### 发送企业微信通知
|
||||
|
||||

|
||||
|
||||
### 日志打印装饰器
|
||||
|
||||

|
||||
|
||||
在requestControl.py中,我单独封装了一个日志装饰器,需要的小伙伴可以不用改动代码,直接使用,如果不需要,直接注释,或者改成False。控制台将不会有日志输出
|
||||
|
||||
### 统计用例运行时长
|
||||

|
||||
|
||||
同样,这里封装了一个统计用例运行时长的装饰器,使用改装饰器前,需要先进行导包
|
||||
|
||||
from utils.logUtils.runTimeDecoratorl import execution_duration
|
||||
导入之后,调用改装饰器,装饰器中填写的用例执行时长,以毫秒为单位,如这里设置的2000ms,那么如果该用例执行大于2000ms,则会输出一条告警日志。
|
||||
|
||||
@execution_duration(2000)
|
||||
|
||||
### 生成allure报告
|
||||
|
||||
我们直接运行主程序 run.py ,运行完成之后,就可以生成漂亮的allure报告啦~
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 其他
|
||||
|
||||
本框架为2.0升级版本,升级之后的功能,现在基本上都是在yaml中维护用例,无需测试人员编写代码,
|
||||
和 1.0版本的区别在于,1.0版本还需要测试人员手动编写多业务逻辑的代码,需要有一定基础编码的能力
|
||||
|
||||
但是1.0版本,同样也可以自动生成代码,yaml中维护数据,对相对简单,如果偏于yaml简单维护的同学,可以切换查看1.0分支
|
||||
下方是1.0分支的操作文档:[点我查看](https://blog.csdn.net/weixin_43865008/article/details/121903028?spm=1001.2014.3001.5502)
|
||||
|
||||
*******************************************************
|
||||
|
||||
以上便是整个框架的使用说明,这个框架属于个人业余时间开发,大家如果在使用中遇到什么问题,或者有相关建议,可以随时反馈给我,
|
||||
_框架内容会随着大家的反馈,持续更新!邮箱地址:1602343211@qq.com
|
||||
|
||||
如果觉得框架有帮助到你,麻烦收藏一下哦~~谢谢。:)
|
||||
|
||||
## 版本更新记录
|
||||
* V2.0.0(2022-04-07)
|
||||
[重构] 新增多业务逻辑依赖处理,统一改成yaml文件中维护用例,无需编写代码,基于V1.0版本进行重构
|
||||
* [查看更多记录点此查看](https://gitee.com/yu_xiao_qi/pytest-auto-api2/wikis/Home)
|
||||
|
||||
## 赞赏
|
||||
|
||||
如果这个库有帮助到你并且你很想支持库的后续开发和维护,那么你可以扫描下方二维码随意打赏我,我将不胜感激
|
||||
|
||||
 
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2019/11/14 14:45
|
||||
# @Author : kira
|
||||
# @Email : 262667641@qq.com
|
||||
# @File : __init__.py.py
|
||||
# @Project : risk_api_project
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,35 @@
|
|||
import os
|
||||
|
||||
|
||||
class BaseDates:
|
||||
# 根目录路径
|
||||
# *****************************************************************
|
||||
base_path = os.path.dirname(os.path.dirname(__file__))
|
||||
current_path = os.path.dirname(__file__)
|
||||
# *****************************************************************
|
||||
# 测试数据所在路径
|
||||
# *****************************************************************
|
||||
test_api = os.path.join(base_path, "data", "module_1", "test_cases", "test_api.xlsx")
|
||||
# *****************************************************************
|
||||
|
||||
# 测试用例脚本目录
|
||||
# *****************************************************************
|
||||
script = os.path.join(base_path, "script", "bgy")
|
||||
# *****************************************************************
|
||||
|
||||
# 测试报告及 log 所在路径
|
||||
# *****************************************************************
|
||||
test_report = os.path.join(base_path, "OutPut", "reports")
|
||||
log_path = os.path.join(base_path, "OutPut", "Log")
|
||||
|
||||
wx_send_url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key="
|
||||
wx_up_url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?type=file&key="
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test = BaseDates()
|
||||
print(test.base_path)
|
||||
print(test.test_api)
|
||||
print(test.test_report)
|
||||
print(test.test_api)
|
||||
print(test.current_path)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,34 @@
|
|||
# coding: utf-8
|
||||
|
||||
# -------------------------------------------------------------------------------
|
||||
# Name: comparator_dict.py
|
||||
# Description: 比较器名词释义
|
||||
# Author: kira
|
||||
# EMAIL: 2419352654@qq.com
|
||||
# Date: 2020/10/29 16:51
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
__all__ = ['comparator_dict']
|
||||
|
||||
# 比较器名词释义
|
||||
comparator_dict = {
|
||||
'eq': 'eq:实际值与期望值相等',
|
||||
'lt': 'lt:实际值小于期望值',
|
||||
'lte': 'lte:实际值小于或等于期望值',
|
||||
'gt': 'gt:实际值大于期望值',
|
||||
'gte': 'gte:实际值大于或等于期望值',
|
||||
'neq': 'neq:实际值与期望值不相等',
|
||||
'str_eq': 'str_eq:字符串实际值与期望值相同',
|
||||
'length_eq': 'length_eq:实际值的长度等于期望长度',
|
||||
'length_gt': 'length_gt:实际值的长度大于期望长度',
|
||||
'length_gte': 'length_gte:实际值的长度大于或等于期望长度',
|
||||
'length_lt': 'length_lt:实际值的长度小于期望长度',
|
||||
'length_lte': 'length_lte:实际值的长度小于或等于期望长度',
|
||||
'contains': 'contains:期望值包含在实际值中',
|
||||
'contained_by': 'contained_by:实际值被包含在期望值中',
|
||||
'type_match': 'type_match:实际值的类型与期望值的类型相匹配',
|
||||
'regex_match': 'type_match:正则匹配(从字符串的起始位置匹配)',
|
||||
'regex_search': 'regex_search:正则匹配(从字符串的任意位置匹配)',
|
||||
'startswith': 'startswith:实际值是以期望值开始',
|
||||
'endswith': 'endswith:实际值是以期望值结束',
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
# coding: utf-8
|
||||
|
||||
# -------------------------------------------------------------------------------
|
||||
# Name: comparators.py
|
||||
# Description: 内建比较器
|
||||
# Author: kira
|
||||
# EMAIL: 262667641@qq.com
|
||||
# Date: 2020/2/25 16:48
|
||||
# -------------------------------------------------------------------------------
|
||||
import json
|
||||
import re
|
||||
|
||||
|
||||
def eq(actual_value, expect_value):
|
||||
"""
|
||||
实际值与期望值相等
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
assert actual_value == expect_value
|
||||
|
||||
|
||||
def lt(actual_value, expect_value):
|
||||
"""
|
||||
实际值小于期望值
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
assert actual_value < expect_value
|
||||
|
||||
|
||||
def lte(actual_value, expect_value):
|
||||
"""
|
||||
实际值小于或等于期望值
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
assert actual_value <= expect_value
|
||||
|
||||
|
||||
def gt(actual_value, expect_value):
|
||||
"""
|
||||
实际值大于期望值
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
assert actual_value > expect_value
|
||||
|
||||
|
||||
def gte(actual_value, expect_value):
|
||||
"""
|
||||
实际值大于或等于期望值
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
assert actual_value >= expect_value
|
||||
|
||||
|
||||
def neq(actual_value, expect_value):
|
||||
"""
|
||||
实际值与期望值不相等
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
assert actual_value != expect_value
|
||||
|
||||
|
||||
def str_eq(actual_value, expect_value):
|
||||
"""
|
||||
字符串实际值与期望值相同
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
assert str(actual_value) == str(expect_value)
|
||||
|
||||
|
||||
def length_eq(actual_value, expect_value):
|
||||
"""
|
||||
实际值的长度等于期望长度
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
assert isinstance(expect_value, (int,))
|
||||
assert len(actual_value) == expect_value
|
||||
|
||||
|
||||
def length_gt(actual_value, expect_value):
|
||||
"""
|
||||
实际值的长度大于期望长度
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
assert isinstance(expect_value, (int,))
|
||||
assert len(actual_value) > expect_value
|
||||
|
||||
|
||||
def length_gte(actual_value, expect_value):
|
||||
"""
|
||||
实际值的长度大于或等于期望长度
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
assert isinstance(expect_value, (int,))
|
||||
assert len(actual_value) >= expect_value
|
||||
|
||||
|
||||
def length_lt(actual_value, expect_value):
|
||||
"""
|
||||
实际值的长度小于期望长度
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
assert isinstance(expect_value, (int,))
|
||||
assert len(actual_value) < expect_value
|
||||
|
||||
|
||||
def length_lte(actual_value, expect_value):
|
||||
"""
|
||||
实际值的长度小于或等于期望长度
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
assert isinstance(expect_value, (int,))
|
||||
assert len(actual_value) <= expect_value
|
||||
|
||||
|
||||
def contains(actual_value, expect_value):
|
||||
"""
|
||||
期望值包含在实际值中
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
assert isinstance(actual_value, (list, tuple, dict, str, bytes))
|
||||
assert expect_value in actual_value
|
||||
|
||||
|
||||
def contained_by(actual_value, expect_value):
|
||||
"""
|
||||
实际值被包含在期望值中
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
assert isinstance(expect_value, (list, tuple, dict, str, bytes))
|
||||
assert actual_value in expect_value
|
||||
|
||||
|
||||
def type_match(actual_value, expect_value):
|
||||
"""
|
||||
实际值的类型与期望值的类型相匹配
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
|
||||
def get_type(name):
|
||||
if isinstance(name, type):
|
||||
return name
|
||||
elif isinstance(name, (str, bytes)):
|
||||
try:
|
||||
return __builtins__[name]
|
||||
except KeyError:
|
||||
raise ValueError(name)
|
||||
else:
|
||||
raise ValueError(name)
|
||||
|
||||
assert isinstance(actual_value, get_type(expect_value))
|
||||
|
||||
|
||||
def regex_match(actual_value, expect_value):
|
||||
"""
|
||||
正则匹配(从字符串的起始位置匹配)
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if not isinstance(actual_value, str):
|
||||
actual_value = json.dumps(actual_value, ensure_ascii=False)
|
||||
if not isinstance(expect_value, str):
|
||||
expect_value = json.dumps(expect_value, ensure_ascii=False)
|
||||
assert re.match(expect_value, actual_value)
|
||||
|
||||
|
||||
def regex_search(actual_value, expect_value):
|
||||
"""
|
||||
正则匹配(从字符串的任意位置匹配)
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if not isinstance(actual_value, str):
|
||||
actual_value = json.dumps(actual_value, ensure_ascii=False)
|
||||
if not isinstance(expect_value, str):
|
||||
expect_value = json.dumps(expect_value, ensure_ascii=False)
|
||||
assert re.search(expect_value, actual_value)
|
||||
|
||||
|
||||
def startswith(actual_value, expect_value):
|
||||
"""
|
||||
实际值是以期望值开始
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
assert str(actual_value).startswith(str(expect_value))
|
||||
|
||||
|
||||
def endswith(actual_value, expect_value):
|
||||
"""
|
||||
实际值是以期望值结束
|
||||
Args:
|
||||
actual_value: 实际值
|
||||
expect_value: 期望值
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
assert str(actual_value).endswith(str(expect_value))
|
|
@ -0,0 +1,87 @@
|
|||
# coding: utf-8
|
||||
|
||||
# -------------------------------------------------------------------------------
|
||||
# Name: extractor.py
|
||||
# Description: 提取器
|
||||
# Author: kira
|
||||
# EMAIL: 262667641@qq.com
|
||||
# Date: 2020/2/25 16:48
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
import json
|
||||
import logging
|
||||
import jsonpath
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Extractor(object):
|
||||
"""
|
||||
提取器
|
||||
主要功能:
|
||||
1、格式化输出变量
|
||||
2、从响应中提取需要输出的变量信息并返回
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.output_variables_mapping = {}
|
||||
|
||||
def uniform_output(self, output_variables):
|
||||
"""
|
||||
统一格式化测试用例的输出变量output
|
||||
Args:
|
||||
output_variables: list、dict、str 示例:["a","b",{"a":"ac"}] or {"a":"ac"} or "a"
|
||||
|
||||
Returns: 示例:[{"alias_key":"original_key"}]
|
||||
list
|
||||
"""
|
||||
if isinstance(output_variables, list):
|
||||
for output_variable in output_variables:
|
||||
self.uniform_output(output_variable)
|
||||
elif isinstance(output_variables, dict):
|
||||
for alias_key, original_key in output_variables.items():
|
||||
if not isinstance(alias_key, str):
|
||||
alias_key = json.dumps(alias_key, ensure_ascii=False)
|
||||
if not isinstance(original_key, str):
|
||||
original_key = json.dumps(original_key, ensure_ascii=False)
|
||||
self.output_variables_mapping.update({alias_key: original_key})
|
||||
elif isinstance(output_variables, str):
|
||||
self.output_variables_mapping.update({output_variables: output_variables})
|
||||
else:
|
||||
raise Exception("参数格式错误!")
|
||||
|
||||
def extract_output(self, resp_obj=None):
|
||||
"""
|
||||
从接口返回中提取待输出变量的值
|
||||
Args:
|
||||
resp_obj: ResponseObject对象的resp_obj属性
|
||||
|
||||
Returns: output_variables_mapping 从resp_obj中提取后的mapping
|
||||
|
||||
"""
|
||||
return {alias_key: self.extract_value_by_jsonpath(resp_obj=resp_obj, expr=original_key) for
|
||||
alias_key, original_key in self.output_variables_mapping.items()}
|
||||
|
||||
@staticmethod
|
||||
def extract_value_by_jsonpath(resp_obj=None, expr=None):
|
||||
"""
|
||||
根据jsonpath从resp_obj中提取相应的值
|
||||
Args:
|
||||
resp_obj: ResponseObject实例
|
||||
expr: 提取条件
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
# logger.info('正在执行数据提取:提取数据源内容:{resp_obj}'.format(resp_obj=resp_obj))
|
||||
logger.info('正在执行数据提取:提取表达式:{expr}'.format(expr=expr))
|
||||
result = jsonpath.jsonpath(resp_obj, expr)
|
||||
if result is False:
|
||||
# jsonpath没有匹配到数据
|
||||
result = []
|
||||
logger.info(f'提取失败:提取表达式:{expr},没有提取到对应的值')
|
||||
elif isinstance(result, list):
|
||||
if len(result) == 1:
|
||||
result = result[0]
|
||||
logger.info(f'输出变量,提取表达式:{expr},提取结果:{result}')
|
||||
return result
|
|
@ -0,0 +1,115 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
"""
|
||||
@author: kira
|
||||
@contact: 262667641@qq.com
|
||||
@file: load_fun_from_modul.py
|
||||
@time: 2023/3/17 16:15
|
||||
@desc:
|
||||
"""
|
||||
# 以下是用Python实现的动态加载指定文件夹下模块并读取函数的示例代码,每行代码都加了注释说明:
|
||||
|
||||
import os
|
||||
import importlib.util
|
||||
|
||||
|
||||
def load_modules_from_folder(folder_path):
|
||||
functions = {} # 创建一个空字典,用于存储读取到的函数
|
||||
|
||||
# 遍历指定文件夹下的所有文件
|
||||
for file_name in os.listdir(folder_path):
|
||||
module_name, ext = os.path.splitext(file_name) # 分离文件名和扩展名
|
||||
if ext == '.py': # 如果是 Python 模块文件
|
||||
module_path = os.path.join(folder_path, file_name) # 获取模块文件的完整路径
|
||||
spec = importlib.util.spec_from_file_location(module_name, module_path) # 根据模块文件路径创建一个模块规范
|
||||
module = importlib.util.module_from_spec(spec) # 根据模块规范创建一个空的模块对象
|
||||
spec.loader.exec_module(module) # 加载模块文件并执行其中的代码,将函数定义添加到 module 对象中
|
||||
# 遍历 module 对象中的所有属性,找出函数并添加到 functions 字典中
|
||||
for attr_name in dir(module):
|
||||
attr = getattr(module, attr_name)
|
||||
if callable(attr):
|
||||
functions[attr_name] = attr
|
||||
|
||||
return functions # 返回存储函数的字典
|
||||
|
||||
# 上述代码主要实现了以下功能:
|
||||
#
|
||||
# 1. 导入了必要的模块,包括 `os` 和 `importlib.util`。
|
||||
# - `os` 模块提供了操作文件系统的功能,如列出文件夹下的所有文件。
|
||||
# - `importlib.util` 模块提供了加载指定路径的模块文件的功能。
|
||||
# 2. 定义了一个名为 `load_functions` 的函数,该函数接受一个参数 `folder_path`,表示要加载模块的文件夹路径。
|
||||
# 3. 在函数内部创建了一个空字典 `functions`,用于存储读取到的函数。
|
||||
# 4. 使用 `os.listdir()` 函数遍历指定文件夹下的所有文件,并使用 `os.path.splitext()` 函数分离出每个文件名和扩展名。
|
||||
# 5. 判断文件扩展名是否为 `.py`,如果是则说明是 Python 模块文件。
|
||||
# 6. 构造模块文件的完整路径,并使用 `importlib.util.spec_from_file_location()` 函数创建一个模块规范对象 `spec`。
|
||||
# 7. 使用 `importlib.util.module_from_spec()` 函数根据模块规范对象创建一个空的模块对象 `module`。
|
||||
# 8. 使用 `spec.loader.exec_module()` 函数加载模块文件并执行其中的代码,这会将函数定义添加到 `module` 对象中。
|
||||
# 9. 遍历 `module` 对象中的所有属性,找出其中的函数并添加到 `functions` 字典中。
|
||||
# 10. 最后返回存储函数的字典 `functions`。
|
||||
#
|
||||
# 这个函数可以方便地动态加载指定文件夹下的模块,并读取其中的函数,存储在字典这样的数据结构中。通过访问字典的 key 可以获取到对应的函数值并调用。
|
||||
|
||||
|
||||
|
||||
import os
|
||||
import importlib
|
||||
import inspect
|
||||
|
||||
|
||||
def load_modules(module_dir):
|
||||
"""
|
||||
动态加载指定文件夹下的模块,并读取模块内的函数,存储在字典这样的数据结构中。
|
||||
|
||||
Parameters:
|
||||
module_dir (str): 模块所在的目录路径。
|
||||
|
||||
Returns:
|
||||
dict: 包含已加载模块的函数的字典。字典的key为函数名,value为对应的函数对象。
|
||||
"""
|
||||
|
||||
functions = {}
|
||||
|
||||
# 循环遍历目录下所有Python文件
|
||||
for filename in os.listdir(module_dir):
|
||||
filepath = os.path.join(module_dir, filename)
|
||||
if not os.path.isfile(filepath) or not filename.endswith('.py'):
|
||||
continue
|
||||
|
||||
# 获取Python文件名(去除扩展名)
|
||||
module_name = filename[:-3]
|
||||
|
||||
try:
|
||||
# 动态加载模块
|
||||
module = importlib.import_module(module_name)
|
||||
|
||||
# 遍历模块内所有对象
|
||||
for name, obj in inspect.getmembers(module):
|
||||
# 如果对象为函数,则将其添加到字典中
|
||||
if inspect.isfunction(obj):
|
||||
functions[name] = obj
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to load module '{module_name}': {str(e)}")
|
||||
|
||||
return functions
|
||||
|
||||
|
||||
|
||||
# 解释每个关键点:
|
||||
# - `os.listdir(module_dir)`
|
||||
# 用于获取指定目录下的所有文件名。
|
||||
# - `os.path.isfile(filepath)`
|
||||
# 用于判断指定路径是否为文件。
|
||||
# - `not filename.endswith('.py')`
|
||||
# 用于判断文件名是否以
|
||||
# `.py
|
||||
# `结尾。
|
||||
# - `importlib.import_module(module_name)`
|
||||
# 用于动态加载指定模块。
|
||||
# - `inspect.getmembers(module)`
|
||||
# 用于获取指定模块内的所有对象,返回一个元组列表。
|
||||
# - `inspect.isfunction(obj)`
|
||||
# 用于判断指定对象是否为函数。
|
||||
# - 将函数以键值对
|
||||
# `(name, obj)`
|
||||
# 的形式添加到字典中。
|
|
@ -0,0 +1,88 @@
|
|||
# coding: utf-8
|
||||
|
||||
# -------------------------------------------------------------------------------
|
||||
# Name: loader.py
|
||||
# Description:
|
||||
# Author: kira
|
||||
# EMAIL: 262667641@qq.com
|
||||
# Date: 2019/11/18 17:32
|
||||
# -------------------------------------------------------------------------------
|
||||
import types
|
||||
|
||||
from common import functions
|
||||
from common.comparator import comparators
|
||||
|
||||
|
||||
def load_built_in_functions():
|
||||
"""
|
||||
加载builtin包中的内建方法
|
||||
Returns:
|
||||
"""
|
||||
built_in_functions = {}
|
||||
|
||||
for name, item in vars(functions).items():
|
||||
if isinstance(item, types.FunctionType):
|
||||
built_in_functions[name] = item
|
||||
return built_in_functions
|
||||
|
||||
|
||||
def load_built_in_comparators() -> object:
|
||||
"""
|
||||
加载包中的内建比较器
|
||||
Returns:
|
||||
|
||||
"""
|
||||
built_in_comparators = {}
|
||||
for name, item in vars(comparators).items():
|
||||
if isinstance(item, types.FunctionType):
|
||||
built_in_comparators[name] = item
|
||||
|
||||
return built_in_comparators
|
||||
|
||||
|
||||
def load_model_fun(model):
|
||||
"""
|
||||
加载指定模块中的所有函数
|
||||
Returns:
|
||||
|
||||
"""
|
||||
model_fun = {}
|
||||
for name, item in vars(model).items():
|
||||
# print("-----", name, item)
|
||||
if isinstance(item, types.FunctionType):
|
||||
model_fun[name] = item
|
||||
|
||||
return model_fun
|
||||
|
||||
|
||||
# def load_ext_method_online():
|
||||
# """
|
||||
# 动态加载ext_method_online.py模块中内容
|
||||
# Returns:
|
||||
# ext_method_online_module:ext_method_online模块
|
||||
# ext_methods_online:ext_method_online模块中的方法
|
||||
# """
|
||||
# ext_method_online_module = None
|
||||
# ext_methods_online = {}
|
||||
# ext_method_online = ExtMethodOnline.objects.filter(name='ext_method_online.py').first()
|
||||
# if ext_method_online:
|
||||
# filename = ext_method_online.name
|
||||
# filepath = ext_method_online.filepath.replace('/', os.sep)
|
||||
# path = os.path.join(settings.BASE_DIR, filepath, filename)
|
||||
# if os.path.exists(path) and os.path.isfile(path):
|
||||
# ext_method_online_module_name = 'apps.HttpAutoTestService.core.ext_methods.ext_method_online'
|
||||
# ext_method_online_module = importlib.import_module(ext_method_online_module_name)
|
||||
# importlib.reload(ext_method_online_module)
|
||||
# for name, item in vars(ext_method_online_module).items():
|
||||
# if isinstance(item, types.FunctionType):
|
||||
# ext_methods_online[name] = item
|
||||
# return ext_method_online_module, ext_methods_online
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from common.functions import random_tools
|
||||
|
||||
# func = load_model_fun(random_tools)
|
||||
# print(func)
|
||||
# print(load_built_in_functions())
|
||||
print(load_built_in_comparators())
|
|
@ -0,0 +1,116 @@
|
|||
# coding: utf-8
|
||||
# -------------------------------------------------------------------------------
|
||||
# Name: validator.py
|
||||
# Description:
|
||||
# Author: kira
|
||||
# EMAIL: 262667641@qq.com
|
||||
# Date: 2023/03/24 17:32
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
from common.comparator.comparator_dict import comparator_dict
|
||||
from common.comparator.extractor import Extractor
|
||||
from common.comparator.loaders import load_built_in_comparators
|
||||
from common.tools.logger import MyLog
|
||||
|
||||
logger = MyLog()
|
||||
|
||||
|
||||
class Validator(object):
|
||||
"""
|
||||
校验器
|
||||
主要功能:
|
||||
1、格式化校验变量
|
||||
2、校验期望结果与实际结果与预期一致,并返回校验结果
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.validate_variables_list = []
|
||||
|
||||
def uniform_validate(self, validate_variables):
|
||||
"""
|
||||
统一格式化测试用例的验证变量validate
|
||||
Args:
|
||||
validate_variables: 参数格式 list、dict
|
||||
示例:
|
||||
[{"check":"result.user.name","comparator":"eq","expect":"chenyongzhi"}]
|
||||
or {"check":"result.user.name","comparator":"eq","expect":"chenyongzhi"}
|
||||
|
||||
Returns: 返回数据格式 list
|
||||
示例:
|
||||
[{"check":"result.user.name","comparator":"eq","expect":"chenyongzhi"}]
|
||||
|
||||
"""
|
||||
if isinstance(validate_variables, list):
|
||||
for item in validate_variables:
|
||||
self.uniform_validate(item)
|
||||
elif isinstance(validate_variables, dict):
|
||||
if "check" in validate_variables.keys() and "expect" in validate_variables.keys():
|
||||
# 如果验证mapping中不包含comparator时,默认为{"comparator": "eq"}
|
||||
check_item = validate_variables.get("check")
|
||||
expect_value = validate_variables.get("expect")
|
||||
comparator = validate_variables.get("comparator", "eq")
|
||||
self.validate_variables_list.append({
|
||||
"check": check_item,
|
||||
"expect": expect_value,
|
||||
"comparator": comparator
|
||||
})
|
||||
else:
|
||||
logger.my_log("参数格式错误!")
|
||||
|
||||
def validate(self, resp_obj=None):
|
||||
"""
|
||||
校验期望结果与实际结果与预期一致
|
||||
Args:
|
||||
resp_obj: ResponseObject对象实例
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
|
||||
validate_pass = "PASS"
|
||||
built_in_comparators = load_built_in_comparators()
|
||||
|
||||
# 记录校验失败的原因
|
||||
failure_reason = []
|
||||
for validate_variable in self.validate_variables_list:
|
||||
check_item = validate_variable['check']
|
||||
expect_value = validate_variable['expect']
|
||||
comparator = validate_variable['comparator']
|
||||
actual_value = Extractor.extract_value_by_jsonpath(resp_obj=resp_obj, expr=check_item)
|
||||
try:
|
||||
# 获取比较器
|
||||
fun = built_in_comparators[comparator]
|
||||
fun(actual_value=actual_value, expect_value=expect_value)
|
||||
except (AssertionError, TypeError):
|
||||
validate_pass = "FAIL"
|
||||
failure_reason.append({
|
||||
'检查项': check_item,
|
||||
'期望值': expect_value,
|
||||
'实际值': actual_value,
|
||||
'断言方法': comparator_dict.get(comparator),
|
||||
})
|
||||
return validate_pass, failure_reason
|
||||
|
||||
def run_validate(self, validate_variables, resp_obj=None):
|
||||
"""
|
||||
统一格式化测试用例的验证变量validate,然后校验期望结果与实际结果与预期一致
|
||||
Args:
|
||||
validate_variables:参数格式 list、dict
|
||||
resp_obj:ResponseObject对象实例
|
||||
|
||||
Returns:返回校验结果
|
||||
|
||||
"""
|
||||
self.uniform_validate(validate_variables)
|
||||
if not self.validate_variables_list:
|
||||
raise "uniform_validate 执行失败,无法进行 validate 校验"
|
||||
return self.validate(resp_obj)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
validate_variables = [{"check": "result.user.name", "comparator": "eq", "expect": "chenyongzhi"}]
|
||||
|
||||
resp_obj = {"result": {"user": {"name": "ch33enyongzhi"}}}
|
||||
t = Validator()
|
||||
res = t.run_validate(validate_variables, resp_obj)
|
||||
print(res)
|
|
@ -0,0 +1,26 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
sys.path.append("./")
|
||||
sys.path.append("./common")
|
||||
|
||||
|
||||
class Dependence:
|
||||
dependence = {} # 定义依赖表
|
||||
PATTERN = re.compile(r"{{(.*?)}}") # 预编译正则表达式
|
||||
pattern = re.compile(r'({)')
|
||||
|
||||
def update_dependence(self, key, value):
|
||||
self.dependence[f"{{{{{key}}}}}"] = value
|
||||
|
||||
def get_dependence(self):
|
||||
return self.dependence
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
dependence = getattr(Dependence, "dependence")
|
||||
print(dependence)
|
||||
setattr(Dependence, "dependence", {"123": "32"})
|
||||
dependence = getattr(Dependence, "dependence")
|
||||
print(dependence)
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
"""
|
||||
@author: kira
|
||||
@contact: 262667641@qq.com
|
||||
@file: __init__.py.py
|
||||
@time: 2023/3/13 14:48
|
||||
@desc:
|
||||
"""
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,90 @@
|
|||
import sys
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
import jsonpath
|
||||
import pymongo
|
||||
|
||||
from dateutil import parser
|
||||
from common.base_datas import BaseDates
|
||||
from common.files_tools.do_excel import DoExcel
|
||||
|
||||
sys.path.append("../")
|
||||
sys.path.append("./common")
|
||||
|
||||
|
||||
class MongoDB(object):
|
||||
|
||||
def __init__(self, db_info):
|
||||
self.mongo_info = eval(db_info) # mongo数据库配置信息,字典形式
|
||||
self.mongo_client = pymongo.MongoClient(host=self.mongo_info["host"], port=self.mongo_info["port"]) # 获取连接实例
|
||||
self.my_db = self.mongo_client.admin # 连接系统默认数据库 admin
|
||||
self.my_db.authenticate(self.mongo_info["username"], self.mongo_info["password"],
|
||||
mechanism='SCRAM-SHA-1') # 让 admin数据库去认证密码登录
|
||||
self.db = self.mongo_client[self.mongo_info["database"]] # 连接自己的数据库需要操作的数据库
|
||||
|
||||
def do_mongo(self, **data):
|
||||
"""
|
||||
Args:
|
||||
**data: sql_file 操作语句
|
||||
Returns:
|
||||
"""
|
||||
result = {}
|
||||
for method, sql_dates in data.items():
|
||||
if method == "select":
|
||||
for table_name, sql_s in sql_dates.items():
|
||||
for index, sql in enumerate(sql_s):
|
||||
result_obj = self.db[table_name].find_one(sql)
|
||||
result["${" + "{}".format(index) + "}"] = result_obj
|
||||
if method == "delete":
|
||||
for table_name, sql_s in sql_dates.items():
|
||||
for index, sql in enumerate(sql_s):
|
||||
self.db[table_name].delete_many(sql)
|
||||
result["#{0}:{1}".format(table_name, sql)] = "删除成功"
|
||||
if method == "insert":
|
||||
for table_name, sql_s in sql_dates.items():
|
||||
for index, sql in enumerate(sql_s):
|
||||
self.db[table_name].insert_one(sql)
|
||||
result["#{0}:{1}".format(table_name, sql)] = "插入成功"
|
||||
self.mongo_client.close()
|
||||
return result
|
||||
|
||||
def insert_data(self):
|
||||
base_path = os.path.join(BaseDates.base_path, "data")
|
||||
names = os.listdir(base_path)
|
||||
for name in names:
|
||||
if re.match(r"(.+?).json", name):
|
||||
table_name = re.match(r"(.*?)\.json", name).group(1)
|
||||
print(table_name)
|
||||
actuator = self.db[table_name]
|
||||
with open(base_path + "\\{}.json".format(table_name), encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
result_data = []
|
||||
for sql in data:
|
||||
if "_id" in sql.keys():
|
||||
sql.pop("_id")
|
||||
created_at = jsonpath.jsonpath(sql, "$.createdAt.$date")[0]
|
||||
updated_at = jsonpath.jsonpath(sql, "$.updatedAt.$date")[0]
|
||||
sql["createdAt"] = parser.parse(created_at)
|
||||
sql["updatedAt"] = parser.parse(updated_at)
|
||||
if "intentionPaymentDate" in sql.keys():
|
||||
intention_payment_date = jsonpath.jsonpath(sql, "$..intentionPaymentDate.$date")[0]
|
||||
sql["intentionPaymentDate"] = parser.parse(intention_payment_date)
|
||||
result_data.append(sql)
|
||||
else:
|
||||
result_data.append(sql)
|
||||
actuator.insert_many(result_data)
|
||||
print("*" * 50)
|
||||
self.mongo_client.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
db_file = BaseDates.test_data_address
|
||||
excel_handle = DoExcel(db_file)
|
||||
excel_init = excel_handle.get_excel_init()
|
||||
mongo_base = excel_init["test_databases"]
|
||||
MongoDB(mongo_base).insert_data()
|
||||
# site = {'name': '我的博客地址', 'alexa': 10000, 'url': 'http://blog.csdn.net/uuihoo/'}
|
||||
# pop_obj = site.pop('name') # 删除要删除的键值对,如{'name':'我的博客地址'}这个键值对
|
||||
# print(pop_obj) # 输出 :我的博客地址
|
||||
# print(site)
|
|
@ -0,0 +1,109 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2019/11/13 14:51
|
||||
# @Author : kira
|
||||
# @Email : 262667641@qq.com
|
||||
# @File : do_mysql.py
|
||||
# @Project : risk_project
|
||||
|
||||
import sys
|
||||
import pymysql.cursors
|
||||
|
||||
sys.path.append("../")
|
||||
sys.path.append("./common")
|
||||
|
||||
from common.tools.logger import MyLog
|
||||
|
||||
|
||||
class DoMysql:
|
||||
|
||||
def __init__(self, db_base: dict):
|
||||
"""
|
||||
|
||||
Args:
|
||||
db_base:数据库字典
|
||||
{
|
||||
"host": "xxxx.xxx.xxx.xx",
|
||||
"port": 3306,
|
||||
"database": "db_name",
|
||||
"user": "root",
|
||||
"password": "xxxx"
|
||||
}
|
||||
"""
|
||||
|
||||
# self.sql = sql
|
||||
try:
|
||||
self.conn = pymysql.connect(**db_base) # 传入字典,连接数据库
|
||||
self.cur = self.conn.cursor(pymysql.cursors.DictCursor) # 操作结果为字典的游标
|
||||
except Exception as e:
|
||||
MyLog().my_log(f"数据库链接失败: {e}")
|
||||
|
||||
def do_mysql(self, sql):
|
||||
"""
|
||||
执行 mysql 数据库操作
|
||||
sql: sql字典嵌套字典嵌套列表集合{
|
||||
"select": [{"查xxx": "select * from tab"},{"":""}],
|
||||
"delete":[{"删除xxx":"delete from xxxx where xxx"}],
|
||||
"update":[],
|
||||
"insert":[]
|
||||
}
|
||||
:sql:
|
||||
:return: 返回操作结果,以字典形式返回
|
||||
"""
|
||||
if not sql:
|
||||
return
|
||||
result = None
|
||||
for method in sql.keys():
|
||||
if method not in ["delete", "update", "insert", "select"]:
|
||||
MyLog().my_log("sql字典集编写格式不符合规范")
|
||||
raise
|
||||
if method in ["delete", "update", "insert"]:
|
||||
for sql_list in sql.values():
|
||||
for sql_name, sql_ in sql_list.items():
|
||||
# 执行 提交 sql
|
||||
try:
|
||||
self.cur.execute(str(sql_))
|
||||
except Exception as err:
|
||||
MyLog().my_log("执行 sql 异常: {}".format(sql_))
|
||||
self.conn.rollback() # 异常回滚
|
||||
raise err
|
||||
self.conn.commit() # 提交事务
|
||||
else:
|
||||
sql_result = {}
|
||||
for sql_data in sql.values():
|
||||
for sql_dat in sql_data:
|
||||
for sql_name, sql_ in sql_dat.items():
|
||||
try:
|
||||
self.cur.execute(sql_) # 执行查询 sql_file
|
||||
sql_result[f"{sql_name}"] = self.cur.fetchall() # 返回所有查询结果
|
||||
except Exception as err:
|
||||
print(f"--->查询异常 sql: {sql_}")
|
||||
raise err
|
||||
result = sql_result
|
||||
return result
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self.cur.close() # 关闭游标
|
||||
self.conn.close() # 关闭链接
|
||||
except Exception as e:
|
||||
MyLog().my_log(f"关闭数据库失败: {e}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sql_2 = {
|
||||
"select": [
|
||||
{
|
||||
"select_sale": "select sale from do_mysql.sales"
|
||||
}
|
||||
]
|
||||
}
|
||||
database_2 = {
|
||||
"host": "localhost",
|
||||
"port": 3306,
|
||||
"database": "do_mysql",
|
||||
"user": "root",
|
||||
"password": "admin"
|
||||
}
|
||||
|
||||
res = DoMysql(database_2).do_mysql(sql_2)
|
||||
print(res)
|
|
@ -0,0 +1,32 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2019/12/6 9:16
|
||||
# @Author : kira
|
||||
# @Email : 262667641@qq.com
|
||||
# @File : do_psycopg.py
|
||||
# @Project : risk_api_project
|
||||
# import psycopg2
|
||||
# import psycopg2.extras
|
||||
|
||||
|
||||
# class DoPostgreSQL:
|
||||
#
|
||||
# def __init__(self):
|
||||
# postgre_config = BaseDates.postgreSql
|
||||
# self.connection = psycopg2.connect(**postgre_config)
|
||||
#
|
||||
# def do_postgre_sql(self, sql_file):
|
||||
# curs = self.connection.cursor()
|
||||
# curs.execute(sql_file)
|
||||
# select_value = curs.fetchone()
|
||||
# # select_value = curs.fetchall()
|
||||
# # self.connection.close()
|
||||
# return select_value
|
||||
#
|
||||
#
|
||||
# class Yoy:
|
||||
# def yoy(self, current, yesteryear):
|
||||
# return current / yesteryear
|
||||
#
|
||||
#
|
||||
# if __name__ == '__main__':
|
||||
# test = DoPostgreSQL()
|
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2021/5/8 10:36
|
||||
# @Author : kira
|
||||
# @Email : 262667641@qq.com
|
||||
# @File : do_redis.py
|
||||
# @Project : api-test-project
|
||||
|
||||
from redis import Redis, ConnectionPool
|
||||
|
||||
|
||||
class DoRedis:
|
||||
def __init__(self):
|
||||
connection = ConnectionPool(host="159.75.107.124", db=1, port=31003, decode_responses=True)
|
||||
self.r = Redis(connection_pool=connection)
|
||||
|
||||
def do_redis(self):
|
||||
r = self.r
|
||||
return r
|
||||
# res = r.get("dis:szpszx")
|
||||
# return res
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test = DoRedis().do_redis()
|
||||
print(test.mget("dis:szpszx","dis:gzpszx"))
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
'''
|
||||
@author: kira
|
||||
@contact: 262667641@qq.com
|
||||
@file: FileAES.py
|
||||
@time: 2022/6/27 17:11
|
||||
@desc:
|
||||
'''
|
||||
from Crypto.Cipher import AES
|
||||
import base64
|
||||
import os
|
||||
|
||||
|
||||
class FileAES:
|
||||
def __init__(self, key):
|
||||
self.key = key # 将密钥转换为字符型数据
|
||||
self.mode = AES.MODE_ECB # 操作模式选择ECB
|
||||
|
||||
def encrypt(self, text):
|
||||
"""加密函数"""
|
||||
file_aes = AES.new(self.key, self.mode) # 创建AES加密对象
|
||||
text = text.encode('utf-8') # 明文必须编码成字节流数据,即数据类型为bytes
|
||||
while len(text) % 16 != 0: # 对字节型数据进行长度判断
|
||||
text += b'\x00' # 如果字节型数据长度不是16倍整数就进行补充
|
||||
en_text = file_aes.encrypt(text) # 明文进行加密,返回加密后的字节流数据
|
||||
return str(base64.b64encode(en_text), encoding='utf-8') # 将加密后得到的字节流数据进行base64编码并再转换为unicode类型
|
||||
|
||||
def decrypt(self, text):
|
||||
"""解密函数"""
|
||||
file_aes = AES.new(self.key, self.mode)
|
||||
text = bytes(text, encoding='utf-8') # 将密文转换为bytes,此时的密文还是由basen64编码过的
|
||||
text = base64.b64decode(text) # 对密文再进行base64解码
|
||||
de_text = file_aes.decrypt(text) # 密文进行解密,返回明文的bytes
|
||||
return str(de_text, encoding='utf-8').strip() # 将解密后得到的bytes型数据转换为str型,并去除末尾的填充
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# key = os.urandom(16) #随即产生n个字节的字符串,可以作为随机加密key使用
|
||||
key = '2l4LoWczlWxlMZJAAp5N0g6EygZZd9A6' # 随即产生n个字节的字符串,可以作为随机加密key使用
|
||||
text = '1915' # 需要加密的内容
|
||||
aes_test = FileAES(key)
|
||||
cipher_text = aes_test.encrypt(text)
|
||||
init_text = aes_test.decrypt(cipher_text)
|
||||
print('加密后:' + cipher_text)
|
||||
print('解密后:' + init_text)
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
"""
|
||||
@author: kira
|
||||
@contact: 262667641@qq.com
|
||||
@file: __init__.py.py
|
||||
@time: 2023/3/14 14:23
|
||||
@desc:
|
||||
"""
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
'''
|
||||
@author: kira
|
||||
@contact: 262667641@qq.com
|
||||
@file: do_AES.py
|
||||
@time: 2022/2/17 15:19
|
||||
@desc:
|
||||
'''
|
||||
|
||||
import base64
|
||||
import sys
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
import binascii
|
||||
|
||||
|
||||
def add_to_16(text):
|
||||
while len(text) % 16 != 0:
|
||||
text += '\0'
|
||||
return text
|
||||
|
||||
|
||||
def encrypt(data, password):
|
||||
if isinstance(password, str):
|
||||
password = password.encode('utf8')
|
||||
|
||||
bs = AES.block_size
|
||||
pad = lambda s: s + (bs - len(s) % bs) * chr(bs - len(s) % bs)
|
||||
cipher = AES.new(password, AES.MODE_ECB)
|
||||
data = cipher.encrypt(pad(data).encode('utf8'))
|
||||
encrypt_data = binascii.b2a_hex(data) # 输出hex
|
||||
# encrypt_data = base64.b64encode(data) # 取消注释,输出Base64格式
|
||||
return encrypt_data.decode('utf8')
|
||||
|
||||
|
||||
def decrypt(decrData, password):
|
||||
if isinstance(password, str):
|
||||
password = password.encode('utf8')
|
||||
|
||||
cipher = AES.new(password, AES.MODE_ECB)
|
||||
plain_text = cipher.decrypt(binascii.a2b_hex(decrData))
|
||||
return plain_text.decode('utf8').rstrip('\0')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# data = sys.argv[1] # 待加密数据
|
||||
data = '4534' # 待加密数据
|
||||
password = '2l4LoWczlWxlMZJAAp5N0g6EygZZd9A6' # 16,24,32位长的密码(密钥)
|
||||
password = add_to_16(password)
|
||||
encrypt_data = encrypt(data, password)
|
||||
# print('加密前数据:{}\n======================='.format(data))
|
||||
# print('加密后的数据:', encrypt_data)
|
||||
print(encrypt_data)
|
||||
|
||||
# decrypt_data = decrypt(encrypt_data, password)
|
||||
# print('解密后的数据:{}'.format(decrypt_data))
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,27 @@
|
|||
import sys
|
||||
|
||||
sys.path.append("./common")
|
||||
sys.path.append("../")
|
||||
from common.tools.logger import MyLog
|
||||
from ext_script import sign
|
||||
|
||||
|
||||
def do_encrypt(method, data):
|
||||
if method == "MD5":
|
||||
try:
|
||||
res = sign.md5_sign(data)
|
||||
except Exception as e:
|
||||
MyLog().my_log(f"MD5加密失败:{e},{data}", "error")
|
||||
else:
|
||||
return res
|
||||
elif method == "sha1":
|
||||
try:
|
||||
res = sign.sha1_sign(data)
|
||||
except Exception as e:
|
||||
MyLog().my_log(f"sha1加密失败参数:{e},{data}", "error")
|
||||
else:
|
||||
return res
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(do_encrypt("sha1", {}))
|
|
@ -0,0 +1,16 @@
|
|||
import base64
|
||||
import hashlib
|
||||
|
||||
|
||||
class HashTools:
|
||||
@staticmethod
|
||||
def hash_tools(file):
|
||||
with open(file, "rb") as f:
|
||||
# 获取 base64 码
|
||||
base64_data = base64.b64encode(f.read())
|
||||
with open(file, "rb") as f:
|
||||
md = hashlib.md5()
|
||||
md.update(f.read())
|
||||
res_md5 = md.hexdigest()
|
||||
|
||||
return str(base64_data, "utf-8"), res_md5
|
|
@ -0,0 +1,222 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
"""
|
||||
@author: kira
|
||||
@contact: 262667641@qq.com
|
||||
@file: str_encryption.py
|
||||
@time: 2023/3/14 16:28
|
||||
@desc:
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import base64
|
||||
import binascii
|
||||
import rsa
|
||||
|
||||
from pyDes import des, CBC, PAD_PKCS5
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
|
||||
def bs64_data_encode(st):
|
||||
"""
|
||||
base64 加密
|
||||
Args:
|
||||
st:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return base64.b64encode(st.encode("utf-8"))
|
||||
|
||||
|
||||
def bs64_data_decode(st):
|
||||
"""
|
||||
base64 解密
|
||||
Args:
|
||||
st:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return base64.b64decode(st).decode()
|
||||
|
||||
|
||||
def md5(st: str) -> str:
|
||||
"""
|
||||
|
||||
Args:
|
||||
st:待加密字符串
|
||||
|
||||
Returns: 返回MD5 加密后的字符串
|
||||
|
||||
"""
|
||||
md = hashlib.md5() # 创建MD5对象
|
||||
md.update(st.encode(encoding="utf-8"))
|
||||
return md.hexdigest()
|
||||
|
||||
|
||||
def sha1_secret_str(st):
|
||||
"""
|
||||
使用sha1加密算法,返回str加密后的字符串
|
||||
Args:
|
||||
st:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
sha = hashlib.sha1(st.encode("utf-8"))
|
||||
return sha.hexdigest()
|
||||
|
||||
|
||||
def sha256_single(st):
|
||||
"""
|
||||
sha256加密
|
||||
Args:
|
||||
st: 加密字符串
|
||||
|
||||
Returns:加密结果转换为16进制字符串,并大写
|
||||
|
||||
"""
|
||||
sha_obj = hashlib.sha256()
|
||||
sha_obj.update(st.encode("utf-8"))
|
||||
return sha_obj.hexdigest().upper()
|
||||
|
||||
|
||||
class Des:
|
||||
def __init__(self, text, key):
|
||||
self.text = text # 原始字符串
|
||||
self.KEY = key # 这个key是固定问开发,
|
||||
|
||||
def des_encrypt(self):
|
||||
"""DES 加密
|
||||
Returns:加密后字符串,16进制
|
||||
|
||||
"""
|
||||
secret_key = self.KEY # 密码
|
||||
iv = secret_key # 偏移
|
||||
# secret_key:加密密钥,CBC:加密模式,iv:偏移, padmode:填充
|
||||
des_obj = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)
|
||||
# 返回为字节
|
||||
secret_bytes = des_obj.encrypt(self.text.encode("utf-8"), padmode=PAD_PKCS5)
|
||||
# 返回为16进制
|
||||
return binascii.b2a_hex(secret_bytes)
|
||||
|
||||
def des_decrypt(self):
|
||||
"""
|
||||
DES 解密
|
||||
Returns:解密后的字符串
|
||||
|
||||
"""
|
||||
secret_key = self.KEY
|
||||
iv = secret_key
|
||||
des_obj = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)
|
||||
decrypt_str = des_obj.decrypt(binascii.a2b_hex(self.text), padmode=PAD_PKCS5)
|
||||
return bytes.decode(decrypt_str) # bytes.decode() 将bit转为str
|
||||
|
||||
|
||||
def add_to_16(text: str):
|
||||
"""
|
||||
使用空格补足16位数
|
||||
Args:
|
||||
text:源字符串
|
||||
|
||||
Returns:补位后字符串
|
||||
|
||||
"""
|
||||
b_text = text.encode("utf-8")
|
||||
add = 0
|
||||
# 计算需要补的位数
|
||||
if len(b_text) % 16:
|
||||
add = 16 - len(b_text) % 16
|
||||
return b_text + b'\0' * add
|
||||
|
||||
|
||||
class AesEcb:
|
||||
|
||||
def __init__(self, text: str, key: str):
|
||||
self.text = text
|
||||
self.KEY = key
|
||||
|
||||
def encrypt_by_aes(self):
|
||||
"""
|
||||
加密函数
|
||||
Returns:加密后字符串,base64 编码输出
|
||||
|
||||
"""
|
||||
key = self.KEY.encode("utf-8")
|
||||
text = add_to_16(self.text) # 补位:16位
|
||||
cryptos = AES.new(key, AES.MODE_ECB) # 加密模式 ECB
|
||||
cipher_text = cryptos.encrypt(text) # 加密
|
||||
return base64.standard_b64encode(cipher_text).decode("utf-8") # 加密结果 base64 编码输出
|
||||
|
||||
def decrypt_by_aes(self):
|
||||
"""
|
||||
解密函数
|
||||
Returns:
|
||||
|
||||
"""
|
||||
key = self.KEY.encode("utf-8")
|
||||
text = self.text.encode("utf-8")
|
||||
text = base64.b64decode(text) # 先用base64 解密
|
||||
cryptos = AES.new(key, AES.MODE_ECB)
|
||||
cipher_text = cryptos.decrypt(text) # 解密
|
||||
return cipher_text.decode("utf-8").strip("\0") # 解密,去掉补位的0
|
||||
|
||||
|
||||
class AesCbc:
|
||||
|
||||
def __init__(self, key: str, iv: str):
|
||||
self.key = key.encode("utf-8") # 初始化密钥
|
||||
self.iv = iv.encode("utf-8") # 初始化偏移量
|
||||
self.length = 16 # 初始化数据快大小
|
||||
self.aes = AES.new(self.key, AES.MODE_CBC, self.iv) # 初始化AES,ECB 模式的实例
|
||||
self.unpad = lambda s: s[0:-s[-1]] # 截断函数,去除填充的字符
|
||||
|
||||
def pad(self, text):
|
||||
"""
|
||||
填充函数,使被加密数据的字节码长度是block_size的整数倍
|
||||
"""
|
||||
count = len(text.encode('utf-8'))
|
||||
add = self.length - (count % self.length)
|
||||
entext = text + (chr(add) * add)
|
||||
return entext
|
||||
|
||||
def encrypt(self, encr_data): # 加密函数
|
||||
a = self.pad(encr_data)
|
||||
res = self.aes.encrypt(a.encode("utf-8"))
|
||||
msg = str(base64.b64encode(res), encoding="utf8")
|
||||
return msg
|
||||
|
||||
def decrypt(self, decr_data): # 解密函数
|
||||
res = base64.decodebytes(decr_data.encode("utf-8"))
|
||||
msg_text = self.aes.decrypt(res)
|
||||
decrypt_text = self.unpad(msg_text).decode('utf8')
|
||||
return decrypt_text
|
||||
|
||||
|
||||
class Rsa:
|
||||
def __init__(self, st: str):
|
||||
self.st = st
|
||||
|
||||
# rsa加密
|
||||
def rsa_encrypt(self):
|
||||
# 生成公钥、私钥
|
||||
(pubkey, privkey) = rsa.newkeys(1024)
|
||||
print("公钥: ", pubkey)
|
||||
print("私钥: ", privkey)
|
||||
# 明文编码格式
|
||||
content = self.st.encode('utf-8')
|
||||
# 公钥加密
|
||||
crypto = rsa.encrypt(content, pubkey)
|
||||
# # 一般加密的密文会以base64编码的方式输出
|
||||
b_res = base64.b64encode(crypto).decode()
|
||||
return b_res, privkey
|
||||
|
||||
# rsa解密
|
||||
def rsa_decrypt(self, pk):
|
||||
# 私钥解密
|
||||
st = base64.b64decode(self.st.encode())
|
||||
content = rsa.decrypt(st, pk)
|
||||
con = content.decode('utf-8')
|
||||
|
||||
return con
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
"""
|
||||
@author: kira
|
||||
@contact: 262667641@qq.com
|
||||
@file: __init__.py.py
|
||||
@time: 2023/3/14 14:21
|
||||
@desc:
|
||||
"""
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue