QA工具开发流程

前言

在项目上线前期,这边根据需求制作了一套QA测试工具。主要分为以下四个模块的测试
dfed7bc872f5d5286fe4bb2a997fd14.png
**图1**

  • **数值测试:**主要包括了角色的等级变更、游戏里货币的变更、(目前已制作的)游戏道具的数量变更。这些可能归一为一类测试模型
  • **动画测试:**包括角色的控制系统的所有Animation资源的播放状态【目前无测试需求】
  • **流程测试:**比如是否需要快速胜利、跳过新手指引、指定比赛胜利类型(胜负、平局)等等一系列流程。
  • **自定测试:**笔者目前没有想到的,可能出现的其他需要测试的分类。

工具架构

主菜单顶部横栏

如图1所示,主菜单是横向布局,静态显示的。

using System.Collections.Generic;
using JetBrains.Annotations;
using QAModule;
using UnityEngine;
using UnityEngine.UI;
using TEngine;

namespace GameLogic.UI
{
    [Window(UILayer.UI)]
    public class QAMainPageUI : UIWindow
    {
        //缓存池对象
        private QAOptionPanel _optionPanelInBuffer;
        private List<TestOption> _optionsList;
        //菜单选项条目
        private Dictionary<TestType, string[]> _menuDictionary; 

        #region 脚本工具生成的代码
        private Image m_imgBg;
        private GameObject m_goOptionPanel;
        private GameObject m_goTestNameRoot;
        private Button m_btnNumericalTest;
        private Button m_btnAnimationTest;
        private Button m_btnProcessTest;
        private Button m_btnBack;
        public override void ScriptGenerator()
        {
            m_imgBg = FindChildComponent<Image>("m_imgBg");
            m_goOptionPanel = FindChild("m_goOptionPanel").gameObject;
            m_goTestNameRoot = FindChild("m_goTestNameRoot").gameObject;
            m_btnBack = FindChildComponent<Button>("m_btnBack");
            m_btnNumericalTest = FindChildComponent<Button>("m_goTestNameRoot/m_btnNumericalTest");
            m_btnAnimationTest = FindChildComponent<Button>("m_goTestNameRoot/m_btnAnimationTest");
            m_btnProcessTest = FindChildComponent<Button>("m_goTestNameRoot/m_btnProcessTest");
            m_btnBack.onClick.AddListener(OnClickBackBtn);
            m_btnNumericalTest.onClick.AddListener(OnClickNumericalTestBtn);
            m_btnAnimationTest.onClick.AddListener(OnClickAnimationTestBtn);
            m_btnProcessTest.onClick.AddListener(OnClickProcessTestBtn);
        }
       
        #endregion
        
        public override void OnCreate()
        {
            base.OnCreate();
            Initialize();
        }

        private void Initialize()
        {
            _optionsList = new List<TestOption>();
            _menuDictionary = new Dictionary<TestType, string[]>();
            QAInitDataTable dataTable = new QAInitDataTable();
            _menuDictionary = dataTable.MenuDictionary;
        }

        /// <summary>
        /// 根据选项展开面板
        /// </summary>
        /// <param name="index"></param>
        private void OpenPanel(TestType type)
        {
            int index = (int)type;
            m_goOptionPanel.SetActive(true);
            m_imgBg.enabled = false;
            //对应属性高亮
            int indexCounts = m_goTestNameRoot.transform.childCount;
            List<Transform>  childrenTrans= m_goTestNameRoot.transform.GetAllChildren();
            for (int i = 0; i < indexCounts; i++)
            {
                var select = childrenTrans[i].Find("Selected").gameObject;
                if (select != null)
                {
                    if (index == i)
                    {
                        select.SetActive(true);
                    }
                    else
                    {
                        if (select.activeInHierarchy)
                        {
                            select.SetActive(false);
                        }
                    }
                }
            }
            //创建面板
            _optionPanelInBuffer ??= CreateWidgetByPath<QAOptionPanel>(m_goOptionPanel.transform, "QAOptionPanel");
            //读取缓存池,刷新选项内容
            _optionPanelInBuffer.Init(_optionsList,type,_menuDictionary[type]);
        }
        
        /// <summary>
        /// 数值类型测试
        /// </summary>
        private void OnClickNumericalTestBtn()
        {
            OpenPanel(TestType.NumericalType);
        }
        /// <summary>
        /// 动画类型测试
        /// </summary>
        private void OnClickAnimationTestBtn()
        {
            OpenPanel(TestType.AnimationType);

        }
        /// <summary>
        /// 流程类型测试
        /// </summary>
        private void OnClickProcessTestBtn()
        {
            OpenPanel(TestType.ProcessType);
        }

        private void OnClickBackBtn()
        {
            if (m_goOptionPanel.activeInHierarchy)
            {
                m_goOptionPanel.SetActive(false);
                m_imgBg.enabled = true;
            }
            else
            {
                GameModule.UI.CloseWindow<QAMainPageUI>();
            }
        }
    }
}

背包面板

点击顶部菜单按钮提示,展开二级选择面板。根据考虑,我选择了类似背包面板的展示模式。
3dc37fa46d9a64dd5c170e2e4cbc43c.png
在面板中通过网格布局,创建需要的测试条目。
面板切换时,使用了一个缓存池做优化。
首次创建时选项的预制体加入缓存池,如果切换面板只需更新UI、更换打开的工作流即可。

缓存池

        /// <summary>
        /// 创建面板里的选项
        /// </summary>
        /// 根据TestType类型创建条目,每个条目已经绑定了打开的显示逻辑
        public void Init(List<TestOption> optionList,TestType type,string[] optionType)
        {
            int typeCounts = optionType.Length;
            int bufferCounts = optionList.Count;
            //缓存池中数量小于需创建的数量,重复部分刷新值,多余部分创建并入池子。
            if (bufferCounts < typeCounts)
            {
                for (int index = 0; index < typeCounts; index++)
                {
                    if (index < bufferCounts)
                    {
                        if (!optionList[index].gameObject.activeInHierarchy)
                        {
                            optionList[index].gameObject.SetActive(true);
                        }
                        optionList[index].Initialize(index,type,optionType[index]);    
                    }
                    else
                    {
                        var testOption = CreateWidgetByPath<TestOption>(m_goContent.transform, "TestOption");
                        testOption.Initialize(index,type,optionType[index]);
                        optionList.Add(testOption);
                    }
                }
            }
            //缓存池中数量大于等于需创建的数量,读取池子刷新内容,多余部分隐藏。
            else
            {
                for (int i = 0; i < bufferCounts; i++)
                {
                    if (i < typeCounts)
                    {
                        optionList[i].Initialize(i,type,optionType[i]);   
                        if (!optionList[i].gameObject.activeInHierarchy)
                        {
                            optionList[i].gameObject.SetActive(true);
                        }
                    }
                    else
                    {
                        optionList[i].gameObject.SetActive(false);
                    }    
                }
            }
        }

具体测试面板

点击进入具体测试面板时,对于面板笔者是这么规划的。

数据类

既然测试的大类型分为了四类,那么自然每个类型都应该有不同的初始化数据
image.png
图2
在面板中,红框的部分是**派生的预制体持有的,**剩余部分应该是每种类型都应该显示的了,那就是标题
以数值类型测试为例,数据脚本如下

namespace QAModule
{
    //基础数据类型存储结构
    public class QABaseData
    {
        public string TestType
        {
            get => _testType;
            set => _testType = value;
        }

        private string _testType;

    }
}
namespace QAModule
{
    /// <summary>
    /// 数值类型字段存储结构
    /// </summary>
    public class QANumericalData : QABaseData
    {
        public string InitDisplayValue
        {
            get => _initDisplayValue;
            set => _initDisplayValue = value;
        }

        public float IncrementRate
        {
            get => _incrementRate;
            set => _incrementRate = value;
        }

        public float DecrementRate
        {
            get => _decrementRate;
            set => _decrementRate = value;
        }

        private string _initDisplayValue;
        private float _incrementRate;
        private float _decrementRate;

    }     
}

物体脚本

那么实现的脚本至少有两层

using GameLogic.UI.QAEvent;
using UnityEngine.UI;
using TEngine;
using QAModule;
using UnityEngine;

namespace GameLogic.UI
{
	[Window(UILayer.UI)]
    public class QAPanelBase<T> :UIWindow where T : QAPanelBase<T>
    {
	    //需要记忆存储的参数
        protected static string _testType;
        
        #region 脚本工具生成的代码
        protected Text m_textType;
        private Button m_btnBack;
        public override void ScriptGenerator()
        {
	        m_textType = FindChildComponent<Text>("Title/m_textType");
	        m_btnBack = FindChildComponent<Button>("m_btnBack");
	        m_btnBack.onClick.AddListener(OnClickBackBtn);
        }
        #endregion
        public override void RegisterEvent()
        {
	        base.RegisterEvent();
	        AddUIEvent<QABaseData>(QAEventDefine.StartWorkflow,OnStartWorkflow);
        }

        protected virtual void InitData(QABaseData data)
        {
	       
        }

        private void CreateWorkflow() //确定工作流,软件模型:瀑布模型
        {
            ReadDataFromMemory();//1.
	        AddListener();
	        InitPanel();
        }

		protected virtual void ReadDataFromMemory()
        {
        }
        protected virtual void AddListener(){}
        protected virtual void InitPanel()
        {
        }
        #region 事件

        private void OnStartWorkflow(QABaseData data)
        {
	        InitData(data);
	        CreateWorkflow();
        }
        
        protected virtual void OnClickBackBtn()
        {
	        //打开主界面
	        Debug.Log("back from base");
	        GameModule.UI.ShowUI<QAMainPageUI>();
        }

        #endregion 
    }
}
using QAModule;
using UnityEngine;
using UnityEngine.UI;
using TEngine;

namespace GameLogic.UI
{
	[Window(UILayer.UI)]
    public class QAPanelNumerical : QAPanelBase<QAPanelNumerical>
    {
	    protected QANumericalData _numericalData;
	    //需要记忆存储的参数
        protected static float _increment;
        protected static float _decrement;
        
        protected static float _incrementRate;
        protected static float _decrementRate;
        // _increment = _incrementRate * _addSliderValue
      
        private static float _incrementSliderValue;
        private static float _decrementSliderValue;
        private static string _displayValue;

        
		#region 脚本工具生成的代码
		protected GameObject m_goAdd;
		protected GameObject m_goMinus;
		private Text m_textDisplayType;
		private Text m_textDisplayValue;
		protected InputField m_inputAddInputField;
		private Text m_textIncrement;
		protected Slider m_sliderAddValues;
		private Button m_btnAddValues;
		protected InputField m_inputMinusInputField ;
		private Text m_textDecrement;
		protected Slider m_sliderMinusValues ;
		private Button m_btnMinusValues;
		public override void ScriptGenerator()
		{
			base.ScriptGenerator();
			m_goAdd = FindChild("ControlZone/m_goAdd").gameObject;
			m_goMinus = FindChild("ControlZone/m_goMinus").gameObject;
			m_textDisplayType = FindChildComponent<Text>("DisplayZone/DisplayBg/m_textDisplayType");
			m_textDisplayValue = FindChildComponent<Text>("DisplayZone/DisplayBorder/m_textDisplayValue");
			m_inputAddInputField = FindChildComponent<InputField>("ControlZone/m_goAdd/m_inputAddInputField");
			m_textIncrement = FindChildComponent<Text>("ControlZone/m_goAdd/m_inputAddInputField/m_textIncrement");
			m_sliderAddValues = FindChildComponent<Slider>("ControlZone/m_goAdd/m_sliderAddValues");
			m_btnAddValues = FindChildComponent<Button>("ControlZone/m_goAdd/m_btnAddValues");
			m_inputMinusInputField  = FindChildComponent<InputField>("ControlZone/m_goMinus/m_inputMinusInputField ");
			m_textDecrement = FindChildComponent<Text>("ControlZone/m_goMinus/m_inputMinusInputField /m_textDecrement");
			m_sliderMinusValues  = FindChildComponent<Slider>("ControlZone/m_goMinus/m_sliderMinusValues ");
			m_btnMinusValues = FindChildComponent<Button>("ControlZone/m_goMinus/m_btnMinusValues");
			m_sliderAddValues.onValueChanged.AddListener(OnSliderAddValuesChange);
			m_btnAddValues.onClick.AddListener(OnClickAddValuesBtn);
			m_sliderMinusValues .onValueChanged.AddListener(OnSliderMinusValuesChange);
			m_btnMinusValues.onClick.AddListener(OnClickMinusValuesBtn);
		}
		#endregion

		protected override void InitData(QABaseData data)
		{
			base.InitData(data);
			_numericalData  = data as QANumericalData;
			m_textType.text = "Test - " +_numericalData?.TestType;
			_displayValue = _numericalData?.InitDisplayValue;
			if (_numericalData != null) _incrementRate = _numericalData.IncrementRate;
			if (_numericalData != null) _decrementRate = _numericalData.DecrementRate;
		}

		protected override void ReadDataFromMemory()
        {
	        m_sliderAddValues.value = _incrementSliderValue == 0 ? 1 : _incrementSliderValue;
	        m_sliderMinusValues.value = _decrementSliderValue == 0 ? 1 : _decrementSliderValue;
        }
        protected override void AddListener()
        {
	        m_inputAddInputField.onValueChanged.AddListener(OnInputAddField);
	        m_inputMinusInputField.onValueChanged.AddListener(OnInputMinusField);
        }

        protected override void InitPanel()
        {
	        //是否是初始数据。是,表中获取。否,用上次设过值的
	        if (_incrementRate == 0 && _decrementRate == 0)
	        {
		        if (_numericalData != null)
		        {
			        _incrementRate = _numericalData.IncrementRate;
			        _decrementRate = _numericalData.DecrementRate;
		        }
	        }
	        _increment = _incrementRate * m_sliderAddValues.value;
	        _decrement = _decrementRate * m_sliderMinusValues.value;
            
	        //初始化面板显示
	        
	        m_textDisplayType.text = "Current" + _testType;
	        m_textIncrement.text = $"Increment:{_increment}";
	        m_textDecrement.text = $"Decrement:{_decrement}";
	        if (_displayValue == null)
	        {
		        _displayValue = _numericalData?.InitDisplayValue;
		        m_textDisplayValue.text =  _numericalData?.InitDisplayValue;    
	        }
	        else
	        {
		        m_textDisplayValue.text = _displayValue;
	        }
	        
        }

        
        #region 事件
        /// <summary>
        /// 设置参数倍率
        /// </summary>
        /// <param name="value"></param>
        private void OnSliderAddValuesChange(float value)
        {
            _increment = value * _incrementRate;
            m_textIncrement.text = $"Increment:{_increment}";
            _incrementSliderValue = value;
        }
        protected virtual void OnClickAddValuesBtn()
        {
            //Change Panel
            float curValue = float.Parse(m_textDisplayValue.text);
            curValue += _increment ;
	       m_textDisplayValue.text = curValue.ToString();
	       _displayValue = curValue.ToString();
	       //Test Function
        }
        private void OnSliderMinusValuesChange(float value)
        {
            _decrement = value * _decrementRate;
            m_textDecrement.text = string.Format("Decrement:{0}", _decrement);
            _decrementSliderValue = value;
        }
        protected virtual void OnClickMinusValuesBtn()
        {
            //Change Panel表现
            float curValue = float.Parse(m_textDisplayValue.text);
            curValue -= _decrement ;
	        m_textDisplayValue.text = curValue.ToString();
            _displayValue = curValue.ToString();
            //Test Function
			//.....
        }
        
        /// <summary>
        /// 通过输入框自定义参数值
        /// </summary>
        private void OnInputMinusField(string inputParma)
        {
	        //更新倍率
	        _decrementRate = float.Parse(inputParma);
			//更新面板
            m_sliderMinusValues.value = 1;
			_decrement = _decrementRate * m_sliderMinusValues.value;
			m_textDecrement.text = string.Format("Decrement:{0}", _decrement);
			
        }

        private void OnInputAddField(string inputParma)
        {
	        _incrementRate = float.Parse(inputParma);
	        m_sliderAddValues.value = 1;
	        _increment = _incrementRate * m_sliderAddValues.value;
	        m_textIncrement.text = string.Format("Increment:{0}", _increment);
        }


        #endregion

    }
}

数值类型的面板如上图2,为了便于控制一次加减的值,我做了4档可输入计算器。可以根据倍率准确定位数值,做到对大小数值的便捷测试。

// _increment = _incrementRate * _addSliderValue
//实际值 = 倍率 * 滑动条的档数

数据和表现分离

所以,以上两层继承,完成了通过点击面板,完成UI界面数值的更替。
现在,需要把相关更替的数值注入到指定的数据集中【即:做出实际的测试功能】
那么只需要再让具体的XX测试 继承数值测试,然后读取相应字段,重写+ -按钮的回调函数,跟据读取的数值,做相应功能就行了。

using TEngine;
using GameLogic.DKSystem.Soical;
using UnityEngine;

namespace GameLogic.UI
{
    [Window(UILayer.UI,"QAPanelNumerical")]
    public class QAPanelGold : QAPanelNumerical
    {
        protected override void OnClickAddValuesBtn()
        {
            //数据
            base.OnClickAddValuesBtn();
            var gold = (int)_increment ;
            PlayerService.AddGold(gold);
            Debug.Log(string.Format("{0} + {1} success.", _testType, gold));
        }

        protected override void OnClickMinusValuesBtn()
        {
            base.OnClickMinusValuesBtn();
            var gold = (int)_decrement;
            PlayerService.AddGold(-gold);
            Debug.Log(string.Format("{0} - {1} success.", _testType, gold));
        }
        
        protected override void OnClickBackBtn()
        {
            base.OnClickBackBtn();
            GameModule.UI.CloseWindow<QAPanelGold>();
        }
    }
}
using TEngine;
using GameLogic.DKSystem.Soical;
using QAModule;
using UnityEngine;

namespace GameLogic.UI
{
    [Window(UILayer.UI,"QAPanelNumerical")]
    public class QAPanelExp : QAPanelNumerical
    {
        protected override void InitPanel()
        {
            base.InitPanel();
            m_goMinus.SetActive(false);
        }

        protected override void OnClickAddValuesBtn()
        {
            //数据
            base.OnClickAddValuesBtn();
            var exp = (int)_increment ;
            PlayerService.AddExp(exp);
            Debug.Log(string.Format("{0} + {1} success.", _testType, exp));
        }

        protected override void OnClickMinusValuesBtn()
        {
            base.OnClickMinusValuesBtn();
            var exp = (int)_decrement;
            PlayerService.AddExp(-exp);
            Debug.Log(string.Format("{0} - {1} success.", _testType, exp));
        }
        protected override void OnClickBackBtn()
        {
            base.OnClickBackBtn();
            GameModule.UI.CloseWindow<QAPanelExp>();
        }
    }
}
using GameLogic.DKSystem;
using TEngine;
using UnityEngine;

namespace GameLogic.UI
{
    [Window(UILayer.UI,"QAPanelNumerical")]
    public class QAPanelStar : QAPanelNumerical
    {
        protected override void InitPanel()
        {
            base.InitPanel();
            m_inputAddInputField.gameObject.SetActive(false);
            m_inputMinusInputField.gameObject.SetActive(false);
            m_sliderAddValues.gameObject.SetActive(false);
            m_sliderMinusValues.gameObject.SetActive(false);
        }

        protected override void OnClickAddValuesBtn()
        {
            base.OnClickAddValuesBtn();
            AnswerRankService.PostSta(new StaData()
            {
                season_id = WikipediaQuizSystem.Instance.SeasonId,
                is_victory = 3
            },null);
            Debug.Log("增加1个星星");
        }

        protected override void OnClickMinusValuesBtn()
        {
            base.OnClickMinusValuesBtn();
            AnswerRankService.PostSta(new StaData()
            {
                season_id = WikipediaQuizSystem.Instance.SeasonId,
                is_victory = 1
            },null);
            Debug.Log("减少1个星星");
        }
        protected override void OnClickBackBtn()
        {
            base.OnClickBackBtn();
            GameModule.UI.CloseWindow<QAPanelStar>();
        }
    }
}

面板

using UnityEngine;
using UnityEngine.UI;
using TEngine;
using QAModule;

namespace GameLogic.UI
{
    [Window(UILayer.UI)]
    public class TestOption : UIWidget
    {
        private TestType _type;
        private int _optionIndex;
        private Button m_btnTestOption;
        #region 脚本工具生成的代码
        private Image m_imgBg;
        private Text m_textTestOption;
        public override void ScriptGenerator()
        {
            m_imgBg = FindChildComponent<Image>("m_imgBg");
            m_textTestOption = FindChildComponent<Text>("m_textTestOption");
        }
        #endregion
        /// <summary>
        /// 根据index查找对应测试的名称类型
        /// </summary>
        /// <param name="index"></param>
        /// <param name="type"></param>
        /// <param name="description"></param>
        public void Initialize(int index,TestType type,string description)
        {
            m_btnTestOption = gameObject.GetComponent<Button>();
            m_btnTestOption.onClick.AddListener(OnClickTestOptionBtn);
            
            _optionIndex = index;
            m_textTestOption.text = description;
            m_imgBg.color = Color.cyan;
        }

        private void InitWorkflow(int index,TestType type)
        {
            TestProcessManager.Instance.CurTestType = type;
            TestProcessManager.Instance.SelectTestProcess(index);
        }
        #region 事件
        private void OnClickTestOptionBtn()
        {
          InitWorkflow(_optionIndex,_type);
        }
       
        #endregion

    }
}

打开并创建界面的核心是 创建工作流、先初始化,再创建。方法在工作流管理器调用

单例的工作流管理器

using Aliyun.OSS;
using GameBase;
using UnityEngine;
using UnityEngine.UI;
using TEngine;
using QAModule;

namespace GameLogic.UI
{
    /// <summary>
    /// 测试项目的种类
    /// </summary>
    public enum TestType
    {
        NumericalType = 0, //数值类型测试
        AnimationType = 1, //动画类型测试
        ProcessType = 2,  // 流程类型测试
        ElseType = 3      //自定义测试类型
    }

    public class TestProcessManager : Singleton<TestProcessManager>
    {
        public TestType CurTestType;

        public void SelectTestProcess(int index)
        {
            switch (CurTestType)
            {
                case TestType.NumericalType:
                    NumericalProcessFlow numericalProcessFlow = new NumericalProcessFlow(index);
                    numericalProcessFlow.CreateTestPanel();
                    break;
                case TestType.AnimationType:
                    
                    break;
                case TestType.ProcessType:
                    
                    break;
            }
        }

        protected override void Initialize()
        {
        }

        protected override void UnInitialize()
        {
        }
    }
 
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/99592.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[管理与领导-60]:IT基层管理者 - 扩展技能 - 3 - 通过面试招到合适的人选

目录 前言&#xff1a; 一、招聘 1.1 什么是招聘 1.2 招聘 VS 招募 1.3 甄选 1.4 招聘中的重要原则 1.5 招聘的本质 1.6 人才匹配的维度 1.7 人员招聘中的误区 二、面试 2.1 何为面试 2.2 为什么面试 2.3 面试的注意事项 2.4 面试的误区 2.5 如何进行面试 前言…

lnmp架构-nginx

6.nginx基础配置 证书 重定向&#xff08;80重定向到443&#xff09; 当访问http时 直接到 https 自动索引&#xff1a; 下载方便 Nginx缓存配置 &#xff1a;缓存可以降低网站带宽&#xff0c;加速用户访问 日志轮询 禁用不必要的日志记录 以节省磁盘IO的消耗 监控的信息 监…

k8s的交付与部署案例操作

一 k8s的概念 1.1 k8s k8s是一个轻量级的&#xff0c;用于管理容器化应用和服务的平台。通过k8s能够进行应用的自动化部署和扩容缩容。 1.2 k8s核心部分 1.prod: 最小的部署单元&#xff1b;一组容器的集合&#xff1b;共享网络&#xff1b;生命周期是短暂的&#xff1b; …

Gazebo仿真环境下的强化学习实现

Gazebo仿真环境下的强化学习实现 主体源码参照《Goal-Driven Autonomous Exploration Through Deep Reinforcement Learning》 文章目录 Gazebo仿真环境下的强化学习实现1. 源码拉取2. 强化学习实现2.1 环境2.2 动作空间2.3 状态空间2.4 奖励空间2.5 TD3训练 3. 总结 1. 源码…

界面控件DevExpress WinForms(v23.2)下半年发展路线图

本文主要概述了官方在下半年&#xff08;v23.2&#xff09;中一些与DevExpress WinForms相关的开发计划&#xff0c;重点关注的领域将是可访问性支持和支持.NET 8。 DevExpress WinForms有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。同时能…

【爬虫】5.6 Selenium等待HTML元素

目录 任务目标 创建Ajax网站 创建服务器程序 Selenium XX 等待 1. Selenium强制等待 2. Selenium隐性等待 3. Selenium循环等待 4. Selenium显示等待 等待方法 任务目标 在浏览器加载网页的过程中&#xff0c;网页的有些元素时常会有延迟的现象&#xff0c;在HTML元素…

RCU501 RMP201-8 KONGSBERG 分布式处理单元

RCU501 RMP201-8 KONGSBERG 分布式处理单元 AutoChief600使用直接安装在主机接线盒中的分布式处理单元。进出发动机的所有信号都在双冗余CAN线路(发动机总线)上传输。 所有不重要的传感器都可以与K-Chief 600报警和监控系统共享&#xff0c;只需要一个主机接口。这一原则大大…

现浇钢筋混泥土楼板施工岗前安全VR实训更安全高效

建筑行业天天与钢筋混凝土砼在&#xff0c;安全施工便成了企业发展的头等大事。 当今社会&#xff0c;人人都奉行生命无价&#xff0c;安全至上。可工地安全事故频繁发生&#xff0c;吞噬掉多少宝贵生命。破坏了多小个家庭?痛定死痛&#xff0c;为了提高施工人员的安全意识。 …

jmeter+ant+jenkins接口自动化测试框架

大致思路&#xff1a;Jmeter可以做接口测试&#xff0c;也能做压力测试&#xff0c;而且是开源软件&#xff1b;Ant是基于Java的构建工具&#xff0c;完成脚本执行并收集结果生成报告&#xff0c;可以跨平台&#xff0c;Jenkins是持续集成工具。将这三者结合起来可以搭建一套We…

STM32f103入门(7)pwm驱动led驱动舵机驱动直流电机

PWM驱动 PWM介绍TIM_OC1Init 配置通道TIM_OCStructInit 输出比较参数默认值输出比较模式 TIM_OCInitstructure输出比较极性 TIM_OCInitstructure设置输出使能以下三个决定了PWM的频率 占空比初始化通道 TIM_OC1Init(TIM2, &TIM_OCInitstructure);GPIO复用 PWM通道 驱动LED复…

深度学习基础篇 第一章:卷积

dummy老弟这几天在复习啊我也跟着他重新复习一轮。 这次打算学的细一点&#xff0c;虽然对工作没什么帮助&#xff0c;但是理论知识也能更扎实吧&#xff01; 从0开始的深度学习大冒险。 参考教程&#xff1a; https://www.zhihu.com/question/22298352 https://zhuanlan.zhih…

[SpringBoot3]视图技术Thymeleaf

七、视图技术Thymeleaf Thymeleaf是一个表现层的模板引擎&#xff0c;一般被使用在Web环境中&#xff0c;它可以处理HTML、XML、JS等文档&#xff0c;简单来说&#xff0c;它可以将JSP作为Java Web应用的表现层&#xff0c;有能力展示与处理数据。这样&#xff0c;同一个模板文…

Java String类(1)

String类的重要性 我们之前在C语言中已经涉及到字符串了&#xff0c;但是在C语言中要表示字符串只能使用字符数组或者字符指针&#xff0c;可以使用标准库提供的字符串系列函数完成大部分操作&#xff0c;但是这种将数据和操作数据的方法分离开的方式不符合面向对象的思想&…

Leetcode: 1. 两数之和 【题解超详细】

前言 有人夜里挑灯看花&#xff0c;有人相爱&#xff0c;有人夜里开车看海&#xff0c;有人leetcode第一题都做不出来。 希望下面的题解可以帮助你们开始 你们的 leetcode 刷题 的 天降之路 题目 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中…

如何让看书变听书?

听书神器 安卓 页面简单&#xff0c;易操作&#xff0c;全网小说随便听 各种声音帮你读你喜欢听的小说&#xff0c;带你进入主人公世界 支持网页版小说、本地小说、图片&#xff0c;都能读给你听 想看小说&#xff0c;又怕伤眼睛的宝子&#xff0c;可以试试看&#xff01;…

四川玖璨电子商务有限公司:短视频账户运营

短视频账户运营&#xff0c;是指对短视频内容进行管理和推广的工作。随着社交媒体的兴起和短视频平台的流行&#xff0c;短视频账户运营已经成为了一种新兴的营销方式。对于企业、个人或组织来说&#xff0c;通过短视频账户运营&#xff0c;不仅可以提高品牌知名度&#xff0c;…

【RISC-V】RISC-V寄存器

一、通用寄存器 32位RISC-V体系结构提供32个32位的整型通用寄存器寄存器别名全称说明X0zero零寄存器可做源寄存器(rs)或目标寄存器(rd)X1ra链接寄存器保存函数返回地址X2sp栈指针寄存器指向栈的地址X3gp全局寄存器用于链接器松弛优化X4tp线程寄存器常用于在OS中保存指向进程控…

Java队列有哪些?8大常用Java队列总结

什么是队列? 队列是一种操作受限的线性表&#xff0c;只允许在表的前端&#xff08;front&#xff09;进行删除操作又称作出队&#xff0c;在表的后端进行插入操作&#xff0c;称为入队&#xff0c;符合先进先出&#xff08;First in First out&#xff09;的特性。在队尾插入…

javaee spring 测试aop 切面

切面类 package com.test.advice;import org.aspectj.lang.ProceedingJoinPoint;//增强类 public class MyAdvice {//将这个增强方法切入到service层的add方法前public void before(){System.out.println("添加用户之前");}}目标类 package com.test.service;publi…

如何在VR头显端实现低延迟的RTSP或RTMP播放

技术背景 VR&#xff08;虚拟现实技术&#xff09;给我们带来身临其境的视觉体验&#xff0c;广泛的应用于城市规划、教育培训、工业仿真、房地产、水利电力、室内设计、文旅、军事等众多领域&#xff0c;常用的行业比如&#xff1a; 教育行业&#xff1a;VR头显可以用于教育…