查看原文
其他

使用Unity Test Runner进行测试驱动开发

Unity Unity官方平台 2019-05-07

测试驱动开发(Test-Driven Development ,简称TDD)是指在编写实际代码之前,针对功能代码编写动态测试的实践方法。本文将由Unity技术经理Sophia Clarke讲解如何将测试驱动开发应用于开发游戏,并说明哪些方法可行,哪些不可行。


TDD不是一个万能的解决方案,但我们可以从该方法中获益。本文我们在项目中使用了Unity Test Runner,它是用于在Unity中编写和执行NUnit测试的系统。

 

TDD的常用工作流程如下:

  1. 确定代码将执行的操作,假设代码会将CodeHasRun的值由False设为True。

  2. 编写测试,检查该代码是否正常运行。此时测试会检查CodeHasRun是否为True。

  3. 运行测试。因为代码还未编写,所以测试应该会失败。如果测试没有失败,那么该测试就存在问题,或是开发者对代码的理解有问题。

  4. 编写实际代码。

  5. 再次运行测试,此时测试应该能够通过。


遵循此工作流程可加快代码重构和改动的过程,因为这样做可以直接发现产生问题的位置和原因。

 

或许你会疑惑为什么我们需要在编写代码前编写测试?这是因为编写代码后,再编写测试往往会导致开发人员为了通过测试而编写测试。如果事先编写失败测试,就能确定它失败的合理原因,并排除误报现象。


TDD通常用于软件开发,很少用于游戏开发。而来自Unity发布工程组不同团队的五名成员开始研究使用TDD来制作游戏。


我们曾了解到,一些开发者认为他们无法使用自己的游戏代码实现自动化游戏,而且也无法使用TDD,所以我们打算亲自进行实验。

 

我们决定使用几款经典游戏进行测试:Pong、Snake、Asteroids和Flappy Bird。这样做的优点是,我们不需要在游戏设计上花费时间,因为我们已经大致了解如何组成游戏内容。

 

虽然我们知道每个游戏的过程,但仍需要深入研究其中的概念,以便了解如何构建测试。以Pong为例,我们知道球板会进行移动,但究竟什么是球板呢?所以我们要对每个概念进行分析。


我们将Pong中的球板划分为以下属性:

  • 一个大小为(0.5, 2, 0)的矩形

  • 可以在XY平面上移动

  • 无法在Z轴移动

  • 无法左右移动

  • 无法移出屏幕边界之外

  • 会发生碰撞

  • 属于Kinematic Rigidbody


这样做给编写测试提供了很好的起点。例如:

       [Test]

       public void AtLeastOnePaddleIsSuccesfullyCreated()

       {

           GameObject[] paddles = CreatePaddles();


           //断言该球板对象已存在

           Assert.IsNotNull(paddles);

           Assert.IsNotNull(paddles);

       }

       [Test]

       public void TwoPaddlesAreSuccesfullyCreated()

       {

           GameObject[] paddles = CreatePaddles();


           // 断言球板的数量等于2

           Assert.AreEqual(2, paddles.Length);

       }


从测试中,可以看见我们需要编写一个名为CreatePaddles的方法,该方法会创建一个包含二个游戏对象的数组。

 

Unity Test Runner 包含UnityTest等功能。UnityTest会返回IEnumerator,并在运行模式以协程的形式运行,从而允许我们测试需要一帧或多帧完成的动作。

 

本示例中,我们将使用UnityTest检查球板无法离开屏幕边界。

[UnityTest]

       public IEnumerator Paddle1StaysInUpperCameraBounds()

       {

           // 提高timeScale,使游戏快速运行

           Time.timeScale = 20.0f;


           //  _setup是TestSetup类的成员,用于保存设置测试场景的代码(从而不必大量复制代码)

           Camera cam = _setup.CreateCameraForTest();


           GameObject[] paddles = _setup.CreatePaddlesForTest();


           float time = 0;

           while (time < 5)

           {

               paddles[0].GetComponent<Paddle>().RenderPaddle();

               paddles[0].GetComponent<Paddle>().MoveUpY("Paddle1");

               time += Time.fixedDeltaTime;

        yield return new WaitForFixedUpdate();

           }


           // 重置 timeScale

           Time.timeScale = 1.0f;


           //  球板边缘不应该离开屏幕边缘

           // (Camera.main.orthographicSize - paddle.transform.localScale.y /2)将计算出球板边界与屏幕边界相接触的位置,0.15是为其等待下一帧设定的误差限度

           Assert.LessOrEqual(paddles[0].transform.position.y, (Camera.main.orthographicSize - paddles[1].transform.localScale.y /2)+0.15f);

        }


单元测试应该是测试功能的最小部分,所以我们测试了每块球板在屏幕顶部和底部的情况。

 

如果我们在此使用deltaTime,该测试会变得不稳定,因为结果会发生变化。我们设置了Time.captureFramerate或fixedDeltaTime,使测试结果可以预测。

 

如果开始时没有设置Time.timeScale,该测试会耗费5秒以上的时间完成,这对测试来说时间太长。将timeScale设为20表示测试速度会比正常速度快20倍,这意味着原本5秒的测试会在约0.25秒的时间内完成。

 

上述测试中,我们使球板向上移动了5秒。该测试检查球板是否受MoveUpY方法的限制而无法移出屏幕。首次编写测试时,MoveUpY没有阻止球板移出屏幕的功能,这表示此时测试会失败。


编写测试后,在编写实际代码前,一定要检查该测试在运行时失败,否则会得到误报情况。

 

项目早期,我们编写了一个测试,但忘记检查它是否会失败,然后该测试即使在游戏功能无法正常运行时也能通过。当回头检查测试时,我们发现编写了错误的代码。我们必须吸取这个教训,并保证以后在编写功能前检查测试是否失败。

 

Unity Test Runner允许开发者重新运行失败的测试,从而有助于加速迭代过程,即使项目中有多个测试。



使用TDD可能在开始时进展较慢,但一旦开始使用它,将是一个非常有益的工作方式。这意味着在项目后期,可以更快更安全地对项目进行改动。

 

这种工作方式也有助于塑造游戏中不同系统的运作方法。当我们重新创建已经熟悉的游戏时,我们不确定设计的各个系统该如何组成。事先编写测试能让我们安排各项功能,然后实现这些功能以了解它们是否可行。需要的话可以由此进行迭代。

 

使用TDD能帮助我们编写出更好更简洁的代码,因为我们需要更多地考虑编写的内容和原因。


一定要记住,TDD不是一个全能的解决方案,但它是一个不错的保障措施,当所有测试都安排好后,就能实现项目的快速迭代。看到Unity Test Runner窗口中出现一个个绿色标记,相信会让你很有满足感。


使用TDD并不意味着你不需要进行其它类型的测试,TDD是一种质量驱动的开发方法,而不是质量保证策略。


最后,希望开发者们使用TDD创作出更佳的游戏。更多Unity技术经验分享尽在Unity官方中文论坛(UnityChina.cn) !



推荐阅读

官方活动

Unity订阅惊喜|邀请你参加圣诞派对 与大咖零距离交流

Unity的新订阅用户在获得优惠礼包的同时,我们将邀请活动期间所有的订阅用户参与Unity的圣诞派对,共庆年终圣诞季![了解详情...


Asset Store年终巨惠Cyber Week劲爆来袭

Asset Store资源商店年终巨惠Cyber Week劲爆来袭!多至2千余款人气热销资源参与促销,低至5折优惠,更有4.5折特惠组合资源包,好资源和低价格一包打尽!

活动地址:

https://assetstore.unity.com


点击“阅读原文”访问Unity官方中文论坛

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存