first commit

This commit is contained in:
chenyongzhiaaron 2023-03-28 09:54:53 +08:00
parent ce87569c70
commit 37c1aedd5e
324 changed files with 42764 additions and 0 deletions

2
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# Default ignored files
/workspace.xml

20
.idea/api_project.iml Normal file
View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

View File

@ -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>

18
.idea/dataSources.xml Normal file
View File

@ -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

View File

@ -0,0 +1,2 @@
#n:information_schema
!<md> [null, 0, null, null, -2147483648, -2147483648]

View File

@ -0,0 +1,2 @@
#n:mysql
!<md> [null, 0, null, null, -2147483648, -2147483648]

View File

@ -0,0 +1,2 @@
#n:performance_schema
!<md> [null, 0, null, null, -2147483648, -2147483648]

View File

@ -0,0 +1,2 @@
#n:sys
!<md> [null, 0, null, null, -2147483648, -2147483648]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
#n:information_schema
!<md> [null, 0, null, null, -2147483648, -2147483648]

View File

@ -0,0 +1,2 @@
#n:mysql
!<md> [null, 0, null, null, -2147483648, -2147483648]

View File

@ -0,0 +1,2 @@
#n:performance_schema
!<md> [null, 0, null, null, -2147483648, -2147483648]

View File

@ -0,0 +1,2 @@
#n:sys
!<md> [null, 0, null, null, -2147483648, -2147483648]

643
.idea/dbnavigator.xml Normal file
View File

@ -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>

View File

@ -0,0 +1,3 @@
<component name="ProjectDictionaryState">
<dictionary name="Administrator" />
</component>

View File

@ -0,0 +1,3 @@
<component name="ProjectDictionaryState">
<dictionary name="swallow" />
</component>

28
.idea/encodings.xml Normal file
View File

@ -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>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

25
.idea/jsonSchemas.xml Normal file
View File

@ -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>

7
.idea/misc.xml Normal file
View File

@ -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>

8
.idea/modules.xml Normal file
View File

@ -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>

29
.idea/mqttclient.xml Normal file
View File

@ -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

View File

@ -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>

6
.idea/sqldialects.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="PROJECT" dialect="MySQL" />
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

1179
HTMLTestRunnerN.py Normal file

File diff suppressed because one or more lines are too long

746
HTMLTestRunnerNew.py Normal file
View File

@ -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,'&amp;');
s = s.replace(/</g,'&lt;');
s = s.replace(/>/g,'&gt;');
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'>&nbsp;</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)

View File

@ -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'

View File

@ -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'}

View File

@ -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 ] (])

View File

@ -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

View File

@ -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}$

View File

@ -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

View File

@ -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,'&amp;');
s = s.replace(/</g,'&lt;');
s = s.replace(/>/g,'&gt;');
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 &lt;module&gt;
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'>&nbsp;</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>

8
OutPut/__init__.py Normal file
View File

@ -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.

39
README.md Normal file
View File

@ -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 |
+--------+------+------+------------+--------------------+----------------------+--------------+-------------+-------------------------+----------------------------------------+--------------------+
```

925
Read.md Normal file
View File

@ -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)
如果对您有帮助,请点亮 小星星 以表支持,谢谢
![img.png](Files/image/starts.png)
## 前言
公司突然要求你做自动化,但是没有代码基础不知道怎么做?或者有自动化基础,但是不知道如何系统性的做自动化,
放在yaml文件中维护不知道如何处理多业务依赖的逻辑
那么这里 Gitte 中开源的自动化框架,将为你解决这些问题。
框架主要使用 python 语言编写,结合 pytest 进行二次开发,用户仅需要在 yaml 文件中编写测试用例,
编写成功之后,会自动生成 pytest 的代码,零基础代码小白,也可以操作。
本框架支持多业务接口依赖多进程执行mysql 数据库断言和 接口响应断言并且用例直接在yaml文件中维护无需编写业务代码
接口pytest框架生成allure报告并且发送 企业微信通知/ 钉钉通知/ 邮箱通知/ 飞书通知,灵活配置。
## 实现功能
* 测试数据隔离, 实现数据驱动
* 支持多接口数据依赖: 如A接口需要同时依赖B、C接口的响应数据作为参数
* 数据库断言: 直接在测试用例中写入查询的sql即可断言无需编写代码
* 动态多断言: 如接口需要同时校验响应数据和sql校验支持多场景断言
* 自动生成用例代码: 测试人员在yaml文件中填写好测试用例, 程序可以直接生成用例代码,纯小白也能使用
* 代理录制: 支持代理录制生成yaml格式的测试用例
* 统计接口的运行时长: 拓展功能,订制开关,可以决定是否需要使用
* 日志模块: 打印每个接口的日志信息,同样订制了开关,可以决定是否需要打印日志
* 钉钉、企业微信通知: 支持多种通知场景,执行成功之后,可选择发送钉钉、或者企业微信、邮箱通知
* 自定义拓展字段: 如用例中需要生成的随机数据,可直接调用
* 多线程执行
* 支持swagger接口文档转成yaml用例节省用例编写时间
## 遇到问题
* 请仔细阅读文档,文档中几乎可以帮你避免所有的问题
* 可以添加微信: being_chaoren, 添加微信会将你拉倒自动化交流群中,群内有很多热心的小伙伴,但是前提是希望你已经阅读了文档中的所有内容
* 你也可以请作者为你解答,当然我不是免费的
![img.png](Files/image/wechat.png)
## 目录结构
├── 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
![img.png](Files/image/安装异常.png)
如果在安装过程中出现如下 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主程序执行所有自动化接口
下面我们来看一下,如何创建用例
### 用例中相关字段的介绍
![img.png](Files/image/case_data.png)
上方截图,就是一个用例中需要维护的相关字段,下面我会对每个字段的作用,做出解释。
![img.png](Files/image/case_detail.png)
### 如何发送get请求
上方了解了用例的数据结构之后下面我们开始编写第一个get请求方式的接口。
首先,开始编写项目之后,我们在 conf.yaml 中配置项目的域名
![img.png](Files/image/conf.png)
域名配置好之后,我们来编写测试用例,在 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 文件夹中
![img.png](Files/image/files.png)
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后方的
![img.png](Files/image/files_up.png)
为了方便大家理解上方将该参数以postman的形式上传
### 多业务逻辑,如何编写测试用例
多业务这一块,我们拿个简单的例子举例,比如登录场景,在登陆之前,我们需要先获取到验证码。
![img.png](Files/image/send_sms_code.png)
![img.png](Files/image/login.png)
首先,我们先创建一个 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
### 请求用例时参数需要从数据库中提取
![img.png](Files/image/sql_params.png)
如上图所示,用例中的 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 填写我们所编写的缓存名称即可。
![img.png](Files/image/img.png)
### 用例中依赖cookie如何设计
![img.png](Files/image/cookie.png)
首先我们在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}
### 用例中如何生成随机数据
比如我们有些特殊的场景,可能会涉及到一些定制化的数据,每次执行数据,需要按照指定规则随机生成。
![img.png](Files/image/randoms.png)
如上图所示,我们用例中的 reason 审核原因后方,需要展示审核的当前时间。那么我们首先需要封装一个获取当前时间的方法
![img.png](Files/image/regular.png)
那么我们就在 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
### 断言类型
下放截图中,是所有断言支持的类型
![img.png](Files/image/assert_type.png)
### 用例中如何进行接口断言和数据库断言
假设现在我需要测试一个报表统计的数据,该接口返回了任务的处理时长 和 处理数量。功能如下截图所示:
![img.png](Files/image/question_coun.png)
假设下方是我们拿到接口响应的数据内容:
{"code": 200, "times": 155.91, "counts": 9}
这个时候我们需要判断该接口返回的数据是否正确就需要编写sql对响应内容进行校验。
![img.png](Files/image/sql.png)
因此我们编写了如上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 ,会跟你设计的测试用例,生成对应的代码。
![img.png](Files/image/write_test_case.png)
### 发送钉钉通知通知
![img.png](Files/image/dingding.png)
### 发送企业微信通知
![img.png](Files/image/wechart.png)
### 日志打印装饰器
![img.png](Files/image/log.png)
在requestControl.py中我单独封装了一个日志装饰器需要的小伙伴可以不用改动代码直接使用如果不需要直接注释或者改成False。控制台将不会有日志输出
### 统计用例运行时长
![img.png](Files/image/run_times.png)
同样,这里封装了一个统计用例运行时长的装饰器,使用改装饰器前,需要先进行导包
from utils.logUtils.runTimeDecoratorl import execution_duration
导入之后调用改装饰器装饰器中填写的用例执行时长以毫秒为单位如这里设置的2000ms那么如果该用例执行大于2000ms则会输出一条告警日志。
@execution_duration(2000)
### 生成allure报告
我们直接运行主程序 run.py 运行完成之后就可以生成漂亮的allure报告啦~
![img.png](Files/image/allure.png)
![img.png](Files/image/allure2.png)
### 其他
本框架为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)
## 赞赏
如果这个库有帮助到你并且你很想支持库的后续开发和维护,那么你可以扫描下方二维码随意打赏我,我将不胜感激
![img_1.png](Files/image/img_1.png) ![img_1.png](Files/image/weixin_pay.png)

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.

10
common/__init__.py Normal file
View File

@ -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.

35
common/base_datas.py Normal file
View File

@ -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)

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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:实际值是以期望值结束',
}

View File

@ -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))

View File

@ -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: listdictstr 示例["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

View File

@ -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)`
# 的形式添加到字典中。

View File

@ -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())

View File

@ -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: 参数格式 listdict
示例
[{"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:参数格式 listdict
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)

26
common/dependence.py Normal file
View File

@ -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)

View File

@ -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.

90
common/do_sql/do_mongo.py Normal file
View File

@ -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)

109
common/do_sql/do_mysql.py Normal file
View File

@ -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)

View File

@ -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()

25
common/do_sql/do_redis.py Normal file
View File

@ -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"))

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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", {}))

View File

@ -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

View File

@ -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

View File

@ -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.

Some files were not shown because too many files have changed in this diff Show More