zoukankan      html  css  js  c++  java
  • TestComplete Tips

    请来信索取最新版本(Email:quicktest##qq.com( 请把##改为@ ))

    1、如何在TestComplete中用Jscript的类封装窗口定义

    参考:

    《TestComplete ( JScript ): Making windows definitions using wrapper classes》

    http://autotestgroup.com/en/blog/69.html

    2、如何控制鼠标进行拖拽操作?

    function DragDrop(obj, deltaX, deltaY)

    {

      var iX = obj.ScreenLeft + obj.Width/2;

      var iY = obj.ScreenTop + obj.Height/2;

      Log.Picture(obj.Picture(), "Object to be moved");

      obj = Sys.Desktop.ObjectFromPoint(iX + deltaX, iY + deltaY);

      Sys.Desktop.MouseDown(VK_LBUTTON, iX, iY);

      obj.HoverMouse(obj.Width/2, obj.Height/2);

      Sys.Desktop.MouseUp(VK_LBUTTON, iX + deltaX, iY + deltaY); 

    }

    function Test3()

    {

      var w1 = Sys.Process("Explorer").Window("Shell_TrayWnd").Window("ToolbarWindow32", "Quick Launch");

      DragDrop(w1, -30, -20);

    }

    参考:

    《TestComplete: when method Drag doesn't work》

    http://autotestgroup.com/en/blog/61.html

    3、如何在TestComplete中让Jscript的typeof返回“date”、“array”类型?

    function _typeof(value)

    {

      switch(typeof(value))

      { 

        case "string":

          return (value.match(/\d{1,2}[\/.-]\d{1,2}[\/.-]\d{2,4}/) != null) ? "date" : "string";

          break;

        case "object":

          try

          { value.join() }

          catch(e)

          {

            if(e.number == -2146827850)

              return "object"

            else

              throw e;

          }

          return "array";

          break;

        default:

          return typeof(value);

      } 

    }

    参考:

    《TestComplete:improving Jscript typeof operator》

    http://autotestgroup.com/en/blog/46.html

    4、如何在延迟脚本执行时显示剩余时间?

    function Sleep(iSeconds)

    {

     i = iSeconds;

     while(i > 0)

     {

        BuiltIn.Delay(500);

        Indicator.PushText("Delaying script execution for " + iSeconds + " seconds. " + i + " seconds left");

        BuiltIn.Delay(500);

        i -= 1;

     }

     Indicator.Clear();

    }

    参考:

    《TestComplete:delaying script execution》

    http://autotestgroup.com/en/blog/42.html

    5、如何定时检查某个窗口是否出现,如果出现则关闭它?

    // TestComplete JScript

    function KillCalculator()

    {

     if(Sys.WaitProcess("calc", 1, 1).Exists)

     {

        Log.Message("Calculator has been found");

        Sys.Process("calc").Terminate();

     }

    }

    function test_timer()

    {

     Utils.Timers.Add(500, "MyUnit.KillCalculator", true);

     BuiltIn.Delay(60000);

    }

    参考:

    《TestComplete:Closing Unexpected Windows》

    http://autotestgroup.com/en/blog/28.html

    6、如何用TC处理IE的文件下载窗口?

    function MegaClick( obj )

    {

       for( i = 1; i <= 2; i++ )

      {

         if( obj.Focused )

          obj.Parent.Keys( "[Tab]" );

         while( !obj.Focused )

          obj.Parent.Keys( "[Tab]" );

      }

      obj.Click();

    }

    参考:

    《A strange behavior of the File Download window》

    http://autotestgroup.com/en/blog/23.html

    7、TestComplete的ClickButton()方法

    录制按钮点击时一般TestComplete会生成ClickButton方法,例如:

    var w = Sys.Process("WindowsApplication3"). WinFormsObject("Form1");

    w.WinFormsObject("button1").ClickButton();

    建议替换成Click(),因为标准按钮可能会被替换成自定义的控件或第三方的控件,而使用了相同的名字,这种情况下ClickButton可能会工作不正常。

    参考:

    http://autotestgroup.com/en/blog/17.html

    8、在TestComplete中如何读写Excel?

    用Jscript可以这样写:

    function TestExcelClass()

    {

      // specifying excel file name and deleting it, if it is already exists

      var excel = new ExcelClass("C:\\temp.xls");

      if(Utilities.FileExists(excel.FileName))

      {

        Utilities.DeleteFile(excel.FileName);

      }

      // creating new Excel file with the specified name and predefined sheets

      var arrSheets = new Array("MySheet1", "MySheet2", "MySheet3", "MySheet4");

      excel.CreateFile(null, arrSheets);

      // writing data to the created Excel file by different ways

      excel.Write("MySheet1", 1, 1, "text in row 1, column 1");

      excel.Write("MySheet1", 1, "B", "text in row 1, column 'B'");

      excel.WriteCell("MySheet1", "C1", "text in cell 'C1'");

      // specifying data for "matrix writing"

      var arrRow1 = new Array("text1", "text2", "text3");

      var arrRow2 = new Array("text4", "text5", "text6");

      var arrMatrix = new Array(arrRow1, arrRow2);

      excel.WriteMatrix("MySheet1", 3, "A", arrMatrix);

      // reading written data using different methods

      Log.Message("Text in cell A1", excel.ReadCell("MySheet1", "A1"));

      Log.Message("Text in cell B1", excel.Read("MySheet1", 1, 2));

      Log.Message("Text in cell C1", excel.Read("MySheet1", 1, "C"));

      var arrNew1 = excel.ReadMatrix("MySheet1", 3, "A", 4, "C");

      Log.Message("Matrix data", arrNew1[0].join("  ") + "\n" + arrNew1[1].join("  "));

      var arrNew2 = excel.ReadCells("MySheet1", "A1", "C1", "A4", "C4");

      Log.Message("Data from several cells", arrNew2.join("\n"));

    }

    ExcelClass类的代码如下:

    // Automated Testing Service Group

    // Excel class

    // Global variables

    var oExcel = Sys.OleObject("Excel.Application");

    function ExcelClass(sFileName)

    {

      // - - - - variables

      var oWorkbook, oSheet, i;

      // - - - - properties

      this.FileName     = (sFileName == null? "" : sFileName);

      this.DefaultSheet = "Sheet1";

      this.MaxColumns   = 256;

      this.MaxRows      = 65536;

      oExcel.ScreenUpdating = false;

      //oExcel.DisplayAlerts = false;

      //oExcel.Interactive = false;

      // - - - - public methods

      this.CreateFile = function(sFileName, aSheets)

      {

        if(sFileName != null)

        { this.FileName = sFileName }

        if(Utilities.FileExists(this.FileName))

        {

          Log.Warning("File is already exist", "You are trying to create a new file which is already exist\n" + this.FileName);

          return true;

        }

        try

        {

          oWorkbook = oExcel.Workbooks.Add();

          if(aSheets != null)

          {

            Log.Message("Creating " + aSheets.length + " new sheets");

            if(aSheets.length > oWorkbook.Sheets.Count)

            {

                for(i = oWorkbook.Sheets.Count+1; i <= aSheets.length; i++)

                { oWorkbook.Sheets.Add(); }

            }

            for(i in aSheets)

            {

              oWorkbook.Sheets(Number(i)+1).Name = aSheets[i];

            }

          }

          oWorkbook.SaveAs(this.FileName);

          oWorkbook.Close();

          //oExcel.Quit();

        }

        catch(e)

        {

          Log.Error("An exception occured, see remarks", "Exception number: " + e.number + "\nException description: " + e.description)

        }

      }

      this.Write = function(sSheet, iRow, Col, value)

      {

        if(sSheet == null) sSheet = this.DefaultSheet;

        var iCol = typeof(Col) == "string" ? this.ConvertColName(Col) : Col;

        try

        {

          oWorkbook = oExcel.Workbooks.Open(this.FileName);

          oWorkbook.Sheets(sSheet).Cells(iRow, iCol).value = value;

          oWorkbook.Save();

          oWorkbook.Close();

        }

        catch(e)

        {

          Log.Error("An exception occured, see remarks", "Exception number: " + e.number + "\nException description: " + e.description);

        }

      }

      this.Read = function(sSheet, iRow, Col)

      {

        var iCol = typeof(Col) == "string" ? this.ConvertColName(Col) : Col;

        oWorkbook = oExcel.Workbooks.Open(this.FileName);

        var value = oWorkbook.Sheets(sSheet).Cells(iRow, iCol).value;

        oWorkbook.Close();

        return value;

      }

      this.ReadRow = function(sSheet, iRow, iMaxColumns)

      {

        var aRet = new Array();

        var i;

        if(iMaxColumns == null) iMaxColumns = this.MaxColumns;

        if(sSheet == null) sSheet = this.DefaultSheet;

        oWorkbook = oExcel.Workbooks.Open(this.FileName);

        for(i = 1; i <= iMaxColumns; i++)

        {

          aRet.push(oWorkbook.Sheets(sSheet).Cells(iRow, i).value);

        }

        oWorkbook.Close();

        return aRet;

      }

      this.ReadCol = function(sSheet, Col, iMaxRows)

      {

        var aRet = new Array();

        var i;

        if(iMaxRows == null) iMaxRows = this.MaxRows;

        if(sSheet == null) sSheet = this.DefaultSheet;

        var iCol = typeof(Col) == "string" ? this.ConvertColName(Col) : Col;

        oWorkbook = oExcel.Workbooks.Open(this.FileName);

        for(i = 1; i <= iMaxRows; i++)

        {

          aRet.push(oWorkbook.Sheets(sSheet).Cells(i, iCol).value);

        }

        oWorkbook.Close();

        return aRet;

      }

      //

      this.ReadMatrix = function(sSheet, iRowStart, ColStart, iRowEnd, ColEnd)

      {

        var i, j;

        var arr  = new Array();

        var aRet = new Array();

        var iColStart = typeof(ColStart) == "string" ? this.ConvertColName(ColStart) : ColStart;

        var iColEnd = typeof(ColEnd) == "string" ? this.ConvertColName(ColEnd) : ColEnd;

        oWorkbook = oExcel.Workbooks.Open(this.FileName);

        for(i = iRowStart; i <= iRowEnd; i++)

        {

          for(j = iColStart; j <= iColEnd; j++)

          {

            arr.push(oWorkbook.Sheets(sSheet).Cells(i, j).value);

          }

          aRet.push(arr);

          arr = new Array();

        }

        oWorkbook.Close();

        return aRet;

      }

      this.WriteMatrix = function(sSheet, iRowStart, ColStart, aData)

      {

        var i, j;

        var iColStart = typeof(ColStart) == "string" ? this.ConvertColName(ColStart) : ColStart;

        var iCol = iColStart;

        var iRow = iRowStart;

        oWorkbook = oExcel.Workbooks.Open(this.FileName);

        for(i = 0; i < aData.length; i++)

        {

          for(j = 0; j < aData[i].length; j++)

          {

            oWorkbook.Sheets(sSheet).Cells(iRow, iCol).value = aData[i][j];

            iCol++;

          }

          iCol = iColStart;

          iRow++;

        }

        oWorkbook.Save();

        oWorkbook.Close();

      }

      //

      this.ReadCells = function(sSheet)

      {

        var aRet = new Array();

        var i;

        if(sSheet == null) sSheet = this.DefaultSheet;

        for(i = 1; i < arguments.length; i++)

        {

          aRet.push(this.Read(sSheet, this.ConvertCellName(arguments[i]).row, this.ConvertCellName(arguments[i]).col));

        }

        return aRet;

      }

      //

      this.WriteCell = function(sSheet, sCell, value)

      {

        var arr = this.ConvertCellName(sCell);

        this.Write(sSheet, arr.row, arr.col, value);

      }

      //

      this.ReadCell = function(sSheet, sCell)

      {

        var arr = this.ConvertCellName(sCell);

        return this.Read(sSheet, arr.row, arr.col);

      }

      // - - - - private methods

      this.ConvertCellName = function(sCellName)

      {

        var aRet = new Array();

        var i;

        for(i = 0; i < sCellName.length; i++)

        {

          if(sCellName.charCodeAt(i) < 64)

          {

            aRet.row = sCellName.substr(i, sCellName.length);

            aRet.col = this.ConvertColName(sCellName.substr(0, i));

            break;

          }

        }

        return aRet;

      }

      this.ConvertColName = function(sColName)

      {

        var iCol = 0;

        switch(sColName.length)

        {

          case 1:

            iCol = sColName.toUpperCase().charCodeAt(0) - 64;

            break;

          case 2:

            iCol = (sColName.toUpperCase().charCodeAt(0) - 64)*26 + (sColName.toUpperCase().charCodeAt(1) - 64);

            break;

          case 3:

            iCol = (sColName.toUpperCase().charCodeAt(0) - 64)*676 + (sColName.toUpperCase().charCodeAt(1) - 64)*26 + (sColName.toUpperCase().charCodeAt(2) - 64);

            break;

          case 4:

            iCol = (sColName.toUpperCase().charCodeAt(0) - 64)*17576 + (sColName.toUpperCase().charCodeAt(0) - 64)*676 + (sColName.toUpperCase().charCodeAt(1) - 64)*26 + (sColName.toUpperCase().charCodeAt(2) - 64);

            break;

          default:

            Log.Error("Column name '" + sColName + "' cannot be converted");

            break;

        }

        return iCol;

      }

    }

    参考:

    http://autotestgroup.com/en/materials/16.html

    9、在用TestComplete测试时如何自动统计代码覆盖率?

    可以结合Ncover进行代码覆盖率的自动统计

    参考:

    http://docs.ncover.com/how-to/running-ncover-with-your-unit-testing-framework/testcomplete/

    10、用Exists属性判断窗口是否存在时,如果窗口不存在会自动写入一条错误日志,如何避免,改为写入自己的日志?

    用Exists属性判断窗口是否存在:

      Sys["Process"]("flight4a")["Window"]("#32770","Login", 1)["Exists"]

    会自动写入错误日志:

    Cannot obtain the window with the windowclass '#32770', window caption 'Login' and index 1.

    改用WaitWindow:

    function Test1()

    {

      loginWindow = Sys["Process"]("flight4a")["WaitWindow"]("#32770","Login", 1)

      if(loginWindow["Exists"])

        Log["Message"]("Exists");

      else 

        Log["Error"]("NotExists")

    }

    11、加快TestComplete执行速度的Tips

    是什么原因导致TestComplete的脚本执行速度不够快呢?这要从TestComplete的3个方面进行了解:

    1、TC在查找对象树时,如果对象不存在,TC会等待一段时间看对象是否出现。

    2、找到对象后,TC将分析对象的类型,然后给对象附加各种可能的方法和属性,这可能要消耗很多的时间,例如对于一个.NET控件对象,除了提取普通的属性和方法外,TC还会看控件对象是否暴露了MSAA、UI Automation等方面的属性和方法,有的话也会一并添加。

    3、有时候TC的脚本执行速度会受到界面控件性能的影响,例如展开一个TreeView控件时,如果Item比较多,而且使用了一些动态效果来重画的话,则TC要等待这些动作完成才能执行下一条语句。

    知道了TC的执行效率受制约的因素后,我们就可以考虑从以下几个方面对TC进行“加速”

    1. 把TC中有些不需要的插件屏蔽掉。

    2. 过滤掉那些不需要使用的应用程序进程。

    3. 如果不需要的话,请把Debug Agent关闭。

    4. 优化Wait选项和方法的使用。

    5. 调整Delay选项。

    6. 在操作系统中调整Double-Click的速度。

    7. 关闭Windows的界面视觉效果。

    8. 使用其他方法替代模拟用户操作,例如用Keys替代Sys.DeskTop.KeyDown和Sys.DeskTop.KeyUp。

    9. 优化low-level 的录制脚本,剔除一些无用的操作和延时。

    参考:http://www.automatedqa.com/techpapers/test_playback_performance_tips.asp

    12、CheckPoint

    Tip 3 - Checkpoints

    As you’re recording your tests,you’ll want to verify that your application is behaving properly.TestComplete’s Checkpoints verify that: information has rendered properly onscreen; information has been successfully written to a database; a web servicereturns the correct value; and much more.

    In Keyword test mode, there’s acheckpoint operations panel that lists all available checkpoints. Simply dragthe desired checkpoint onto the test panel and follow the prompts provided bythe wizard that’s displayed.

    If you’re not sure which checkpointyou should use, there’s a CheckpointWizard that asks a series of simple questions to help youdecide which checkpoint is right for a given test. A series of videos thatdescribe some of TestComplete’s checkpoints can be seen here:
    http://www.automatedqa.com/products/testcomplete/screencasts/

    13、如何读写COM端口?

    Sub TestCOMPort

     Const ForWriting = 2, TriStateFalse = 0

      Dimfso, f

      Setfso = CreateObject("Scripting.FileSystemObject")

      Setf = fso.OpenTextFile("COM1:", ForWriting, False, TriStateFalse)

      'Write data to the port

     f.Write Chr(26)

     f.Write Chr(32)

     f.Write Chr(27)

     f.Close

    End Sub

    or

    Sub Test
      Dim Port, i, s

      Set Port =dotNET.System_IO_Ports.SerialPort.zctor_4("COM1", 9600)
      Port.Open

      ' Writing data to the port
      Port.Write "A " & Chr(27)
      ' Waiting for response
      aqUtils.Delay 1000
      ' Processing response
      If Port.BytesToRead <> 0 Then
        s = Port.ReadExisting
        Log.Message s
      Else
        Log.Warning "No data"
      End If

      Port.Close
    End Sub

    14、如何表示十六进制?

    VBScript中可以用类似&HFF的格式表示十六进制数

    15、怎么判断一个对象是否存在某个属性?

    用aqObject.GetProperties

    例:

    Set p =Sys.Process("MyApplication")

    Set props = aqObject.GetProperties(p)

    While props.HasNext

      Setprop = props.Next

     Log.Message prop.Name & ": " & aqConvert.VarToStr(prop.Value)

    Wend

    16、如何执行ClickOnce应用程序?

    http://www.automatedqa.com/support/viewarticle/?aid=17600

    Sub RunClickOnceApplication()

      Dimpath 

     path = "<pathToTheApplication>" ' e.g. "c:\SampleClickOnce\sample.appref-ms"  SetWshShell = CreateObject("WScript.Shell")

     WshShell.Run("rundll32.exe dfshim.dll,ShOpenVerbShortcut "& path)

    End Sub

    17、如何获取当前测试日志?

    http://www.automatedqa.com/support/viewarticle.aspx?aid=9047

    Sub Test()

      Call Log.Message("Currentlog items", GetLogItems())

    End Sub 

    Function GetLogItems()

      Dim tempFolder, xDoc,result 

      tempFolder = aqEnvironment.GetEnvironmentVariable("temp")& "\" &_

      GetTickCount() & "\"

      If 0 <> aqFileSystem.CreateFolder(tempFolder)Then 

        Log.Error("The " &tempFolder & " temp folder was not created")   

        GetLogItems = ""

        Exit Function 

      End If 

      If Not Log.SaveResultsAs(tempFolder,lsHTML) Then   

        Log.Error("Log was not exported tothe " & tempFolder & " temp folder")

        GetLogItems = ""   

        Exit Function 

      End If 

      Set  xDoc = Sys.OleObject("MSXML2.DOMDocument.4.0") 

      xDoc.load(tempFolder &"root.xml")                                            

      result =LogDataToText(xDoc.childNodes.item(1), 0, "  ")   

      Call aqFileSystem.DeleteFolder(tempFolder,True

      GetLogItems = result

    End Function

    Function LogDataToText(logData, indentIndex,indentSymbol)  Dim i, result

      If "LogData"<> logData.nodeName Then 

        LogDataToText =""   

        Exit Function 

      End If

      result = "" 

      For i = 0 ToindentIndex - 1

        result = result &indentSymbol 

      Next 

      result = result & "Name:" & logData.getAttribute("name") & ", status:" &_            

     GetTextOfStatus(logData.getAttribute("status")) & vbCrLf

      For i = 0 TologData.childNodes.length - 1

        result = result &LogDataToText(logData.childNodes.item(i),_                                   

        indentIndex + 1, indentSymbol)

      Next 

      LogDataToText = result

    End Function

    Function GetTextOfStatus(statusIndex)  

      Select CasestatusIndex

        Case "0"GetTextOfStatus = "OK"   

        Case "2"GetTextOfStatus = "FAILED"

        Case ElseGetTextOfStatus = "UNDEFINED"

      End Select 

    End Function 

    18、如何检查COM对象是否已经注册?

    http://www.automatedqa.com/support/viewarticle/?aid=17606

    Sub Test1()

      Dim objName  

      objName = "C:\ProgramFiles\Automated QA\TestComplete 8\Bin\TestComplete.exe"

      If(IsCOMObjectRegistered("localhost", objectName)) Then   

      Call Log.Message("Anobject is registered")

      Else   

      Call Log.Error("Anobject was not registered")

      End If 

    End Sub 

     

    Function IsCOMObjectRegistered(computerName, objectPath)

      Dim wmiService,wmiObjectPath, objectsList 

      Set wmiService =GetObject("WinMgmts:{impersonationLevel=impersonate}!\\" &_

                                  computerName & "\root\cimv2")

      wmiObjectPath =Replace(objectPath, "\", "\\") 

      Set objectsList = wmiService.ExecQuery("SELECT* FROM " &_

       "Win32_ClassicCOMClassSetting WHERE LocalServer32='" &wmiObjectPath & "'") 

      IsCOMObjectRegistered = False

      For Each ObjService inObjectsList   

        IsCOMObjectRegistered = True 

        Exit Function 

      Next 

    End Function

    19、如何通过命令行传入参数给TestComplete的脚本进行处理?

    Pass parameters via the command line

    http://www.automatedqa.com/support/viewarticle.aspx?aid=9021

    Sub ProcessCommandLine

      For i = 1 To BuiltIn.ParamCount   

      ProcessCommandLineArgument BuiltIn.ParamStr(i)

      Next 

    End Sub 

    Sub ProcessCommandLineArgument(arg)

      Dim items 

      items = Split(arg,"=") 

      If UBound(items)<> 1 Then 

        Exit Sub 

      End If 

        Select Case aqString.ToLower(aqString.Trim(items(0)))

        Case"apppath"      'Project.Variables.AppPath= aqString.Trim(items(1)) 

          Log.Message "The 'apppath' argumentis found! The value is '"_

                   & aqString.Trim(items(1))& "'"

        Case"appver"      'Project.Variables.AppVer= aqString.Trim(items(1)) 

          Log.Message "The 'appver' argumentis found! The value is '"_

                   & aqString.Trim(items(1))& "'"

      End Select 

    End Sub

    20、处理非预期窗口的设置

    TestComplete - Tip 8 (Handling UnexpectedWindows)

    Each of these options is listed in your project’s properties panel, andyou can enable or disable any combination of them according to your uniqueneeds.

    21、等待对象

    Tip 9 Waiting for Objects

    Sometimes you need to have your automated tests pause for a certainlength of time. Maybe you need to wait while your application reads informationfrom a database, or you need to wait for a file to download. TestCompleteoffers several ways for you to delay your tests execution for situations likethis.

    First is the Delay command. Thispauses the test for a specific number of milliseconds. In keyword tests, youinsert delays by dragging the Delay operation from the miscellaneous group box.

    In a script based test, you can simply enter the word Delay andenter the number of milliseconds you want to wait. Refer to the TestCompletehelp file’s Delay help topic for syntax examples of each language.

    You can also pause test execution until a certain window or objectexists onscreen. This is accomplished by using built-in commands likeWaitWindow and WaitChild. A video demonstrating how to use these commands canbe found here: http://www.automatedqa.com/products/testcomplete/screencasts/waiting-objects/

    22、如何扩展TestComplete

    Tip 11Extensions
    TestCompletewas designed to be extensible, and you can create your own custom checkpoints,scripting objects, and helper functions via a feature called Extensions. Theselet you transform complicated or lengthy operations into single clicksolutions. For example, you could create a checkpoint that verified zip codeswere formatted properly, or that temperatures in Celsius were successfullyconverted to Fahrenheit. There are several prebuilt extensions available on ourwebsite:

    http://www.automatedqa.com/blogs/post/08-11-20/7-ready-made-test-extensions/

    You can learn more about building custom extensions from this video:

    http://www.automatedqa.com/products/testcomplete/screencasts/scriptextensions/

    23、TestComplete设置断点不生效

    可以尝试安装MicrosoftScript Debuger或重新注册PMD.dll,参考:

    http://www.automatedqa.com/support/viewarticle/8874/

  • 相关阅读:
    合同主体列表添加两条合同主体,返回合并支付页面,支付总弹"请选择合同主体",删除后,竟然还能支付(改合并支付页面的字段状态)
    (TODO:)下载图片,报错:warning: could not load any Objective-C class information from the dyld shared cache. This will significantly reduce the quality of type information available.
    GCD死锁 多线程
    iOS知识总结
    快速排序,冒泡排序,选择排序
    fight
    3D Touch
    Xcode 调试技巧
    右滑退出手势及隐藏导航栏存在的风险
    C语言-第5课
  • 原文地址:https://www.cnblogs.com/testware/p/1729492.html
Copyright © 2011-2022 走看看