Commit 631a27fe0dad5aca229f4186385488aad4662bee

Authored by biqiao
0 parents
Exists in master

first commit

Showing 51 changed files with 5323 additions and 0 deletions   Show diff stats
.idea/apiTest.iml
... ... @@ -0,0 +1,11 @@
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<module type="PYTHON_MODULE" version="4">
  3 + <component name="NewModuleRootManager">
  4 + <content url="file://$MODULE_DIR$" />
  5 + <orderEntry type="inheritedJdk" />
  6 + <orderEntry type="sourceFolder" forTests="false" />
  7 + </component>
  8 + <component name="TestRunnerService">
  9 + <option name="PROJECT_TEST_RUNNER" value="Unittests" />
  10 + </component>
  11 +</module>
0 12 \ No newline at end of file
... ...
.idea/inspectionProfiles/Project_Default.xml
... ... @@ -0,0 +1,12 @@
  1 +<component name="InspectionProjectProfileManager">
  2 + <profile version="1.0">
  3 + <option name="myName" value="Project Default" />
  4 + <inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
  5 + <option name="ignoredIdentifiers">
  6 + <list>
  7 + <option value="elecCard_setUp" />
  8 + </list>
  9 + </option>
  10 + </inspection_tool>
  11 + </profile>
  12 +</component>
0 13 \ No newline at end of file
... ...
... ... @@ -0,0 +1,4 @@
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6.2 (/Library/Frameworks/Python.framework/Versions/3.6/bin/python3.6)" project-jdk-type="Python SDK" />
  4 +</project>
0 5 \ No newline at end of file
... ...
.idea/modules.xml
... ... @@ -0,0 +1,8 @@
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="ProjectModuleManager">
  4 + <modules>
  5 + <module fileurl="file://$PROJECT_DIR$/.idea/apiTest.iml" filepath="$PROJECT_DIR$/.idea/apiTest.iml" />
  6 + </modules>
  7 + </component>
  8 +</project>
0 9 \ No newline at end of file
... ...
... ... @@ -0,0 +1,6 @@
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="VcsDirectoryMappings">
  4 + <mapping directory="$PROJECT_DIR$" vcs="Git" />
  5 + </component>
  6 +</project>
0 7 \ No newline at end of file
... ...
.idea/workspace.xml
... ... @@ -0,0 +1,920 @@
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="ChangeListManager">
  4 + <list default="true" id="33187cb8-da74-4b13-8a55-31c4cae60a20" name="Default" comment="" />
  5 + <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
  6 + <option name="TRACKING_ENABLED" value="true" />
  7 + <option name="SHOW_DIALOG" value="false" />
  8 + <option name="HIGHLIGHT_CONFLICTS" value="true" />
  9 + <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
  10 + <option name="LAST_RESOLUTION" value="IGNORE" />
  11 + </component>
  12 + <component name="CoverageDataManager">
  13 + <SUITE FILE_PATH="coverage/apiTest$debugggggg.coverage" NAME="debugggggg Coverage Results" MODIFIED="1514886595342" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test_cases" />
  14 + <SUITE FILE_PATH="coverage/apiTest$run_test.coverage" NAME="run_test Coverage Results" MODIFIED="1515151494257" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
  15 + </component>
  16 + <component name="DatabaseView">
  17 + <option name="SHOW_INTERMEDIATE" value="true" />
  18 + <option name="GROUP_SCHEMA" value="true" />
  19 + <option name="GROUP_CONTENTS" value="false" />
  20 + <option name="SORT_POSITIONED" value="false" />
  21 + <option name="SHOW_TABLE_DETAILS" value="true" />
  22 + <option name="SHOW_EMPTY_GROUPS" value="false" />
  23 + <option name="AUTO_SCROLL_FROM_SOURCE" value="false" />
  24 + <expand />
  25 + <select />
  26 + </component>
  27 + <component name="FileEditorManager">
  28 + <leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
  29 + <file leaf-file-name="mysql_db.py" pinned="false" current-in-tab="false">
  30 + <entry file="file://$PROJECT_DIR$/data_fixture/mysql_db.py">
  31 + <provider selected="true" editor-type-id="text-editor">
  32 + <state relative-caret-position="34">
  33 + <caret line="2" column="0" lean-forward="true" selection-start-line="2" selection-start-column="0" selection-end-line="2" selection-end-column="0" />
  34 + <folding>
  35 + <marker date="1514616463000" expanded="true" signature="1672:1692" ph="select * fro... " />
  36 + <marker date="1514616463000" expanded="true" signature="1672:1694" ph="select * fro... missing_value" />
  37 + <marker date="1514616463000" expanded="true" signature="1672:1718" ph="select count... missing_value" />
  38 + <marker date="1514616463000" expanded="true" signature="1672:1719" ph="select count... missing_value" />
  39 + </folding>
  40 + </state>
  41 + </provider>
  42 + </entry>
  43 + </file>
  44 + <file leaf-file-name="app_record_statistic.py" pinned="false" current-in-tab="false">
  45 + <entry file="file://$PROJECT_DIR$/test_cases/app_record_statistic.py">
  46 + <provider selected="true" editor-type-id="text-editor">
  47 + <state relative-caret-position="761">
  48 + <caret line="170" column="45" lean-forward="false" selection-start-line="170" selection-start-column="45" selection-end-line="170" selection-end-column="45" />
  49 + <folding>
  50 + <element signature="e#47#62#0" expanded="true" />
  51 + </folding>
  52 + </state>
  53 + </provider>
  54 + </entry>
  55 + </file>
  56 + <file leaf-file-name="run_test.py" pinned="false" current-in-tab="true">
  57 + <entry file="file://$PROJECT_DIR$/run_test.py">
  58 + <provider selected="true" editor-type-id="text-editor">
  59 + <state relative-caret-position="289">
  60 + <caret line="17" column="0" lean-forward="true" selection-start-line="17" selection-start-column="0" selection-end-line="17" selection-end-column="0" />
  61 + <folding>
  62 + <element signature="e#47#62#0" expanded="true" />
  63 + </folding>
  64 + </state>
  65 + </provider>
  66 + </entry>
  67 + </file>
  68 + <file leaf-file-name="debugggggg.py" pinned="false" current-in-tab="false">
  69 + <entry file="file://$PROJECT_DIR$/test_cases/debugggggg.py">
  70 + <provider selected="true" editor-type-id="text-editor">
  71 + <state relative-caret-position="34">
  72 + <caret line="2" column="0" lean-forward="false" selection-start-line="2" selection-start-column="0" selection-end-line="2" selection-end-column="0" />
  73 + <folding />
  74 + </state>
  75 + </provider>
  76 + </entry>
  77 + </file>
  78 + <file leaf-file-name="subject_sync.py" pinned="false" current-in-tab="false">
  79 + <entry file="file://$PROJECT_DIR$/test_cases/subject_sync.py">
  80 + <provider selected="true" editor-type-id="text-editor">
  81 + <state relative-caret-position="85">
  82 + <caret line="5" column="66" lean-forward="true" selection-start-line="5" selection-start-column="66" selection-end-line="5" selection-end-column="66" />
  83 + <folding />
  84 + </state>
  85 + </provider>
  86 + </entry>
  87 + </file>
  88 + <file leaf-file-name="create_testdata.py" pinned="false" current-in-tab="false">
  89 + <entry file="file://$PROJECT_DIR$/data_fixture/create_testdata.py">
  90 + <provider selected="true" editor-type-id="text-editor">
  91 + <state relative-caret-position="310">
  92 + <caret line="356" column="79" lean-forward="false" selection-start-line="356" selection-start-column="67" selection-end-line="356" selection-end-column="79" />
  93 + <folding>
  94 + <element signature="e#47#83#0" expanded="true" />
  95 + <marker date="1515219431000" expanded="true" signature="1421:1488" ph="select * fro... ozing_customermachine" />
  96 + <marker date="1515219431000" expanded="true" signature="1421:1489" ph="select * fro... ozing_customermachine" />
  97 + <marker date="1515219431000" expanded="true" signature="1421:1491" ph="select * fro... ozing_customermachine" />
  98 + <marker date="1515219431000" expanded="true" signature="1421:1492" ph="select * fro... ozing_customermachine" />
  99 + <marker date="1515219431000" expanded="true" signature="1421:1518" ph="select * fro... ozing_customermachine" />
  100 + <marker date="1515219431000" expanded="true" signature="1549:1608" ph="SELECT * FRO... ozing_machine" />
  101 + <marker date="1515219431000" expanded="true" signature="1549:1609" ph="SELECT * FRO... ozing_machine" />
  102 + <marker date="1515219431000" expanded="true" signature="1549:1612" ph="SELECT * FRO... ozing_machine" />
  103 + <marker date="1515219431000" expanded="true" signature="1549:1613" ph="SELECT * FRO... ozing_machine" />
  104 + <marker date="1515219431000" expanded="true" signature="1549:1639" ph="SELECT * FRO... ozing_machine" />
  105 + <marker date="1515219431000" expanded="true" signature="1752:2214" ph="insert into acornuser.ozing_customermachine... " />
  106 + <marker date="1515219431000" expanded="true" signature="2928:2997" ph="SELECT * FRO... ozing_samplemachine" />
  107 + <marker date="1515219431000" expanded="true" signature="3049:3113" ph="SELECT * FRO... ozing_machine" />
  108 + <marker date="1515219431000" expanded="true" signature="4735:4806" ph="select * fro... ozing_customermachine" />
  109 + <marker date="1515219431000" expanded="true" signature="5151:5215" ph="SELECT * FRO... ozing_machine" />
  110 + <marker date="1515219431000" expanded="true" signature="5276:5356" ph="SELECT * FRO... ozing_machine" />
  111 + <marker date="1515219431000" expanded="true" signature="6222:6293" ph="select * fro... ozing_customermachine" />
  112 + <marker date="1515219431000" expanded="true" signature="6578:6641" ph="select * fro... ozing_machine" />
  113 + <marker date="1515219431000" expanded="true" signature="7715:7780" ph="update acorn... " />
  114 + <marker date="1515219431000" expanded="true" signature="7715:7804" ph="update acorn... " />
  115 + <marker date="1515219431000" expanded="true" signature="7928:8009" ph="select * fro... child_user" />
  116 + <marker date="1515219431000" expanded="true" signature="8075:8156" ph="select * fro... child_user" />
  117 + <marker date="1515219431000" expanded="true" signature="8075:8203" ph="select * fro... child_user" />
  118 + <marker date="1515219431000" expanded="true" signature="8075:8205" ph="select * fro... child_user" />
  119 + <marker date="1515219431000" expanded="true" signature="9093:9178" ph="select * fro... acorn_user_status" />
  120 + <marker date="1515219431000" expanded="true" signature="9320:9420" ph="select * fro... acorn_user_status" />
  121 + <marker date="1515219431000" expanded="true" signature="10158:10258" ph="select * fro... acorn_user_status" />
  122 + <marker date="1515219431000" expanded="true" signature="11302:11360" ph="select * fro... acorn_user_extra" />
  123 + <marker date="1515219431000" expanded="true" signature="11397:11483" ph="select * fro... acorn_user_extra" />
  124 + <marker date="1515219431000" expanded="true" signature="11914:11983" ph="select * fro... subAccount_user_extra" />
  125 + <marker date="1515219431000" expanded="true" signature="11914:11984" ph="select * fro... subAccount_user_extra" />
  126 + <marker date="1515219431000" expanded="true" signature="11914:11988" ph="select * fro... subAccount_user_extra" />
  127 + <marker date="1515219431000" expanded="true" signature="12023:12092" ph="select * fro... subAccount_user_extra" />
  128 + <marker date="1515219431000" expanded="true" signature="12023:12122" ph="select * fro... subAccount_user_extra" />
  129 + <marker date="1515219431000" expanded="true" signature="12023:12138" ph="select * fro... subAccount_user_extra" />
  130 + <marker date="1515219431000" expanded="true" signature="13348:13448" ph="select * fro... ozing_student" />
  131 + <marker date="1515219431000" expanded="true" signature="13550:13636" ph="update acorn... " />
  132 + <marker date="1515219431000" expanded="true" signature="13550:13638" ph="update acorn... " />
  133 + <marker date="1515219431000" expanded="true" signature="13726:13834" ph="select * fro... ozing_student" />
  134 + <marker date="1515219431000" expanded="true" signature="15966:16006" ph="select max(i... acorn_user" />
  135 + <marker date="1515219431000" expanded="true" signature="15966:16008" ph="select max(i... acorn_user" />
  136 + <marker date="1515219431000" expanded="true" signature="16721:16796" ph="select passw... parents_space_pass" />
  137 + <marker date="1515219431000" expanded="true" signature="17752:17804" ph="update analy... " />
  138 + <marker date="1515219431000" expanded="true" signature="17752:17821" ph="update analy... " />
  139 + <marker date="1515219431000" expanded="true" signature="17752:17849" ph="update analy... " />
  140 + <marker date="1515219431000" expanded="true" signature="17752:17850" ph="update analy... " />
  141 + </folding>
  142 + </state>
  143 + </provider>
  144 + </entry>
  145 + </file>
  146 + <file leaf-file-name="config_data.py" pinned="false" current-in-tab="false">
  147 + <entry file="file://$PROJECT_DIR$/data_fixture/config_data.py">
  148 + <provider selected="true" editor-type-id="text-editor">
  149 + <state relative-caret-position="114">
  150 + <caret line="12" column="14" lean-forward="false" selection-start-line="12" selection-start-column="14" selection-end-line="12" selection-end-column="14" />
  151 + <folding />
  152 + </state>
  153 + </provider>
  154 + </entry>
  155 + </file>
  156 + </leaf>
  157 + </component>
  158 + <component name="FileTemplateManagerImpl">
  159 + <option name="RECENT_TEMPLATES">
  160 + <list>
  161 + <option value="Python Script" />
  162 + </list>
  163 + </option>
  164 + </component>
  165 + <component name="FindInProjectRecents">
  166 + <findStrings>
  167 + <find>_generate_report</find>
  168 + <find>Data</find>
  169 + <find>test_unbindCard_success</find>
  170 + <find>login</find>
  171 + <find>DEVICE_NUMBER_EDIT_PHONE</find>
  172 + <find>pre_SetUpElecCard</find>
  173 + <find>authCode</find>
  174 + <find>subaccountswitch001</find>
  175 + <find>pre_AddSubAccount</find>
  176 + <find>parent_id</find>
  177 + <find>USER_ID</find>
  178 + <find>USER_PHONE_EDIT</find>
  179 + <find>RegisterExtrainfoCheck</find>
  180 + <find>png</find>
  181 + <find>checkSignatureExists</find>
  182 + <find>SUB_ACC_GET_ID_1</find>
  183 + <find>SUB_ACC_GET_ID_</find>
  184 + <find>SUB_ACC_DEL_ID_2</find>
  185 + <find>SUB_ACC_SWITCH_ID_1</find>
  186 + <find>SUB_ACC</find>
  187 + <find>pre_subAccount</find>
  188 + <find>pre_elecCard</find>
  189 + <find>保卡</find>
  190 + <find>'time_spend'</find>
  191 + <find>select_</find>
  192 + <find>Data.DEVICE_NUMBER_CUS_BIND</find>
  193 + <find>SUB_ACC_</find>
  194 + <find>test_getAppRecordOneday_success</find>
  195 + <find>get_parentSpace_password</find>
  196 + <find>print</find>
  197 + </findStrings>
  198 + <replaceStrings>
  199 + <replace>app_pid</replace>
  200 + <replace>'app_pid'</replace>
  201 + <replace>'time_spent'</replace>
  202 + <replace>user_id</replace>
  203 + <replace>device_number</replace>
  204 + <replace>PARENT_ID</replace>
  205 + </replaceStrings>
  206 + <dirStrings>
  207 + <dir>$PROJECT_DIR$</dir>
  208 + </dirStrings>
  209 + </component>
  210 + <component name="IdeDocumentHistory">
  211 + <option name="CHANGED_PATHS">
  212 + <list>
  213 + <option value="$PROJECT_DIR$/tests/elecCard.py" />
  214 + <option value="$PROJECT_DIR$/db_fixture/mysql_db.py" />
  215 + <option value="$PROJECT_DIR$/tests/configParse'.py" />
  216 + <option value="$PROJECT_DIR$/tests/configParse.py" />
  217 + <option value="$PROJECT_DIR$/tests/personalCenter/elecCardFlow_test.py" />
  218 + <option value="$PROJECT_DIR$/tests/personalCenter/elecCard_setUp.py" />
  219 + <option value="$PROJECT_DIR$/tests/personalCenter/elecCard/elecCard_setUp.py" />
  220 + <option value="$PROJECT_DIR$/tests/personalCenter/elecCard/elecCard_check.py" />
  221 + <option value="$PROJECT_DIR$/tests/test_suites/test_elecCard.py" />
  222 + <option value="$PROJECT_DIR$/tests/test_cases/__init__.py" />
  223 + <option value="$PROJECT_DIR$/test_suites/test_elecCard.py" />
  224 + <option value="$PROJECT_DIR$/data_fixture/UthCode.py" />
  225 + <option value="/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/HtmlTestRunner/runner.py" />
  226 + <option value="$PROJECT_DIR$/config.ini" />
  227 + <option value="$PROJECT_DIR$/HTMLTestRunner.py" />
  228 + <option value="$PROJECT_DIR$/test_cases/elecCard_check.py" />
  229 + <option value="$PROJECT_DIR$/data_fixture/authCode.py" />
  230 + <option value="$PROJECT_DIR$/test_cases/elecCard_setUp.py" />
  231 + <option value="$PROJECT_DIR$/data_fixture/test_verify.py" />
  232 + <option value="$PROJECT_DIR$/test_cases/region_grade_school.py" />
  233 + <option value="$PROJECT_DIR$/test_cases/eleccard_setUp.py" />
  234 + <option value="$PROJECT_DIR$/test_cases/press.py" />
  235 + <option value="$PROJECT_DIR$/test_cases/sub_account.py" />
  236 + <option value="$PROJECT_DIR$/test_cases/register.py" />
  237 + <option value="$PROJECT_DIR$/test_cases/personal_info.py" />
  238 + <option value="$PROJECT_DIR$/data_fixture/config_data.py" />
  239 + <option value="$PROJECT_DIR$/data_fixture/mysql_db.py" />
  240 + <option value="$PROJECT_DIR$/test_cases/parent_space.py" />
  241 + <option value="$PROJECT_DIR$/test_cases/xueketongbu.py" />
  242 + <option value="$PROJECT_DIR$/test_cases/app_record_statistic.py" />
  243 + <option value="$PROJECT_DIR$/data_fixture/create_testdata.py" />
  244 + <option value="$PROJECT_DIR$/test_cases/debugggggg.py" />
  245 + <option value="$PROJECT_DIR$/run_test.py" />
  246 + <option value="$PROJECT_DIR$/test_cases/__init__.py" />
  247 + <option value="$PROJECT_DIR$/data_fixture/__init__.py" />
  248 + <option value="$PROJECT_DIR$/test_cases/subject_sync.py" />
  249 + </list>
  250 + </option>
  251 + </component>
  252 + <component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
  253 + <component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER" />
  254 + <component name="JsGulpfileManager">
  255 + <detection-done>true</detection-done>
  256 + <sorting>DEFINITION_ORDER</sorting>
  257 + </component>
  258 + <component name="ProjectFrameBounds" extendedState="6">
  259 + <option name="x" value="23" />
  260 + <option name="y" value="85" />
  261 + <option name="width" value="1920" />
  262 + <option name="height" value="977" />
  263 + </component>
  264 + <component name="ProjectView">
  265 + <navigator currentView="ProjectPane" proportions="" version="1">
  266 + <flattenPackages />
  267 + <showMembers />
  268 + <showModules />
  269 + <showLibraryContents />
  270 + <hideEmptyPackages />
  271 + <abbreviatePackageNames />
  272 + <autoscrollToSource />
  273 + <autoscrollFromSource />
  274 + <sortByType />
  275 + <manualOrder />
  276 + <foldersAlwaysOnTop value="true" />
  277 + </navigator>
  278 + <panes>
  279 + <pane id="Scope" />
  280 + <pane id="ProjectPane">
  281 + <subPane>
  282 + <expand>
  283 + <path>
  284 + <item name="apiTest" type="b2602c69:ProjectViewProjectNode" />
  285 + <item name="apiTest" type="462c0819:PsiDirectoryNode" />
  286 + </path>
  287 + <path>
  288 + <item name="apiTest" type="b2602c69:ProjectViewProjectNode" />
  289 + <item name="apiTest" type="462c0819:PsiDirectoryNode" />
  290 + <item name="data_fixture" type="462c0819:PsiDirectoryNode" />
  291 + </path>
  292 + <path>
  293 + <item name="apiTest" type="b2602c69:ProjectViewProjectNode" />
  294 + <item name="apiTest" type="462c0819:PsiDirectoryNode" />
  295 + <item name="report" type="462c0819:PsiDirectoryNode" />
  296 + </path>
  297 + <path>
  298 + <item name="apiTest" type="b2602c69:ProjectViewProjectNode" />
  299 + <item name="apiTest" type="462c0819:PsiDirectoryNode" />
  300 + <item name="test_cases" type="462c0819:PsiDirectoryNode" />
  301 + </path>
  302 + <path>
  303 + <item name="apiTest" type="b2602c69:ProjectViewProjectNode" />
  304 + <item name="apiTest" type="462c0819:PsiDirectoryNode" />
  305 + <item name="test_suites" type="462c0819:PsiDirectoryNode" />
  306 + </path>
  307 + </expand>
  308 + <select />
  309 + </subPane>
  310 + </pane>
  311 + <pane id="Scratches" />
  312 + </panes>
  313 + </component>
  314 + <component name="PropertiesComponent">
  315 + <property name="WebServerToolWindowFactoryState" value="false" />
  316 + <property name="settings.editor.selected.configurable" value="editor.preferences.folding" />
  317 + </component>
  318 + <component name="RecentsManager">
  319 + <key name="MoveFile.RECENT_KEYS">
  320 + <recent name="$PROJECT_DIR$/test_cases" />
  321 + <recent name="$PROJECT_DIR$" />
  322 + <recent name="$PROJECT_DIR$/tests" />
  323 + <recent name="$PROJECT_DIR$/tests/test_cases" />
  324 + <recent name="$PROJECT_DIR$/tests/test_cases/personalCenter" />
  325 + </key>
  326 + <key name="CopyFile.RECENT_KEYS">
  327 + <recent name="$PROJECT_DIR$/test_cases" />
  328 + <recent name="$PROJECT_DIR$" />
  329 + </key>
  330 + </component>
  331 + <component name="RunDashboard">
  332 + <option name="ruleStates">
  333 + <list>
  334 + <RuleState>
  335 + <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
  336 + </RuleState>
  337 + <RuleState>
  338 + <option name="name" value="StatusDashboardGroupingRule" />
  339 + </RuleState>
  340 + </list>
  341 + </option>
  342 + </component>
  343 + <component name="RunManager" selected="Python.run_test">
  344 + <configuration name="debugggggg" type="PythonConfigurationType" factoryName="Python" temporary="true">
  345 + <option name="INTERPRETER_OPTIONS" value="" />
  346 + <option name="PARENT_ENVS" value="true" />
  347 + <envs>
  348 + <env name="PYTHONUNBUFFERED" value="1" />
  349 + </envs>
  350 + <option name="SDK_HOME" value="" />
  351 + <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test_cases" />
  352 + <option name="IS_MODULE_SDK" value="true" />
  353 + <option name="ADD_CONTENT_ROOTS" value="true" />
  354 + <option name="ADD_SOURCE_ROOTS" value="true" />
  355 + <module name="apiTest" />
  356 + <EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
  357 + <option name="SCRIPT_NAME" value="$PROJECT_DIR$/test_cases/debugggggg.py" />
  358 + <option name="PARAMETERS" value="" />
  359 + <option name="SHOW_COMMAND_LINE" value="false" />
  360 + <option name="EMULATE_TERMINAL" value="false" />
  361 + </configuration>
  362 + <configuration name="run_test" type="PythonConfigurationType" factoryName="Python" temporary="true">
  363 + <option name="INTERPRETER_OPTIONS" value="" />
  364 + <option name="PARENT_ENVS" value="true" />
  365 + <envs>
  366 + <env name="PYTHONUNBUFFERED" value="1" />
  367 + </envs>
  368 + <option name="SDK_HOME" value="" />
  369 + <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
  370 + <option name="IS_MODULE_SDK" value="true" />
  371 + <option name="ADD_CONTENT_ROOTS" value="true" />
  372 + <option name="ADD_SOURCE_ROOTS" value="true" />
  373 + <module name="apiTest" />
  374 + <EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
  375 + <option name="SCRIPT_NAME" value="$PROJECT_DIR$/run_test.py" />
  376 + <option name="PARAMETERS" value="" />
  377 + <option name="SHOW_COMMAND_LINE" value="false" />
  378 + <option name="EMULATE_TERMINAL" value="false" />
  379 + </configuration>
  380 + <list size="2">
  381 + <item index="0" class="java.lang.String" itemvalue="Python.run_test" />
  382 + <item index="1" class="java.lang.String" itemvalue="Python.debugggggg" />
  383 + </list>
  384 + <recent_temporary>
  385 + <list size="2">
  386 + <item index="0" class="java.lang.String" itemvalue="Python.run_test" />
  387 + <item index="1" class="java.lang.String" itemvalue="Python.debugggggg" />
  388 + </list>
  389 + </recent_temporary>
  390 + </component>
  391 + <component name="ShelveChangesManager" show_recycled="false">
  392 + <option name="remove_strategy" value="false" />
  393 + </component>
  394 + <component name="TaskManager">
  395 + <task active="true" id="Default" summary="Default task">
  396 + <changelist id="33187cb8-da74-4b13-8a55-31c4cae60a20" name="Default" comment="" />
  397 + <created>1512799492607</created>
  398 + <option name="number" value="Default" />
  399 + <option name="presentableId" value="Default" />
  400 + <updated>1512799492607</updated>
  401 + </task>
  402 + <servers />
  403 + </component>
  404 + <component name="TodoView">
  405 + <todo-panel id="selected-file">
  406 + <is-autoscroll-to-source value="true" />
  407 + </todo-panel>
  408 + <todo-panel id="all">
  409 + <are-packages-shown value="true" />
  410 + <is-autoscroll-to-source value="true" />
  411 + </todo-panel>
  412 + </component>
  413 + <component name="ToolWindowManager">
  414 + <frame x="-9" y="23" width="1920" height="977" extended-state="6" />
  415 + <editor active="true" />
  416 + <layout>
  417 + <window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32993197" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
  418 + <window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="true" content_ui="tabs" />
  419 + <window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
  420 + <window_info id="Python Console" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.24829932" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
  421 + <window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.2857143" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
  422 + <window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32993197" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
  423 + <window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.17891374" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
  424 + <window_info id="Database" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32960597" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
  425 + <window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
  426 + <window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
  427 + <window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
  428 + <window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4580499" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
  429 + <window_info id="Data View" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.2284345" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
  430 + <window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
  431 + <window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
  432 + <window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
  433 + <window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
  434 + <window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
  435 + <window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
  436 + </layout>
  437 + <layout-to-restore>
  438 + <window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32993197" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
  439 + <window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
  440 + <window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
  441 + <window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
  442 + <window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="true" content_ui="tabs" />
  443 + <window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
  444 + <window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="false" weight="0.33" sideWeight="0.5" order="8" side_tool="false" content_ui="tabs" />
  445 + <window_info id="Python Console" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32993197" sideWeight="0.5" order="9" side_tool="false" content_ui="tabs" />
  446 + <window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.22108844" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
  447 + <window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32993197" sideWeight="0.5" order="10" side_tool="false" content_ui="tabs" />
  448 + <window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.17571884" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
  449 + <window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
  450 + <window_info id="Database" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
  451 + <window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
  452 + <window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
  453 + <window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
  454 + <window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
  455 + <window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.34807256" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
  456 + <window_info id="Data View" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
  457 + </layout-to-restore>
  458 + </component>
  459 + <component name="TypeScriptGeneratedFilesManager">
  460 + <option name="version" value="1" />
  461 + </component>
  462 + <component name="VcsContentAnnotationSettings">
  463 + <option name="myLimit" value="2678400000" />
  464 + </component>
  465 + <component name="XDebuggerManager">
  466 + <breakpoint-manager>
  467 + <breakpoints>
  468 + <line-breakpoint enabled="true" suspend="THREAD" type="python-line">
  469 + <url>file://$PROJECT_DIR$/test_cases/sub_account.py</url>
  470 + <line>56</line>
  471 + <option name="timeStamp" value="82" />
  472 + </line-breakpoint>
  473 + <line-breakpoint enabled="true" suspend="THREAD" type="python-line">
  474 + <url>file://$PROJECT_DIR$/test_cases/register.py</url>
  475 + <line>19</line>
  476 + <option name="timeStamp" value="102" />
  477 + </line-breakpoint>
  478 + <line-breakpoint enabled="true" suspend="THREAD" type="python-line">
  479 + <url>file://$PROJECT_DIR$/test_cases/personal_info.py</url>
  480 + <line>253</line>
  481 + <option name="timeStamp" value="113" />
  482 + </line-breakpoint>
  483 + <line-breakpoint enabled="true" suspend="THREAD" type="python-line">
  484 + <url>file://$PROJECT_DIR$/test_cases/debugggggg.py</url>
  485 + <line>18</line>
  486 + <option name="timeStamp" value="140" />
  487 + </line-breakpoint>
  488 + <line-breakpoint enabled="true" suspend="THREAD" type="python-line">
  489 + <url>file://$PROJECT_DIR$/test_cases/app_record_statistic.py</url>
  490 + <line>181</line>
  491 + <option name="timeStamp" value="188" />
  492 + </line-breakpoint>
  493 + </breakpoints>
  494 + <breakpoints-dialog>
  495 + <breakpoints-dialog />
  496 + </breakpoints-dialog>
  497 + <option name="time" value="189" />
  498 + </breakpoint-manager>
  499 + <watches-manager />
  500 + </component>
  501 + <component name="editorHistoryManager">
  502 + <entry file="file:///Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/suite.py">
  503 + <provider selected="true" editor-type-id="text-editor">
  504 + <state relative-caret-position="1360">
  505 + <caret line="83" column="0" lean-forward="false" selection-start-line="83" selection-start-column="0" selection-end-line="83" selection-end-column="0" />
  506 + <folding />
  507 + </state>
  508 + </provider>
  509 + </entry>
  510 + <entry file="file:///Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/codecs.py">
  511 + <provider selected="true" editor-type-id="text-editor">
  512 + <state relative-caret-position="5627">
  513 + <caret line="331" column="0" lean-forward="false" selection-start-line="331" selection-start-column="0" selection-end-line="331" selection-end-column="0" />
  514 + </state>
  515 + </provider>
  516 + </entry>
  517 + <entry file="file:///Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/HtmlTestRunner/runner.py" />
  518 + <entry file="file://$PROJECT_DIR$/config.ini" />
  519 + <entry file="file://$PROJECT_DIR$/data_fixture/mysql_db.py">
  520 + <provider selected="true" editor-type-id="text-editor">
  521 + <state relative-caret-position="0">
  522 + <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="18" selection-end-column="0" />
  523 + <folding>
  524 + <marker date="1514616463000" expanded="true" signature="1672:1692" ph="select * fro... " />
  525 + <marker date="1514616463000" expanded="true" signature="1672:1694" ph="select * fro... missing_value" />
  526 + <marker date="1514616463000" expanded="true" signature="1672:1718" ph="select count... missing_value" />
  527 + <marker date="1514616463000" expanded="true" signature="1672:1719" ph="select count... missing_value" />
  528 + </folding>
  529 + </state>
  530 + </provider>
  531 + </entry>
  532 + <entry file="file://$PROJECT_DIR$/configParse.py">
  533 + <provider selected="true" editor-type-id="text-editor">
  534 + <state relative-caret-position="238">
  535 + <caret line="16" column="45" lean-forward="false" selection-start-line="16" selection-start-column="45" selection-end-line="16" selection-end-column="45" />
  536 + </state>
  537 + </provider>
  538 + </entry>
  539 + <entry file="file://$PROJECT_DIR$/test_cases/eleccard_setUp.py">
  540 + <provider selected="true" editor-type-id="text-editor">
  541 + <state relative-caret-position="748">
  542 + <caret line="47" column="0" lean-forward="false" selection-start-line="47" selection-start-column="0" selection-end-line="47" selection-end-column="0" />
  543 + <folding />
  544 + </state>
  545 + </provider>
  546 + </entry>
  547 + <entry file="file://$PROJECT_DIR$/test_cases/eleccard_check.py">
  548 + <provider selected="true" editor-type-id="text-editor">
  549 + <state relative-caret-position="816">
  550 + <caret line="51" column="23" lean-forward="false" selection-start-line="51" selection-start-column="23" selection-end-line="51" selection-end-column="23" />
  551 + <folding />
  552 + </state>
  553 + </provider>
  554 + </entry>
  555 + <entry file="file://$PROJECT_DIR$/config.ini" />
  556 + <entry file="file://$PROJECT_DIR$/data_fixture/mysql_db.py">
  557 + <provider selected="true" editor-type-id="text-editor">
  558 + <state relative-caret-position="0">
  559 + <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="18" selection-end-column="0" />
  560 + <folding>
  561 + <marker date="1514616463000" expanded="true" signature="1672:1692" ph="select * fro... " />
  562 + <marker date="1514616463000" expanded="true" signature="1672:1694" ph="select * fro... missing_value" />
  563 + <marker date="1514616463000" expanded="true" signature="1672:1718" ph="select count... missing_value" />
  564 + <marker date="1514616463000" expanded="true" signature="1672:1719" ph="select count... missing_value" />
  565 + </folding>
  566 + </state>
  567 + </provider>
  568 + </entry>
  569 + <entry file="file://$PROJECT_DIR$/configParse.py">
  570 + <provider selected="true" editor-type-id="text-editor">
  571 + <state relative-caret-position="272">
  572 + <caret line="16" column="45" lean-forward="true" selection-start-line="16" selection-start-column="45" selection-end-line="16" selection-end-column="45" />
  573 + </state>
  574 + </provider>
  575 + </entry>
  576 + <entry file="file://$PROJECT_DIR$/test_cases/eleccard_setUp.py">
  577 + <provider selected="true" editor-type-id="text-editor">
  578 + <state relative-caret-position="799">
  579 + <caret line="47" column="0" lean-forward="true" selection-start-line="47" selection-start-column="0" selection-end-line="47" selection-end-column="0" />
  580 + <folding />
  581 + </state>
  582 + </provider>
  583 + </entry>
  584 + <entry file="file://$PROJECT_DIR$/test_cases/eleccard_check.py">
  585 + <provider selected="true" editor-type-id="text-editor">
  586 + <state relative-caret-position="867">
  587 + <caret line="51" column="23" lean-forward="true" selection-start-line="51" selection-start-column="23" selection-end-line="51" selection-end-column="23" />
  588 + <folding />
  589 + </state>
  590 + </provider>
  591 + </entry>
  592 + <entry file="file://$PROJECT_DIR$/configParse.py">
  593 + <provider selected="true" editor-type-id="text-editor">
  594 + <state relative-caret-position="0">
  595 + <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
  596 + </state>
  597 + </provider>
  598 + </entry>
  599 + <entry file="file://$PROJECT_DIR$/tests/test_cases/__init__.py" />
  600 + <entry file="file://$PROJECT_DIR$/configParse.py">
  601 + <provider selected="true" editor-type-id="text-editor">
  602 + <state relative-caret-position="272">
  603 + <caret line="16" column="45" lean-forward="false" selection-start-line="16" selection-start-column="45" selection-end-line="16" selection-end-column="45" />
  604 + </state>
  605 + </provider>
  606 + </entry>
  607 + <entry file="file:///Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/codecs.py">
  608 + <provider selected="true" editor-type-id="text-editor">
  609 + <state relative-caret-position="5627">
  610 + <caret line="331" column="0" lean-forward="false" selection-start-line="331" selection-start-column="0" selection-end-line="331" selection-end-column="0" />
  611 + </state>
  612 + </provider>
  613 + </entry>
  614 + <entry file="file://$PROJECT_DIR$/reports/report/Test_region_grade_school.Getrades_2017-12-13_11-29-12.html" />
  615 + <entry file="file:///Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/HtmlTestRunner/runner.py" />
  616 + <entry file="file:///Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/encodings/ascii.py">
  617 + <provider selected="true" editor-type-id="text-editor">
  618 + <state relative-caret-position="372">
  619 + <caret line="25" column="0" lean-forward="false" selection-start-line="25" selection-start-column="0" selection-end-line="25" selection-end-column="0" />
  620 + </state>
  621 + </provider>
  622 + </entry>
  623 + <entry file="file://$PROJECT_DIR$/config.ini" />
  624 + <entry file="file://$PROJECT_DIR$/HTMLTestRunner.py">
  625 + <provider selected="true" editor-type-id="text-editor">
  626 + <state relative-caret-position="313">
  627 + <caret line="188" column="6" lean-forward="true" selection-start-line="185" selection-start-column="4" selection-end-line="296" selection-end-column="3" />
  628 + <folding>
  629 + <element signature="e#8852#10678#0" expanded="false" />
  630 + </folding>
  631 + </state>
  632 + </provider>
  633 + </entry>
  634 + <entry file="file://$PROJECT_DIR$/HTMLTestRunner_bak.py">
  635 + <provider selected="true" editor-type-id="text-editor">
  636 + <state relative-caret-position="0">
  637 + <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
  638 + </state>
  639 + </provider>
  640 + </entry>
  641 + <entry file="file:///Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/suite.py">
  642 + <provider selected="true" editor-type-id="text-editor">
  643 + <state relative-caret-position="149">
  644 + <caret line="83" column="0" lean-forward="false" selection-start-line="83" selection-start-column="0" selection-end-line="83" selection-end-column="0" />
  645 + <folding />
  646 + </state>
  647 + </provider>
  648 + </entry>
  649 + <entry file="file://$PROJECT_DIR$/test_suites/test_elecCard.py">
  650 + <provider selected="true" editor-type-id="text-editor">
  651 + <state relative-caret-position="0">
  652 + <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="1" selection-end-column="23" />
  653 + </state>
  654 + </provider>
  655 + </entry>
  656 + <entry file="file://$PROJECT_DIR$/data_fixture/authCode.py" />
  657 + <entry file="file://$PROJECT_DIR$/test_cases/eleccard_check.py">
  658 + <provider selected="true" editor-type-id="text-editor">
  659 + <state relative-caret-position="136">
  660 + <caret line="8" column="44" lean-forward="true" selection-start-line="8" selection-start-column="44" selection-end-line="8" selection-end-column="44" />
  661 + <folding />
  662 + </state>
  663 + </provider>
  664 + </entry>
  665 + <entry file="file://$PROJECT_DIR$/data_fixture/test_verify.py">
  666 + <provider selected="true" editor-type-id="text-editor">
  667 + <state relative-caret-position="187">
  668 + <caret line="11" column="0" lean-forward="true" selection-start-line="11" selection-start-column="0" selection-end-line="11" selection-end-column="0" />
  669 + </state>
  670 + </provider>
  671 + </entry>
  672 + <entry file="file://$PROJECT_DIR$/test_cases/region_grade_school.py">
  673 + <provider selected="true" editor-type-id="text-editor">
  674 + <state relative-caret-position="340">
  675 + <caret line="20" column="81" lean-forward="true" selection-start-line="8" selection-start-column="0" selection-end-line="20" selection-end-column="81" />
  676 + <folding>
  677 + <element signature="e#47#62#0" expanded="false" />
  678 + </folding>
  679 + </state>
  680 + </provider>
  681 + </entry>
  682 + <entry file="file://$PROJECT_DIR$/test_cases/252ED989-0B16-4AB7-81C1-974ABCF6CA11.png" />
  683 + <entry file="file:///Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/pymysql/cursors.py">
  684 + <provider selected="true" editor-type-id="text-editor">
  685 + <state relative-caret-position="240">
  686 + <caret line="166" column="0" lean-forward="false" selection-start-line="166" selection-start-column="0" selection-end-line="166" selection-end-column="0" />
  687 + <folding />
  688 + </state>
  689 + </provider>
  690 + </entry>
  691 + <entry file="file://$PROJECT_DIR$/test_cases/eleccard_setUp.py">
  692 + <provider selected="true" editor-type-id="text-editor">
  693 + <state relative-caret-position="209">
  694 + <caret line="102" column="26" lean-forward="true" selection-start-line="102" selection-start-column="26" selection-end-line="102" selection-end-column="26" />
  695 + <folding />
  696 + </state>
  697 + </provider>
  698 + </entry>
  699 + <entry file="file://$APPLICATION_HOME_DIR$/helpers/pydev/pydevd.py">
  700 + <provider selected="true" editor-type-id="text-editor">
  701 + <state relative-caret-position="169">
  702 + <caret line="1595" column="0" lean-forward="false" selection-start-line="1595" selection-start-column="0" selection-end-line="1595" selection-end-column="0" />
  703 + <folding />
  704 + </state>
  705 + </provider>
  706 + </entry>
  707 + <entry file="file://$PROJECT_DIR$/test_cases/press.py">
  708 + <provider selected="true" editor-type-id="text-editor">
  709 + <state relative-caret-position="0">
  710 + <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="11" selection-end-column="0" />
  711 + <folding />
  712 + </state>
  713 + </provider>
  714 + </entry>
  715 + <entry file="file://$PROJECT_DIR$/test_cases/register.py">
  716 + <provider selected="true" editor-type-id="text-editor">
  717 + <state relative-caret-position="17">
  718 + <caret line="205" column="22" lean-forward="false" selection-start-line="205" selection-start-column="22" selection-end-line="205" selection-end-column="22" />
  719 + <folding>
  720 + <element signature="e#47#62#0" expanded="true" />
  721 + </folding>
  722 + </state>
  723 + </provider>
  724 + </entry>
  725 + <entry file="file://$PROJECT_DIR$/HTMLTestRunner.py">
  726 + <provider selected="true" editor-type-id="text-editor">
  727 + <state relative-caret-position="165">
  728 + <caret line="643" column="0" lean-forward="false" selection-start-line="643" selection-start-column="0" selection-end-line="643" selection-end-column="0" />
  729 + <folding>
  730 + <element signature="e#8852#10678#0" expanded="false" />
  731 + </folding>
  732 + </state>
  733 + </provider>
  734 + </entry>
  735 + <entry file="file://$PROJECT_DIR$/test_cases/personal_info.py">
  736 + <provider selected="true" editor-type-id="text-editor">
  737 + <state relative-caret-position="415">
  738 + <caret line="251" column="26" lean-forward="false" selection-start-line="251" selection-start-column="26" selection-end-line="251" selection-end-column="26" />
  739 + <folding>
  740 + <element signature="e#47#62#0" expanded="true" />
  741 + </folding>
  742 + </state>
  743 + </provider>
  744 + </entry>
  745 + <entry file="file:///Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/pymysql/connections.py">
  746 + <provider selected="true" editor-type-id="text-editor">
  747 + <state relative-caret-position="176">
  748 + <caret line="1334" column="0" lean-forward="false" selection-start-line="1334" selection-start-column="0" selection-end-line="1334" selection-end-column="0" />
  749 + <folding />
  750 + </state>
  751 + </provider>
  752 + </entry>
  753 + <entry file="file://$PROJECT_DIR$/test_cases/parent_space.py">
  754 + <provider selected="true" editor-type-id="text-editor">
  755 + <state relative-caret-position="713">
  756 + <caret line="105" column="0" lean-forward="false" selection-start-line="105" selection-start-column="0" selection-end-line="105" selection-end-column="0" />
  757 + <folding>
  758 + <element signature="e#47#62#0" expanded="true" />
  759 + <marker date="1514955274000" expanded="true" signature="2800:2884" ph="SELECT custo... ozing_customermachine" />
  760 + </folding>
  761 + </state>
  762 + </provider>
  763 + </entry>
  764 + <entry file="file://$PROJECT_DIR$/test_cases/sub_account.py">
  765 + <provider selected="true" editor-type-id="text-editor">
  766 + <state relative-caret-position="423">
  767 + <caret line="51" column="41" lean-forward="true" selection-start-line="51" selection-start-column="41" selection-end-line="51" selection-end-column="41" />
  768 + <folding>
  769 + <element signature="e#47#62#0" expanded="true" />
  770 + </folding>
  771 + </state>
  772 + </provider>
  773 + </entry>
  774 + <entry file="file://$PROJECT_DIR$/report/test_report.html">
  775 + <provider selected="true" editor-type-id="text-editor">
  776 + <state relative-caret-position="0">
  777 + <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
  778 + <folding />
  779 + </state>
  780 + </provider>
  781 + </entry>
  782 + <entry file="file:///Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/case.py">
  783 + <provider selected="true" editor-type-id="text-editor">
  784 + <state relative-caret-position="359">
  785 + <caret line="600" column="0" lean-forward="false" selection-start-line="600" selection-start-column="0" selection-end-line="600" selection-end-column="0" />
  786 + <folding />
  787 + </state>
  788 + </provider>
  789 + </entry>
  790 + <entry file="file://$PROJECT_DIR$/data_fixture/config_data.py">
  791 + <provider selected="true" editor-type-id="text-editor">
  792 + <state relative-caret-position="114">
  793 + <caret line="12" column="14" lean-forward="false" selection-start-line="12" selection-start-column="14" selection-end-line="12" selection-end-column="14" />
  794 + <folding />
  795 + </state>
  796 + </provider>
  797 + </entry>
  798 + <entry file="file://$PROJECT_DIR$/data_fixture/create_testdata.py">
  799 + <provider selected="true" editor-type-id="text-editor">
  800 + <state relative-caret-position="310">
  801 + <caret line="356" column="79" lean-forward="false" selection-start-line="356" selection-start-column="67" selection-end-line="356" selection-end-column="79" />
  802 + <folding>
  803 + <element signature="e#47#83#0" expanded="true" />
  804 + <marker date="1515219431000" expanded="true" signature="1421:1488" ph="select * fro... ozing_customermachine" />
  805 + <marker date="1515219431000" expanded="true" signature="1421:1489" ph="select * fro... ozing_customermachine" />
  806 + <marker date="1515219431000" expanded="true" signature="1421:1491" ph="select * fro... ozing_customermachine" />
  807 + <marker date="1515219431000" expanded="true" signature="1421:1492" ph="select * fro... ozing_customermachine" />
  808 + <marker date="1515219431000" expanded="true" signature="1421:1518" ph="select * fro... ozing_customermachine" />
  809 + <marker date="1515219431000" expanded="true" signature="1549:1608" ph="SELECT * FRO... ozing_machine" />
  810 + <marker date="1515219431000" expanded="true" signature="1549:1609" ph="SELECT * FRO... ozing_machine" />
  811 + <marker date="1515219431000" expanded="true" signature="1549:1612" ph="SELECT * FRO... ozing_machine" />
  812 + <marker date="1515219431000" expanded="true" signature="1549:1613" ph="SELECT * FRO... ozing_machine" />
  813 + <marker date="1515219431000" expanded="true" signature="1549:1639" ph="SELECT * FRO... ozing_machine" />
  814 + <marker date="1515219431000" expanded="true" signature="1752:2214" ph="insert into acornuser.ozing_customermachine... " />
  815 + <marker date="1515219431000" expanded="true" signature="2928:2997" ph="SELECT * FRO... ozing_samplemachine" />
  816 + <marker date="1515219431000" expanded="true" signature="3049:3113" ph="SELECT * FRO... ozing_machine" />
  817 + <marker date="1515219431000" expanded="true" signature="4735:4806" ph="select * fro... ozing_customermachine" />
  818 + <marker date="1515219431000" expanded="true" signature="5151:5215" ph="SELECT * FRO... ozing_machine" />
  819 + <marker date="1515219431000" expanded="true" signature="5276:5356" ph="SELECT * FRO... ozing_machine" />
  820 + <marker date="1515219431000" expanded="true" signature="6222:6293" ph="select * fro... ozing_customermachine" />
  821 + <marker date="1515219431000" expanded="true" signature="6578:6641" ph="select * fro... ozing_machine" />
  822 + <marker date="1515219431000" expanded="true" signature="7715:7780" ph="update acorn... " />
  823 + <marker date="1515219431000" expanded="true" signature="7715:7804" ph="update acorn... " />
  824 + <marker date="1515219431000" expanded="true" signature="7928:8009" ph="select * fro... child_user" />
  825 + <marker date="1515219431000" expanded="true" signature="8075:8156" ph="select * fro... child_user" />
  826 + <marker date="1515219431000" expanded="true" signature="8075:8203" ph="select * fro... child_user" />
  827 + <marker date="1515219431000" expanded="true" signature="8075:8205" ph="select * fro... child_user" />
  828 + <marker date="1515219431000" expanded="true" signature="9093:9178" ph="select * fro... acorn_user_status" />
  829 + <marker date="1515219431000" expanded="true" signature="9320:9420" ph="select * fro... acorn_user_status" />
  830 + <marker date="1515219431000" expanded="true" signature="10158:10258" ph="select * fro... acorn_user_status" />
  831 + <marker date="1515219431000" expanded="true" signature="11302:11360" ph="select * fro... acorn_user_extra" />
  832 + <marker date="1515219431000" expanded="true" signature="11397:11483" ph="select * fro... acorn_user_extra" />
  833 + <marker date="1515219431000" expanded="true" signature="11914:11983" ph="select * fro... subAccount_user_extra" />
  834 + <marker date="1515219431000" expanded="true" signature="11914:11984" ph="select * fro... subAccount_user_extra" />
  835 + <marker date="1515219431000" expanded="true" signature="11914:11988" ph="select * fro... subAccount_user_extra" />
  836 + <marker date="1515219431000" expanded="true" signature="12023:12092" ph="select * fro... subAccount_user_extra" />
  837 + <marker date="1515219431000" expanded="true" signature="12023:12122" ph="select * fro... subAccount_user_extra" />
  838 + <marker date="1515219431000" expanded="true" signature="12023:12138" ph="select * fro... subAccount_user_extra" />
  839 + <marker date="1515219431000" expanded="true" signature="13348:13448" ph="select * fro... ozing_student" />
  840 + <marker date="1515219431000" expanded="true" signature="13550:13636" ph="update acorn... " />
  841 + <marker date="1515219431000" expanded="true" signature="13550:13638" ph="update acorn... " />
  842 + <marker date="1515219431000" expanded="true" signature="13726:13834" ph="select * fro... ozing_student" />
  843 + <marker date="1515219431000" expanded="true" signature="15966:16006" ph="select max(i... acorn_user" />
  844 + <marker date="1515219431000" expanded="true" signature="15966:16008" ph="select max(i... acorn_user" />
  845 + <marker date="1515219431000" expanded="true" signature="16721:16796" ph="select passw... parents_space_pass" />
  846 + <marker date="1515219431000" expanded="true" signature="17752:17804" ph="update analy... " />
  847 + <marker date="1515219431000" expanded="true" signature="17752:17821" ph="update analy... " />
  848 + <marker date="1515219431000" expanded="true" signature="17752:17849" ph="update analy... " />
  849 + <marker date="1515219431000" expanded="true" signature="17752:17850" ph="update analy... " />
  850 + </folding>
  851 + </state>
  852 + </provider>
  853 + </entry>
  854 + <entry file="file://$PROJECT_DIR$/test_cases/debugggggg.py">
  855 + <provider selected="true" editor-type-id="text-editor">
  856 + <state relative-caret-position="34">
  857 + <caret line="2" column="0" lean-forward="false" selection-start-line="2" selection-start-column="0" selection-end-line="2" selection-end-column="0" />
  858 + <folding />
  859 + </state>
  860 + </provider>
  861 + </entry>
  862 + <entry file="file://$PROJECT_DIR$/test_cases/app_record_statistic.py">
  863 + <provider selected="true" editor-type-id="text-editor">
  864 + <state relative-caret-position="761">
  865 + <caret line="170" column="45" lean-forward="false" selection-start-line="170" selection-start-column="45" selection-end-line="170" selection-end-column="45" />
  866 + <folding>
  867 + <element signature="e#47#62#0" expanded="true" />
  868 + </folding>
  869 + </state>
  870 + </provider>
  871 + </entry>
  872 + <entry file="file://$PROJECT_DIR$/test_cases/__init__.py">
  873 + <provider selected="true" editor-type-id="text-editor">
  874 + <state relative-caret-position="0">
  875 + <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
  876 + <folding />
  877 + </state>
  878 + </provider>
  879 + </entry>
  880 + <entry file="file://$PROJECT_DIR$/data_fixture/__init__.py">
  881 + <provider selected="true" editor-type-id="text-editor">
  882 + <state relative-caret-position="0">
  883 + <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
  884 + <folding />
  885 + </state>
  886 + </provider>
  887 + </entry>
  888 + <entry file="file://$PROJECT_DIR$/data_fixture/mysql_db.py">
  889 + <provider selected="true" editor-type-id="text-editor">
  890 + <state relative-caret-position="34">
  891 + <caret line="2" column="0" lean-forward="true" selection-start-line="2" selection-start-column="0" selection-end-line="2" selection-end-column="0" />
  892 + <folding>
  893 + <marker date="1514616463000" expanded="true" signature="1672:1692" ph="select * fro... " />
  894 + <marker date="1514616463000" expanded="true" signature="1672:1694" ph="select * fro... missing_value" />
  895 + <marker date="1514616463000" expanded="true" signature="1672:1718" ph="select count... missing_value" />
  896 + <marker date="1514616463000" expanded="true" signature="1672:1719" ph="select count... missing_value" />
  897 + </folding>
  898 + </state>
  899 + </provider>
  900 + </entry>
  901 + <entry file="file://$PROJECT_DIR$/test_cases/subject_sync.py">
  902 + <provider selected="true" editor-type-id="text-editor">
  903 + <state relative-caret-position="85">
  904 + <caret line="5" column="66" lean-forward="true" selection-start-line="5" selection-start-column="66" selection-end-line="5" selection-end-column="66" />
  905 + <folding />
  906 + </state>
  907 + </provider>
  908 + </entry>
  909 + <entry file="file://$PROJECT_DIR$/run_test.py">
  910 + <provider selected="true" editor-type-id="text-editor">
  911 + <state relative-caret-position="289">
  912 + <caret line="17" column="0" lean-forward="true" selection-start-line="17" selection-start-column="0" selection-end-line="17" selection-end-column="0" />
  913 + <folding>
  914 + <element signature="e#47#62#0" expanded="true" />
  915 + </folding>
  916 + </state>
  917 + </provider>
  918 + </entry>
  919 + </component>
  920 +</project>
0 921 \ No newline at end of file
... ...
HTMLTestRunner.py
... ... @@ -0,0 +1,857 @@
  1 +# -*- coding: utf-8 -*-
  2 +
  3 +"""
  4 +A TestRunner for use with the Python unit testing framework. It
  5 +generates a HTML report to show the result at a glance.
  6 +
  7 +The simplest way to use this is to invoke its main method. E.g.
  8 +
  9 + import unittest
  10 + import HTMLTestRunner
  11 +
  12 + ... define your tests ...
  13 +
  14 + if __name__ == '__main__':
  15 + HTMLTestRunner.main()
  16 +
  17 +
  18 +For more customization options, instantiates a HTMLTestRunner object.
  19 +HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.
  20 +
  21 + # output to a file
  22 + fp = file('my_report.html', 'wb')
  23 + runner = HTMLTestRunner.HTMLTestRunner(
  24 + stream=fp,
  25 + title='My unit test',
  26 + description='This demonstrates the report output by HTMLTestRunner.'
  27 + )
  28 +
  29 + # Use an external stylesheet.
  30 + # See the Template_mixin class for more customizable options
  31 + runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'
  32 +
  33 + # run the test
  34 + runner.run(my_test_suite)
  35 +
  36 +
  37 +------------------------------------------------------------------------
  38 +Copyright (c) 2004-2007, Wai Yip Tung
  39 +All rights reserved.
  40 +
  41 +Redistribution and use in source and binary forms, with or without
  42 +modification, are permitted provided that the following conditions are
  43 +met:
  44 +
  45 +* Redistributions of source code must retain the above copyright notice,
  46 + this list of conditions and the following disclaimer.
  47 +* Redistributions in binary form must reproduce the above copyright
  48 + notice, this list of conditions and the following disclaimer in the
  49 + documentation and/or other materials provided with the distribution.
  50 +* Neither the name Wai Yip Tung nor the names of its contributors may be
  51 + used to endorse or promote products derived from this software without
  52 + specific prior written permission.
  53 +
  54 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  55 +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  56 +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
  57 +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
  58 +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  59 +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  60 +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  61 +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  62 +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  63 +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  64 +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  65 +"""
  66 +
  67 +# URL: http://tungwaiyip.info/software/HTMLTestRunner.html
  68 +
  69 +__author__ = "Wai Yip Tung"
  70 +__version__ = "0.8.2"
  71 +
  72 +
  73 +"""
  74 +Change History
  75 +
  76 +Version 0.8.2
  77 +* Show output inline instead of popup window (Viorel Lupu).
  78 +
  79 +Version in 0.8.1
  80 +* Validated XHTML (Wolfgang Borgert).
  81 +* Added description of test classes and test cases.
  82 +
  83 +Version in 0.8.0
  84 +* Define Template_mixin class for customization.
  85 +* Workaround a IE 6 bug that it does not treat <script> block as CDATA.
  86 +
  87 +Version in 0.7.1
  88 +* Back port to Python 2.3 (Frank Horowitz).
  89 +* Fix missing scroll bars in detail log (Podi).
  90 +"""
  91 +
  92 +# TODO: color stderr
  93 +# TODO: simplify javascript using ,ore than 1 class in the class attribute?
  94 +
  95 +import datetime
  96 +import io
  97 +import sys
  98 +import time
  99 +import unittest
  100 +from xml.sax import saxutils
  101 +
  102 +
  103 +# ------------------------------------------------------------------------
  104 +# The redirectors below are used to capture output during testing. Output
  105 +# sent to sys.stdout and sys.stderr are automatically captured. However
  106 +# in some cases sys.stdout is already cached before HTMLTestRunner is
  107 +# invoked (e.g. calling logging.basicConfig). In order to capture those
  108 +# output, use the redirectors for the cached stream.
  109 +#
  110 +# e.g.
  111 +# >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
  112 +# >>>
  113 +
  114 +class OutputRedirector(object):
  115 + """ Wrapper to redirect stdout or stderr """
  116 + def __init__(self, fp):
  117 + self.fp = fp
  118 +
  119 + def write(self, s):
  120 + self.fp.write(s)
  121 +
  122 + def writelines(self, lines):
  123 + self.fp.writelines(lines)
  124 +
  125 + def flush(self):
  126 + self.fp.flush()
  127 +
  128 +stdout_redirector = OutputRedirector(sys.stdout)
  129 +stderr_redirector = OutputRedirector(sys.stderr)
  130 +
  131 +
  132 +
  133 +# ----------------------------------------------------------------------
  134 +# Template
  135 +
  136 +class Template_mixin(object):
  137 + """
  138 + Define a HTML template for report customerization and generation.
  139 +
  140 + Overall structure of an HTML report
  141 +
  142 + HTML
  143 + +------------------------+
  144 + |<html> |
  145 + | <head> |
  146 + | |
  147 + | STYLESHEET |
  148 + | +----------------+ |
  149 + | | | |
  150 + | +----------------+ |
  151 + | |
  152 + | </head> |
  153 + | |
  154 + | <body> |
  155 + | |
  156 + | HEADING |
  157 + | +----------------+ |
  158 + | | | |
  159 + | +----------------+ |
  160 + | |
  161 + | REPORT |
  162 + | +----------------+ |
  163 + | | | |
  164 + | +----------------+ |
  165 + | |
  166 + | ENDING |
  167 + | +----------------+ |
  168 + | | | |
  169 + | +----------------+ |
  170 + | |
  171 + | </body> |
  172 + |</html> |
  173 + +------------------------+
  174 + """
  175 +
  176 + STATUS = {
  177 + 0: 'pass',
  178 + 1: 'fail',
  179 + 2: 'error',
  180 + }
  181 +
  182 + DEFAULT_TITLE = 'Unit Test Report'
  183 + DEFAULT_DESCRIPTION = ''
  184 +
  185 + # ------------------------------------------------------------------------
  186 + # HTML Template
  187 +
  188 + HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
  189 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  190 +<html xmlns="http://www.w3.org/1999/xhtml">
  191 +<head>
  192 + <title>%(title)s</title>
  193 + <meta name="generator" content="%(generator)s"/>
  194 + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  195 + %(stylesheet)s
  196 +</head>
  197 +<body>
  198 +<script language="javascript" type="text/javascript"><!--
  199 +output_list = Array();
  200 +
  201 +/* level - 0:Summary; 1:Failed; 2:All */
  202 +function showCase(level) {
  203 + trs = document.getElementsByTagName("tr");
  204 + for (var i = 0; i < trs.length; i++) {
  205 + tr = trs[i];
  206 + id = tr.id;
  207 + if (id.substr(0,2) == 'ft') {
  208 + if (level < 1) {
  209 + tr.className = 'hiddenRow';
  210 + }
  211 + else {
  212 + tr.className = '';
  213 + }
  214 + }
  215 + if (id.substr(0,2) == 'pt') {
  216 + if (level > 1) {
  217 + tr.className = '';
  218 + }
  219 + else {
  220 + tr.className = 'hiddenRow';
  221 + }
  222 + }
  223 + }
  224 +}
  225 +
  226 +
  227 +function showClassDetail(cid, count) {
  228 + var id_list = Array(count);
  229 + var toHide = 1;
  230 + for (var i = 0; i < count; i++) {
  231 + tid0 = 't' + cid.substr(1) + '.' + (i+1);
  232 + tid = 'f' + tid0;
  233 + tr = document.getElementById(tid);
  234 + if (!tr) {
  235 + tid = 'p' + tid0;
  236 + tr = document.getElementById(tid);
  237 + }
  238 + id_list[i] = tid;
  239 + if (tr.className) {
  240 + toHide = 0;
  241 + }
  242 + }
  243 + for (var i = 0; i < count; i++) {
  244 + tid = id_list[i];
  245 + if (toHide) {
  246 + document.getElementById('div_'+tid).style.display = 'none'
  247 + document.getElementById(tid).className = 'hiddenRow';
  248 + }
  249 + else {
  250 + document.getElementById(tid).className = '';
  251 + }
  252 + }
  253 +}
  254 +
  255 +
  256 +function showTestDetail(div_id){
  257 + var details_div = document.getElementById(div_id)
  258 + var displayState = details_div.style.display
  259 + // alert(displayState)
  260 + if (displayState != 'block' ) {
  261 + displayState = 'block'
  262 + details_div.style.display = 'block'
  263 + }
  264 + else {
  265 + details_div.style.display = 'none'
  266 + }
  267 +}
  268 +
  269 +
  270 +function html_escape(s) {
  271 + s = s.replace(/&/g,'&amp;');
  272 + s = s.replace(/</g,'&lt;');
  273 + s = s.replace(/>/g,'&gt;');
  274 + return s;
  275 +}
  276 +
  277 +/* obsoleted by detail in <div>
  278 +function showOutput(id, name) {
  279 + var w = window.open("", //url
  280 + name,
  281 + "resizable,scrollbars,status,width=800,height=450");
  282 + d = w.document;
  283 + d.write("<pre>");
  284 + d.write(html_escape(output_list[id]));
  285 + d.write("\n");
  286 + d.write("<a href='javascript:window.close()'>close</a>\n");
  287 + d.write("</pre>\n");
  288 + d.close();
  289 +}
  290 +*/
  291 +--></script>
  292 +
  293 +%(heading)s
  294 +%(report)s
  295 +%(ending)s
  296 +
  297 +</body>
  298 +</html>
  299 +"""
  300 + # variables: (title, generator, stylesheet, heading, report, ending)
  301 +
  302 +
  303 + # ------------------------------------------------------------------------
  304 + # Stylesheet
  305 + #
  306 + # alternatively use a <link> for external style sheet, e.g.
  307 + # <link rel="stylesheet" href="$url" type="text/css">
  308 +
  309 + STYLESHEET_TMPL = """
  310 +<style type="text/css" media="screen">
  311 +body { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; }
  312 +table { font-size: 100%; }
  313 +pre { }
  314 +
  315 +/* -- heading ---------------------------------------------------------------------- */
  316 +h1 {
  317 + font-size: 16pt;
  318 + color: gray;
  319 +}
  320 +.heading {
  321 + margin-top: 0ex;
  322 + margin-bottom: 1ex;
  323 +}
  324 +
  325 +.heading .attribute {
  326 + margin-top: 1ex;
  327 + margin-bottom: 0;
  328 +}
  329 +
  330 +.heading .description {
  331 + margin-top: 4ex;
  332 + margin-bottom: 6ex;
  333 +}
  334 +
  335 +/* -- css div popup ------------------------------------------------------------------------ */
  336 +a.popup_link {
  337 +}
  338 +
  339 +a.popup_link:hover {
  340 + color: red;
  341 +}
  342 +
  343 +.popup_window {
  344 + display: none;
  345 + position: relative;
  346 + left: 0px;
  347 + top: 0px;
  348 + /*border: solid #627173 1px; */
  349 + padding: 10px;
  350 + background-color: #E6E6D6;
  351 + font-family: "Lucida Console", "Courier New", Courier, monospace;
  352 + text-align: left;
  353 + font-size: 8pt;
  354 + width: 500px;
  355 +}
  356 +
  357 +}
  358 +/* -- report ------------------------------------------------------------------------ */
  359 +#show_detail_line {
  360 + margin-top: 3ex;
  361 + margin-bottom: 1ex;
  362 +}
  363 +#result_table {
  364 + width: 80%;
  365 + border-collapse: collapse;
  366 + border: 1px solid #777;
  367 +}
  368 +#header_row {
  369 + font-weight: bold;
  370 + color: white;
  371 + background-color: #777;
  372 +}
  373 +#result_table td {
  374 + border: 1px solid #777;
  375 + padding: 2px;
  376 +}
  377 +#total_row { font-weight: bold; }
  378 +.passClass { background-color: #6c6; }
  379 +.failClass { background-color: #c60; }
  380 +.errorClass { background-color: #c00; }
  381 +.passCase { color: #6c6; }
  382 +.failCase { color: #c60; font-weight: bold; }
  383 +.errorCase { color: #c00; font-weight: bold; }
  384 +.hiddenRow { display: none; }
  385 +.testcase { margin-left: 2em; }
  386 +
  387 +
  388 +/* -- ending ---------------------------------------------------------------------- */
  389 +#ending {
  390 +}
  391 +
  392 +</style>
  393 +"""
  394 +
  395 +
  396 +
  397 + # ------------------------------------------------------------------------
  398 + # Heading
  399 + #
  400 +
  401 + HEADING_TMPL = """<div class='heading'>
  402 +<h1>%(title)s</h1>
  403 +%(parameters)s
  404 +<p class='description'>%(description)s</p>
  405 +</div>
  406 +
  407 +""" # variables: (title, parameters, description)
  408 +
  409 + HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>
  410 +""" # variables: (name, value)
  411 +
  412 +
  413 +
  414 + # ------------------------------------------------------------------------
  415 + # Report
  416 + #
  417 +
  418 + REPORT_TMPL = """
  419 +<p id='show_detail_line'>Show
  420 +<a href='javascript:showCase(0)'>Summary</a>
  421 +<a href='javascript:showCase(1)'>Failed</a>
  422 +<a href='javascript:showCase(2)'>All</a>
  423 +</p>
  424 +<table id='result_table'>
  425 +<colgroup>
  426 +<col align='left' />
  427 +<col align='right' />
  428 +<col align='right' />
  429 +<col align='right' />
  430 +<col align='right' />
  431 +<col align='right' />
  432 +</colgroup>
  433 +<tr id='header_row'>
  434 + <td>Test Group/Test case</td>
  435 + <td>Count</td>
  436 + <td>Pass</td>
  437 + <td>Fail</td>
  438 + <td>Error</td>
  439 + <td>View</td>
  440 +</tr>
  441 +%(test_list)s
  442 +<tr id='total_row'>
  443 + <td>Total</td>
  444 + <td>%(count)s</td>
  445 + <td>%(Pass)s</td>
  446 + <td>%(fail)s</td>
  447 + <td>%(error)s</td>
  448 + <td>&nbsp;</td>
  449 +</tr>
  450 +</table>
  451 +""" # variables: (test_list, count, Pass, fail, error)
  452 +
  453 + REPORT_CLASS_TMPL = r"""
  454 +<tr class='%(style)s'>
  455 + <td>%(desc)s</td>
  456 + <td>%(count)s</td>
  457 + <td>%(Pass)s</td>
  458 + <td>%(fail)s</td>
  459 + <td>%(error)s</td>
  460 + <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Detail</a></td>
  461 +</tr>
  462 +""" # variables: (style, desc, count, Pass, fail, error, cid)
  463 +
  464 +
  465 + REPORT_TEST_WITH_OUTPUT_TMPL = r"""
  466 +<tr id='%(tid)s' class='%(Class)s'>
  467 + <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
  468 + <td colspan='5' align='center'>
  469 +
  470 + <!--css div popup start-->
  471 + <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >
  472 + %(status)s</a>
  473 +
  474 + <div id='div_%(tid)s' class="popup_window">
  475 + <div style='text-align: right; color:red;cursor:pointer'>
  476 + <a onfocus='this.blur();' onclick="document.getElementById('div_%(tid)s').style.display = 'none' " >
  477 + [x]</a>
  478 + </div>
  479 + <pre>
  480 + %(script)s
  481 + </pre>
  482 + </div>
  483 + <!--css div popup end-->
  484 +
  485 + </td>
  486 +</tr>
  487 +""" # variables: (tid, Class, style, desc, status)
  488 +
  489 +
  490 + REPORT_TEST_NO_OUTPUT_TMPL = r"""
  491 +<tr id='%(tid)s' class='%(Class)s'>
  492 + <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
  493 + <td colspan='5' align='center'>%(status)s</td>
  494 +</tr>
  495 +""" # variables: (tid, Class, style, desc, status)
  496 +
  497 +
  498 + REPORT_TEST_OUTPUT_TMPL = r"""
  499 +%(id)s: %(output)s
  500 +""" # variables: (id, output)
  501 +
  502 +
  503 +
  504 + # ------------------------------------------------------------------------
  505 + # ENDING
  506 + #
  507 +
  508 + ENDING_TMPL = """<div id='ending'>&nbsp;</div>"""
  509 +
  510 +# -------------------- The end of the Template class -------------------
  511 +
  512 +
  513 +TestResult = unittest.TestResult
  514 +
  515 +class _TestResult(TestResult):
  516 + # note: _TestResult is a pure representation of results.
  517 + # It lacks the output and reporting ability compares to unittest._TextTestResult.
  518 +
  519 + def __init__(self, verbosity=1):
  520 + TestResult.__init__(self)
  521 + self.stdout0 = None
  522 + self.stderr0 = None
  523 + self.success_count = 0
  524 + self.failure_count = 0
  525 + self.error_count = 0
  526 + self.verbosity = verbosity
  527 +
  528 + # result is a list of result in 4 tuple
  529 + # (
  530 + # result code (0: success; 1: fail; 2: error),
  531 + # TestCase object,
  532 + # Test output (byte string),
  533 + # stack trace,
  534 + # )
  535 + self.result = []
  536 +
  537 +
  538 + def startTest(self, test):
  539 + TestResult.startTest(self, test)
  540 + # just one buffer for both stdout and stderr
  541 + self.outputBuffer = io.StringIO()
  542 + stdout_redirector.fp = self.outputBuffer
  543 + stderr_redirector.fp = self.outputBuffer
  544 + self.stdout0 = sys.stdout
  545 + self.stderr0 = sys.stderr
  546 + sys.stdout = stdout_redirector
  547 + sys.stderr = stderr_redirector
  548 +
  549 +
  550 + def complete_output(self):
  551 + """
  552 + Disconnect output redirection and return buffer.
  553 + Safe to call multiple times.
  554 + """
  555 + if self.stdout0:
  556 + sys.stdout = self.stdout0
  557 + sys.stderr = self.stderr0
  558 + self.stdout0 = None
  559 + self.stderr0 = None
  560 + return self.outputBuffer.getvalue()
  561 +
  562 +
  563 + def stopTest(self, test):
  564 + # Usually one of addSuccess, addError or addFailure would have been called.
  565 + # But there are some path in unittest that would bypass this.
  566 + # We must disconnect stdout in stopTest(), which is guaranteed to be called.
  567 + self.complete_output()
  568 +
  569 +
  570 + def addSuccess(self, test):
  571 + self.success_count += 1
  572 + TestResult.addSuccess(self, test)
  573 + output = self.complete_output()
  574 + self.result.append((0, test, output, ''))
  575 + if self.verbosity > 1:
  576 + sys.stderr.write('ok ')
  577 + sys.stderr.write(str(test))
  578 + sys.stderr.write('\n')
  579 + else:
  580 + sys.stderr.write('.')
  581 +
  582 + def addError(self, test, err):
  583 + self.error_count += 1
  584 + TestResult.addError(self, test, err)
  585 + _, _exc_str = self.errors[-1]
  586 + output = self.complete_output()
  587 + self.result.append((2, test, output, _exc_str))
  588 + if self.verbosity > 1:
  589 + sys.stderr.write('E ')
  590 + sys.stderr.write(str(test))
  591 + sys.stderr.write('\n')
  592 + else:
  593 + sys.stderr.write('E')
  594 +
  595 + def addFailure(self, test, err):
  596 + self.failure_count += 1
  597 + TestResult.addFailure(self, test, err)
  598 + _, _exc_str = self.failures[-1]
  599 + output = self.complete_output()
  600 + self.result.append((1, test, output, _exc_str))
  601 + if self.verbosity > 1:
  602 + sys.stderr.write('F ')
  603 + sys.stderr.write(str(test))
  604 + sys.stderr.write('\n')
  605 + else:
  606 + sys.stderr.write('F')
  607 +
  608 +
  609 +class HTMLTestRunner(Template_mixin):
  610 + """
  611 + """
  612 + #def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None):
  613 + def __init__(self, outputdir, verbosity=1, title=None, description=None, report_name='test_report.html'):
  614 + #self.stream = self._create_output_file(outputdir, report_name)
  615 + self.outputdir = outputdir
  616 + self.report_name = report_name
  617 + self.verbosity = verbosity
  618 + if title is None:
  619 + self.title = self.DEFAULT_TITLE
  620 + else:
  621 + self.title = title
  622 + if description is None:
  623 + self.description = self.DEFAULT_DESCRIPTION
  624 + else:
  625 + self.description = description
  626 +
  627 + self.startTime = datetime.datetime.now()
  628 +
  629 +
  630 + def _create_output_file(self, output, report_name):
  631 + import os
  632 + """ Generate the report file in the given path. """
  633 + current_dir = os.getcwd()
  634 + dir_to = os.path.join(current_dir, output)
  635 + if not os.path.exists(dir_to):
  636 + os.makedirs(dir_to)
  637 + path_file = os.path.join(dir_to, report_name)
  638 + return path_file
  639 +
  640 +
  641 + def run(self, test):
  642 + "Run the given test case or test suite."
  643 + result = _TestResult(self.verbosity)
  644 + test(result)
  645 + self.stopTime = datetime.datetime.now()
  646 + self.generateReport(test, result)
  647 + # print >> sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)
  648 + print(sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime))
  649 + return result
  650 +
  651 +
  652 + def sortResult(self, result_list):
  653 + # unittest does not seems to run in any particular order.
  654 + # Here at least we want to group them together by class.
  655 + rmap = {}
  656 + classes = []
  657 + for n,t,o,e in result_list:
  658 + cls = t.__class__
  659 + if not cls in rmap:
  660 + rmap[cls] = []
  661 + classes.append(cls)
  662 + rmap[cls].append((n,t,o,e))
  663 + r = [(cls, rmap[cls]) for cls in classes]
  664 + return r
  665 +
  666 +
  667 + def getReportAttributes(self, result):
  668 + """
  669 + Return report attributes as a list of (name, value).
  670 + Override this to add custom attributes.
  671 + """
  672 + startTime = str(self.startTime)[:19]
  673 + duration = str(self.stopTime - self.startTime)
  674 + status = []
  675 + if result.success_count: status.append('Pass %s' % result.success_count)
  676 + if result.failure_count: status.append('Failure %s' % result.failure_count)
  677 + if result.error_count: status.append('Error %s' % result.error_count )
  678 + if status:
  679 + status = ' '.join(status)
  680 + else:
  681 + status = 'none'
  682 + return [
  683 + ('Start Time', startTime),
  684 + ('Duration', duration),
  685 + ('Status', status),
  686 + ]
  687 +
  688 +
  689 +
  690 + def _generate_stylesheet(self):
  691 + return self.STYLESHEET_TMPL
  692 +
  693 +
  694 + def _generate_heading(self, report_attrs):
  695 + a_lines = []
  696 + for name, value in report_attrs:
  697 + line = self.HEADING_ATTRIBUTE_TMPL % dict(
  698 + name = saxutils.escape(name),
  699 + value = saxutils.escape(value),
  700 + )
  701 + a_lines.append(line)
  702 + heading = self.HEADING_TMPL % dict(
  703 + title = saxutils.escape(self.title),
  704 + parameters = ''.join(a_lines),
  705 + description = saxutils.escape(self.description),
  706 + )
  707 + return heading
  708 +
  709 +
  710 + def _generate_report(self, result):
  711 + rows = []
  712 + sortedResult = self.sortResult(result.result)
  713 + for cid, (cls, cls_results) in enumerate(sortedResult):
  714 + # subtotal for a class
  715 + np = nf = ne = 0
  716 + for n,t,o,e in cls_results:
  717 + if n == 0: np += 1
  718 + elif n == 1: nf += 1
  719 + else: ne += 1
  720 +
  721 + # format class description
  722 + if cls.__module__ == "__main__":
  723 + name = cls.__name__
  724 + else:
  725 + name = "%s.%s" % (cls.__module__, cls.__name__)
  726 + doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
  727 + desc = doc and '%s: %s' % (name, doc) or name
  728 +
  729 + row = self.REPORT_CLASS_TMPL % dict(
  730 + style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
  731 + desc = desc,
  732 + count = np+nf+ne,
  733 + Pass = np,
  734 + fail = nf,
  735 + error = ne,
  736 + cid = 'c%s' % (cid+1),
  737 + )
  738 + rows.append(row)
  739 +
  740 + for tid, (n,t,o,e) in enumerate(cls_results):
  741 + self._generate_report_test(rows, cid, tid, n, t, o, e)
  742 +
  743 + report = self.REPORT_TMPL % dict(
  744 + test_list = ''.join(rows),
  745 + count = str(result.success_count+result.failure_count+result.error_count),
  746 + Pass = str(result.success_count),
  747 + fail = str(result.failure_count),
  748 + error = str(result.error_count),
  749 + )
  750 + return report
  751 +
  752 +
  753 + def _generate_report_test(self, rows, cid, tid, n, t, o, e):
  754 + # e.g. 'pt1.1', 'ft1.1', etc
  755 + has_output = bool(o or e)
  756 + tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1)
  757 + name = t.id().split('.')[-1]
  758 + doc = t.shortDescription() or ""
  759 + desc = doc and ('%s: %s' % (name, doc)) or name
  760 + tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL
  761 +
  762 + # o and e should be byte string because they are collected from stdout and stderr?
  763 + if isinstance(o,str):
  764 + # TODO: some problem with 'string_escape': it escape \n and mess up formating
  765 + # uo = unicode(o.encode('string_escape'))
  766 + # uo = o.decode('latin-1')
  767 + uo = e
  768 + else:
  769 + uo = o
  770 + if isinstance(e,str):
  771 + # TODO: some problem with 'string_escape': it escape \n and mess up formating
  772 + # ue = unicode(e.encode('string_escape'))
  773 + # ue = e.decode('latin-1')
  774 + ue = e
  775 + else:
  776 + ue = e
  777 +
  778 + script = self.REPORT_TEST_OUTPUT_TMPL % dict(
  779 + id = tid,
  780 + output = saxutils.escape(str(uo)+ue),
  781 + )
  782 +
  783 + row = tmpl % dict(
  784 + tid = tid,
  785 + Class = (n == 0 and 'hiddenRow' or 'none'),
  786 + style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'),
  787 + desc = desc,
  788 + script = script,
  789 + status = self.STATUS[n],
  790 + )
  791 + rows.append(row)
  792 + if not has_output:
  793 + return
  794 +
  795 + def _generate_ending(self):
  796 + return self.ENDING_TMPL
  797 +
  798 +
  799 + def generateReport(self, test, result):
  800 + report_attrs = self.getReportAttributes(result)
  801 + generator = 'HTMLTestRunner %s' % __version__
  802 + stylesheet = self._generate_stylesheet()
  803 + heading = self._generate_heading(report_attrs)
  804 + report = self._generate_report(result)
  805 + ending = self._generate_ending()
  806 + # output = self.HTML_TMPL % dict(
  807 + # title = saxutils.escape(self.title),
  808 + # generator = generator,
  809 + # stylesheet = stylesheet,
  810 + # heading = heading,
  811 + # report = report,
  812 + # ending = ending,
  813 + # )
  814 +
  815 + output = self.HTML_TMPL % dict(
  816 + title=saxutils.escape(self.title),
  817 + generator=generator,
  818 + stylesheet=stylesheet,
  819 + heading=heading,
  820 + report=report,
  821 + ending=ending,
  822 + )
  823 +
  824 + path_file = self._create_output_file(self.outputdir, self.report_name)
  825 + with open(path_file, 'wb') as report_file:
  826 + report_file.write(output.encode('utf-8'))
  827 + #report_file.write(output)
  828 +
  829 +
  830 +##############################################################################
  831 +# Facilities for running tests from the command line
  832 +##############################################################################
  833 +
  834 +# Note: Reuse unittest.TestProgram to launch test. In the future we may
  835 +# build our own launcher to support more specific command line
  836 +# parameters like test title, CSS, etc.
  837 +class TestProgram(unittest.TestProgram):
  838 + """
  839 + A variation of the unittest.TestProgram. Please refer to the base
  840 + class for command line parameters.
  841 + """
  842 + def runTests(self):
  843 + # Pick HTMLTestRunner as the default test runner.
  844 + # base class's testRunner parameter is not useful because it means
  845 + # we have to instantiate HTMLTestRunner before we know self.verbosity.
  846 + if self.testRunner is None:
  847 + self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
  848 + unittest.TestProgram.runTests(self)
  849 +
  850 +main = TestProgram
  851 +
  852 +##############################################################################
  853 +# Executing this module from the command line
  854 +##############################################################################
  855 +
  856 +if __name__ == "__main__":
  857 + main(module=None)
... ...
HTMLTestRunner.pyc
No preview for this file type
HTMLTestRunner_bak.py
... ... @@ -0,0 +1,857 @@
  1 +"""
  2 +A TestRunner for use with the Python unit testing framework. It
  3 +generates a HTML report to show the result at a glance.
  4 +
  5 +The simplest way to use this is to invoke its main method. E.g.
  6 +
  7 + import unittest
  8 + import HTMLTestRunner
  9 +
  10 + ... define your tests ...
  11 +
  12 + if __name__ == '__main__':
  13 + HTMLTestRunner.main()
  14 +
  15 +
  16 +For more customization options, instantiates a HTMLTestRunner object.
  17 +HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.
  18 +
  19 + # output to a file
  20 + fp = file('my_report.html', 'wb')
  21 + runner = HTMLTestRunner.HTMLTestRunner(
  22 + stream=fp,
  23 + title='My unit test',
  24 + description='This demonstrates the report output by HTMLTestRunner.'
  25 + )
  26 +
  27 + # Use an external stylesheet.
  28 + # See the Template_mixin class for more customizable options
  29 + runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'
  30 +
  31 + # run the test
  32 + runner.run(my_test_suite)
  33 +
  34 +
  35 +------------------------------------------------------------------------
  36 +Copyright (c) 2004-2007, Wai Yip Tung
  37 +All rights reserved.
  38 +
  39 +Redistribution and use in source and binary forms, with or without
  40 +modification, are permitted provided that the following conditions are
  41 +met:
  42 +
  43 +* Redistributions of source code must retain the above copyright notice,
  44 + this list of conditions and the following disclaimer.
  45 +* Redistributions in binary form must reproduce the above copyright
  46 + notice, this list of conditions and the following disclaimer in the
  47 + documentation and/or other materials provided with the distribution.
  48 +* Neither the name Wai Yip Tung nor the names of its contributors may be
  49 + used to endorse or promote products derived from this software without
  50 + specific prior written permission.
  51 +
  52 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  53 +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  54 +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
  55 +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
  56 +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  57 +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  58 +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  59 +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  60 +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  61 +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  62 +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  63 +"""
  64 +
  65 +# URL: http://tungwaiyip.info/software/HTMLTestRunner.html
  66 +
  67 +__author__ = "Wai Yip Tung"
  68 +__version__ = "0.8.2"
  69 +
  70 +
  71 +"""
  72 +Change History
  73 +
  74 +Version 0.8.2
  75 +* Show output inline instead of popup window (Viorel Lupu).
  76 +
  77 +Version in 0.8.1
  78 +* Validated XHTML (Wolfgang Borgert).
  79 +* Added description of test classes and test cases.
  80 +
  81 +Version in 0.8.0
  82 +* Define Template_mixin class for customization.
  83 +* Workaround a IE 6 bug that it does not treat <script> block as CDATA.
  84 +
  85 +Version in 0.7.1
  86 +* Back port to Python 2.3 (Frank Horowitz).
  87 +* Fix missing scroll bars in detail log (Podi).
  88 +"""
  89 +
  90 +# TODO: color stderr
  91 +# TODO: simplify javascript using ,ore than 1 class in the class attribute?
  92 +
  93 +import datetime
  94 +import io
  95 +import sys
  96 +import time
  97 +import unittest
  98 +from xml.sax import saxutils
  99 +
  100 +
  101 +# ------------------------------------------------------------------------
  102 +# The redirectors below are used to capture output during testing. Output
  103 +# sent to sys.stdout and sys.stderr are automatically captured. However
  104 +# in some cases sys.stdout is already cached before HTMLTestRunner is
  105 +# invoked (e.g. calling logging.basicConfig). In order to capture those
  106 +# output, use the redirectors for the cached stream.
  107 +#
  108 +# e.g.
  109 +# >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
  110 +# >>>
  111 +
  112 +class OutputRedirector(object):
  113 + """ Wrapper to redirect stdout or stderr """
  114 + def __init__(self, fp):
  115 + self.fp = fp
  116 +
  117 + def write(self, s):
  118 + self.fp.write(s)
  119 +
  120 + def writelines(self, lines):
  121 + self.fp.writelines(lines)
  122 +
  123 + def flush(self):
  124 + self.fp.flush()
  125 +
  126 +stdout_redirector = OutputRedirector(sys.stdout)
  127 +stderr_redirector = OutputRedirector(sys.stderr)
  128 +
  129 +
  130 +
  131 +# ----------------------------------------------------------------------
  132 +# Template
  133 +
  134 +class Template_mixin(object):
  135 + """
  136 + Define a HTML template for report customerization and generation.
  137 +
  138 + Overall structure of an HTML report
  139 +
  140 + HTML
  141 + +------------------------+
  142 + |<html> |
  143 + | <head> |
  144 + | |
  145 + | STYLESHEET |
  146 + | +----------------+ |
  147 + | | | |
  148 + | +----------------+ |
  149 + | |
  150 + | </head> |
  151 + | |
  152 + | <body> |
  153 + | |
  154 + | HEADING |
  155 + | +----------------+ |
  156 + | | | |
  157 + | +----------------+ |
  158 + | |
  159 + | REPORT |
  160 + | +----------------+ |
  161 + | | | |
  162 + | +----------------+ |
  163 + | |
  164 + | ENDING |
  165 + | +----------------+ |
  166 + | | | |
  167 + | +----------------+ |
  168 + | |
  169 + | </body> |
  170 + |</html> |
  171 + +------------------------+
  172 + """
  173 +
  174 + STATUS = {
  175 + 0: 'pass',
  176 + 1: 'fail',
  177 + 2: 'error',
  178 + }
  179 +
  180 + DEFAULT_TITLE = 'Unit Test Report'
  181 + DEFAULT_DESCRIPTION = ''
  182 +
  183 + # ------------------------------------------------------------------------
  184 + # HTML Template
  185 +
  186 + HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
  187 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  188 +<html xmlns="http://www.w3.org/1999/xhtml">
  189 +<head>
  190 + <title>%(title)s</title>
  191 + <meta name="generator" content="%(generator)s"/>
  192 + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  193 + %(stylesheet)s
  194 +</head>
  195 +<body>
  196 +<script language="javascript" type="text/javascript"><!--
  197 +output_list = Array();
  198 +
  199 +/* level - 0:Summary; 1:Failed; 2:All */
  200 +function showCase(level) {
  201 + trs = document.getElementsByTagName("tr");
  202 + for (var i = 0; i < trs.length; i++) {
  203 + tr = trs[i];
  204 + id = tr.id;
  205 + if (id.substr(0,2) == 'ft') {
  206 + if (level < 1) {
  207 + tr.className = 'hiddenRow';
  208 + }
  209 + else {
  210 + tr.className = '';
  211 + }
  212 + }
  213 + if (id.substr(0,2) == 'pt') {
  214 + if (level > 1) {
  215 + tr.className = '';
  216 + }
  217 + else {
  218 + tr.className = 'hiddenRow';
  219 + }
  220 + }
  221 + }
  222 +}
  223 +
  224 +
  225 +function showClassDetail(cid, count) {
  226 + var id_list = Array(count);
  227 + var toHide = 1;
  228 + for (var i = 0; i < count; i++) {
  229 + tid0 = 't' + cid.substr(1) + '.' + (i+1);
  230 + tid = 'f' + tid0;
  231 + tr = document.getElementById(tid);
  232 + if (!tr) {
  233 + tid = 'p' + tid0;
  234 + tr = document.getElementById(tid);
  235 + }
  236 + id_list[i] = tid;
  237 + if (tr.className) {
  238 + toHide = 0;
  239 + }
  240 + }
  241 + for (var i = 0; i < count; i++) {
  242 + tid = id_list[i];
  243 + if (toHide) {
  244 + document.getElementById('div_'+tid).style.display = 'none'
  245 + document.getElementById(tid).className = 'hiddenRow';
  246 + }
  247 + else {
  248 + document.getElementById(tid).className = '';
  249 + }
  250 + }
  251 +}
  252 +
  253 +
  254 +function showTestDetail(div_id){
  255 + var details_div = document.getElementById(div_id)
  256 + var displayState = details_div.style.display
  257 + // alert(displayState)
  258 + if (displayState != 'block' ) {
  259 + displayState = 'block'
  260 + details_div.style.display = 'block'
  261 + }
  262 + else {
  263 + details_div.style.display = 'none'
  264 + }
  265 +}
  266 +
  267 +
  268 +function html_escape(s) {
  269 + s = s.replace(/&/g,'&amp;');
  270 + s = s.replace(/</g,'&lt;');
  271 + s = s.replace(/>/g,'&gt;');
  272 + return s;
  273 +}
  274 +
  275 +/* obsoleted by detail in <div>
  276 +function showOutput(id, name) {
  277 + var w = window.open("", //url
  278 + name,
  279 + "resizable,scrollbars,status,width=800,height=450");
  280 + d = w.document;
  281 + d.write("<pre>");
  282 + d.write(html_escape(output_list[id]));
  283 + d.write("\n");
  284 + d.write("<a href='javascript:window.close()'>close</a>\n");
  285 + d.write("</pre>\n");
  286 + d.close();
  287 +}
  288 +*/
  289 +--></script>
  290 +
  291 +%(heading)s
  292 +%(report)s
  293 +%(ending)s
  294 +
  295 +</body>
  296 +</html>
  297 +"""
  298 + # variables: (title, generator, stylesheet, heading, report, ending)
  299 +
  300 +
  301 + # ------------------------------------------------------------------------
  302 + # Stylesheet
  303 + #
  304 + # alternatively use a <link> for external style sheet, e.g.
  305 + # <link rel="stylesheet" href="$url" type="text/css">
  306 +
  307 + STYLESHEET_TMPL = """
  308 +<style type="text/css" media="screen">
  309 +body { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; }
  310 +table { font-size: 100%; }
  311 +pre { }
  312 +
  313 +/* -- heading ---------------------------------------------------------------------- */
  314 +h1 {
  315 + font-size: 16pt;
  316 + color: gray;
  317 +}
  318 +.heading {
  319 + margin-top: 0ex;
  320 + margin-bottom: 1ex;
  321 +}
  322 +
  323 +.heading .attribute {
  324 + margin-top: 1ex;
  325 + margin-bottom: 0;
  326 +}
  327 +
  328 +.heading .description {
  329 + margin-top: 4ex;
  330 + margin-bottom: 6ex;
  331 +}
  332 +
  333 +/* -- css div popup ------------------------------------------------------------------------ */
  334 +a.popup_link {
  335 +}
  336 +
  337 +a.popup_link:hover {
  338 + color: red;
  339 +}
  340 +
  341 +.popup_window {
  342 + display: none;
  343 + position: relative;
  344 + left: 0px;
  345 + top: 0px;
  346 + /*border: solid #627173 1px; */
  347 + padding: 10px;
  348 + background-color: #E6E6D6;
  349 + font-family: "Lucida Console", "Courier New", Courier, monospace;
  350 + text-align: left;
  351 + font-size: 8pt;
  352 + width: 500px;
  353 +}
  354 +
  355 +}
  356 +/* -- report ------------------------------------------------------------------------ */
  357 +#show_detail_line {
  358 + margin-top: 3ex;
  359 + margin-bottom: 1ex;
  360 +}
  361 +#result_table {
  362 + width: 80%;
  363 + border-collapse: collapse;
  364 + border: 1px solid #777;
  365 +}
  366 +#header_row {
  367 + font-weight: bold;
  368 + color: white;
  369 + background-color: #777;
  370 +}
  371 +#result_table td {
  372 + border: 1px solid #777;
  373 + padding: 2px;
  374 +}
  375 +#total_row { font-weight: bold; }
  376 +.passClass { background-color: #6c6; }
  377 +.failClass { background-color: #c60; }
  378 +.errorClass { background-color: #c00; }
  379 +.passCase { color: #6c6; }
  380 +.failCase { color: #c60; font-weight: bold; }
  381 +.errorCase { color: #c00; font-weight: bold; }
  382 +.hiddenRow { display: none; }
  383 +.testcase { margin-left: 2em; }
  384 +
  385 +
  386 +/* -- ending ---------------------------------------------------------------------- */
  387 +#ending {
  388 +}
  389 +
  390 +</style>
  391 +"""
  392 +
  393 +
  394 +
  395 + # ------------------------------------------------------------------------
  396 + # Heading
  397 + #
  398 +
  399 + HEADING_TMPL = """<div class='heading'>
  400 +<h1>%(title)s</h1>
  401 +%(parameters)s
  402 +<p class='description'>%(description)s</p>
  403 +</div>
  404 +
  405 +""" # variables: (title, parameters, description)
  406 +
  407 + HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>
  408 +""" # variables: (name, value)
  409 +
  410 +
  411 +
  412 + # ------------------------------------------------------------------------
  413 + # Report
  414 + #
  415 +
  416 + REPORT_TMPL = """
  417 +<p id='show_detail_line'>Show
  418 +<a href='javascript:showCase(0)'>Summary</a>
  419 +<a href='javascript:showCase(1)'>Failed</a>
  420 +<a href='javascript:showCase(2)'>All</a>
  421 +</p>
  422 +<table id='result_table'>
  423 +<colgroup>
  424 +<col align='left' />
  425 +<col align='right' />
  426 +<col align='right' />
  427 +<col align='right' />
  428 +<col align='right' />
  429 +<col align='right' />
  430 +</colgroup>
  431 +<tr id='header_row'>
  432 + <td>Test Group/Test case</td>
  433 + <td>Count</td>
  434 + <td>Pass</td>
  435 + <td>Fail</td>
  436 + <td>Error</td>
  437 + <td>View</td>
  438 +</tr>
  439 +%(test_list)s
  440 +<tr id='total_row'>
  441 + <td>Total</td>
  442 + <td>%(count)s</td>
  443 + <td>%(Pass)s</td>
  444 + <td>%(fail)s</td>
  445 + <td>%(error)s</td>
  446 + <td>&nbsp;</td>
  447 +</tr>
  448 +</table>
  449 +""" # variables: (test_list, count, Pass, fail, error)
  450 +
  451 + REPORT_CLASS_TMPL = r"""
  452 +<tr class='%(style)s'>
  453 + <td>%(desc)s</td>
  454 + <td>%(count)s</td>
  455 + <td>%(Pass)s</td>
  456 + <td>%(fail)s</td>
  457 + <td>%(error)s</td>
  458 + <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Detail</a></td>
  459 +</tr>
  460 +""" # variables: (style, desc, count, Pass, fail, error, cid)
  461 +
  462 +
  463 + REPORT_TEST_WITH_OUTPUT_TMPL = r"""
  464 +<tr id='%(tid)s' class='%(Class)s'>
  465 + <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
  466 + <td colspan='5' align='center'>
  467 +
  468 + <!--css div popup start-->
  469 + <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >
  470 + %(status)s</a>
  471 +
  472 + <div id='div_%(tid)s' class="popup_window">
  473 + <div style='text-align: right; color:red;cursor:pointer'>
  474 + <a onfocus='this.blur();' onclick="document.getElementById('div_%(tid)s').style.display = 'none' " >
  475 + [x]</a>
  476 + </div>
  477 + <pre>
  478 + %(script)s
  479 + </pre>
  480 + </div>
  481 + <!--css div popup end-->
  482 +
  483 + </td>
  484 +</tr>
  485 +""" # variables: (tid, Class, style, desc, status)
  486 +
  487 +
  488 + REPORT_TEST_NO_OUTPUT_TMPL = r"""
  489 +<tr id='%(tid)s' class='%(Class)s'>
  490 + <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
  491 + <td colspan='5' align='center'>%(status)s</td>
  492 +</tr>
  493 +""" # variables: (tid, Class, style, desc, status)
  494 +
  495 +
  496 + REPORT_TEST_OUTPUT_TMPL = r"""
  497 +%(id)s: %(output)s
  498 +""" # variables: (id, output)
  499 +
  500 +
  501 +
  502 + # ------------------------------------------------------------------------
  503 + # ENDING
  504 + #
  505 +
  506 + ENDING_TMPL = """<div id='ending'>&nbsp;</div>"""
  507 +
  508 +# -------------------- The end of the Template class -------------------
  509 +
  510 +
  511 +TestResult = unittest.TestResult
  512 +
  513 +class _TestResult(TestResult):
  514 + # note: _TestResult is a pure representation of results.
  515 + # It lacks the output and reporting ability compares to unittest._TextTestResult.
  516 +
  517 + def __init__(self, verbosity=1):
  518 + TestResult.__init__(self)
  519 + self.stdout0 = None
  520 + self.stderr0 = None
  521 + self.success_count = 0
  522 + self.failure_count = 0
  523 + self.error_count = 0
  524 + self.verbosity = verbosity
  525 +
  526 + # result is a list of result in 4 tuple
  527 + # (
  528 + # result code (0: success; 1: fail; 2: error),
  529 + # TestCase object,
  530 + # Test output (byte string),
  531 + # stack trace,
  532 + # )
  533 + self.result = []
  534 +
  535 +
  536 + def startTest(self, test):
  537 + TestResult.startTest(self, test)
  538 + # just one buffer for both stdout and stderr
  539 + self.outputBuffer = io.StringIO()
  540 + stdout_redirector.fp = self.outputBuffer
  541 + stderr_redirector.fp = self.outputBuffer
  542 + self.stdout0 = sys.stdout
  543 + self.stderr0 = sys.stderr
  544 + sys.stdout = stdout_redirector
  545 + sys.stderr = stderr_redirector
  546 +
  547 +
  548 + def complete_output(self):
  549 + """
  550 + Disconnect output redirection and return buffer.
  551 + Safe to call multiple times.
  552 + """
  553 + if self.stdout0:
  554 + sys.stdout = self.stdout0
  555 + sys.stderr = self.stderr0
  556 + self.stdout0 = None
  557 + self.stderr0 = None
  558 + return self.outputBuffer.getvalue()
  559 +
  560 +
  561 + def stopTest(self, test):
  562 + # Usually one of addSuccess, addError or addFailure would have been called.
  563 + # But there are some path in unittest that would bypass this.
  564 + # We must disconnect stdout in stopTest(), which is guaranteed to be called.
  565 + self.complete_output()
  566 +
  567 +
  568 + def addSuccess(self, test):
  569 + self.success_count += 1
  570 + TestResult.addSuccess(self, test)
  571 + output = self.complete_output()
  572 + self.result.append((0, test, output, ''))
  573 + if self.verbosity > 1:
  574 + sys.stderr.write('ok ')
  575 + sys.stderr.write(str(test))
  576 + sys.stderr.write('\n')
  577 + else:
  578 + sys.stderr.write('.')
  579 +
  580 + def addError(self, test, err):
  581 + self.error_count += 1
  582 + TestResult.addError(self, test, err)
  583 + _, _exc_str = self.errors[-1]
  584 + output = self.complete_output()
  585 + self.result.append((2, test, output, _exc_str))
  586 + if self.verbosity > 1:
  587 + sys.stderr.write('E ')
  588 + sys.stderr.write(str(test))
  589 + sys.stderr.write('\n')
  590 + else:
  591 + sys.stderr.write('E')
  592 +
  593 + def addFailure(self, test, err):
  594 + self.failure_count += 1
  595 + TestResult.addFailure(self, test, err)
  596 + _, _exc_str = self.failures[-1]
  597 + output = self.complete_output()
  598 + self.result.append((1, test, output, _exc_str))
  599 + if self.verbosity > 1:
  600 + sys.stderr.write('F ')
  601 + sys.stderr.write(str(test))
  602 + sys.stderr.write('\n')
  603 + else:
  604 + sys.stderr.write('F')
  605 +
  606 +
  607 +class HTMLTestRunner(Template_mixin):
  608 + """
  609 + """
  610 + #def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None):
  611 + def __init__(self, outputdir, verbosity=1, title=None, description=None, report_name='test_report.html'):
  612 + #self.stream = self._create_output_file(outputdir, report_name)
  613 + self.outputdir = outputdir
  614 + self.report_name = report_name
  615 + self.verbosity = verbosity
  616 + if title is None:
  617 + self.title = self.DEFAULT_TITLE
  618 + else:
  619 + self.title = title
  620 + if description is None:
  621 + self.description = self.DEFAULT_DESCRIPTION
  622 + else:
  623 + self.description = description
  624 +
  625 + self.startTime = datetime.datetime.now()
  626 +
  627 +
  628 + def _create_output_file(self, output, report_name):
  629 + import os
  630 + """ Generate the report file in the given path. """
  631 + current_dir = os.getcwd()
  632 + dir_to = os.path.join(current_dir, output)
  633 + if not os.path.exists(dir_to):
  634 + os.makedirs(dir_to)
  635 + path_file = os.path.join(dir_to, report_name)
  636 + return path_file
  637 +
  638 +
  639 + def generateReport(self, test, result):
  640 + report_attrs = self.getReportAttributes(result)
  641 + generator = 'HTMLTestRunner %s' % __version__
  642 + stylesheet = self._generate_stylesheet()
  643 + heading = self._generate_heading(report_attrs)
  644 + report = self._generate_report(result)
  645 + ending = self._generate_ending()
  646 + # output = self.HTML_TMPL % dict(
  647 + # title = saxutils.escape(self.title),
  648 + # generator = generator,
  649 + # stylesheet = stylesheet,
  650 + # heading = heading,
  651 + # report = report,
  652 + # ending = ending,
  653 + # )
  654 +
  655 + output = self.HTML_TMPL % dict(
  656 + title=saxutils.escape(self.title),
  657 + generator=generator,
  658 + stylesheet=stylesheet,
  659 + heading=heading,
  660 + report=report,
  661 + ending=ending,
  662 + )
  663 + path_file = self._create_output_file(self.outputdir, self.report_name)
  664 + with open(path_file, 'w') as report_file:
  665 + report_file.write(output.encode('utf-8'))
  666 +
  667 +
  668 + def run(self, test):
  669 + "Run the given test case or test suite."
  670 + result = _TestResult(self.verbosity)
  671 + test(result)
  672 + self.stopTime = datetime.datetime.now()
  673 + self.generateReport(test, result)
  674 + # print >> sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)
  675 + print(sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime))
  676 + return result
  677 +
  678 +
  679 + def sortResult(self, result_list):
  680 + # unittest does not seems to run in any particular order.
  681 + # Here at least we want to group them together by class.
  682 + rmap = {}
  683 + classes = []
  684 + for n,t,o,e in result_list:
  685 + cls = t.__class__
  686 + if not cls in rmap:
  687 + rmap[cls] = []
  688 + classes.append(cls)
  689 + rmap[cls].append((n,t,o,e))
  690 + r = [(cls, rmap[cls]) for cls in classes]
  691 + return r
  692 +
  693 +
  694 + def getReportAttributes(self, result):
  695 + """
  696 + Return report attributes as a list of (name, value).
  697 + Override this to add custom attributes.
  698 + """
  699 + startTime = str(self.startTime)[:19]
  700 + duration = str(self.stopTime - self.startTime)
  701 + status = []
  702 + if result.success_count: status.append('Pass %s' % result.success_count)
  703 + if result.failure_count: status.append('Failure %s' % result.failure_count)
  704 + if result.error_count: status.append('Error %s' % result.error_count )
  705 + if status:
  706 + status = ' '.join(status)
  707 + else:
  708 + status = 'none'
  709 + return [
  710 + ('Start Time', startTime),
  711 + ('Duration', duration),
  712 + ('Status', status),
  713 + ]
  714 +
  715 +
  716 +
  717 +
  718 +
  719 +
  720 +
  721 + def _generate_stylesheet(self):
  722 + return self.STYLESHEET_TMPL
  723 +
  724 +
  725 + def _generate_heading(self, report_attrs):
  726 + a_lines = []
  727 + for name, value in report_attrs:
  728 + line = self.HEADING_ATTRIBUTE_TMPL % dict(
  729 + name = saxutils.escape(name),
  730 + value = saxutils.escape(value),
  731 + )
  732 + a_lines.append(line)
  733 + heading = self.HEADING_TMPL % dict(
  734 + title = saxutils.escape(self.title),
  735 + parameters = ''.join(a_lines),
  736 + description = saxutils.escape(self.description),
  737 + )
  738 + return heading
  739 +
  740 +
  741 + def _generate_report(self, result):
  742 + rows = []
  743 + sortedResult = self.sortResult(result.result)
  744 + for cid, (cls, cls_results) in enumerate(sortedResult):
  745 + # subtotal for a class
  746 + np = nf = ne = 0
  747 + for n,t,o,e in cls_results:
  748 + if n == 0: np += 1
  749 + elif n == 1: nf += 1
  750 + else: ne += 1
  751 +
  752 + # format class description
  753 + if cls.__module__ == "__main__":
  754 + name = cls.__name__
  755 + else:
  756 + name = "%s.%s" % (cls.__module__, cls.__name__)
  757 + doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
  758 + desc = doc and '%s: %s' % (name, doc) or name
  759 +
  760 + row = self.REPORT_CLASS_TMPL % dict(
  761 + style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
  762 + desc = desc,
  763 + count = np+nf+ne,
  764 + Pass = np,
  765 + fail = nf,
  766 + error = ne,
  767 + cid = 'c%s' % (cid+1),
  768 + )
  769 + rows.append(row)
  770 +
  771 + for tid, (n,t,o,e) in enumerate(cls_results):
  772 + self._generate_report_test(rows, cid, tid, n, t, o, e)
  773 +
  774 + report = self.REPORT_TMPL % dict(
  775 + test_list = ''.join(rows),
  776 + count = str(result.success_count+result.failure_count+result.error_count),
  777 + Pass = str(result.success_count),
  778 + fail = str(result.failure_count),
  779 + error = str(result.error_count),
  780 + )
  781 + return report
  782 +
  783 +
  784 + def _generate_report_test(self, rows, cid, tid, n, t, o, e):
  785 + # e.g. 'pt1.1', 'ft1.1', etc
  786 + has_output = bool(o or e)
  787 + tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1)
  788 + name = t.id().split('.')[-1]
  789 + doc = t.shortDescription() or ""
  790 + desc = doc and ('%s: %s' % (name, doc)) or name
  791 + tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL
  792 +
  793 + # o and e should be byte string because they are collected from stdout and stderr?
  794 + if isinstance(o,str):
  795 + # TODO: some problem with 'string_escape': it escape \n and mess up formating
  796 + # uo = unicode(o.encode('string_escape'))
  797 + # uo = o.decode('latin-1')
  798 + uo = e
  799 + else:
  800 + uo = o
  801 + if isinstance(e,str):
  802 + # TODO: some problem with 'string_escape': it escape \n and mess up formating
  803 + # ue = unicode(e.encode('string_escape'))
  804 + # ue = e.decode('latin-1')
  805 + ue = e
  806 + else:
  807 + ue = e
  808 +
  809 + script = self.REPORT_TEST_OUTPUT_TMPL % dict(
  810 + id = tid,
  811 + output = saxutils.escape(str(uo)+ue),
  812 + )
  813 +
  814 + row = tmpl % dict(
  815 + tid = tid,
  816 + Class = (n == 0 and 'hiddenRow' or 'none'),
  817 + style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'),
  818 + desc = desc,
  819 + script = script,
  820 + status = self.STATUS[n],
  821 + )
  822 + rows.append(row)
  823 + if not has_output:
  824 + return
  825 +
  826 + def _generate_ending(self):
  827 + return self.ENDING_TMPL
  828 +
  829 +
  830 +##############################################################################
  831 +# Facilities for running tests from the command line
  832 +##############################################################################
  833 +
  834 +# Note: Reuse unittest.TestProgram to launch test. In the future we may
  835 +# build our own launcher to support more specific command line
  836 +# parameters like test title, CSS, etc.
  837 +class TestProgram(unittest.TestProgram):
  838 + """
  839 + A variation of the unittest.TestProgram. Please refer to the base
  840 + class for command line parameters.
  841 + """
  842 + def runTests(self):
  843 + # Pick HTMLTestRunner as the default test runner.
  844 + # base class's testRunner parameter is not useful because it means
  845 + # we have to instantiate HTMLTestRunner before we know self.verbosity.
  846 + if self.testRunner is None:
  847 + self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
  848 + unittest.TestProgram.runTests(self)
  849 +
  850 +main = TestProgram
  851 +
  852 +##############################################################################
  853 +# Executing this module from the command line
  854 +##############################################################################
  855 +
  856 +if __name__ == "__main__":
  857 + main(module=None)
... ...
... ... @@ -0,0 +1 @@
  1 +API test scripts about APIs in Pad3.o
... ...
__pycache__/HTMLTestRunner.cpython-36.pyc
No preview for this file type
__pycache__/HTMLTestRunner_bak.cpython-36.pyc
No preview for this file type
__pycache__/configParse.cpython-36.pyc
No preview for this file type
__pycache__/run_test.cpython-36.pyc
No preview for this file type
... ... @@ -0,0 +1,23 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +import configparser
  5 +import pymysql.cursors
  6 +import os
  7 +
  8 +base_dir = str(os.path.dirname(os.path.dirname(__file__)))
  9 +config_dir = base_dir.replace('\\', '/')
  10 +config_path = config_dir + '/config.ini'
  11 +
  12 +cp = configparser.ConfigParser()
  13 +cp.read(config_path)
  14 +DB_host = cp.get('mysqlconf', 'host')
  15 +DB_port = cp.get('mysqlconf', 'port')
  16 +DB_username = cp.get('mysqlconf', 'user')
  17 +DB_password = cp.get('mysqlconf', 'password')
  18 +DB_dbName = cp.get('mysqlconf', 'db_name')
  19 +
  20 +userPhone = cp.get('userinfo', 'userPhone')
  21 +deviceNumber = cp.get('userinfo','deviceNumber')
  22 +admin_host = 'http://admin.test.hjx.com/'
  23 +boss_host = 'http://boss.test.hjx.com/'
0 24 \ No newline at end of file
... ...
data_fixture/__pycache__/config_data.cpython-36.pyc
No preview for this file type
data_fixture/__pycache__/create_testdata.cpython-36.pyc
No preview for this file type
data_fixture/__pycache__/mysql_db.cpython-36.pyc
No preview for this file type
data_fixture/config_data.py
... ... @@ -0,0 +1,85 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +# DB connection
  5 +DB_HOST='115.29.194.25'
  6 +DB_PORT=3307
  7 +DB_USERNAME='cloud'
  8 +DB_PASSWORD='cloud123'
  9 +DB_DBNAME='acornuser'
  10 +
  11 +
  12 +# DB_host='192.168.4.135'
  13 +# DB_port=3306
  14 +# DB_username='cloud'
  15 +# DB_password='wQHPo9H6s7'
  16 +# DB_db_name='acornuser'
  17 +
  18 +HOST_BOSS = 'http://boss.test.hjx.com'
  19 +HOST_ADMIN = 'http://admin.test.hjx.com'
  20 +HOST_STA = 'http://sta.test.hjx.com'
  21 +HOST_RES = 'http://res.test.hjx.com'
  22 +
  23 +#USER_ID = '90000000123456'
  24 +
  25 +USER_ID = '7000000054686773'
  26 +USER_ID_INCOMPLETE = '7000000054686777' #注册未完成账号
  27 +USER_ID_VIDEO = '7000000054686773'
  28 +USER_ID_NO_VIDEO = '7000000054686776'
  29 +MODEL = 'AAAAA'
  30 +USER_PHONE = '13811111111' # 注册登陆
  31 +USER_PHONE_EDIT = '13811111122' # 修改保卡
  32 +USER_PHONE_CHANGE = '13811111133' # 修改手机号
  33 +USER_PHONE_CHANGE_EXISTS = '13811111166'
  34 +USER_PHONE_USED = '13811111144'
  35 +USER_PHONE_UNUSED = '13811111155'
  36 +
  37 +
  38 +PARENT_ID = '7000000054686775'
  39 +
  40 +
  41 +## 保卡 ------------------------
  42 +# 客机,已绑定保卡
  43 +DEVICE_NUMBER_CUS_BIND = 'CUSBIND123456789'
  44 +MAC_CUS_BIND = '00:00:00:00:00:11'
  45 +# 客机, 没有保卡
  46 +DEVICE_NUMBER_CUS_UNBIND = 'CUSUNBIND123456789'
  47 +MAC_CUS_UNBIND = '00:00:00:00:00:22'
  48 +# 样机
  49 +DEVICE_NUMBER_SAM = 'SAM123456789'
  50 +MAC_SAM = '00:00:00:00:00:33'
  51 +
  52 +#添加客机保卡
  53 +DEVICE_NUMBER_NEW = 'NEW123456789'
  54 +MAC_NEW = '00:00:00:00:00:44'
  55 +
  56 +# 置为样机,提交终端信息
  57 +DEVICE_NUMBER_TO_SAM = 'TOSAM123456789'
  58 +
  59 +#置为客机
  60 +DEVICE_NUMBER_TO_CUS = 'TOCUS123456789'
  61 +
  62 +#解绑保卡
  63 +DEVICE_NUMBER_UNBIND = 'UNBIND123456789'
  64 +
  65 +#修改保卡
  66 +DEVICE_NUMBER_EDIT = 'EDIT123456789'
  67 +
  68 +
  69 +## 子账户 -----------------------
  70 +#子账户头像
  71 +SUB_ACC_IMAGE = 'http://hjxprodbucket.oss.aliyuncs.com/static/upload/boss_api/announcement/2017-08-29/a00de899-2f6d-43fb-9e30-71883842540e.png'
  72 +#子账户区域
  73 +SUB_ACC_REGION_NAME_1 = '河北秦皇岛青龙'
  74 +SUB_ACC_REGION_NAME_2 = '江苏南京玄武'
  75 +#子账户区域ID
  76 +SUB_ACC_REGION_ID_1 = 130321
  77 +SUB_ACC_REGION_ID_2 = 320102
  78 +#子账户学校ID
  79 +SUB_ACC_SCHOOL_ID_1 = 43470 ## 小学
  80 +SUB_ACC_SCHOOL_ID_2 = 500016 ## 中学
  81 +
  82 +
  83 +
  84 +
  85 +
... ...
data_fixture/create_testdata.py
... ... @@ -0,0 +1,484 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +from data_fixture.mysql_db import DB
  5 +from data_fixture import config_data as Data
  6 +from datetime import datetime, date, timedelta
  7 +import time
  8 +from data_fixture.config_data import HOST_BOSS
  9 +import requests
  10 +import calendar
  11 +import uuid
  12 +
  13 +
  14 +db = DB()
  15 +
  16 +## *********************************************************************************************************************
  17 +## 验证码
  18 +## *********************************************************************************************************************
  19 +def fet_authCode(mobile):
  20 + url = HOST_BOSS + "/ozing/timer/user/fetchAuthCode"
  21 + headers = {'Accept': '*/*'}
  22 + postData = {'mobile': mobile, 'type': 'general'}
  23 + r = requests.post(url, headers=headers, data=postData)
  24 + result = r.json()
  25 + if result['status'] == 100:
  26 + return result['jsessionid']
  27 + else :
  28 + raise FetchException('fetch auth code Error!')
  29 +
  30 +
  31 +class FetchException(Exception):
  32 + pass
  33 +
  34 +## *********************************************************************************************************************
  35 +## 保卡
  36 +## *********************************************************************************************************************
  37 +# 保卡数据 -- 新建保卡
  38 +def pre_elecCard(device_cus_bind='0', device_sam='0', device_cus_unbind='0'):
  39 +
  40 + if device_cus_bind != '0':
  41 + # 客机,已绑定保卡
  42 + select_customermachine = "select * from acornuser.ozing_customermachine where deviceNumber = '{}' ".format(device_cus_bind)
  43 + select_machine_cus = "SELECT * FROM acornuser.ozing_machine where deviceNumber = '{}' ".format(device_cus_bind)
  44 + insert_customermachine_tabel = 'acornuser.ozing_customermachine'
  45 + insert_customermachine_data = {
  46 + 'deviceNumber': device_cus_bind,
  47 + 'userId': Data.USER_ID,
  48 + 'customerName':'测试customer',
  49 + 'customerAddress':'内蒙古巴彦淖尔市',
  50 + 'customerPhone':'13822222222',
  51 + 'buyTime': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  52 + 'buyAddress':'内蒙古巴彦淖尔市',
  53 + 'alterSaleCall':'51518888',
  54 + 'cmstate': '1',
  55 + 'createTime': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  56 + }
  57 + insert_machine_cus_table = 'acornuser.ozing_machine'
  58 + insert_machine_cus_data = {'productModel': Data.MODEL,
  59 + 'deviceNumber': device_cus_bind,
  60 + 'macAddress': Data.MAC_CUS_BIND,
  61 + 'state': '1'
  62 + }
  63 + if db.select(select_customermachine):
  64 + pass
  65 + else:
  66 + db.insert(insert_customermachine_tabel, insert_customermachine_data)
  67 +
  68 + if db.select(select_machine_cus):
  69 + pass
  70 + else:
  71 + db.insert(insert_machine_cus_table, insert_machine_cus_data)
  72 +
  73 +# 样机
  74 + if device_sam !='0':
  75 + select_samplemachine = "SELECT * FROM acornuser.ozing_samplemachine where deviceNumber = '{}' ".format(device_sam)
  76 + select_machine_sam = "SELECT * FROM acornuser.ozing_machine where deviceNumber = '{}' ".format(device_sam)
  77 + insert_samplemachine_tabel = 'acornuser.ozing_samplemachine'
  78 + insert_samplemachine_data= {'deviceNumber': device_sam,
  79 + 'userId': Data.USER_ID,
  80 + 'terminalAddress': "内蒙古巴彦淖尔市",
  81 + 'distributor': "新华书店",
  82 + 'saleClerk':"张三",
  83 + 'mobilePhone': "18622222222",
  84 + 'photo':'[{"photoUrl":"static/upload/online_api/samplePhoto/2017-12-13/b6480129-e720-4109-a455-6130fd640f16.jpg"},{"photoUrl":"static/upload/online_api/samplePhoto/2017-12-13/b8317fa4-cfa5-4ea4-91d4-3ca020e06bca.jpg"},{"photoUrl":"static/upload/online_api/samplePhoto/2017-12-13/110dc30b-34f1-4e0b-88e0-a030b8ab4af7.jpg"}]',
  85 + 'smstate': '1',
  86 + 'createTime': datetime.now().strftime('%Y-%m-%d %H:%M:%S') }
  87 + insert_machine_sam_tabel = 'acornuser.ozing_machine'
  88 + insert_machine_sam_data = {'productModel': Data.MODEL,
  89 + 'deviceNumber': device_sam,
  90 + 'macAddress': Data.MAC_SAM,
  91 + 'state': '0'}
  92 +
  93 + if db.select(select_samplemachine):
  94 + pass
  95 + else:
  96 + db.insert(insert_samplemachine_tabel, insert_samplemachine_data)
  97 +
  98 + if db.select(select_machine_sam):
  99 + pass
  100 + else:
  101 + db.insert(insert_machine_sam_tabel, insert_machine_sam_data)
  102 +
  103 +# 未绑定 -- 客机
  104 + if device_cus_unbind != '0':
  105 + select_customermachine_unbind = "select * from acornuser.ozing_customermachine where deviceNumber = '{}' " \
  106 + .format(device_cus_unbind)
  107 + update_customermachine_unbind_tabel = 'acornuser.ozing_customermachine'
  108 + update_customermachine_unbind_set = {'deviceNumber':str(time.time())}
  109 + update_customermachine_unbind_where = {'deviceNumber': device_cus_unbind}
  110 +
  111 + select_machine_cus_1 = "SELECT * FROM acornuser.ozing_machine where deviceNumber = '{}' ".format(device_cus_unbind)
  112 + select_machine_cus_2 = "SELECT * FROM acornuser.ozing_machine where deviceNumber = '{}' and state = '1' ".format(device_cus_unbind)
  113 +
  114 + update_machine_cus_unbind_table = 'acornuser.ozing_machine'
  115 + update_machine_cus_unbind_set = {'state': '1'}
  116 + update_machine_cus_unbind_where = {'deviceNumber': device_cus_unbind}
  117 +
  118 + # 保卡表中有数据
  119 + if db.select(select_customermachine_unbind):
  120 + db.update(update_customermachine_unbind_tabel, update_customermachine_unbind_set, update_customermachine_unbind_where)
  121 + # 机器表中有数据,并且状态是1
  122 + if db.select(select_machine_cus_1):
  123 + if db.select(select_machine_cus_2):
  124 + pass
  125 + else:
  126 + # update state =1
  127 + db.update(update_machine_cus_unbind_table, update_machine_cus_unbind_set, update_machine_cus_unbind_where)
  128 + # 机器表中没数据
  129 + else:
  130 + pass
  131 +
  132 +# #添加客机保卡
  133 +def pre_SetUpElecCard():
  134 + select_customermachine_new = "select * from acornuser.ozing_customermachine where deviceNumber = '{}' ".format(Data.DEVICE_NUMBER_NEW)
  135 + update_customermachine_new_tabel = 'acornuser.ozing_customermachine'
  136 + update_customermachine_new_set = {'deviceNumber':str(time.time())}
  137 + update_customermachine_new_where = {'deviceNumber': Data.DEVICE_NUMBER_NEW}
  138 +
  139 + select_machine_new = "select * from acornuser.ozing_machine where deviceNumber = '{}' ".format(Data.DEVICE_NUMBER_NEW)
  140 + update_machine_new_tabel = 'acornuser.ozing_machine'
  141 + update_machine_new_set = {'deviceNumber': str(time.time())}
  142 + update_machine_new_where = {'deviceNumber': Data.DEVICE_NUMBER_NEW}
  143 +
  144 + if db.select(select_customermachine_new):
  145 + db.update(update_customermachine_new_tabel, update_customermachine_new_set, update_customermachine_new_where)
  146 + if db.select(select_machine_new):
  147 + db.update(update_machine_new_tabel, update_machine_new_set, update_machine_new_where)
  148 +
  149 +
  150 +## *********************************************************************************************************************
  151 +## 子账户
  152 +## *********************************************************************************************************************
  153 +
  154 +def pre_subAccount(parent_id, sub_account_id, status, deviceNumber=Data.DEVICE_NUMBER_CUS_BIND): #status: child status
  155 +
  156 + # 子账户数据
  157 + TABEL_CHILD_USER = 'acornuser.child_user'
  158 + if status == 1 : # make sure only 1 sub account's status marked as '1'
  159 + update_all_0_sub = "update acornuser.child_user set status = 0 where parent_id = '{}' and deviceNumber = '{}'" \
  160 + .format(parent_id, deviceNumber)
  161 + db.update_(update_all_0_sub)
  162 +
  163 + select_sub_acc = "select * from acornuser.child_user where parent_id = '{}' and subAccountId = '{}' ".format(parent_id, sub_account_id)
  164 + select_sub_acc_status = "select * from acornuser.child_user where parent_id = '{}' and subAccountId = '{}' " \
  165 + "and status = {} ".format(parent_id, sub_account_id, status)
  166 + update_set = {'status': status}
  167 + update_where = {'subAccountId': sub_account_id }
  168 + insert_data = {'parent_id':parent_id,
  169 + 'image': Data.SUB_ACC_IMAGE,
  170 + 'name' : '测试sub',
  171 + 'grade_id':'6',
  172 + 'school_id': Data.SUB_ACC_SCHOOL_ID_2,
  173 + 'region_id': Data.SUB_ACC_REGION_ID_2,
  174 + 'status':status,
  175 + 'region_name': Data.SUB_ACC_REGION_NAME_2,
  176 + 'deviceNumber':Data.DEVICE_NUMBER_CUS_BIND,
  177 + 'subAccountId':sub_account_id
  178 + }
  179 +
  180 + if db.select(select_sub_acc_status):
  181 + pass
  182 + elif db.select(select_sub_acc):
  183 + db.update(TABEL_CHILD_USER, update_set, update_where)
  184 + else:
  185 + db.insert(TABEL_CHILD_USER, insert_data)
  186 +
  187 +
  188 + #主账户数据
  189 + select_acc_1 = "select * from acornuser.acorn_user_status where userId = '{}' and deviceNumber = '{}' " \
  190 + .format(parent_id, deviceNumber)
  191 + if status == 1: # if child status == 1, parent status should be 0
  192 + select_acc_2 = "select * from acornuser.acorn_user_status where userId = '{}' and deviceNumber = '{}' and status = 0 " \
  193 + .format(parent_id, deviceNumber)
  194 +
  195 + insert_data_parent_0 = {'userId': parent_id,
  196 + 'status': 0,
  197 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND
  198 + }
  199 + set_0 = {'status': 0}
  200 + where = {'userId': parent_id,
  201 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND
  202 + }
  203 +
  204 + if db.select(select_acc_2):
  205 + pass
  206 + elif db.select(select_acc_1):
  207 + db.update("acornuser.acorn_user_status", set_0, where)
  208 + else:
  209 + db.insert("acornuser.acorn_user_status", insert_data_parent_0)
  210 + else: # if child status == 0, parent status should be 1
  211 + select_acc_3 = "select * from acornuser.acorn_user_status where userId = '{}' and deviceNumber = '{}' and status = 1 " \
  212 + .format(parent_id, deviceNumber)
  213 + set_1 = {'status': 1}
  214 + where = {'userId': parent_id,
  215 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND
  216 + }
  217 + insert_data_parent_1 = {'userId': parent_id,
  218 + 'status': 1,
  219 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND
  220 + }
  221 + if db.select(select_acc_3):
  222 + pass
  223 + elif db.select(select_acc_1):
  224 + db.update("acornuser.acorn_user_status", set_1, where)
  225 + else:
  226 + db.insert("acornuser.acorn_user_status", insert_data_parent_1)
  227 +
  228 +
  229 +
  230 +
  231 +# 删除对应的parentID的所有数据 便于验证添加成功
  232 +def pre_AddSubAccount(parent_Id):
  233 + where_data = {'parent_Id': parent_Id}
  234 + set_data = {'parent_Id': calendar.timegm(time.gmtime())}
  235 + if db.select_('acornuser.child_user', where_data):
  236 + db.update('acornuser.child_user', set_data, where_data)
  237 +
  238 +
  239 +#检查signature存在
  240 +def checkSignatureExists(userId, type):
  241 + #主账户
  242 + if type == 1:
  243 + sql_1 = 'select * from acornuser.acorn_user_extra where user_id = {} '.format(userId)
  244 + sql_2 = 'select * from acornuser.acorn_user_extra where user_id = {} and signature is not NULL'.format(userId)
  245 +
  246 + if db.select(sql_2):
  247 + pass
  248 + elif db.select(sql_1):
  249 + set = {'signature': '聪明的波利'}
  250 + where = {'user_id': userId}
  251 + db.update('acornuser.acorn_user_extra', set, where)
  252 + else:
  253 + insert = {'user_id': userId, 'signature': '聪明的波利'}
  254 + db.insert('acornuser.acorn_user_extra', insert)
  255 +
  256 + # 子账户
  257 + if type == 2:
  258 + sql_1 = "select * from acornuser.subAccount_user_extra where sub_account_id = '{}' ".format(userId)
  259 + sql_2 = "select * from acornuser.subAccount_user_extra where sub_account_id = '{}'and signature is not NULL".format(userId)
  260 +
  261 + if db.select(sql_2):
  262 + pass
  263 + elif db.select(sql_1):
  264 + set = {'signature': '聪明的波利 sub~'}
  265 + where = {'sub_account_id': userId}
  266 + db.update('acornuser.subAccount_user_extra', set, where)
  267 + else:
  268 + insert = {'sub_account_id': userId, 'signature': '聪明的波利 sub~'}
  269 + db.insert('acornuser.subAccount_user_extra', insert)
  270 +
  271 +
  272 +## *********************************************************************************************************************
  273 +## 教材版本
  274 +## *********************************************************************************************************************
  275 +
  276 +# 用户版本信息
  277 +def pre_GetUserPressInfo(userId):
  278 + set = {'chinese': '北京师范大学出版社'}
  279 + where = {'user_id': userId}
  280 + db.update('acornuser.user_press', set, where)
  281 +
  282 +
  283 +## *********************************************************************************************************************
  284 +## 登录注册
  285 +## *********************************************************************************************************************
  286 +# 检查用户注册信息是否完整
  287 +def pre_register_extrainfo_check(user_id, complete):
  288 + #不完整
  289 + if complete == False:
  290 + sql = "select * from acornuser.ozing_student where user_id = {} and region_id is NULL and school_id is NULL".format(user_id)
  291 + if db.select(sql):
  292 + pass
  293 + else:
  294 + update_sql = "update acornuser.ozing_student set region_id = NULL, school_id = NULL where user_id = {}".format(user_id)
  295 + db.update_(update_sql)
  296 +
  297 + # 完整
  298 + else:
  299 + sql = "select * from acornuser.ozing_student where user_id = {} and region_id is not NULL and school_id is not NULL".format(
  300 + user_id)
  301 + if db.select(sql):
  302 + pass
  303 + else:
  304 + set = {'region_id': '140600', 'school_id': '496299', 'region_name':'江苏苏州吴中'}
  305 + where = {'user_id': user_id}
  306 + db.update('acornuser.ozing_student', set, where)
  307 +
  308 +## *********************************************************************************************************************
  309 +## 最近观看视频
  310 +## *********************************************************************************************************************
  311 +def pre_getRecentVideo(user_id, status): # status=0 novideo, status=1 has video
  312 + where = {'user_id': user_id}
  313 + if status == 0 :
  314 + set = {'user_id': str(time.time())}
  315 + if db.select_('acornuser.user_video_watch', where):
  316 + db.update('acornuser.user_video_watch', set, where)
  317 +
  318 + if status == 1 :
  319 + if db.select_('acornuser.user_video_watch', where):
  320 + pass
  321 + else:
  322 + insert_data = {'user_id': user_id,
  323 + 'data_id': 486600,
  324 + 'data_name':'人教7上_1 春_程诗尧.mpc',
  325 + 'play_online_url':'http://fd.xuexiao100.com/mp4/黄冈视频/初中语文/7年级上 人民教育出版社_2017版/人教7上_1 春_程诗尧.mp4?k=e8f8a7429a42aff00cb96faa6f48821e',
  326 + 'cover_url': 'http://hjxprodbucket.oss.aliyuncs.com/static/upload/boss_api/announcement/2017-10-18/34cfe338-2305-4aa0-96d8-c952be4dd800.jpg',
  327 + 'app_unique_name': 'famous-teacher',
  328 + 'created_time': '2017-12-27 14:52:08',
  329 + 'modified_time': '2017-12-27 14:52:08'
  330 + }
  331 + db.insert('acornuser.user_video_watch', insert_data)
  332 +
  333 +## *********************************************************************************************************************
  334 +## 手机号重复验证
  335 +## *********************************************************************************************************************
  336 +def pre_phoneUsedCheck(phone, used): #used = True :used
  337 + where = {'username': phone}
  338 + if used:
  339 + if db.select_('acornuser.acorn_user', where):
  340 + pass
  341 + else:
  342 + max_id = db.select('select max(id) from acornuser.acorn_user')[0]['max(id)']
  343 + update_where = {'id': max_id - 150}
  344 + count = db.select_('acornuser.acorn_user', update_where)
  345 +
  346 + db.update('acornuser.acorn_user', where, update_where)
  347 + else:
  348 + if db.select_('acornuser.acorn_user', where):
  349 + update_set = {'username': str(time.time())}
  350 + db.update('acornuser.acorn_user', update_set, where)
  351 +
  352 +
  353 +## *********************************************************************************************************************
  354 +## 家长控制 密码
  355 +## *********************************************************************************************************************
  356 +def get_parentSpace_password(device_number):
  357 + sql = "select password from acornuser.parents_space_pass where deviceNumber = '{}'".format(device_number)
  358 + result = db.select(sql)
  359 + if result:
  360 + return result[0]['password']
  361 + else:
  362 + return '123456'
  363 +
  364 +
  365 +## *********************************************************************************************************************
  366 +## 家长控制 app使用统计
  367 +## *********************************************************************************************************************
  368 +# "now" format: timestamp , create app using data about this week, month, year, last year(according 'now' time)
  369 +def create_app_use_record(now, user_id, device_number):
  370 + today = date.fromtimestamp(now)
  371 + year_start_time = int(str(time.mktime(date(today.year, 1, 1).timetuple())).split('.')[0])
  372 + where_equal = {'user_id':user_id, 'device_number':device_number}
  373 + where_unequal = ' time_end > {} '.format(year_start_time)
  374 + if db.select_('analytics.app_record', where_equal, where_unequal): # data existing
  375 + update_data = "update analytics.app_record set device_number = '{}' where user_id = '{}' and device_number = '{}' " \
  376 + .format(str(time.time()), user_id, device_number)
  377 + db.update_(update_data)
  378 + study_apps = [{'app_name':'百度英语资料大全', 'app_pid':'com.sailang.EnglishBook','category_id':'25', 'source_id':'1', \
  379 + 'time_spent': 100, 'user_id': user_id, 'device_number': device_number},
  380 + {'app_name': '开心大学士', 'app_pid': 'com.ksense.study','category_id':'26', 'source_id':'1', \
  381 + 'time_spent': 200, 'user_id': user_id, 'device_number': device_number},
  382 + {'app_name': '驾考宝典', 'app_pid': 'com.handsgo.jiakao.android' ,'category_id':'27', 'source_id':'1', \
  383 + 'time_spent': 300, 'user_id': user_id, 'device_number': device_number},
  384 + {'app_name': '我爱汉字', 'app_pid': 'com.cronlygames.hanzi' ,'category_id':'28', 'source_id':'1',\
  385 + 'time_spent': 400, 'user_id': user_id, 'device_number': device_number},
  386 + {'app_name': '拖拖乐3', 'app_pid': 'cn.com.wiisoft.tuotuo' ,'category_id':'57', 'source_id':'1', \
  387 + 'time_spent': 100, 'user_id': user_id, 'device_number': device_number},
  388 + {'app_name': '幼儿数字算数学习', 'app_pid': 'com.syhrobert1991.infantlearning' ,'category_id':'25', \
  389 + 'source_id':'1', 'time_spent': 200, 'user_id': user_id, 'device_number': device_number},
  390 + {'app_name': '轻松背单词之初中英语', 'app_pid': 'petpestzx.wordroid.model' ,'category_id':'26', \
  391 + 'source_id':'2', 'time_spent': 300, 'user_id': user_id, 'device_number': device_number},
  392 + {'app_name': '有谱-爱学习(数理化)', 'app_pid': 'com.emingren.youpu' ,'category_id':'27', 'source_id':'2', \
  393 + 'time_spent': 400, 'user_id': user_id, 'device_number': device_number},
  394 + {'app_name': '疯狂音标', 'app_pid': 'com.neo.crazyphonetic' ,'category_id':'28', 'source_id':'2', \
  395 + 'time_spent': 100, 'user_id': user_id, 'device_number': device_number},
  396 + {'app_name': '互动作业V3.18.6', 'app_pid': 'com.v.study' ,'category_id':'57', 'source_id':'2', \
  397 + 'time_spent': 250, 'user_id': user_id, 'device_number': device_number},
  398 + {'app_name': '发音背单词', 'app_pid': 'org.liberty.android.fantastischmemo' ,'category_id':'57', \
  399 + 'source_id':'2', 'time_spent': 350, 'user_id': user_id, 'device_number': device_number},
  400 + {'app_name': '语文100', 'app_pid': 'com.kk.kkyuwen' ,'category_id':'57', 'source_id':'2', \
  401 + 'time_spent': 450, 'user_id': user_id, 'device_number': device_number}
  402 + ]
  403 +
  404 + game_apps = [{'app_name': '小伴龙新', 'app_pid': 'com.xiaobanlong.main' ,'category_id':'37', 'source_id':'1',\
  405 + 'time_spent': 100, 'user_id': user_id, 'device_number': device_number},
  406 + {'app_name': '三国群英传', 'app_pid': 'com.tencent.tmgp.sgqyz' ,'category_id':'38', 'source_id':'1', \
  407 + 'time_spent': 200, 'user_id': user_id, 'device_number': device_number},
  408 + {'app_name': '童言童语', 'app_pid': 'com.lingshi.kids' ,'category_id':'39', 'source_id':'2', \
  409 + 'time_spent': 300, 'user_id': user_id, 'device_number': device_number},
  410 + {'app_name': '从前啊', 'app_pid': 'com.mojie.longlongago' ,'category_id':'37', 'source_id':'2', \
  411 + 'time_spent': 400, 'user_id': user_id, 'device_number': device_number},
  412 + {'app_name': '永恒纪元', 'app_pid': 'com.m37.dtszj.uc' ,'category_id':'38', 'source_id':'2', \
  413 + 'time_spent': 100, 'user_id': user_id, 'device_number': device_number},
  414 + {'app_name': '我的世界新', 'app_pid': 'com.netease.mc.aligames' ,'category_id':'39', 'source_id':'2', \
  415 + 'time_spent': 200, 'user_id': user_id, 'device_number': device_number},
  416 + {'app_name': '球球大作战', 'app_pid': 'com.ztgame.bob', 'category_id': '37', 'source_id': '2', \
  417 + 'time_spent': 300, 'user_id': user_id, 'device_number': device_number},
  418 + {'app_name': '葫芦侠我的世界', 'app_pid': 'com.huluxia.mctool', 'category_id': '38', 'source_id': '2',\
  419 + 'time_spent': 400, 'user_id': user_id, 'device_number': device_number},
  420 + {'app_name': 'QQ游戏V6.8.7', 'app_pid': 'com.tencent.qqgame', 'category_id': '39', 'source_id': '2',\
  421 + 'time_spent': 100, 'user_id': user_id, 'device_number': device_number},
  422 + {'app_name': '99围棋最新', 'app_pid': 'com.r99weiqi.dvd', 'category_id': '37', 'source_id': '2', \
  423 + 'time_spent': 50, 'user_id': user_id, 'device_number': device_number},
  424 + {'app_name': '凯蒂环球之旅', 'app_pid': 'com.tencent.HelloKitty', 'category_id': '37', 'source_id': '2',\
  425 + 'time_spent': 150, 'user_id': user_id, 'device_number': device_number},
  426 + {'app_name': '贪吃蛇大作战', 'app_pid': 'com.wepie.snake.qihoo', 'category_id': '100', 'source_id': '2',\
  427 + 'time_spent': 250, 'user_id': user_id, 'device_number': device_number}
  428 + ]
  429 +
  430 + # get date of the last 7 days(include today)
  431 + days = [] # the day should insert app records
  432 + if today.month == 1: # the first month of this year
  433 + if today.day <= 7: # when today < 7th day of this month , 7 records chould cover this week ,this month, this year
  434 + for i in range(0, 7):
  435 + day_delta = timedelta(days=i)
  436 + days.append(str(time.mktime((today - day_delta).timetuple())).split('.')[0])
  437 + else: # when today > 7th day of this month , should create 8 records to cover this week ,this month, this year
  438 + for i in range(0, 8):
  439 + day_delta = timedelta(days=i)
  440 + days.append(str(time.mktime((today - day_delta).timetuple())).split('.')[0])
  441 + else: # create january data to cover this year
  442 + for i in range(0, 8):
  443 + day_delta = timedelta(days=i)
  444 + days.append(str(time.mktime((today - day_delta).timetuple())).split('.')[0])
  445 + days.append(str(time.mktime(date(today.year, 1, 1).timetuple())).split('.')[0])
  446 +
  447 +
  448 + app_rec_to_insert = []
  449 + for day in days:
  450 + for i in range(0, 12):
  451 + time_end_1 = {'time_end': int(day) + 28800 + i * 1000, 'id':str(uuid.uuid4()).replace('-', '')}
  452 + time_end_2 = {'time_end': int(day) + 29800 + i * 500, 'id':str(uuid.uuid4()).replace('-', '')}
  453 + study_app = study_apps[i].copy()
  454 + study_app.update(time_end_1)
  455 + game_app = game_apps[i].copy()
  456 + game_app.update(time_end_2)
  457 + app_rec_to_insert.append(study_app)
  458 + app_rec_to_insert.append(game_app)
  459 +
  460 + for rec in app_rec_to_insert:
  461 + db.insert('analytics.app_record', rec)
  462 +
  463 +
  464 +
  465 +
  466 +
  467 +
  468 +
  469 +
  470 +
  471 +
  472 +
  473 +
  474 +
  475 +
  476 +
  477 +
  478 +
  479 +
  480 +
  481 +
  482 +
  483 +
  484 +
... ...
data_fixture/mysql_db.py
... ... @@ -0,0 +1,87 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +import configparser
  5 +import pymysql.cursors
  6 +import os
  7 +
  8 +from . import config_data as Data
  9 +#
  10 +# base_dir = str(os.path.dirname(os.path.dirname(__file__)))
  11 +# config_dir = base_dir.replace('\\', '/')
  12 +# config_path = config_dir + '/config.ini'
  13 +#
  14 +# cp = configparser.ConfigParser()
  15 +# cp.read(config_path)
  16 +# DB_host = cp.get('mysqlconf', 'host')
  17 +# DB_port = cp.get('mysqlconf', 'port')
  18 +# DB_username = cp.get('mysqlconf', 'user')
  19 +# DB_password = cp.get('mysqlconf', 'password')
  20 +# DB_dbName = cp.get('mysqlconf', 'db_name')
  21 +
  22 +
  23 +class DB:
  24 +
  25 + def __init__ (self):
  26 + try:
  27 + self.connection = pymysql.connect(host=Data.DB_HOST,
  28 + port=Data.DB_PORT,
  29 + user=Data.DB_USERNAME,
  30 + password=Data.DB_PASSWORD,
  31 + charset='utf8mb4',
  32 + cursorclass=pymysql.cursors.DictCursor
  33 + )
  34 + except pymysql.err.OperationalError as e:
  35 + print ("MySql error %d: %s" % (e.args[0], e.args[1]))
  36 +
  37 + def select(self, sql):
  38 + with self.connection.cursor() as cursor:
  39 + if cursor.execute(sql):
  40 + return cursor.fetchall()
  41 + else:
  42 + return None
  43 +
  44 + def select_(self, tabel_name, where_data_equal, where_data_unequal = None ):
  45 + sql_where = ' and '.join("{} = '{}' ".format(key, value) for (key, value) in where_data_equal.items())
  46 + if where_data_unequal:
  47 + sql_where = sql_where + ' and ' + where_data_unequal
  48 + sql = 'select count(1) from ' + tabel_name + ' where ' + sql_where
  49 + with self.connection.cursor() as cursor:
  50 + cursor.execute(sql)
  51 + result = cursor.fetchone()
  52 + return result['count(1)']
  53 +
  54 +
  55 + def insert(self, table_name, table_data):
  56 + for key in table_data:
  57 + table_data[key] = " '" + str(table_data[key]) + "'"
  58 + key = ','.join(table_data.keys())
  59 + value = ','.join(table_data.values())
  60 + real_sql = 'INSERT INTO ' + table_name + " (" + key + " ) VALUES ( "\
  61 + + value + " )"
  62 + with self.connection.cursor() as cursor:
  63 + cursor.execute(real_sql)
  64 + self.connection.commit()
  65 +
  66 + def update(self, table_name, set_data, where_data):
  67 + sql_set = ','.join("{}='{}'".format(key, value) for (key, value) in set_data.items())
  68 + sql_where = ' and '.join("{}='{}'".format(key, value) for (key, value) in where_data.items())
  69 + print(sql_set, sql_where)
  70 + real_sql = "UPDATE " + table_name + " SET " + sql_set + " WHERE " + sql_where
  71 +
  72 + with self.connection.cursor() as cursor:
  73 + cursor.execute(real_sql)
  74 + self.connection.commit()
  75 +
  76 +
  77 + def update_(self, sql):
  78 + with self.connection.cursor() as cursor:
  79 + cursor.execute(sql)
  80 + self.connection.commit()
  81 +
  82 +
  83 + def close(self):
  84 + self.connection.close()
  85 +
  86 +
  87 +
... ...
data_fixture/test_verify.py
... ... @@ -0,0 +1,11 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +from data_fixture.mysql_db import DB
  5 +from data_fixture import config_data as Data
  6 +from datetime import datetime
  7 +import time
  8 +from data_fixture.config_data import HOST_BOSS
  9 +import requests
  10 +
  11 +
... ...
report/test_report.html
... ... @@ -0,0 +1,303 @@
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  3 +<html xmlns="http://www.w3.org/1999/xhtml">
  4 +<head>
  5 + <title>Unit Test Report</title>
  6 + <meta name="generator" content="HTMLTestRunner 0.8.2"/>
  7 + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  8 +
  9 +<style type="text/css" media="screen">
  10 +body { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; }
  11 +table { font-size: 100%; }
  12 +pre { }
  13 +
  14 +/* -- heading ---------------------------------------------------------------------- */
  15 +h1 {
  16 + font-size: 16pt;
  17 + color: gray;
  18 +}
  19 +.heading {
  20 + margin-top: 0ex;
  21 + margin-bottom: 1ex;
  22 +}
  23 +
  24 +.heading .attribute {
  25 + margin-top: 1ex;
  26 + margin-bottom: 0;
  27 +}
  28 +
  29 +.heading .description {
  30 + margin-top: 4ex;
  31 + margin-bottom: 6ex;
  32 +}
  33 +
  34 +/* -- css div popup ------------------------------------------------------------------------ */
  35 +a.popup_link {
  36 +}
  37 +
  38 +a.popup_link:hover {
  39 + color: red;
  40 +}
  41 +
  42 +.popup_window {
  43 + display: none;
  44 + position: relative;
  45 + left: 0px;
  46 + top: 0px;
  47 + /*border: solid #627173 1px; */
  48 + padding: 10px;
  49 + background-color: #E6E6D6;
  50 + font-family: "Lucida Console", "Courier New", Courier, monospace;
  51 + text-align: left;
  52 + font-size: 8pt;
  53 + width: 500px;
  54 +}
  55 +
  56 +}
  57 +/* -- report ------------------------------------------------------------------------ */
  58 +#show_detail_line {
  59 + margin-top: 3ex;
  60 + margin-bottom: 1ex;
  61 +}
  62 +#result_table {
  63 + width: 80%;
  64 + border-collapse: collapse;
  65 + border: 1px solid #777;
  66 +}
  67 +#header_row {
  68 + font-weight: bold;
  69 + color: white;
  70 + background-color: #777;
  71 +}
  72 +#result_table td {
  73 + border: 1px solid #777;
  74 + padding: 2px;
  75 +}
  76 +#total_row { font-weight: bold; }
  77 +.passClass { background-color: #6c6; }
  78 +.failClass { background-color: #c60; }
  79 +.errorClass { background-color: #c00; }
  80 +.passCase { color: #6c6; }
  81 +.failCase { color: #c60; font-weight: bold; }
  82 +.errorCase { color: #c00; font-weight: bold; }
  83 +.hiddenRow { display: none; }
  84 +.testcase { margin-left: 2em; }
  85 +
  86 +
  87 +/* -- ending ---------------------------------------------------------------------- */
  88 +#ending {
  89 +}
  90 +
  91 +</style>
  92 +
  93 +</head>
  94 +<body>
  95 +<script language="javascript" type="text/javascript"><!--
  96 +output_list = Array();
  97 +
  98 +/* level - 0:Summary; 1:Failed; 2:All */
  99 +function showCase(level) {
  100 + trs = document.getElementsByTagName("tr");
  101 + for (var i = 0; i < trs.length; i++) {
  102 + tr = trs[i];
  103 + id = tr.id;
  104 + if (id.substr(0,2) == 'ft') {
  105 + if (level < 1) {
  106 + tr.className = 'hiddenRow';
  107 + }
  108 + else {
  109 + tr.className = '';
  110 + }
  111 + }
  112 + if (id.substr(0,2) == 'pt') {
  113 + if (level > 1) {
  114 + tr.className = '';
  115 + }
  116 + else {
  117 + tr.className = 'hiddenRow';
  118 + }
  119 + }
  120 + }
  121 +}
  122 +
  123 +
  124 +function showClassDetail(cid, count) {
  125 + var id_list = Array(count);
  126 + var toHide = 1;
  127 + for (var i = 0; i < count; i++) {
  128 + tid0 = 't' + cid.substr(1) + '.' + (i+1);
  129 + tid = 'f' + tid0;
  130 + tr = document.getElementById(tid);
  131 + if (!tr) {
  132 + tid = 'p' + tid0;
  133 + tr = document.getElementById(tid);
  134 + }
  135 + id_list[i] = tid;
  136 + if (tr.className) {
  137 + toHide = 0;
  138 + }
  139 + }
  140 + for (var i = 0; i < count; i++) {
  141 + tid = id_list[i];
  142 + if (toHide) {
  143 + document.getElementById('div_'+tid).style.display = 'none'
  144 + document.getElementById(tid).className = 'hiddenRow';
  145 + }
  146 + else {
  147 + document.getElementById(tid).className = '';
  148 + }
  149 + }
  150 +}
  151 +
  152 +
  153 +function showTestDetail(div_id){
  154 + var details_div = document.getElementById(div_id)
  155 + var displayState = details_div.style.display
  156 + // alert(displayState)
  157 + if (displayState != 'block' ) {
  158 + displayState = 'block'
  159 + details_div.style.display = 'block'
  160 + }
  161 + else {
  162 + details_div.style.display = 'none'
  163 + }
  164 +}
  165 +
  166 +
  167 +function html_escape(s) {
  168 + s = s.replace(/&/g,'&amp;');
  169 + s = s.replace(/</g,'&lt;');
  170 + s = s.replace(/>/g,'&gt;');
  171 + return s;
  172 +}
  173 +
  174 +/* obsoleted by detail in <div>
  175 +function showOutput(id, name) {
  176 + var w = window.open("", //url
  177 + name,
  178 + "resizable,scrollbars,status,width=800,height=450");
  179 + d = w.document;
  180 + d.write("<pre>");
  181 + d.write(html_escape(output_list[id]));
  182 + d.write("\n");
  183 + d.write("<a href='javascript:window.close()'>close</a>\n");
  184 + d.write("</pre>\n");
  185 + d.close();
  186 +}
  187 +*/
  188 +--></script>
  189 +
  190 +<div class='heading'>
  191 +<h1>Unit Test Report</h1>
  192 +<p class='attribute'><strong>Start Time:</strong> 2018-01-08 18:05:31</p>
  193 +<p class='attribute'><strong>Duration:</strong> 0:00:02.208474</p>
  194 +<p class='attribute'><strong>Status:</strong> Pass 6</p>
  195 +
  196 +<p class='description'></p>
  197 +</div>
  198 +
  199 +
  200 +
  201 +<p id='show_detail_line'>Show
  202 +<a href='javascript:showCase(0)'>Summary</a>
  203 +<a href='javascript:showCase(1)'>Failed</a>
  204 +<a href='javascript:showCase(2)'>All</a>
  205 +</p>
  206 +<table id='result_table'>
  207 +<colgroup>
  208 +<col align='left' />
  209 +<col align='right' />
  210 +<col align='right' />
  211 +<col align='right' />
  212 +<col align='right' />
  213 +<col align='right' />
  214 +</colgroup>
  215 +<tr id='header_row'>
  216 + <td>Test Group/Test case</td>
  217 + <td>Count</td>
  218 + <td>Pass</td>
  219 + <td>Fail</td>
  220 + <td>Error</td>
  221 + <td>View</td>
  222 +</tr>
  223 +
  224 +<tr class='passClass'>
  225 + <td>subject_sync.ClassNameVideo</td>
  226 + <td>2</td>
  227 + <td>2</td>
  228 + <td>0</td>
  229 + <td>0</td>
  230 + <td><a href="javascript:showClassDetail('c1',2)">Detail</a></td>
  231 +</tr>
  232 +
  233 +<tr id='pt1.1' class='hiddenRow'>
  234 + <td class='none'><div class='testcase'>test_ClassNameVideo_noData</div></td>
  235 + <td colspan='5' align='center'>pass</td>
  236 +</tr>
  237 +
  238 +<tr id='pt1.2' class='hiddenRow'>
  239 + <td class='none'><div class='testcase'>test_ClassNameVideo_success</div></td>
  240 + <td colspan='5' align='center'>pass</td>
  241 +</tr>
  242 +
  243 +<tr class='passClass'>
  244 + <td>subject_sync.ConsolidationExercise</td>
  245 + <td>1</td>
  246 + <td>1</td>
  247 + <td>0</td>
  248 + <td>0</td>
  249 + <td><a href="javascript:showClassDetail('c2',1)">Detail</a></td>
  250 +</tr>
  251 +
  252 +<tr id='pt2.1' class='hiddenRow'>
  253 + <td class='none'><div class='testcase'>test_ConsolidationExercise_success</div></td>
  254 + <td colspan='5' align='center'>pass</td>
  255 +</tr>
  256 +
  257 +<tr class='passClass'>
  258 + <td>subject_sync.PointVideo</td>
  259 + <td>2</td>
  260 + <td>2</td>
  261 + <td>0</td>
  262 + <td>0</td>
  263 + <td><a href="javascript:showClassDetail('c3',2)">Detail</a></td>
  264 +</tr>
  265 +
  266 +<tr id='pt3.1' class='hiddenRow'>
  267 + <td class='none'><div class='testcase'>test_PointVideo_noData</div></td>
  268 + <td colspan='5' align='center'>pass</td>
  269 +</tr>
  270 +
  271 +<tr id='pt3.2' class='hiddenRow'>
  272 + <td class='none'><div class='testcase'>test_PointVideo_success</div></td>
  273 + <td colspan='5' align='center'>pass</td>
  274 +</tr>
  275 +
  276 +<tr class='passClass'>
  277 + <td>subject_sync.SubjectTest</td>
  278 + <td>1</td>
  279 + <td>1</td>
  280 + <td>0</td>
  281 + <td>0</td>
  282 + <td><a href="javascript:showClassDetail('c4',1)">Detail</a></td>
  283 +</tr>
  284 +
  285 +<tr id='pt4.1' class='hiddenRow'>
  286 + <td class='none'><div class='testcase'>test_SubjectTest_success</div></td>
  287 + <td colspan='5' align='center'>pass</td>
  288 +</tr>
  289 +
  290 +<tr id='total_row'>
  291 + <td>Total</td>
  292 + <td>6</td>
  293 + <td>6</td>
  294 + <td>0</td>
  295 + <td>0</td>
  296 + <td>&nbsp;</td>
  297 +</tr>
  298 +</table>
  299 +
  300 +<div id='ending'>&nbsp;</div>
  301 +
  302 +</body>
  303 +</html>
... ...
... ... @@ -0,0 +1,31 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +import unittest
  5 +import HTMLTestRunner
  6 +from test_cases import sub_account
  7 +
  8 +from test_suites import test_elecCard
  9 +from test_cases import register
  10 +from test_cases import personal_info
  11 +from test_cases import parent_space
  12 +from test_cases import app_record_statistic
  13 +from test_cases import subject_sync
  14 +
  15 +start_dir = './test_cases'
  16 +suite_run = unittest.TestSuite()
  17 +suite_run = unittest.defaultTestLoader.discover(start_dir=start_dir, pattern='subject_sync.py')
  18 +
  19 +
  20 +#suite_run.addTest(app_record_statistic.AppRecordEveryday('test_getAppRecordEveryday_success'))
  21 +
  22 +#suite_run.addTest(app_record_statistic.AppRecordReset('test_AppRecordReset_success'))
  23 +#suite_run.addTest(parent_space.ParentSpaceNewPassword('test_ParentSpaceNewPassword_success'))
  24 +#suite_run.addTest(parent_space.ParentSpaceNewPassword('test_ParentSpaceNewPassword_authCodeError'))
  25 +runner = HTMLTestRunner.HTMLTestRunner(outputdir='report')
  26 +
  27 +if __name__ == '__main__':
  28 +
  29 + runner.run(suite_run)
  30 +
  31 +
... ...
No preview for this file type

216 KB

216 KB

test_cases/__pycache__/app_record_statistic.cpython-36.pyc
No preview for this file type
test_cases/__pycache__/debugggggg.cpython-36.pyc
No preview for this file type
test_cases/__pycache__/eleccard_check.cpython-36.pyc
No preview for this file type
test_cases/__pycache__/eleccard_setUp.cpython-36.pyc
No preview for this file type
test_cases/__pycache__/parent_space.cpython-36.pyc
No preview for this file type
test_cases/__pycache__/personal_info.cpython-36.pyc
No preview for this file type
test_cases/__pycache__/press.cpython-36.pyc
No preview for this file type
test_cases/__pycache__/region_grade_school.cpython-36.pyc
No preview for this file type
test_cases/__pycache__/register.cpython-36.pyc
No preview for this file type
test_cases/__pycache__/sub_account.cpython-36.pyc
No preview for this file type
test_cases/__pycache__/subject_sync.cpython-36.pyc
No preview for this file type
test_cases/app_record_statistic.py
... ... @@ -0,0 +1,259 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +import unittest
  5 +import requests
  6 +from data_fixture.config_data import HOST_BOSS, HOST_STA
  7 +from data_fixture import create_testdata as CreateTestData
  8 +from data_fixture import config_data as Data
  9 +from data_fixture.mysql_db import DB
  10 +import time
  11 +from datetime import date
  12 +
  13 +db_test = DB()
  14 +
  15 +
  16 +#提交app使用记录
  17 +class AppRecordSave(unittest.TestCase):
  18 + def setUp(self):
  19 + self.base_url = HOST_STA + '/app/record/save'
  20 +
  21 + def tearDown(self):
  22 + print(self.result)
  23 +
  24 + def test_AppRecordSave_success(self):
  25 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  26 + postData = {'appName': '学科同步',
  27 + 'appPid': 'com.hjx.synsubject',
  28 + 'timeSpent': 1088,
  29 + 'userId': Data.USER_ID,
  30 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND}
  31 + send_time = str(time.time()).split('.')[0]
  32 + r = requests.post(self.base_url, headers=headers, data=postData)
  33 + self.result = r.json()
  34 + self.assertEqual(self.result['status'], 1, 'test_AppRecordSave_success Error')
  35 + where = {'app_name': '学科同步',
  36 + 'app_pid': 'com.hjx.synsubject',
  37 + 'time_spent': 1088,
  38 + 'user_id': Data.USER_ID,
  39 + 'device_number': Data.DEVICE_NUMBER_CUS_BIND}
  40 + where_2 = " time_end >= {} ".format(send_time)
  41 + self.assertTrue(db_test.select_('analytics.app_record', where, where_2) >= 1, 'test_AppRecordSave_success data Error')
  42 +
  43 +
  44 +# 按时间段获取app使用统计
  45 +class AppRecordStats(unittest.TestCase):
  46 + def setUp(self):
  47 + self.base_url = HOST_STA + '/app/record/stats'
  48 + now = int(str(time.time()).split('.')[0])
  49 + CreateTestData.create_app_use_record(now, Data.USER_ID, Data.DEVICE_NUMBER_CUS_BIND)
  50 +
  51 + def tearDown(self):
  52 + pass
  53 +
  54 + def test_getAppRecordStats_all_success(self):
  55 + getData = {'userId': Data.USER_ID,
  56 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND,
  57 + 'type': 'all'}
  58 + r = requests.get(self.base_url, params=getData)
  59 + self.result_1 = r.json()
  60 + self.assertEqual(self.result_1['status'], 1, 'test_getAppRecordStats_all_success Error')
  61 + self.assertTrue(self.result_1['data']['gameTime'] > 0)
  62 + self.assertTrue(self.result_1['data']['studyTime'] > 0)
  63 +
  64 + def test_getAppRecordStats_year_success(self):
  65 + getData = {'userId': Data.USER_ID,
  66 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND,
  67 + 'type': 'year'}
  68 + r = requests.get(self.base_url, params=getData)
  69 + self.result_2 = r.json()
  70 + self.assertEqual(self.result_2['status'], 1, 'test_getAppRecordStats_year_success Error')
  71 + self.assertTrue(self.result_2['data']['gameTime'] > 0)
  72 + self.assertTrue(self.result_2['data']['studyTime'] > 0)
  73 +
  74 + def test_getAppRecordStats_month_success(self):
  75 + getData = {'userId': Data.USER_ID,
  76 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND,
  77 + 'type': 'month'}
  78 + r = requests.get(self.base_url, params=getData)
  79 + self.result_3 = r.json()
  80 + self.assertEqual(self.result_3['status'], 1, 'test_getAppRecordStats_month_success Error')
  81 + self.assertTrue(self.result_3['data']['gameTime'] > 0)
  82 + self.assertTrue(self.result_3['data']['studyTime'] > 0)
  83 +
  84 + def test_getAppRecordStats_week_success(self):
  85 + getData = {'userId': Data.USER_ID,
  86 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND,
  87 + 'type': 'week'}
  88 + r = requests.get(self.base_url, params=getData)
  89 + self.result_4 = r.json()
  90 + self.assertEqual(self.result_4['status'], 1, 'test_getAppRecordStats_week_success Error')
  91 + self.assertTrue(self.result_4['data']['gameTime'] > 0)
  92 + self.assertTrue(self.result_4['data']['studyTime'] > 0)
  93 +
  94 +
  95 +# 获取应用统计top排名
  96 +class AppRecordTop(unittest.TestCase):
  97 + def setUp(self):
  98 + self.base_url = HOST_STA + '/app/record/top'
  99 + now = int(str(time.time()).split('.')[0])
  100 + CreateTestData.create_app_use_record(now, Data.USER_ID, Data.DEVICE_NUMBER_CUS_BIND)
  101 +
  102 + def tearDown(self):
  103 + pass
  104 +
  105 + def test_getAppRecordTop_hjx(self):
  106 + getData = {'userId': Data.USER_ID,
  107 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND,
  108 + 'type': 'hjx'}
  109 + r = requests.get(self.base_url, params=getData)
  110 + self.result_1 = r.json()
  111 + self.assertEqual(self.result_1['status'], 1, 'test_getAppRecordTop_hjx Error')
  112 + self.assertTrue(len(self.result_1['data']) > 0)
  113 +
  114 + def test_getAppRecordTop_other(self):
  115 + getData = {'userId': Data.USER_ID,
  116 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND,
  117 + 'type': 'other'}
  118 + r = requests.get(self.base_url, params=getData)
  119 + self.result_2 = r.json()
  120 + self.assertEqual(self.result_2['status'], 1, 'test_getAppRecordTop_other Error')
  121 + self.assertTrue(len(self.result_2['data']) > 0)
  122 +
  123 +
  124 +# 获取某一天的app统计
  125 +class AppRecordOneday(unittest.TestCase):
  126 + def setUp(self):
  127 + self.base_url = HOST_STA + '/app/record/oneday'
  128 + self.now = int(str(time.time()).split('.')[0])
  129 + CreateTestData.create_app_use_record(self.now, Data.USER_ID, Data.DEVICE_NUMBER_CUS_BIND)
  130 +
  131 + def tearDown(self):
  132 + pass
  133 +
  134 + def test_getAppRecordOneday_success(self):
  135 + getData = {'userId': Data.USER_ID,
  136 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND,
  137 + 'dayTimestamp': str(time.mktime(date.today().timetuple())).split('.')[0]}
  138 + pageNum = 1
  139 + appRecord = []
  140 + while True:
  141 + getData.update({'pageNum':pageNum})
  142 + r = requests.get(self.base_url, params=getData)
  143 + self.result = r.json()
  144 + appRecord = appRecord + self.result['data']
  145 + if len(self.result['data']) == 0:
  146 + break
  147 + pageNum += 1
  148 +
  149 + self.result_1 = r.json()
  150 + self.assertTrue(len(appRecord) == 24)
  151 +
  152 + app_name = appRecord[0]['appName']
  153 + time_spent = appRecord[0]['timeSpentTotal']
  154 + time_end = appRecord[0]['latestTimeEnd']
  155 + source_id = appRecord[0]['sourceId']
  156 + category_id = appRecord[0]['categoryId']
  157 + categoryType = appRecord[0]['categoryType']
  158 + study_category = [25,26,27,28,57]
  159 + game_category = [37,38,39,100]
  160 +
  161 + where_1 = {'app_name':app_name,
  162 + 'time_spent':time_spent,
  163 + 'time_end': time_end,
  164 + 'source_id':source_id,
  165 + 'category_id':category_id,
  166 + 'user_id': Data.USER_ID,
  167 + 'device_number': Data.DEVICE_NUMBER_CUS_BIND
  168 + }
  169 + self.assertEqual(db_test.select_('analytics.app_record', where_1) , 1)
  170 + if category_id in study_category:
  171 + self.assertEqual(categoryType, 1)
  172 + if category_id in game_category:
  173 + self.assertEqual(categoryType, 2)
  174 +
  175 +
  176 +# 获取几天的app统计
  177 +class AppRecordEveryday(unittest.TestCase):
  178 + def setUp(self):
  179 + self.base_url = HOST_STA + '/app/record/everyday'
  180 + self.now = int(str(time.time()).split('.')[0])
  181 + # CreateTestData.create_app_use_record(self.now, Data.USER_ID, Data.DEVICE_NUMBER_CUS_BIND)
  182 + CreateTestData.create_app_use_record(self.now, '7000000054686780', '123456789002')
  183 +
  184 +
  185 + def tearDown(self):
  186 + pass
  187 +
  188 + def test_getAppRecordEveryday_success(self):
  189 + # getData = {'userId': Data.USER_ID,
  190 + # 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND,
  191 + # 'pageNum': 1,
  192 + # 'pageSize': 7}
  193 + getData = {'userId': '7000000054686780',
  194 + 'deviceNumber': '123456789002',
  195 + 'pageNum': 1,
  196 + 'pageSize': 7}
  197 + r = requests.get(self.base_url, params=getData)
  198 + self.result = r.json()
  199 + self.assertEqual(self.result['status'], 1, 'test_getAppRecordEveryday_success Error')
  200 + self.assertEqual(self.result['data'][0]['gameTimeSpent'], 2550)
  201 + self.assertEqual(self.result['data'][0]['studyTimeSpent'], 3150)
  202 +
  203 +
  204 +# 获取当天的app统计
  205 +class AppRecordToday(unittest.TestCase):
  206 + def setUp(self):
  207 + self.base_url = HOST_STA + '/app/record/today'
  208 + self.now = int(str(time.time()).split('.')[0])
  209 + CreateTestData.create_app_use_record(self.now, Data.USER_ID, Data.DEVICE_NUMBER_CUS_BIND)
  210 +
  211 + def tearDown(self):
  212 + pass
  213 +
  214 + def test_AppRecordToday_success(self):
  215 + getData = {'userId': Data.USER_ID,
  216 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND}
  217 + r = requests.get(self.base_url, params=getData)
  218 + self.result = r.json()
  219 + self.assertEqual(self.result['status'], 1, 'AppRecordToday Error')
  220 + self.assertEqual(self.result['data']['gameTimeSpent'], 2550)
  221 + self.assertEqual(self.result['data']['studyTimeSpent'], 3150)
  222 +
  223 +
  224 +#重置数据接口
  225 +class AppRecordReset(unittest.TestCase):
  226 + def setUp(self):
  227 + self.base_url = HOST_STA + '/app/record/reset'
  228 + # create elec card , to get mobile phpne
  229 + self.password = CreateTestData.get_parentSpace_password(Data.DEVICE_NUMBER_CUS_BIND)
  230 + now = int(str(time.time()).split('.')[0])
  231 + CreateTestData.create_app_use_record(now, Data.USER_ID, Data.DEVICE_NUMBER_CUS_BIND)
  232 +
  233 + def tearDown(self):
  234 + pass
  235 +
  236 + def test_AppRecordReset_success(self):
  237 + getData = {'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND,
  238 + 'password': self.password,
  239 + 'userId': Data.USER_ID
  240 + }
  241 + r = requests.post(self.base_url, data=getData)
  242 + self.result = r.json()
  243 + self.assertEqual(self.result['status'], 1, 'test_AppRecordReset_success Error')
  244 + where = {'device_number': Data.DEVICE_NUMBER_CUS_BIND,
  245 + 'user_id': Data.USER_ID
  246 + }
  247 + self.assertEqual(db_test.select_('analytics.app_record', where), 0)
  248 +
  249 + def test_AppRecordReset_passwordError(self):
  250 + getData = {'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND,
  251 + 'userId': Data.USER_ID,
  252 + 'password': self.password + '111'
  253 + }
  254 + r = requests.post(self.base_url, data=getData)
  255 + self.result_2 = r.json()
  256 + self.assertEqual(self.result_2['status'], 1005, 'test_AppRecordReset_passwordError Error')
  257 +
  258 +
  259 +
... ...
test_cases/debugggggg.py
... ... @@ -0,0 +1,118 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +import uuid
  5 +
  6 +def create_app_use_record(user_id, device_number):
  7 +
  8 + study_apps = [{'app_name':"百度英语资料大全", 'app_pid':'com.sailang.EnglishBook','category_id':'25', \
  9 + 'source_id':'1','time_spent': 100, 'user_id': user_id, 'device_number': device_number},
  10 + {'app_name': '开心大学士', 'app_pid': 'com.ksense.study','category_id':'26', 'source_id':'1', \
  11 + 'time_spent': 200, },
  12 + {'app_name': '驾考宝典', 'app_pid': 'com.handsgo.jiakao.android' ,'category_id':'27', 'source_id':'1', \
  13 + 'time_spent': 300 },
  14 + {'app_name': '我爱汉字', 'app_pid': 'com.cronlygames.hanzi' ,'category_id':'28', 'source_id':'1',\
  15 + 'time_spent': 400},
  16 + {'app_name': '拖拖乐3', 'app_pid': 'cn.com.wiisoft.tuotuo' ,'category_id':'57', 'source_id':'1', \
  17 + 'time_spent': 100}]
  18 +
  19 + for j in range(0,2):
  20 + for i in range(0, 2):
  21 + time_end_1 = {'time_end': 28800 + i * 1000, 'id':str(uuid.uuid4()).replace('-', '')}
  22 + study_app = study_apps[i]
  23 + study_app.update(time_end_1)
  24 + mm = study_app
  25 + ll = len(study_app['app_name'])
  26 + ss = study_app['app_name']
  27 +
  28 +
  29 +################################################
  30 +
  31 +def create_app_use_record_2(user_id, device_number):
  32 + study_apps = [{'app_name':'百度英语资料大全', 'app_pid':'com.sailang.EnglishBook','category_id':'25', 'source_id':'1', \
  33 + 'time_spent': 100, 'user_id': user_id, 'device_number': device_number}
  34 + # {'app_name': '开心大学士', 'app_pid': 'com.ksense.study','category_id':'26', 'source_id':'1', \
  35 + # 'time_spent': 200, 'user_id': user_id, 'device_number': device_number},
  36 + # {'app_name': '驾考宝典', 'app_pid': 'com.handsgo.jiakao.android' ,'category_id':'27', 'source_id':'1', \
  37 + # 'time_spent': 300, 'user_id': user_id, 'device_number': device_number},
  38 + # {'app_name': '我爱汉字', 'app_pid': 'com.cronlygames.hanzi' ,'category_id':'28', 'source_id':'1',\
  39 + # 'time_spent': 400, 'user_id': user_id, 'device_number': device_number},
  40 + # {'app_name': '拖拖乐3', 'app_pid': 'cn.com.wiisoft.tuotuo' ,'category_id':'57', 'source_id':'1', \
  41 + # 'time_spent': 100, 'user_id': user_id, 'device_number': device_number},
  42 + # {'app_name': '幼儿数字算数学习', 'app_pid': 'com.syhrobert1991.infantlearning' ,'category_id':'25', \
  43 + # 'source_id':'1', 'time_spent': 200, 'user_id': user_id, 'device_number': device_number},
  44 + # {'app_name': '轻松背单词之初中英语', 'app_pid': 'petpestzx.wordroid.model' ,'category_id':'26', \
  45 + # 'source_id':'2', 'time_spent': 300, 'user_id': user_id, 'device_number': device_number},
  46 + # {'app_name': '有谱-爱学习(数理化)', 'app_pid': 'com.emingren.youpu' ,'category_id':'27', 'source_id':'2', \
  47 + # 'time_spent': 400, 'user_id': user_id, 'device_number': device_number},
  48 + # {'app_name': '疯狂音标', 'app_pid': 'com.neo.crazyphonetic' ,'category_id':'28', 'source_id':'2', \
  49 + # 'time_spent': 100, 'user_id': user_id, 'device_number': device_number},
  50 + # {'app_name': '互动作业V3.18.6', 'app_pid': 'com.v.study' ,'category_id':'57', 'source_id':'2', \
  51 + # 'time_spent': 250, 'user_id': user_id, 'device_number': device_number},
  52 + # {'app_name': '发音背单词', 'app_pid': 'org.liberty.android.fantastischmemo' ,'category_id':'57', \
  53 + # 'source_id':'2', 'time_spent': 350, 'user_id': user_id, 'device_number': device_number},
  54 + # {'app_name': '语文100', 'app_pid': 'com.kk.kkyuwen' ,'category_id':'57', 'source_id':'2', \
  55 + # 'time_spent': 450, 'user_id': user_id, 'device_number': device_number}
  56 + ]
  57 +
  58 + game_apps = [{'app_name': '小伴龙新', 'app_pid': 'com.xiaobanlong.main' ,'category_id':'37', 'source_id':'1',\
  59 + 'time_spent': 100, 'user_id': user_id, 'device_number': device_number},
  60 + {'app_name': '三国群英传', 'app_pid': 'com.tencent.tmgp.sgqyz' ,'category_id':'38', 'source_id':'1', \
  61 + 'time_spent': 200, 'user_id': user_id, 'device_number': device_number},
  62 + {'app_name': '童言童语', 'app_pid': 'com.lingshi.kids' ,'category_id':'39', 'source_id':'2', \
  63 + 'time_spent': 300, 'user_id': user_id, 'device_number': device_number},
  64 + {'app_name': '从前啊', 'app_pid': 'com.mojie.longlongago' ,'category_id':'37', 'source_id':'2', \
  65 + 'time_spent': 400, 'user_id': user_id, 'device_number': device_number},
  66 + {'app_name': '永恒纪元', 'app_pid': 'com.m37.dtszj.uc' ,'category_id':'38', 'source_id':'2', \
  67 + 'time_spent': 100, 'user_id': user_id, 'device_number': device_number},
  68 + {'app_name': '我的世界新', 'app_pid': 'com.netease.mc.aligames' ,'category_id':'39', 'source_id':'2', \
  69 + 'time_spent': 200, 'user_id': user_id, 'device_number': device_number},
  70 + {'app_name': '球球大作战', 'app_pid': 'com.ztgame.bob', 'category_id': '37', 'source_id': '2', \
  71 + 'time_spent': 300, 'user_id': user_id, 'device_number': device_number},
  72 + {'app_name': '葫芦侠我的世界', 'app_pid': 'com.huluxia.mctool', 'category_id': '38', 'source_id': '2',\
  73 + 'time_spent': 400, 'user_id': user_id, 'device_number': device_number},
  74 + {'app_name': 'QQ游戏V6.8.7', 'app_pid': 'com.tencent.qqgame', 'category_id': '39', 'source_id': '2',\
  75 + 'time_spent': 100, 'user_id': user_id, 'device_number': device_number},
  76 + {'app_name': '99围棋最新', 'app_pid': 'com.r99weiqi.dvd', 'category_id': '37', 'source_id': '2', \
  77 + 'time_spent': 50, 'user_id': user_id, 'device_number': device_number},
  78 + {'app_name': '凯蒂环球之旅', 'app_pid': 'com.tencent.HelloKitty', 'category_id': '37', 'source_id': '2',\
  79 + 'time_spent': 150, 'user_id': user_id, 'device_number': device_number},
  80 + {'app_name': '贪吃蛇大作战', 'app_pid': 'com.wepie.snake.qihoo', 'category_id': '100', 'source_id': '2',\
  81 + 'time_spent': 250, 'user_id': user_id, 'device_number': device_number}
  82 + ]
  83 +
  84 + # get date of the last 7 days(include today)
  85 + days = ['111111','222222','333333'] # the day should insert app records
  86 +
  87 + for day in days:
  88 + for i in range(0, 12):
  89 + time_end_1 = {'time_end': int(day) + 28800 + i * 1000, 'id':str(uuid.uuid4()).replace('-', '')}
  90 + time_end_2 = {'time_end': int(day) + 29800 + i * 500, 'id':str(uuid.uuid4()).replace('-', '')}
  91 + #study_apps[i].update(time_end_1)
  92 + #game_apps[i].update(time_end_2)
  93 + study_app = study_apps[0]
  94 + study_app.update(time_end_1)
  95 + ll = len(study_app['app_name'])
  96 + ss = study_app['app_name']
  97 + game_app = game_apps[i]
  98 + game_app.update(time_end_2)
  99 + #db.insert('analytics.app_record_copy', study_app)
  100 + # db.insert('analytics.app_record_copy', game_app)
  101 +
  102 +
  103 +if __name__ == '__main__':
  104 + create_app_use_record_2('556600', 'HJKKJ1223123')
  105 +
  106 +
  107 +
  108 +
  109 +
  110 +
  111 +
  112 +
  113 +
  114 +
  115 +
  116 +
  117 +
  118 +
... ...
test_cases/eleccard_check.py
... ... @@ -0,0 +1,60 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +import logging
  5 +import unittest
  6 +import requests
  7 +from data_fixture.config_data import HOST_BOSS
  8 +from data_fixture import create_testdata as CreateTestData
  9 +from data_fixture import config_data as Data
  10 +
  11 +
  12 +#检查是否需要绑定保卡
  13 +class CheckElecCardBind(unittest.TestCase):
  14 + def setUp(self):
  15 + self.base_url = HOST_BOSS + "/electronicCard/check"
  16 + CreateTestData.pre_elecCard(Data.DEVICE_NUMBER_CUS_BIND, Data.DEVICE_NUMBER_SAM, Data.DEVICE_NUMBER_CUS_UNBIND)
  17 +
  18 + def tearDown(self):
  19 + print(self.result)
  20 +
  21 + # 已绑定 -- 客机
  22 + def test_checkElecCard_customerMachine(self):
  23 + headers = {'Accept': '*/*'}
  24 + getData = {'deviceNumber':Data.DEVICE_NUMBER_CUS_BIND}
  25 + r = requests.get(self.base_url, headers=headers, params=getData)
  26 + self.result = r.json()
  27 + self.assertEqual(self.result['status'], 1, 'checkElecCard_customerMachine Error')
  28 +
  29 + # 样机
  30 + def test_checkElecCard_sampleMachine(self):
  31 + headers = {'Accept': '*/*'}
  32 + getData = {'deviceNumber': Data.DEVICE_NUMBER_SAM}
  33 + r = requests.get(self.base_url, headers=headers, params=getData)
  34 + self.result = r.json()
  35 + self.assertEqual(self.result['status'], 2002, 'checkElecCard_sampleMachine Error')
  36 +
  37 + # 未绑定 -- 客机
  38 + def test_checkElecCard_unbind(self):
  39 + headers = {'Accept': '*/*'}
  40 + getData = {'deviceNumber': Data.DEVICE_NUMBER_CUS_UNBIND}
  41 + r = requests.get(self.base_url, headers=headers, params=getData)
  42 + self.result = r.json()
  43 + self.assertEqual(self.result['status'], 2001, 'checkElecCard_unbind Error')
  44 +
  45 +
  46 +#获取保卡信息
  47 +class GetCardInfo(unittest.TestCase):
  48 + def setUp(self):
  49 + self.base_url = HOST_BOSS + "/electronicCard/info"
  50 + CreateTestData.pre_elecCard(Data.DEVICE_NUMBER_CUS_BIND)
  51 +
  52 + def tearDown(self):
  53 + print(self.result)
  54 +
  55 + def test_getCardInfo_success(self):
  56 + headers = {'Accept': '*/*'}
  57 + getData = {'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND}
  58 + r = requests.get(self.base_url, headers=headers, params=getData)
  59 + self.result = r.json()
  60 + self.assertEqual(self.result['status'], 1, 'getCardInfo Error')
... ...
test_cases/eleccard_setUp.py
... ... @@ -0,0 +1,128 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +import unittest
  5 +import requests
  6 +from data_fixture.config_data import HOST_BOSS, HOST_ADMIN
  7 +from data_fixture import create_testdata as CreateTestData
  8 +from data_fixture import config_data as Data
  9 +
  10 +
  11 +#添加客机保卡
  12 +class SetUpElecCard(unittest.TestCase):
  13 + def setUp(self):
  14 + self.base_url = HOST_BOSS + "/electronicCard/addCustomer"
  15 + CreateTestData.pre_SetUpElecCard()
  16 + def tearDown(self):
  17 + print(self.result)
  18 +
  19 + def test_addElecCard_success(self):
  20 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  21 + postData = {'userId':Data.USER_ID,
  22 + 'customerName':'customer测试',
  23 + 'customerAddress':'田林路487号',
  24 + 'buyAddress':'虹梅路888号',
  25 + 'buyTime':'2017-12-02',
  26 + 'alterSaleCall':'55558888',
  27 + 'productModel':'TEST',
  28 + 'deviceNumber': Data.DEVICE_NUMBER_NEW,
  29 + 'macAddress':Data.MAC_NEW,
  30 + 'customerPhone':Data.USER_PHONE
  31 + }
  32 + r = requests.post(self.base_url, headers=headers, data=postData)
  33 + self.result = r.json()
  34 + self.assertEqual(self.result['status'], 1, 'addElecCard Error')
  35 +
  36 +
  37 +# 置为样机,提交终端信息
  38 +class SetToSample(unittest.TestCase):
  39 +
  40 + def setUp(self):
  41 + self.base_url = HOST_ADMIN + "/admin/elecCard/updateToSample"
  42 + CreateTestData.pre_elecCard(device_cus_bind=Data.DEVICE_NUMBER_TO_SAM)
  43 +
  44 + def tearDown(self):
  45 + pass
  46 +
  47 + def test_updateToSample_success(self):
  48 + headers = {'Content-Type': 'multipart/form-data', 'Accept': 'application/json'}
  49 + postData = {'userId':Data.USER_ID,
  50 + 'deviceNumber':Data.DEVICE_NUMBER_TO_SAM,
  51 + 'terminalAddress':'terminalAddress终端地址',
  52 + 'distributor':'distributor经销商',
  53 + 'saleClerk':'saleClerk销售员',
  54 + 'mobilePhone':'18944444444'
  55 + }
  56 + files = {'photo':('252ED989-0B16-4AB7-81C1-974ABCF6CA11.png','image/png')}
  57 + r = requests.post(self.base_url, headers=headers, files=files, data=postData, allow_redirects=False)
  58 + self.assertEqual(r.status_code, 302, 'updateToSampleError')
  59 +
  60 +
  61 +#置为客机
  62 +class SetToCustomer(unittest.TestCase):
  63 +
  64 + def setUp(self):
  65 + self.base_url = HOST_ADMIN + "/admin/elecCard/updateToCustomer"
  66 + CreateTestData.pre_elecCard(device_sam=Data.DEVICE_NUMBER_TO_CUS)
  67 +
  68 + def tearDown(self):
  69 + pass
  70 +
  71 + def test_updateToCustomer_success(self):
  72 + headers = {'Accept': 'application/json'}
  73 + getData = {'userId': Data.USER_ID, 'deviceNumber': Data.DEVICE_NUMBER_TO_CUS}
  74 + r = requests.get(self.base_url, data=getData, allow_redirects=False)
  75 + self.assertEqual(r.status_code, 302, 'updateToCustomerError')
  76 +
  77 +
  78 +#解绑保卡
  79 +class Unbind(unittest.TestCase):
  80 + def setUp(self):
  81 + self.base_url = HOST_ADMIN + "/admin/elecCard/delete"
  82 + CreateTestData.pre_elecCard(device_cus_bind=Data.DEVICE_NUMBER_UNBIND)
  83 +
  84 + def tearDown(self):
  85 + pass
  86 +
  87 + def test_unbindCard_success(self):
  88 + headers = {'Accept': '*/*'}
  89 + getData = {'deviceNumber': Data.DEVICE_NUMBER_UNBIND}
  90 + r = requests.get(self.base_url, headers=headers, data=getData, allow_redirects=False)
  91 + # self.result = r.json()
  92 + self.assertEqual(r.status_code, 302, 'unbindCardError')
  93 +
  94 +
  95 +#修改保卡
  96 +class Card_Modify(unittest.TestCase):
  97 + def setUp(self):
  98 + self.base_url = HOST_BOSS + "/electronicCard/updateByUserId"
  99 + CreateTestData.pre_elecCard(device_cus_bind=Data.DEVICE_NUMBER_EDIT)
  100 + self.authCode = CreateTestData.fet_authCode(Data.USER_PHONE_EDIT)
  101 +
  102 + def tearDown(self):
  103 + print(self.result)
  104 +
  105 + def test_modifyCardInfo_phone_success(self):
  106 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  107 + postData = {
  108 + 'customerPhone':Data.USER_PHONE_EDIT,
  109 + 'authCode':self.authCode,
  110 + 'deviceNumber': Data.DEVICE_NUMBER_EDIT
  111 + }
  112 + r = requests.post(self.base_url, headers=headers, data=postData)
  113 + self.result = r.json()
  114 + self.assertEqual(self.result['status'], 1, 'elecCardModifyError')
  115 +
  116 + def test_modifyCardInfo_address_success(self):
  117 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  118 + postData = {
  119 + 'deviceNumber': Data.DEVICE_NUMBER_EDIT,
  120 + 'customerAddress': '上海徐汇区田林路888号修改',
  121 + 'customerPhone': Data.USER_PHONE_EDIT,
  122 + 'authCode':self.authCode
  123 + }
  124 + r = requests.post(self.base_url, headers=headers, data=postData)
  125 + self.result = r.json()
  126 + self.assertEqual(self.result['status'], 1, 'elecCardModifyError')
  127 +
  128 +
... ...
test_cases/parent_space.py
... ... @@ -0,0 +1,106 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +import unittest
  5 +import requests
  6 +from data_fixture.config_data import HOST_BOSS, HOST_STA
  7 +from data_fixture import create_testdata as CreateTestData
  8 +from data_fixture import config_data as Data
  9 +from data_fixture.mysql_db import DB
  10 +import uuid
  11 +import time
  12 +
  13 +
  14 +db_test = DB()
  15 +
  16 +# 家长控制登录
  17 +class ParentSpaceLogin(unittest.TestCase):
  18 + def setUp(self):
  19 + self.base_url = HOST_BOSS + '/parentsSpacePass/login'
  20 + self.password = CreateTestData.get_parentSpace_password(Data.DEVICE_NUMBER_CUS_BIND)
  21 +
  22 + def tearDown(self):
  23 + print(self.result)
  24 +
  25 + def test_ParentSpaceLogin_success(self):
  26 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  27 + postData = {'password': self.password,
  28 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND
  29 + }
  30 + r = requests.post(self.base_url, headers=headers, data=postData)
  31 + self.result = r.json()
  32 + self.assertEqual(self.result['status'], 1, 'ParentSpaceLogin Error')
  33 +
  34 + def test_ParentSpaceLogin_WrongPassword(self):
  35 + password_wrong = self.password + '11'
  36 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  37 + postData = {'password': password_wrong,
  38 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND
  39 + }
  40 + r = requests.post(self.base_url, headers=headers, data=postData)
  41 + self.result = r.json()
  42 + self.assertEqual(self.result['status'], 1005, 'test_ParentSpaceLogin_WrongPassword Error')
  43 +
  44 +
  45 +# 家长控制修改密码
  46 +class ParentSpaceChangePassword(unittest.TestCase):
  47 + def setUp(self):
  48 + self.base_url = HOST_BOSS + '/parentsSpacePass/changePassword'
  49 + self.oldPass = CreateTestData.get_parentSpace_password(Data.DEVICE_NUMBER_CUS_BIND)
  50 +
  51 + def tearDown(self):
  52 + print(self.result)
  53 +
  54 + def test_ParentSpaceChangePassword_success(self):
  55 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  56 + postData = {'oldPass': self.oldPass,
  57 + 'newPass': '111111',
  58 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND
  59 + }
  60 + r = requests.post(self.base_url, headers=headers, data=postData)
  61 + self.result = r.json()
  62 + self.assertEqual(self.result['status'], 1, 'ParentSpacehangePassword Error')
  63 + where = {'password': '111111',
  64 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND }
  65 + self.assertEqual(db_test.select_('acornuser.parents_space_pass', where), 1, 'ParentSpacehangePassword data Error')
  66 +
  67 +
  68 +#家长控制忘记密码
  69 +class ParentSpaceNewPassword(unittest.TestCase):
  70 + def setUp(self):
  71 + self.base_url = HOST_BOSS + '/parentsSpacePass/newpassword'
  72 + CreateTestData.pre_elecCard(Data.DEVICE_NUMBER_CUS_BIND)
  73 + sql = "SELECT customerPhone FROM acornuser.ozing_customermachine where deviceNumber = '{}'".format(Data.DEVICE_NUMBER_CUS_BIND)
  74 + self.mobile = db_test.select(sql)[0]["customerPhone"].strip()
  75 + self.auth = CreateTestData.fet_authCode(self.mobile)
  76 +
  77 + def tearDown(self):
  78 + pass
  79 +
  80 + def test_ParentSpaceNewPassword_success(self):
  81 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  82 + postData = {'userName': self.mobile,
  83 + 'authCode': self.auth,
  84 + 'password': '111111',
  85 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND
  86 + }
  87 + r = requests.post(self.base_url, headers=headers, data=postData)
  88 + self.result = r.json()
  89 + self.assertEqual(self.result['status'], 1, 'test_ParentSpaceNewPassword_success Error')
  90 + where = {'password': '111111',
  91 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND }
  92 + self.assertEqual(db_test.select_('acornuser.parents_space_pass', where), 1, 'ParentSpacehangePassword data Error')
  93 +
  94 + def test_ParentSpaceNewPassword_authCodeError(self):
  95 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  96 + postData = {'userName': self.mobile,
  97 + 'authCode': str(int(self.auth) - 1),
  98 + 'password': '111111',
  99 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND
  100 + }
  101 + r = requests.post(self.base_url, headers=headers, data=postData)
  102 + self.result_2 = r.json()
  103 + self.assertEqual(self.result_2['status'], 1001, 'test_ParentSpaceNewPassword_success Error')
  104 +
  105 +
  106 +
... ...
test_cases/personal_info.py
... ... @@ -0,0 +1,257 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +import unittest
  5 +import requests
  6 +from data_fixture.config_data import HOST_BOSS
  7 +from data_fixture import config_data as Data
  8 +from data_fixture import create_testdata
  9 +from data_fixture.mysql_db import DB
  10 +import os
  11 +import uuid
  12 +
  13 +db_test = DB()
  14 +current_dir = str(os.path.dirname(__file__))
  15 +
  16 +#获取个人信息
  17 +class GetPersonalInfo(unittest.TestCase):
  18 + def setUp(self):
  19 + self.base_url = HOST_BOSS + "/personal/get"
  20 + self.subAccount = str(uuid.uuid4()).replace('-', '')
  21 + create_testdata.pre_subAccount(Data.USER_ID, self.subAccount, 1)
  22 +
  23 + def tearDown(self):
  24 + pass
  25 +
  26 + def test_getParentAccountInfo_success(self):
  27 + headers = {'Accept': '*/*'}
  28 + getData = {'userId': Data.USER_ID, 'type': 1}
  29 + r = requests.get(self.base_url, headers=headers, params=getData)
  30 + self.result_1 = r.json()
  31 + self.assertEqual(self.result_1['status'], 1, 'get parent info fail')
  32 +
  33 + def test_getChildAccountInfo_success(self):
  34 + headers = {'Accept': '*/*'}
  35 + getData = {'userId': Data.USER_ID, 'type': 2}
  36 + r = requests.get(self.base_url, headers=headers, params=getData)
  37 + self.result_2 = r.json()
  38 + self.assertEqual(self.result_2['status'], 1, 'get child info fail')
  39 +
  40 +
  41 +#修改个人信息
  42 +class UpdatePersonalInfo(unittest.TestCase):
  43 + def setUp(self):
  44 + self.base_url = HOST_BOSS + "/personal/update"
  45 + self.subAccount = str(uuid.uuid4()).replace('-', '')
  46 + create_testdata.pre_subAccount(Data.PARENT_ID, self.subAccount, 1)
  47 +
  48 + def tearDown(self):
  49 + pass
  50 +
  51 + def test_UpdateParentInfo_success(self):
  52 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  53 + postData = {
  54 + 'userId':Data.PARENT_ID,
  55 + 'type': 1,
  56 + 'name':'主账户',
  57 + 'birthday':'2003-12-12',
  58 + 'gradeId': '9',
  59 + 'regionId' : '320100',
  60 + 'schoolId': '500018',
  61 + 'qq': '1313131313',
  62 + 'gender': 'female',
  63 + 'regionName': '江苏南京玄武',
  64 + 'deviceNumber': Data.DEVICE_NUMBER_NEW,
  65 + 'address':'幸福小区308'
  66 + }
  67 + where_data_1 = {'id':Data.PARENT_ID, 'qq': '1313131313'}
  68 + where_data_2 = {'user_id':Data.PARENT_ID, 'school_id': '500018'}
  69 + where_data_3 = {'user_id':Data.PARENT_ID, 'address':'幸福小区308'}
  70 + r = requests.post(self.base_url, headers=headers, data=postData)
  71 + self.result_1 = r.json()
  72 + self.assertEqual(self.result_1['status'], 1, 'UpdateParentInfo ERROR')
  73 + self.assertTrue(db_test.select_('acornuser.acorn_user', where_data_1), 1)
  74 + self.assertTrue(db_test.select_('acornuser.ozing_student', where_data_2), 1)
  75 + self.assertTrue(db_test.select_('acornuser.acorn_user_extra', where_data_3), 1)
  76 +
  77 +
  78 + def test_UpdateChildInfo_success(self):
  79 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  80 + postData = {
  81 + 'userId': Data.PARENT_ID,
  82 + 'type': 2,
  83 + 'name': '子账户',
  84 + 'birthday': '2010-12-12',
  85 + 'gradeId': '6',
  86 + 'regionId': '330100',
  87 + 'schoolId': '6129',
  88 + 'qq': '1515151515',
  89 + 'gender': 'female',
  90 + 'regionName': '浙江杭州西湖',
  91 + 'deviceNumber': Data.DEVICE_NUMBER_NEW,
  92 + 'address': '鲜花小区808'
  93 + }
  94 + where_data_child = {'qq': '1515151515', 'address': '鲜花小区808'}
  95 + r = requests.post(self.base_url, headers=headers, data=postData)
  96 + self.result_2 = r.json()
  97 + self.assertEqual(self.result_2['status'], 1, 'UpdateParentInfo ERROR')
  98 + self.assertTrue(db_test.select_('acornuser.child_user', where_data_child))
  99 +
  100 +
  101 +# 添加和修改个性签名
  102 +class AddorUpdateSignature(unittest.TestCase):
  103 + def setUp(self):
  104 + self.base_url = HOST_BOSS + "/signature/addOrUpdateSignature"
  105 + self.subAccount = str(uuid.uuid4()).replace('-', '')
  106 + create_testdata.pre_subAccount(Data.PARENT_ID, self.subAccount, 0)
  107 + create_testdata.checkSignatureExists(Data.PARENT_ID, 1)
  108 + create_testdata.checkSignatureExists(self.subAccount, 2)
  109 +
  110 + def tearDown(self):
  111 + pass
  112 +
  113 + def test_AddParentSignature_success(self):
  114 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  115 + postData = {'userId':Data.PARENT_ID,
  116 + 'signature':'who is the clever Polly 谁是聪明的鹦鹉',
  117 + 'type': 1}
  118 + r = requests.post(self.base_url, headers=headers, data=postData)
  119 + self.result_1 = r.json()
  120 + self.assertEqual(self.result_1['status'], 1, 'AddParentSignature ERROR')
  121 + where_1 = {'user_id':Data.PARENT_ID,
  122 + 'signature':'who is the clever Polly 谁是聪明的鹦鹉'}
  123 + self.assertEqual(db_test.select_('acornuser.acorn_user_extra', where_1), 1)
  124 +
  125 + def test_UpdateParentSignature_success(self):
  126 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  127 + postData = {'userId':Data.PARENT_ID,
  128 + 'signature':'clever Polly 一只吵人的鹦鹉',
  129 + 'type': 1}
  130 + r = requests.post(self.base_url, headers=headers, data=postData)
  131 + self.result_2 = r.json()
  132 + self.assertEqual(self.result_2['status'], 1, 'UpdateParentSignature ERROR')
  133 + where_2 = {'user_id':Data.PARENT_ID,
  134 + 'signature':'clever Polly 一只吵人的鹦鹉'}
  135 + self.assertEqual(db_test.select_('acornuser.acorn_user_extra', where_2), 1, 'UpdateParentSignature ERROR')
  136 +
  137 + def test_AddChildSignature_success(self):
  138 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  139 + postData = {'userId':self.subAccount,
  140 + 'signature':'Polly can you spell its name 波利',
  141 + 'type': 2}
  142 + r = requests.post(self.base_url, headers=headers, data=postData)
  143 + self.result_3 = r.json()
  144 + self.assertEqual(self.result_3['status'], 1, 'AddChildSignature ERROR')
  145 + where_1 = {'sub_account_id':self.subAccount,
  146 + 'signature':'Polly can you spell its name 波利'}
  147 + self.assertEqual(db_test.select_('acornuser.subAccount_user_extra', where_1), 1, 'AddChildSignature ERROR')
  148 +
  149 + def test_UpdateChildSignature_success(self):
  150 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  151 + postData = {'userId':self.subAccount,
  152 + 'signature':'波利 P-O-L-L-Y',
  153 + 'type': 2}
  154 + r = requests.post(self.base_url, headers=headers, data=postData)
  155 + self.result_4 = r.json()
  156 + self.assertEqual(self.result_4['status'], 1, 'UPDATEChildSignature ERROR')
  157 + where_1 = {'sub_account_id':self.subAccount,
  158 + 'signature':'波利 P-O-L-L-Y'}
  159 + self.assertEqual(db_test.select_('acornuser.subAccount_user_extra', where_1), 1, 'UPDATEChildSignature ERROR')
  160 +
  161 +
  162 +# 显示个性签名
  163 +class GetSignature(unittest.TestCase):
  164 + def setUp(self):
  165 + self.base_url = HOST_BOSS + "/signature/info"
  166 + self.subAccount = str(uuid.uuid4()).replace('-', '')
  167 + create_testdata.checkSignatureExists(Data.PARENT_ID, 1)
  168 + create_testdata.checkSignatureExists(self.subAccount, 2)
  169 +
  170 + def tearDown(self):
  171 + pass
  172 +
  173 + def test_getParentSignature_success(self):
  174 + headers = {'Accept': '*/*'}
  175 + getData = {'userId': Data.PARENT_ID, 'type': 1}
  176 + r = requests.get(self.base_url, headers=headers, params=getData)
  177 + self.result_1 = r.json()
  178 + self.assertEqual(self.result_1['status'], 1, 'get parent Signature fail')
  179 +
  180 + def test_getChildSignature_success(self):
  181 + headers = {'Accept': '*/*'}
  182 + getData = {'userId': self.subAccount, 'type': 2}
  183 + r = requests.get(self.base_url, headers=headers, params=getData)
  184 + self.result_2 = r.json()
  185 + self.assertEqual(self.result_2['status'], 1, 'get child Signature fail')
  186 +
  187 +
  188 +#更新用户头像 -- 默认图片
  189 +class UpdatePortraitDefault(unittest.TestCase):
  190 + def setUp(self):
  191 + self.base_url = HOST_BOSS + "/profile/picture/default"
  192 + self.subAccount = str(uuid.uuid4()).replace('-', '')
  193 + create_testdata.pre_subAccount(Data.PARENT_ID, self.subAccount, 0)
  194 +
  195 + def tearDown(self):
  196 + pass
  197 +
  198 + def test_UpdateParentPortraitDefault_success(self):
  199 + headers = {'Accept': '*/*', 'Content-Type':'application/x-www-form-urlencoded'}
  200 + postData = {'userId': Data.PARENT_ID, 'type': 1,
  201 + 'defaultImg':'http://hjxprodbucket.oss.aliyuncs.com/static/upload/boss_api/announcement/2017-08-29/61e8d8cf-651f-49c9-beb2-ff1387af390a.png'}
  202 + r = requests.post(self.base_url, headers=headers, data=postData)
  203 + self.result_1 = r.json()
  204 + self.assertEqual(self.result_1['status'], 1, 'UpdateParentPortrait fail')
  205 + where = {'user_id': Data.PARENT_ID,
  206 + 'portrait':'static/upload/boss_api/announcement/2017-08-29/61e8d8cf-651f-49c9-beb2-ff1387af390a.png'}
  207 + self.assertEqual(db_test.select_('acornuser.ozing_student', where), 1, 'UpdateParentPortraitDefault fail')
  208 +
  209 + def test_UpdateChildPortraitDefault_success(self):
  210 + headers = {'Accept': '*/*', 'Content-Type':'application/x-www-form-urlencoded'}
  211 + postData = {'userId': self.subAccount, 'type': 2,
  212 + 'defaultImg': 'http://hjxprodbucket.oss.aliyuncs.com/static/upload/boss_api/announcement/2017-08-29/61e8d8cf-651f-49c9-beb2-ff1387af390a.png'}
  213 + r = requests.post(self.base_url, headers=headers, data=postData)
  214 + self.result_2 = r.json()
  215 + self.assertEqual(self.result_2['status'], 1, 'UpdateChildPortrait fail')
  216 + where = {'subAccountId': self.subAccount,
  217 + 'image': 'static/upload/boss_api/announcement/2017-08-29/61e8d8cf-651f-49c9-beb2-ff1387af390a.png'}
  218 + self.assertEqual(db_test.select_('acornuser.child_user', where), 1, 'UpdateChildPortraitDefault fail')
  219 +
  220 +
  221 +#更新用户头像-- 上传文件
  222 +class UpdatePortrait(unittest.TestCase):
  223 + def setUp(self):
  224 + self.base_url = HOST_BOSS + "/profile/picture/update"
  225 + img_file_1 = current_dir + '/1.jpg'
  226 + img_file_2 = current_dir + '/1.png'
  227 + self.img_1 = open(img_file_1, 'rb')
  228 + self.img_2 = open(img_file_2, 'rb')
  229 + self.subAccount = str(uuid.uuid4()).replace('-', '')
  230 + create_testdata.pre_subAccount(Data.PARENT_ID, self.subAccount, 0)
  231 +
  232 + def tearDown(self):
  233 + pass
  234 +
  235 + def test_UpdateParentPortraitDefault_success(self):
  236 + headers = {'Accept': '*/*', 'Content-Type':'multipart/form-data; boundary=cada83b1d4b82a7ccd28ae8f7f6d6'}
  237 + postData = {'userId': Data.PARENT_ID, 'type': 1}
  238 + file = {'img': self.img_1}
  239 + r = requests.post(self.base_url, data=postData, files=file)
  240 + ss = r.request
  241 + self.img_1.close()
  242 + self.result_1 = r.json()
  243 + self.assertEqual(self.result_1['status'], 1, 'UpdateParentPortrait fail')
  244 + self.assertEqual(len(self.result_1['data']), 1, 'UpdateParentPortrait data fail')
  245 +
  246 + def test_UpdateChildPortrait_success(self):
  247 + headers = {'Accept': '*/*', 'Content-Type':'multipart/form-data; boundary=fa0cada83b1d4b82a7ccd28ae8f7f6d6'}
  248 + postData = {'userId': self.subAccount, 'type': 2}
  249 + file = {'img': self.img_2}
  250 + r = requests.post(self.base_url, data=postData, files=file)
  251 + ss = r.request
  252 + self.img_2.close()
  253 + self.result_2 = r.json()
  254 + self.assertEqual(self.result_2['status'], 1, 'UpdateChildPortrait fail')
  255 + self.assertEqual(len(self.result_2['data']), 1, 'UpdateChildPortrait data fail')
  256 +
  257 +
... ...
test_cases/press.py
... ... @@ -0,0 +1,91 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +import unittest
  5 +import requests
  6 +from data_fixture.config_data import HOST_BOSS
  7 +from data_fixture import create_testdata as CreateTestData
  8 +from data_fixture import config_data as Data
  9 +from data_fixture.mysql_db import DB
  10 +
  11 +db_test = DB()
  12 +
  13 +
  14 +# 获取教材版本列表
  15 +class GetPressList(unittest.TestCase):
  16 + def setUp(self):
  17 + self.base_url = HOST_BOSS + '/press/list'
  18 +
  19 + def tearDown(self):
  20 + print(self.result)
  21 +
  22 + def test_getPressList_success(self):
  23 + headers = {'Accept': '*/*'}
  24 + getData = {'subjectName': '数学'}
  25 + r = requests.get(self.base_url, headers=headers, params=getData)
  26 + self.result = r.json()
  27 + self.assertEqual(self.result['status'], 1, 'getPressList Error')
  28 + self.assertTrue(len(self.result['data']) > 1, 'getPressList data Error')
  29 +
  30 +
  31 +# 获取用户版本信息接口
  32 +class GetUserPressInfo(unittest.TestCase):
  33 + def setUp(self):
  34 + self.base_url = HOST_BOSS + '/userPress/info'
  35 + CreateTestData.pre_GetUserPressInfo(Data.USER_ID)
  36 +
  37 + def tearDown(self):
  38 + print(self.result)
  39 +
  40 + def test_getPressList_success(self):
  41 + headers = {'Accept': '*/*'}
  42 + getData = {'userId': Data.USER_ID, 'gradeId': 9}
  43 + r = requests.get(self.base_url, headers=headers, params=getData)
  44 + self.result = r.json()
  45 + self.assertEqual(self.result['status'], 1, 'GetUserPressInfo Error')
  46 + data = self.result['data']
  47 + for item in data:
  48 + if item['subject'] == '语文':
  49 + press_to_check = item['press']
  50 + self.assertEqual(press_to_check, '北京师范大学出版社', 'GetUserPressInfo data Error')
  51 +
  52 +
  53 +# 修改版本信息接口
  54 +class UpdateUserPressInfo(unittest.TestCase):
  55 + def setUp(self):
  56 + self.base_url = HOST_BOSS + '/userPress/update'
  57 +
  58 + def tearDown(self):
  59 + print(self.result)
  60 +
  61 + def test_getPressList_success(self):
  62 + headers = {'Accept': '*/*', 'Content-Type': 'application/x-www-form-urlencoded'}
  63 + postData = {'userId': Data.USER_ID,
  64 + 'chemistry': '山东教育出版社',
  65 + 'chinese': '江苏教育出版社',
  66 + 'biology': '人民教育出版社',
  67 + 'geography': '人民教育出版社',
  68 + 'physics': '人民教育出版社',
  69 + 'english': '外语教学与研究出版社',
  70 + 'political': '人民教育出版社',
  71 + 'math': '江苏科学技术出版社',
  72 + 'history': '人民教育出版社'
  73 + }
  74 + r = requests.post(self.base_url, headers=headers, data=postData)
  75 + self.result = r.json()
  76 + self.assertEqual(self.result['status'], 1, 'UpdateUserPressInfo Error')
  77 + where = {'user_id': Data.USER_ID,
  78 + 'chemistry': '山东教育出版社',
  79 + 'chinese': '江苏教育出版社',
  80 + 'biology': '人民教育出版社',
  81 + 'geography': '人民教育出版社',
  82 + 'physics': '人民教育出版社',
  83 + 'english': '外语教学与研究出版社',
  84 + 'political': '人民教育出版社',
  85 + 'math': '江苏科学技术出版社',
  86 + 'history': '人民教育出版社'
  87 + }
  88 + self.assertEqual(db_test.select_('acornuser.user_press', where), 1, 'UpdateUserPressInfo data Error')
  89 +
  90 +
  91 +
... ...
test_cases/region_grade_school.py
... ... @@ -0,0 +1,89 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +import unittest
  5 +import requests
  6 +from data_fixture.config_data import HOST_BOSS
  7 +
  8 +# 省
  9 +class GetProvince(unittest.TestCase):
  10 + def setUp(self):
  11 + self.base_url = HOST_BOSS + "/ozing/provinces"
  12 +
  13 + def tearDown(self):
  14 + print(self.result)
  15 +
  16 + def test_getProvince_success(self):
  17 + headers = {'Accept': '*/*'}
  18 + r = requests.get(self.base_url, headers=headers)
  19 + self.result = r.json()
  20 + self.assertEqual(self.result['status'], 100, 'get province fail')
  21 + self.assertTrue(len(self.result['provinces']) > 0, 'province data wrong')
  22 +
  23 +# 市
  24 +class GetCities(unittest.TestCase):
  25 + def setUp(self):
  26 + self.base_url = HOST_BOSS + "/ozing/cities"
  27 + self.regionId = '330000' #浙江
  28 + self.city ='330100' # 杭州
  29 +
  30 + def tearDown(self):
  31 + print(self.result)
  32 +
  33 + def test_getCities_success(self):
  34 + headers = {'Accept': '*/*'}
  35 + getData = {'regionId': self.regionId}
  36 + r = requests.get(self.base_url, params=getData)
  37 + self.result = r.json()
  38 + self.assertEqual(self.result['status'], 100, 'get cities fail')
  39 + self.assertTrue(len(self.result['cities']) > 0, 'cities data wrong')
  40 +
  41 +# 区
  42 +class GetCounties(unittest.TestCase):
  43 + def setUp(self):
  44 + self.base_url = HOST_BOSS + "/ozing/counties"
  45 + self.regionId = '330100' # 杭州
  46 + self.county ='330106' # 西湖
  47 +
  48 + def tearDown(self):
  49 + print(self.result)
  50 +
  51 + def test_getCounties_success(self):
  52 + headers = {'Accept': '*/*'}
  53 + getData = {'regionId': self.regionId}
  54 + r = requests.get(self.base_url, params=getData)
  55 + self.result = r.json()
  56 + self.assertEqual(self.result['status'], 100, 'get counties fail')
  57 + self.assertTrue(len(self.result['counties']) > 0, 'counties data wrong')
  58 +
  59 +# 年级
  60 +class GetGrades(unittest.TestCase):
  61 + def setUp(self):
  62 + self.base_url = HOST_BOSS + "/grades"
  63 +
  64 + def tearDown(self):
  65 + print(self.result)
  66 +
  67 + def test_getGrades_success(self):
  68 + headers = {'Accept': 'application/json'}
  69 + r = requests.get(self.base_url, headers=headers)
  70 + self.result = r.json()
  71 + self.assertTrue(len(self.result['data']) > 0, 'grades data wrong')
  72 +
  73 +
  74 +# 学校
  75 +class GetSchools(unittest.TestCase):
  76 + def setUp(self):
  77 + self.base_url = HOST_BOSS + "/school/get"
  78 + self.regionId = '330106' #西湖
  79 + self.gradeId = '6' #五年级
  80 +
  81 + def tearDown(self):
  82 + print(self.result)
  83 +
  84 + def test_getSchools_success(self):
  85 + headers = {'Accept': 'application/json'}
  86 + getData = {'regionId': self.regionId, 'gradeId': self.gradeId}
  87 + r = requests.get(self.base_url, headers=headers, params=getData)
  88 + self.result = r.json()
  89 + self.assertTrue(len(self.result['data']) > 0, 'school data wrong')
... ...
test_cases/register.py
... ... @@ -0,0 +1,243 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +import unittest
  5 +import requests
  6 +from data_fixture.config_data import HOST_BOSS
  7 +from data_fixture import create_testdata as CreateTestData
  8 +from data_fixture import config_data as Data
  9 +from data_fixture.mysql_db import DB
  10 +import os
  11 +from requests_toolbelt import MultipartEncoder
  12 +
  13 +db_test = DB()
  14 +current_dir = str(os.path.dirname(__file__))
  15 +
  16 +#手机号重复验证
  17 +class PhoneUsedCheck(unittest.TestCase):
  18 + def setUp(self):
  19 + self.base_url = HOST_BOSS + "/ozing/timer/user/registered"
  20 + CreateTestData.pre_phoneUsedCheck(Data.USER_PHONE_USED, True)
  21 + CreateTestData.pre_phoneUsedCheck(Data.USER_PHONE_UNUSED, False)
  22 +
  23 + def tearDown(self):
  24 + print(self.result)
  25 +
  26 + # 注册过的手机号
  27 + def test_phone_used(self):
  28 + getData = {'mobile':Data.USER_PHONE_USED}
  29 + r = requests.get(self.base_url, params=getData)
  30 + self.result = r.json()
  31 + self.assertEqual(self.result['status'], True)
  32 +
  33 + # 手机号未注册
  34 + def test_phone_unused(self):
  35 + getData = {'mobile':Data.USER_PHONE_UNUSED}
  36 + r = requests.get(self.base_url, params=getData)
  37 + self.result = r.json()
  38 + self.assertEqual(self.result['status'], False)
  39 +
  40 +
  41 +# 获取验证码 --register
  42 +class GetAuthCode(unittest.TestCase):
  43 + def setUp(self):
  44 + self.base_url = HOST_BOSS + "/ozing/timer/user/fetchAuthCode"
  45 + self.mobile = '13833333333'
  46 +
  47 + def tearDown(self):
  48 + print(self.result)
  49 +
  50 + def test_getAuthCode_success(self):
  51 + headers = {'Accept': '*/*'}
  52 + postData = {'mobile': self.mobile, 'type': 'register'}
  53 + r = requests.post(self.base_url, headers=headers, data=postData)
  54 + self.result = r.json()
  55 + self.assertEqual(self.result['status'], 100)
  56 +
  57 +
  58 +#用户注册
  59 +class SignIn(unittest.TestCase):
  60 + def setUp(self):
  61 + self.base_url = HOST_BOSS + "/ozing/timer/anking/user"
  62 + self.smsCode = CreateTestData.fet_authCode(Data.USER_PHONE)
  63 + CreateTestData.pre_phoneUsedCheck(Data.USER_PHONE, False)
  64 +
  65 + def tearDown(self):
  66 + print(self.result)
  67 +
  68 + def test_signIn_success(self):
  69 + headers = {'Content-Type': 'application/json', 'Accept': '*/*'}
  70 + postData = {
  71 + "username": Data.USER_PHONE,
  72 + "password": 'Hjx111111',
  73 + "source": 'Android',
  74 + "smscode": self.smsCode
  75 + }
  76 + r = requests.post(self.base_url, headers=headers, json=postData)
  77 + self.result = r.json()
  78 + self.assertEqual(self.result['status'], 100)
  79 +
  80 +
  81 +# 检查用户注册信息是否完整
  82 +class RegisterExtrainfoCheck(unittest.TestCase):
  83 + def setUp(self):
  84 + self.base_url = HOST_BOSS + '/register/extrainfo/check'
  85 + CreateTestData.pre_register_extrainfo_check(Data.USER_ID, True)
  86 + CreateTestData.pre_register_extrainfo_check(Data.USER_ID_INCOMPLETE, False)
  87 +
  88 + def tearDown(self):
  89 + pass
  90 +
  91 + def test_checkRegisterExtrainfo_Complete_success(self):
  92 + headers = {'Accept': '*/*'}
  93 + getData = {'userId': Data.USER_ID}
  94 + r = requests.get(self.base_url, headers=headers, params=getData)
  95 + self.result_1 = r.json()
  96 + self.assertEqual(self.result_1['status'], 1, 'test_checkRegisterExtrainfo_Complete fail')
  97 + self.assertEqual(self.result_1['data']['isRegisterInfoComplete'], True, 'test_checkRegisterExtrainfo_Complete data fail')
  98 +
  99 + def test_checkRegisterExtrainfo_inComplete_success(self):
  100 + headers = {'Accept': '*/*'}
  101 + getData = {'userId': Data.USER_ID_INCOMPLETE}
  102 + r = requests.get(self.base_url, headers=headers, params=getData)
  103 + self.result_2 = r.json()
  104 + self.assertEqual(self.result_2['status'], 1, 'test_checkRegisterExtrainfo_inComplete fail')
  105 + self.assertEqual(self.result_2['data']['isRegisterInfoComplete'], False,
  106 + 'test_checkRegisterExtrainfo_inComplete data fail')
  107 +
  108 +
  109 +# 提交注册信息
  110 +class RegisterExtrainfoSubmit(unittest.TestCase):
  111 + def setUp(self):
  112 + self.base_url = HOST_BOSS + '/register/extrainfo/submit'
  113 +
  114 + def tearDown(self):
  115 + print(self.result)
  116 +
  117 + def test_RegisterExtrainfoSubmit_success(self):
  118 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  119 + postData = {
  120 + 'name': '测试测试lalalallal',
  121 + 'gradeId': 7,
  122 + 'schoolId': 68779,
  123 + 'regionName': '浙江杭州西湖',
  124 + 'regionId': 330106,
  125 + 'userId': Data.USER_ID,
  126 + 'chinese': '北京出版社',
  127 + 'english': '人民教育出版社',
  128 + 'math': '人民教育出版社'
  129 + }
  130 + r = requests.post(self.base_url, headers=headers, data=postData)
  131 + self.result = r.json()
  132 + self.assertEqual(self.result['status'], 1, 'RegisterExtrainfoSubmit Error')
  133 + where_1 = {'id': Data.USER_ID,'nickname': '测试测试lalalallal'}
  134 + where_2 = {'user_id': Data.USER_ID,
  135 + 'grade_id': 7,
  136 + 'school_id': 68779,
  137 + 'region_name': '浙江杭州西湖',
  138 + 'region_id': 330106}
  139 + where_3 = {'user_id': Data.USER_ID,
  140 + 'chinese': '北京出版社',
  141 + 'english': '人民教育出版社',
  142 + 'math': '人民教育出版社'
  143 + }
  144 + self.assertEqual(db_test.select_('acornuser.acorn_user', where_1), 1, 'RegisterExtrainfoSubmit Error')
  145 + self.assertEqual(db_test.select_('acornuser.ozing_student', where_2), 1, 'RegisterExtrainfoSubmit Error')
  146 + self.assertEqual(db_test.select_('acornuser.user_press', where_3), 1, 'RegisterExtrainfoSubmit Error')
  147 +
  148 +
  149 +#账户管理界面更换手机号绑定
  150 +class UpdateUserNameByUserId(unittest.TestCase):
  151 + def setUp(self):
  152 + self.base_url = HOST_BOSS + '/electronicCard/updateUserNameByUserId'
  153 + self.authCode_1 = CreateTestData.fet_authCode(Data.USER_PHONE_CHANGE_EXISTS)
  154 + self.authCode_2 = CreateTestData.fet_authCode(Data.USER_PHONE_CHANGE)
  155 + CreateTestData.pre_phoneUsedCheck(Data.USER_PHONE_CHANGE_EXISTS, True)
  156 + CreateTestData.pre_phoneUsedCheck(Data.USER_PHONE_CHANGE, False)
  157 +
  158 + def tearDown(self):
  159 + print(self.result)
  160 +
  161 + def test_UpdateUserNameByUserId_phoneExists_success(self):
  162 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  163 + postData = {
  164 + 'username': Data.USER_PHONE_CHANGE_EXISTS,
  165 + 'authCode': self.authCode_1,
  166 + 'userId': Data.USER_ID
  167 + }
  168 + r = requests.post(self.base_url, headers=headers, data=postData)
  169 + self.result = r.json()
  170 + self.assertEqual(self.result['status'], 1006, 'UpdateUserNameByUserId_phoneExists Error')
  171 +
  172 + def test_UpdateUserNameByUserId_success(self):
  173 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  174 + postData = {
  175 + 'username': Data.USER_PHONE_CHANGE,
  176 + 'authCode': self.authCode_2,
  177 + 'userId': Data.USER_ID
  178 + }
  179 + r = requests.post(self.base_url, headers=headers, data=postData)
  180 + self.result = r.json()
  181 + self.assertEqual(self.result['status'], 1, 'UpdateUserNameByUserId Error')
  182 +
  183 +
  184 +# 添加用户反馈
  185 +class AddFeedBack(unittest.TestCase):
  186 + def setUp(self):
  187 + self.base_url = HOST_BOSS + '/feedback/add'
  188 + img_file = current_dir + '/1.png'
  189 + self.img = open(img_file, 'rb')
  190 +
  191 + def tearDown(self):
  192 + print(self.result)
  193 +
  194 + def test_AddFeedBack_success(self):
  195 + headers = {'Content-Type': 'multipart/form-data; boundary=fa0cada83b1d4b82a7ccd28ae8f7f6d6', 'Accept': '*/*'}
  196 + postData = {
  197 + 'userId': Data.USER_ID,
  198 + 'content': '11111feedback哦哦哦',
  199 + 'contact': '00000000',
  200 + 'feedtype': '个人中心'
  201 + }
  202 + file = {'imgs': self.img}
  203 +
  204 + r = requests.post(self.base_url, data=postData, files=file)
  205 + #r = requests.post(self.base_url, headers=headers, data=postData)
  206 + ss = r.request
  207 + self.img.close()
  208 + self.result = r.json()
  209 + self.assertEqual(self.result['status'], 1, 'AddFeedBack Error')
  210 + select = {'userId': Data.USER_ID,
  211 + 'content': '11111feedback哦哦哦',
  212 + 'contact': '00000000',
  213 + 'feedtype': '个人中心'}
  214 + self.assertTrue(db_test.select_('acornuser.feedback', select) > 0, 'AddFeedBack data insert Error')
  215 +
  216 +
  217 +# 获取最近观看视频列表
  218 +class GetRecentVideo(unittest.TestCase):
  219 + def setUp(self):
  220 + self.base_url = HOST_BOSS + '/personal/video/recent'
  221 + CreateTestData.pre_getRecentVideo(Data.USER_ID_NO_VIDEO, 0)
  222 + CreateTestData.pre_getRecentVideo(Data.USER_ID_VIDEO, 1)
  223 +
  224 + def tearDown(self):
  225 + pass
  226 +
  227 + def test_GetRecentVideo_noVideo_success(self):
  228 + getData = {'userId': Data.USER_ID_NO_VIDEO,
  229 + 'pageNum': 1}
  230 + r = requests.get(self.base_url, params=getData)
  231 + self.result_1 = r.json()
  232 + self.assertEqual(self.result_1['status'], 1000, 'GetRecentVideo-noVideo fail')
  233 +
  234 + def test_GetRecentVideo_success(self):
  235 + getData = {'userId': Data.USER_ID_VIDEO,
  236 + 'pageNum': 1}
  237 + r = requests.get(self.base_url, params=getData)
  238 + self.result_2 = r.json()
  239 + self.assertEqual(self.result_2['status'], 1, 'GetRecentVideo fail')
  240 +
  241 +
  242 +
  243 +
... ...
test_cases/sub_account.py
... ... @@ -0,0 +1,118 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +import unittest
  5 +import requests
  6 +from data_fixture.config_data import HOST_BOSS
  7 +from data_fixture import create_testdata as CreateTestData
  8 +from data_fixture import config_data as Data
  9 +from data_fixture.mysql_db import DB
  10 +import uuid
  11 +
  12 +db_test = DB()
  13 +
  14 +#添加子账户
  15 +class AddSubAccount(unittest.TestCase):
  16 + def setUp(self):
  17 + self.base_url = HOST_BOSS + '/childUser/addChildUser'
  18 + CreateTestData.pre_AddSubAccount(Data.PARENT_ID)
  19 +
  20 + def tearDown(self):
  21 + print(self.result)
  22 +
  23 + def test_addSubAccount_success(self):
  24 + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*'}
  25 + postData = {'parentId': Data.PARENT_ID,
  26 + 'image': Data.SUB_ACC_IMAGE,
  27 + 'name': '测试sub',
  28 + 'gradeId': '6',
  29 + 'schoolId': Data.SUB_ACC_SCHOOL_ID_1,
  30 + 'regionId': Data.SUB_ACC_REGION_ID_1,
  31 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND,
  32 + 'regionName': Data.SUB_ACC_REGION_NAME_1
  33 + }
  34 + r = requests.post(self.base_url, headers=headers, data=postData)
  35 + self.result = r.json()
  36 + self.assertEqual(db_test.select_('acornuser.child_user', {'parent_id': Data.PARENT_ID}), 1, 'addSubAccount Error')
  37 + self.assertEqual(self.result['status'], 1, 'addSubAccount Error')
  38 +
  39 +
  40 +#查找子账户信息
  41 +class GetSubAccount(unittest.TestCase):
  42 + def setUp(self):
  43 + self.base_url = HOST_BOSS + '/childUser/info'
  44 + self.subAccount_1 = str(uuid.uuid4()).replace('-', '')
  45 + self.subAccount_2 = str(uuid.uuid4()).replace('-', '')
  46 + CreateTestData.pre_subAccount(Data.PARENT_ID, self.subAccount_1, 0)
  47 + CreateTestData.pre_subAccount(Data.PARENT_ID, self.subAccount_2, 1)
  48 +
  49 + def tearDown(self):
  50 + print(self.result)
  51 +
  52 + def test_getSubAccount_success(self):
  53 + headers = {'Accept': '*/*'}
  54 + getData = {'userId': Data.PARENT_ID, 'deviceNumber':Data.DEVICE_NUMBER_CUS_BIND}
  55 + r = requests.get(self.base_url, headers=headers, params=getData)
  56 + self.result = r.json()
  57 + self.assertEqual(self.result['status'], 1, 'getSubAccount Error')
  58 + self.assertTrue(len(self.result['data']) > 1, 'getSubAccount data Error')
  59 +
  60 +
  61 +#删除子账户
  62 +class DelSubAccount(unittest.TestCase):
  63 + def setUp(self):
  64 + self.base_url = HOST_BOSS + '/childUser/delete'
  65 + self.subAccount = str(uuid.uuid4()).replace('-', '')
  66 + CreateTestData.pre_subAccount(Data.PARENT_ID, self.subAccount, 0)
  67 +
  68 + def tearDown(self):
  69 + print(self.result)
  70 +
  71 + def test_delSubAccount_success(self):
  72 + headers = {'Accept': '*/*'}
  73 + getData = {'subAccountId': self.subAccount}
  74 + r = requests.get(self.base_url, headers=headers, params=getData)
  75 + self.result = r.json()
  76 + self.assertEqual(db_test.select_('acornuser.child_user', {'subAccountId': self.subAccount}), 0, 'delSubAccount Error')
  77 + self.assertEqual(self.result['status'], 1, 'delSubAccount ERROR')
  78 +
  79 +
  80 +#切换账户
  81 +class SwitchAccounts(unittest.TestCase):
  82 + def setUp(self):
  83 + self.base_url = HOST_BOSS + '/childUser/update'
  84 + self.subAccount = str(uuid.uuid4()).replace('-', '')
  85 + CreateTestData.pre_subAccount(Data.PARENT_ID, self.subAccount, 0)
  86 +
  87 + def tearDown(self):
  88 + print(self.result)
  89 +
  90 + def test_swichToSubAccount_success(self):
  91 + headers = {'Accept': '*/*'}
  92 + getData = {'subAccountId': self.subAccount,
  93 + 'userId': Data.PARENT_ID,
  94 + 'deviceNumber' : Data.DEVICE_NUMBER_CUS_BIND,
  95 + 'type': 2}
  96 + r = requests.get(self.base_url, headers=headers, params=getData)
  97 + self.result = r.json()
  98 + where_data = {'parent_id': Data.PARENT_ID,'subAccountId': self.subAccount, 'status':1}
  99 + self.assertEqual(db_test.select_('acornuser.child_user', where_data), 1, 'swichToSubAccount Error')
  100 + self.assertEqual(self.result['status'], 1, 'swichToSubAccount ERROR')
  101 +
  102 + def test_swichToParentAccount_success(self):
  103 + headers = {'Accept': '*/*'}
  104 + getData = {'userId': Data.PARENT_ID,
  105 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND,
  106 + 'type': 1}
  107 + r = requests.get(self.base_url, headers=headers, params=getData)
  108 + self.result = r.json()
  109 + where_data = {'userId': Data.PARENT_ID,
  110 + 'deviceNumber': Data.DEVICE_NUMBER_CUS_BIND,
  111 + 'status': 1}
  112 + self.assertEqual(db_test.select_('acornuser.acorn_user_status', where_data), 1, 'swichToParentAccount Error')
  113 + self.assertEqual(self.result['status'], 1, 'swichToParentAccount ERROR')
  114 +
  115 +
  116 +
  117 +
  118 +
... ...
test_cases/subject_sync.py
... ... @@ -0,0 +1,111 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +import unittest
  5 +import requests
  6 +from data_fixture.config_data import HOST_BOSS, HOST_STA, HOST_RES
  7 +from data_fixture import create_testdata as CreateTestData
  8 +from data_fixture import config_data as Data
  9 +from data_fixture.mysql_db import DB
  10 +import time
  11 +from datetime import date
  12 +
  13 +db_test = DB()
  14 +
  15 +#获取同步视频接口
  16 +class ClassNameVideo(unittest.TestCase):
  17 + def setUp(self):
  18 + self.base_url = HOST_RES + '/className/video'
  19 +
  20 + def tearDown(self):
  21 + pass
  22 +
  23 + def test_ClassNameVideo_success(self):
  24 + getData = {'textName': '秋天的怀念',
  25 + 'press': '人民教育出版社',
  26 + 'subject': '语文',
  27 + 'gradeName': '7年级'}
  28 + r = requests.get(self.base_url, params=getData)
  29 + self.result_1 = r.json()
  30 + self.assertEqual(self.result_1['status'], 1, 'test_ClassNameVideo_success Error')
  31 +
  32 + def test_ClassNameVideo_noData(self):
  33 + getData = {'textName': '秋天 的怀念',
  34 + 'press': '人民教育出版社',
  35 + 'subject': '语文',
  36 + 'gradeName': '7年级'}
  37 + r = requests.get(self.base_url, params=getData)
  38 + self.result_2 = r.json()
  39 + self.assertEqual(self.result_2['status'], 1000, 'test_ClassNameVideo_noData Error')
  40 +
  41 +
  42 +#获取知识点视频
  43 +class PointVideo(unittest.TestCase):
  44 + def setUp(self):
  45 + self.base_url = HOST_RES + '/point/video'
  46 +
  47 + def tearDown(self):
  48 + pass
  49 +
  50 + def test_PointVideo_success(self):
  51 + getData = {'textName': '秋天的怀念',
  52 + 'press': '人民教育出版社',
  53 + 'subject': '语文',
  54 + 'gradeName': '7年级',
  55 + 'point':'说明文阅读5说明文的结构||议论文的定义和分类'}
  56 + r = requests.get(self.base_url, params=getData)
  57 + self.result_1 = r.json()
  58 + self.assertEqual(self.result_1['status'], 1, 'test_PointVideo_success Error')
  59 +
  60 + def test_PointVideo_noData(self):
  61 + getData = {'textName': '秋天的怀念',
  62 + 'press': '人民教育出版社',
  63 + 'subject': '语文',
  64 + 'gradeName': '7年级',
  65 + 'point': '说明文阅读5说明文的结构'}
  66 + r = requests.get(self.base_url, params=getData)
  67 + self.result_2 = r.json()
  68 + self.assertEqual(self.result_2['status'], 1000, 'test_PointVideo_noData Error')
  69 +
  70 +
  71 +#课程中心PK练习抽题接口
  72 +class SubjectTest(unittest.TestCase):
  73 + def setUp(self):
  74 + self.base_url = HOST_BOSS + '/subject/test/'
  75 +
  76 + def tearDown(self):
  77 + pass
  78 +
  79 + def test_SubjectTest_success(self):
  80 + getData = {'subjectName': '数学',
  81 + 'questionPoint': '二元一次方程||二元一次方程组的解',
  82 + 'gradeId': '10',
  83 + 'textName': '二元一次方程组'}
  84 + url = self.base_url + 'math'
  85 + r = requests.get(url, params=getData)
  86 + self.result_1 = r.json()
  87 + self.assertEqual(self.result_1['status'], 1, 'test_SubjectTest_success Error')
  88 +
  89 +
  90 +#巩固练习抽题接口
  91 +class ConsolidationExercise(unittest.TestCase):
  92 + def setUp(self):
  93 + self.base_url = HOST_BOSS + '/consolidation/exercise'
  94 +
  95 + def tearDown(self):
  96 + pass
  97 +
  98 + def test_ConsolidationExercise_success(self):
  99 + getData = {'subjectName': '数学',
  100 + 'gradeId': '10',
  101 + 'secondPoint': '二元一次方程||二元一次方程组的解',
  102 + 'textName': '二元一次方程组',
  103 + 'difficultyLevel': '基础卷'}
  104 + r = requests.get(self.base_url, params=getData)
  105 + self.result = r.json()
  106 + self.assertEqual(self.result['status'], 1, 'test_ConsolidationExercise_success Error')
  107 + self.assertTrue(len(self.result['data']) > 1)
  108 +
  109 +
  110 +
  111 +
... ...
test_suites/__pycache__/test_elecCard.cpython-36.pyc
No preview for this file type
test_suites/test_elecCard.py
... ... @@ -0,0 +1,43 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +import unittest
  5 +
  6 +from test_cases import eleccard_setUp
  7 +
  8 +from test_cases import eleccard_check
  9 +
  10 +# 添加保卡
  11 +def suites_addElecCard():
  12 + suite = unittest.TestSuite()
  13 + suite.addTest(eleccard_check.CheckElecCardBind('test_checkElecCard_unbind'))
  14 + suite.addTest(eleccard_setUp.SetUpElecCard('test_addElecCard_success'))
  15 + suite.addTest(eleccard_check.CheckElecCardBind('test_checkElecCard_customerMachine'))
  16 + suite.addTest(eleccard_check.GetCardInfo('test_getCardInfo_success'))
  17 + return suite
  18 +
  19 +# 客机置为样机
  20 +def suites_setElecCardtoSample():
  21 + suite = unittest.TestSuite()
  22 + # 机器是客机状态
  23 + suite.addTest(eleccard_check.CheckElecCardBind('test_checkElecCard_customerMachine'))
  24 + # 置为样机
  25 + suite.addTest(eleccard_setUp.SetToSample('test_updateToSample_success'))
  26 + # 检验判断为客机
  27 + suite.addTest(eleccard_check.CheckElecCardBind('test_checkElecCard_sampleMachine'))
  28 + return suite
  29 +
  30 +# 样机置为客机
  31 +def suites_setElecCardtoCustomer():
  32 + suite = unittest.TestSuite()
  33 + # 机器是样机状态
  34 + suite.addTest(eleccard_check.CheckElecCardBind('test_checkElecCard_sampleMachine'))
  35 + # 置为客机
  36 + suite.addTest(eleccard_setUp.SetToCustomerMachine('test_updateToCustomer_success'))
  37 + # 检查需要添加保卡
  38 + suite.addTest(eleccard_check.CheckElecCardBind('test_checkElecCard_unbind'))
  39 +
  40 +# 客机修改保卡
  41 +
  42 +
  43 +# 客机解绑保卡
0 44 \ No newline at end of file
... ...