用 Pytest+Appium+Allure 做 UI 自动化测试的那些事儿
本文首发于 TesterHome 社区, 文末有福利 !链接 https://testerhome.com/topics/19327
前言
做 UI 自动化测试有段时间了,在 TesterHome
社区看了大量文章,也在网上搜集了不少资料,加上自己写代码、调试过程中摸索了很多东西,踩了不少坑,才有了这篇文章。希望能给做 UI
自动化测试小伙伴们提供些许帮助。
文本主要介绍用 Pytest+Allure+Appium 实现 UI
自动化测试过程中的一些好用的方法和避坑经验。文章可能有点干,看官们多喝水!O(∩_∩)O~
主要用了啥:
- Python3
- Appium
- Allure-pytest
- Pytest
Appium 不常见却好用的方法
1. Appium 直接执行 adb shell 方法
1. # Appium 启动时增加 --relaxed-security 参数 Appium 即可执行类似adb shell的方法2. 3. 2. > appium -p 4723 --relaxed-security4. 5. 6. 7. 8. 1. # 使用方法9. 10. 2. def adb_shell(self, command, args, includeStderr=False):11. 12. 3. """13. 14. 4. appium --relaxed-security 方式启动15. 16. 5. adb_shell('ps',['|','grep','android'])17. 18. 6. 19. 20. 21. 7. :param command:命令22. 23. 8. :param args:参数24. 25. 9. :param includeStderr: 为 True 则抛异常26. 27. 10. :return:28. 29. 11. """30. 31. 12. result = self.driver.execute_script('mobile: shell', {32. 33. 13. 'command': command,34. 35. 14. 'args': args,36. 37. 15. 'includeStderr': includeStderr,38. 39. 16. 'timeout': 500040. 41. 17. })42. 43. 18. return result['stdout']44. 45. 46.
2. Appium 直接截取元素图片的方法
1. element = self.driver.find_element_by_id('cn.xxxxxx:id/login_sign')2. 3. 2. pngbyte = element.screenshot_as_png4. 5. 3. image_data = BytesIO(pngbyte)6. 7. 4. img = Image.open(image_data)8. 9. 5. img.save('element.png')10. 11. 6. # 该方式能直接获取到登录按钮区域的截图12. 13. 14.
3. Appium 直接获取手机端日志
1. # 使用该方法后,手机端 logcat 缓存会清除归零,从新记录2. 3. 2. # 建议每条用例执行完执行一边清理,遇到错误再保存减少陈余 log 输出4. 5. 3. # Android6. 7. 4. logcat = self.driver.get_log('logcat')8. 9. 5. 10. 11. 12. 6. # iOS 需要安装 brew install libimobiledevice13. 14. 7. logcat = self.driver.get_log('syslog')15. 16. 8. 17. 18. 19. 9. # web 获取控制台日志20. 21. 10. logcat = self.driver.get_log('browser')22. 23. 11. 24. 25. 26. 12. c = '\n'.join([i['message'] for i in logcat])27. 28. 13. allure.attach(c, 'APPlog', allure.attachment_type.TEXT)29. 30. 14. #写入到 allure 测试报告中31. 32. 33.
4. Appium 直接与设备传输文件
1. # 发送文件2. 3. 2. #Android4. 5. 3. driver.push_file('/sdcard/element.png', source_path='D:\works\element.png')6. 7. 4. 8. 9. 10. 5. # 获取手机文件11. 12. 6. png = driver.pull_file('/sdcard/element.png')13. 14. 7. with open('element.png', 'wb') as png1:15. 16. 8. png1.write(base64.b64decode(png))17. 18. 9. 19. 20. 21. 10. # 获取手机文件夹,导出的是zip文件22. 23. 11. folder = driver.pull_folder('/sdcard/test')24. 25. 12. with open('test.zip', 'wb') as folder1:26. 27. 13. folder1.write(base64.b64decode(folder))28. 29. 14. 30. 31. 32. 15. # iOS33. 34. 16. # 需要安装 ifuse35. 36. 17. # > brew install ifuse 或者 > brew cask install osxfuse 或者 自行搜索安装方式37. 38. 18. 39. 40. 41. 19. driver.push_file('/Documents/xx/element.png', source_path='D:\works\element.png')42. 43. 20. 44. 45. 46. 21. # 向 App 沙盒中发送文件47. 48. 22. # iOS 8.3 之后需要应用开启 UIFileSharingEnabled 权限不然会报错49. 50. 23. bundleId = 'cn.xxx.xxx' # APP名字51. 52. 24. driver.push_file('@{bundleId}/Documents/xx/element.png'.format(bundleId=bundleId), source_path='D:\works\element.png')53. 54. 55.
Pytest 与 Unittest 初始化上的区别
很多人都使用过 Unitest,先说一下 Pytest 和 Unitest 在 Hook method上的一些区别:
1. Pytest 与 Unitest 类似,但有些许区别
以下是 Pytest
1. class TestExample:2. 3. 2. def setup(self):4. 5. 3. print("setup class:TestStuff")6. 7. 4. 8. 9. 10. 5. def teardown(self):11. 12. 6. print ("teardown class:TestStuff")13. 14. 7. 15. 16. 17. 8. def setup_class(cls):18. 19. 9. print ("setup_class class:%s" % cls.__name__)20. 21. 10. 22. 23. 24. 11. def teardown_class(cls):25. 26. 12. print ("teardown_class class:%s" % cls.__name__)27. 28. 13. 29. 30. 31. 14. def setup_method(self, method):32. 33. 15. print ("setup_method method:%s" % method.__name__)34. 35. 16. 36. 37. 38. 17. def teardown_method(self, method):39. 40. 18. print ("teardown_method method:%s" % method.__name__)41. 42. 43.
2. 使用 pytest.fixture()
1. @pytest.fixture()2. 3. 2. def driver_setup(request):4. 5. 3. request.instance.Action = DriverClient().init_driver('android')6. 7. 4. def driver_teardown():8. 9. 5. request.instance.Action.quit()10. 11. 6. request.addfinalizer(driver_teardown)12. 13. 14.
初始化实例
1. setup_class 方式调用
1. class Singleton(object):2. 3. 2. """单例4. 5. 3. ElementActions 为自己封装操作类"""6. 7. 4. Action = None8. 9. 5. 10. 11. 12. 6. def __new__(cls, *args, **kw):13. 14. 7. if not hasattr(cls, '_instance'):15. 16. 8. desired_caps={}17. 18. 9. host = "http://localhost:4723/wd/hub"19. 20. 10. driver = webdriver.Remote(host, desired_caps)21. 22. 11. Action = ElementActions(driver, desired_caps)23. 24. 12. orig = super(Singleton, cls)25. 26. 13. cls._instance = orig.__new__(cls, *args, **kw)27. 28. 14. cls._instance.Action = Action29. 30. 15. return cls._instance31. 32. 16. 33. 34. 35. 17. class DriverClient(Singleton):36. 37. 18. pass38. 39. 40.
测试用例中调用
1. class TestExample:2. 3. 2. def setup_class(cls):4. 5. 3. cls.Action = DriverClient().Action6. 7. 4. 8. 9. 10. 5. def teardown_class(cls):11. 12. 6. cls.Action.clear()13. 14. 7. 15. 16. 17. 8. 18. 19. 20. 9. def test_demo(self)21. 22. 10. self.Action.driver.launch_app()23. 24. 11. self.Action.set_text('123')25. 26. 27.
2. pytest.fixture() 方式调用
1. class DriverClient():2. 3. 2. 4. 5. 6. 3. def init_driver(self,device_name):7. 8. 4. desired_caps={}9. 10. 5. host = "http://localhost:4723/wd/hub"11. 12. 6. driver = webdriver.Remote(host, desired_caps)13. 14. 7. Action = ElementActions(driver, desired_caps)15. 16. 8. return Action17. 18. 9. 19. 20. 21. 10. 22. 23. 24. 11. 25. 26. 27. 12. # 该函数需要放置在 conftest.py, pytest 运行时会自动拾取28. 29. 13. @pytest.fixture()30. 31. 14. def driver_setup(request):32. 33. 15. request.instance.Action = DriverClient().init_driver()34. 35. 16. def driver_teardown():36. 37. 17. request.instance.Action.clear()38. 39. 18. request.addfinalizer(driver_teardown)40. 41. 42.
测试用例中调用
1. #该装饰器会直接引入driver_setup函数2. 3. 2. @pytest.mark.usefixtures('driver_setup')4. 5. 3. class TestExample:6. 7. 4. 8. 9. 10. 5. def test_demo(self):11. 12. 6. self.Action.driver.launch_app()13. 14. 7. self.Action.set_text('123')15. 16. 17.
Pytest 参数化方法
1. 第一种方法 parametrize 装饰器参数化方法
1. @pytest.mark.parametrize(('kewords'), [(u"小明"), (u"小红"), (u"小白")])2. 3. 2. def test_kewords(self,kewords):4. 5. 3. print(kewords)6. 7. 4. 8. 9. 10. 5. # 多个参数11. 12. 6. @pytest.mark.parametrize("test_input,expected", [13. 14. 7. ("3+5", 8),15. 16. 8. ("2+4", 6),17. 18. 9. ("6*9", 42),19. 20. 10. ])21. 22. 11. def test_eval(test_input, expected):23. 24. 12. assert eval(test_input) == expected25. 26. 27.
2.第二种方法,使用 pytest hook 批量加参数化
1. # conftest.py2. 3. 2. def pytest_generate_tests(metafunc):4. 5. 3. """6. 7. 4. 使用 hook 给用例加加上参数8. 9. 5. metafunc.cls.params 对应类中的 params 参数10. 11. 6. 12. 13. 14. 7. """15. 16. 8. try:17. 18. 9. if metafunc.cls.params and metafunc.function.__name__ in metafunc.cls.params: ## 对应 TestClass params19. 20. 10. funcarglist = metafunc.cls.params[metafunc.function.__name__]21. 22. 11. argnames = list(funcarglist[0])23. 24. 12. metafunc.parametrize(argnames, [[funcargs[name] for name in argnames] for funcargs in funcarglist])25. 26. 13. except AttributeError:27. 28. 14. pass29. 30. 15. 31. 32. 33. 16. # test_demo.py34. 35. 17. class TestClass:36. 37. 18. """38. 39. 19. :params 对应 hook 中 metafunc.cls.params40. 41. 20. """42. 43. 21. # params = Parameterize('TestClass.yaml').getdata()44. 45. 22. 46. 47. 48. 23. params = {49. 50. 24. 'test_a': [{'a': 1, 'b': 2}, {'a': 1, 'b': 2}],51. 52. 25. 'test_b': [{'a': 1, 'b': 2}, {'a': 1, 'b': 2}],53. 54. 26. }55. 56. 27. def test_a(self, a, b):57. 58. 28. assert a == b59. 60. 29. def test_b(self, a, b):61. 62. 30. assert a == b63. 64. 65.
Pytest 用例依赖关系
使用 pytest-dependency 库可以创造依赖关系。
当上层用例没通过,后续依赖关系用例将直接跳过,可以跨 Class 类筛选。如果需要跨 .py
文件运行 需要将 site- packages/pytest_dependency.py
文件的
1. class DependencyManager(object):2. 3. 2. """Dependency manager, stores the results of tests.4. 5. 3. """6. 7. 4. 8. 9. 10. 5. ScopeCls = {'module':pytest.Module, 'session':pytest.Session}11. 12. 6. 13. 14. 15. 7. @classmethod16. 17. 8. def getManager(cls, item, scope='session'): # 这里修改成 session18. 19. 20.
如果
1. > pip install pytest-dependency2. 3. 4. 5. 6. 1. class TestExample(object):7. 8. 2. 9. 10. 11. 3. @pytest.mark.dependency()12. 13. 4. def test_a(self):14. 15. 5. assert False16. 17. 6. 18. 19. 20. 7. @pytest.mark.dependency()21. 22. 8. def test_b(self):23. 24. 9. assert False25. 26. 10. 27. 28. 29. 11. @pytest.mark.dependency(depends=["TestExample::test_a"])30. 31. 12. def test_c(self):32. 33. 13. # TestExample::test_a 没通过则不执行该条用例34. 35. 14. # 可以跨 Class 筛选36. 37. 15. print("Hello I am in test_c")38. 39. 16. 40. 41. 42. 17. @pytest.mark.dependency(depends=["TestExample::test_a","TestExample::test_b"])43. 44. 18. def test_d(self):45. 46. 19. print("Hello I am in test_d")47. 48. 49. 50. 51. 1. pytest -v test_demo.py52. 53. 2. 2 failed54. 55. 3. - test_1.py:6 TestExample.test_a56. 57. 4. - test_1.py:10 TestExample.test_b58. 59. 5. 2 skipped60. 61. 62.
Pytest 自定义标记,执行用例筛选作用
1. 使用 @pytest.mark 模块给类或者函数加上标记,用于执行用例时进行筛选
1. @pytest.mark.webtest2. 3. 2. def test_webtest():4. 5. 3. pass 6. 7. 4. 8. 9. 10. 5. 11. 12. 13. 6. @pytest.mark.apitest14. 15. 7. class TestExample(object):16. 17. 8. def test_a(self):18. 19. 9. pass20. 21. 10. 22. 23. 24. 11. @pytest.mark.httptest25. 26. 12. def test_b(self):27. 28. 13. pass29. 30. 31.
仅执行标记 webtest 的用例
1. pytest -v -m webtest2. 3. 2. 4. 5. 6. 3. Results (0.03s):7. 8. 4. 1 passed9. 10. 5. 2 deselected11. 12. 13.
执行标记多条用例
1. pytest -v -m "webtest or apitest"2. 3. 2. 4. 5. 6. 3. Results (0.05s):7. 8. 4. 3 passed9. 10. 11.
仅不执行标记 webtest 的用例
1. pytest -v -m "not webtest"2. 3. 2. 4. 5. 6. 3. Results (0.04s):7. 8. 4. 2 passed9. 10. 5. 1 deselected11. 12. 13.
不执行标记多条用例
1. pytest -v -m "not webtest and not apitest"2. 3. 2. 4. 5. 6. 3. Results (0.02s):7. 8. 4. 3 deselected9. 10. 11.
2. 根据 test 节点选择用例
1. pytest -v Test_example.py::TestClass::test_a2. 3. 2. pytest -v Test_example.py::TestClass4. 5. 3. pytest -v Test_example.py Test_example2.py6. 7. 8.
3. 使用 pytest hook 批量标记用例
1. # conftet.py2. 3. 2. 4. 5. 6. 3. def pytest_collection_modifyitems(items):7. 8. 4. """9. 10. 5. 获取每个函数名字,当用例中含有该字符则打上标记11. 12. 6. """13. 14. 7. for item in items:15. 16. 8. if "http" in item.nodeid:17. 18. 9. item.add_marker(pytest.mark.http)19. 20. 10. elif "api" in item.nodeid:21. 22. 11. item.add_marker(pytest.mark.api)23. 24. 25. 26. 27. 1. class TestExample(object):28. 29. 2. def test_api_1(self):30. 31. 3. pass32. 33. 4. 34. 35. 36. 5. def test_api_2(self):37. 38. 6. pass39. 40. 7. 41. 42. 43. 8. def test_http_1(self):44. 45. 9. pass46. 47. 10. 48. 49. 50. 11. def test_http_2(self):51. 52. 12. pass53. 54. 13. def test_demo(self):55. 56. 14. pass57. 58. 59.
仅执行标记 API 的用例
1. pytest -v -m api2. 3. 2. Results (0.03s):4. 5. 3. 2 passed6. 7. 4. 3 deselected8. 9. 5. 可以看到使用批量标记之后,测试用例中只执行了带有 api 的方法10. 11. 12.
用例错误处理截图,App 日志等
1. 第一种使用 python 函数装饰器方法
1. def monitorapp(function):2. 3. 2. """4. 5. 3. 用例装饰器,截图,日志,是否跳过等6. 7. 4. 获取系统log,Android logcat、ios 使用syslog8. 9. 5. """10. 11. 6. 12. 13. 14. 7. @wraps(function)15. 16. 8. def wrapper(self, *args, **kwargs):17. 18. 9. try:19. 20. 10. allure.dynamic.description('用例开始时间:{}'.format(datetime.datetime.now()))21. 22. 11. function(self, *args, **kwargs)23. 24. 12. self.Action.driver.get_log('logcat')25. 26. 13. except Exception as E:27. 28. 14. f = self.Action.driver.get_screenshot_as_png()29. 30. 15. allure.attach(f, '失败截图', allure.attachment_type.PNG)31. 32. 16. logcat = self.Action.driver.get_log('logcat')33. 34. 17. c = '\n'.join([i['message'] for i in logcat])35. 36. 18. allure.attach(c, 'APPlog', allure.attachment_type.TEXT)37. 38. 19. raise E39. 40. 20. finally:41. 42. 21. if self.Action.get_app_pid() != self.Action.Apppid:43. 44. 22. raise Exception('设备进程 ID 变化,可能发生崩溃')45. 46. 23. return wrapper47. 48. 49.
2. 第二种使用 pytest hook 方法 (与方法1二选一)
1. @pytest.hookimpl(tryfirst=True, hookwrapper=True)2. 3. 2. def pytest_runtest_makereport(item, call):4. 5. 3. Action = DriverClient().Action6. 7. 4. outcome = yield8. 9. 5. rep = outcome.get_result()10. 11. 6. if rep.when == "call" and rep.failed:12. 13. 7. f = Action.driver.get_screenshot_as_png()14. 15. 8. allure.attach(f, '失败截图', allure.attachment_type.PNG)16. 17. 9. logcat = Action.driver.get_log('logcat')18. 19. 10. c = '\n'.join([i['message'] for i in logcat])20. 21. 11. allure.attach(c, 'APPlog', allure.attachment_type.TEXT)22. 23. 12. if Action.get_app_pid() != Action.apppid:24. 25. 13. raise Exception('设备进程 ID 变化,可能发生崩溃')26. 27. 28.
Pytest 另一些 hook 的使用方法
1. 自定义 Pytest 参数
1. > pytest -s -all2. 3. 4. 5. 6. 1. # content of conftest.py7. 8. 2. def pytest_addoption(parser):9. 10. 3. """11. 12. 4. 自定义参数13. 14. 5. """15. 16. 6. parser.addoption("--all", action="store_true",default="type1",help="run all combinations")17. 18. 7. 19. 20. 21. 8. def pytest_generate_tests(metafunc):22. 23. 9. if 'param' in metafunc.fixturenames:24. 25. 10. if metafunc.config.option.all: # 这里能获取到自定义参数26. 27. 11. paramlist = [1,2,3]28. 29. 12. else:30. 31. 13. paramlist = [1,2,4]32. 33. 14. metafunc.parametrize("param",paramlist) # 给用例加参数化34. 35. 15. 36. 37. 38. 16. # 怎么在测试用例中获取自定义参数呢39. 40. 17. # content of conftest.py41. 42. 18. def pytest_addoption(parser):43. 44. 19. """45. 46. 20. 自定义参数47. 48. 21. """49. 50. 22. parser.addoption("--cmdopt", action="store_true",default="type1",help="run all combinations")51. 52. 23. 53. 54. 55. 24. 56. 57. 58. 25. @pytest.fixture59. 60. 26. def cmdopt(request):61. 62. 27. return request.config.getoption("--cmdopt")63. 64. 28. 65. 66. 67. 29. 68. 69. 70. 30. # test_sample.py 测试用例中使用71. 72. 31. def test_sample(cmdopt):73. 74. 32. if cmdopt == "type1":75. 76. 33. print("first")77. 78. 34. elif cmdopt == "type2":79. 80. 35. print("second")81. 82. 36. assert 183. 84. 37. 85. 86. 87. 38. > pytest -q --cmdopt=type288. 89. 39. second90. 91. 40. .92. 93. 41. 1 passed in 0.09 seconds94. 95. 96.
2. Pytest 过滤测试目录
1. #过滤 pytest 需要执行的文件夹或者文件名字2. 3. 2. def pytest_ignore_collect(path,config):4. 5. 3. if 'logcat' in path.dirname:6. 7. 4. return True #返回 True 则该文件不执行8. 9. 10.
Pytest 一些常用方法
1. Pytest 用例优先级(比如优先登录什么的)
1. > pip install pytest-ordering2. 3. 4. 5. 6. 1. @pytest.mark.run(order=1)7. 8. 2. class TestExample:9. 10. 3. def test_a(self):11. 12. 13.
2. Pytest 用例失败重试
1. #原始方法2. 3. 2. pytet -s test_demo.py4. 5. 3. pytet -s --lf test_demo.py #第二次执行时,只会执行失败的用例6. 7. 4. pytet -s --ll test_demo.py #第二次执行时,会执行所有用例,但会优先执行失败用例8. 9. 5. #使用第三方插件10. 11. 6. pip install pytest-rerunfailures #使用插件12. 13. 7. pytest --reruns 2 # 失败case重试两次14. 15. 16.
3. Pytest 其他常用参数
1. pytest --maxfail=10 #失败超过10次则停止运行2. 3. 2. pytest -x test_demo.py #出现失败则停止4. 5. 6.
总结
以上,尽可能的汇总了常见的问题和好用的方法,希望对测试同学们有帮助!下一篇文章将计划讲解 用 Pytest hook 函数运行 yaml 文件来驱动
Appium 做自动化测试实战,并提供测试源码,敬请期待!
**-
来霍格沃兹测试开发学社,学习更多软件测试与测试开发的进阶技术,知识点涵盖web自动化测试 app自动化测试、接口自动化测试、测试框架、性能测试、安全测试、持续集成/持续交付/DevOps,测试左移、测试右移、精准测试、测试平台开发、测试管理等内容,课程技术涵盖bash、pytest、junit、selenium、appium、postman、requests、httprunner、jmeter、jenkins、docker、k8s、elk、sonarqube、jacoco、jvm-sandbox等相关技术,全面提升测试开发工程师的技术实力
QQ交流群:484590337
公众号 TestingStudio
视频资料领取:https://qrcode.testing-studio.com/f?from=CSDN&url=https://ceshiren.com/t/topic/15844
点击查看更多信息
用 Pytest+Appium+Allure 做 UI 自动化测试的那些事儿相关推荐
- 深圳软件测试培训:Pytest+Appium+Allure 做 UI 自动化的那些事
深圳软件测试培训:Pytest+Appium+Allure 做 UI 自动化的那些事 文本主要介绍下 Pytest+Allure+Appium 记录一些过程和经历. 法主要用了啥: Python3 A ...
- 【自动化测试】Pytest+Appium+Allure 做 UI 自动化的那些事
文本主要介绍下 Pytest+Allure+Appium 记录一些过程和经历. 法主要用了啥: Python3 Appium Allure-pytest Pytest Appium 不常见却好用的方法 ...
- UI自动化测试是什么?什么项目适合做UI自动化测试
目录 前言 1.about自动化测试 2.自动化测试分层 3.什么样的项目适合自动化测试 4.常见的自动化测试工具简介 5.UI自动化测试要不要做? 6.什么样的项目更适合做自动化测试 结语 前言 本 ...
- 基于Selenium与Pytest框架的Web UI自动化测试系统的设计与实现
摘要 随着互联网的高速发展,软件技术日新月异,产品更新换代的加快等,始终都离不开一个最核心的要素就是保证产品的质量,测试人员则在其中担任着不可或缺的角色.测试人员的主要工作职责就是通过各种测试手段去发 ...
- Appium PO模式UI自动化测试框架——设计与实践
阅读目录 1. 目的 2. 意义 3. 设计理念 4. PO模式 5. 框架设计 5.1 目录结构 5.2 实现步骤 5.3 具体实现 5.3.1 base部分 5.3.2 po部分 5.3.3 te ...
- 不会做UI自动化测试?一起设计框架再实践吧
目的 相信做过测试的同学都听说过自动化测试,而UI自动化无论何时对测试来说都是比较吸引人的存在. 相较于接口自动化来说它可以最大程度的模拟真实用户的日常操作与特定业务场景的模拟,那么存在即合理,自动化 ...
- Python+Behave+Allure Web UI自动化测试
基于BDD模式的Web UI自动化测试体验,集成了python,behave,allure,非主流的一个路线,可以一起玩玩. 1. 概念解释 Python: 大家都懂,3以上版本 Behave:行为驱 ...
- 今天清华学长手把手带你做UI自动化测试
互联网产品的迭代速度远高于传统软件,尤其是移动APP不但更新频繁,还需要在不同硬件.系统版本的环境下进行大量兼容测试,这就给传统测试方法和测试工具带来了巨大挑战.为满足产品敏捷开发.快速迭代的需求,自 ...
- 清华学长手把手带你做UI自动化测试
互联网产品的迭代速度远高于传统软件,尤其是移动APP不但更新频繁,还需要在不同硬件.系统版本的环境下进行大量兼容测试,这就给传统测试方法和测试工具带来了巨大挑战.为满足产品敏捷开发.快速迭代的需求,自 ...
最新文章
- 如何使用ui-router中的ui-sref将参数传递给控制器
- 【RecyclerView】 十二、RecyclerView 数据更新 ( 修改单条数据 | 批量修改数据 )
- Maven之搭建本地私服(nexus)仓库
- Flask-SQLAlchemy 中如何不区分大小写查询?
- [leetcode](4.21)4. 有效子数组的数目
- Win7系统桌面壁纸换不了怎么办
- 怎样使用navicat将mysql的数据表导出保存(转储SQL文件)
- 机器学习之--数据构造,函数图显示
- CSS 背景 background属性
- c语言 程序统计注释比例,C语言统计单词数量程序 超详解
- 【LeetCode】剑指 Offer 30. 包含min函数的栈
- 计算机中 堆 、栈、
- 【Java入门提高篇】Day26 Java容器类详解(八)HashSet源码分析
- WePhone网络电话灰色运营?专家:若无许可属非法
- 花了4000多的钱,领导让我去开8000多元的发票,我该怎么办??
- WinDBG技巧:this指针的常见误区 (ECX寄存器存放this指针)
- OPENSSL EVP_AES部分翻译
- 学会用打码平台处理验证码
- linux开源软件推荐,10个Linux平台开源ERP软件推荐
- 项目管理(PMP)项目风险管理