unity 单元测试_使用Unity测试工具以光速进行单元测试

unity 单元测试_使用Unity测试工具以光速进行单元测试

unity 单元测试

It’s time to tell a little bit more about NSubstitute library that ships with our Unity Test Tools and patterns of it’s effective usage.

现在该介绍一下我们Unity测试工具附带的NSubstitute库及其有效使用模式的更多信息。

Each software system consists of units of functionality. In object oriented languages, the minimal unit of functionality is a method. These methods usually depend on other methods and classes. If you have to test a method, some challenges will arise.

每个软件系统都包含功能单元。 在面向对象的语言中,功能的最小单位是一种方法。 这些方法通常取决于其他方法和类。 如果您必须测试一种方法,将会遇到一些挑战。

  • First challenge is that external dependencies are not easy to set up, e.g. some objects with complex initialization might be required.

    第一个挑战是外部依存关系不容易建立,例如,可能需要某些具有复杂初始化的对象。
  • Second challenge is that the test verifies specific execution path that requires certain behavior from other classes that are used.

    第二个挑战是该测试验证特定的执行路径,该路径要求所使用的其他类具有某些行为。
  • Finally, calling methods of external classes might lead to some changes in an environment that could not be rolled back, e.g. deleting a real record from a database.

    最后,外部类的调用方法可能会导致环境中的某些更改无法回滚,例如,从数据库中删除真实记录。

Unit test is about testing unit of functionality in an isolated environment. Isolated means with all dependencies mocked up. Which means that test works in a test specific environment where only one execution path is possible.

单元测试是关于在隔离的环境中测试功能单元。 孤立的意味着模拟了所有依赖关系。 这意味着测试只能在一个特定的测试环境中工作,该环境只能有一个执行路径。

Test doubles substitute real dependencies of unit under test, forming test specific environment and making unit tests fast and robust.

测试将替代被测单元的实际依赖性加倍,从而形成了特定于测试的环境,并使单元测试变得快速而健壮。

There are five test double patterns: Dummy object, Test stub, Test spy, Mock and Fake.

有五种测试双重模式:虚拟对象,测试存根,测试间谍,模拟和伪造。

Dummy object

虚拟对象

This article uses a simple Space Trader game clone to demonstrate the usage of test doubles and the NSubstitute library. This game is centred around the spaceship controlled by the player.

本文使用一个简单的Space Trader游戏克隆来演示测试双打和NSubstitute库的用法。 该游戏以玩家控制的飞船为中心。

The player can equip weapons to fight pirates. The spaceship has weapon slots and a weapon can be equipped if an empty weapon slot is available.

玩家可以装备武器来打击海盗。 飞船有武器插槽,如果有空武器插槽,则可以装备武器。

Test scenario: make sure that weapon slot is occupied when weapon is equipped.

测试场景:装备武器时,请确保武器插槽已被占用。

  • Take a spaceship with one free weapon slot

    带一个免费武器槽的太空船
  • Take any weapon

    拿任何武器
  • Equip the weapon

    装备武器
  • Check that no weapon slots are available

    检查没有可用的武器插槽
unity 单元测试_使用Unity测试工具以光速进行单元测试
unity 单元测试_使用Unity测试工具以光速进行单元测试

Test with dummy object created manually.

使用手动创建的虚拟对象进行测试。

unity 单元测试_使用Unity测试工具以光速进行单元测试

Test with NSubstitute based dummy object

使用基于NSubstitute的虚拟对象进行测试

Weapon object is nominal in this scenario which means that its methods and properties are not intended to be used in this execution path. It is just a required parameter and anything that implements IWeapon interface can be used, including null when method has no null parameter check. This object usage is called dummy object pattern.

在这种情况下,武器对象是名义上的,这意味着该方法的路径和属性不打算在此执行路径中使用。 它只是一个必需参数,可以使用实现IWeapon接口的任何东西,包括当方法没有空参数检查时包括空值。 该对象用法称为伪对象模式。

There are two approaches to create dummy objects.

有两种创建虚拟对象的方法。

The first approach is to create dummy object manually. It makes sense to have only one dummy object per real object/interface and IDE functions will help you to generate classes that implement interface. Creating dummy objects this way and storing them with your tests is not a big deal.

第一种方法是手动创建虚拟对象。 每个真实对象/接口只有一个伪对象是有意义的,IDE函数将帮助您生成实现接口的类。 这样创建虚拟对象并将其与测试一起存储并不重要。

unity 单元测试_使用Unity测试工具以光速进行单元测试
unity 单元测试_使用Unity测试工具以光速进行单元测试

The second approach is to use NSubstitute that is shipped with Unity Test Tools:

第二种方法是使用Unity Test Tools随附的NSubstitute:

Remember that passing null as a parameter when method has no null parameter check is also considered to be dummy object pattern. Null is not descriptive though.

请记住,当方法没有空参数检查时,将空值作为参数传递也被视为伪对象模式。 Null不是描述性的。

All described methods are valid and very easy to use.

所有描述的方法都是有效的,并且非常易于使用。

Test Stub

测试存根

But what if spaceship’s method should return some value and this value is taken from weapon object? In this case, weapon is not a Dummy object anymore. It’s time for a test stub.

但是,如果飞船的方法应该返回一些值并且该值取自武器对象,该怎么办? 在这种情况下,武器不再是虚拟对象。 该是测试存根的时候了。

Test stub returns values for testing one specific execution path. E.g. BrokenWeaponStub, IncompatibleWeaponStub and others that let you test specific scenarios.

测试存根返回用于测试一个特定执行路径的值。 例如BrokenWeaponStubIncompatibleWeaponStub和其他可让您测试特定方案的项目。

Test scenario: make sure that ship shoots at least one shot if functional weapon is equipped

测试场景:如果装备了功能性武器,请确保飞船至少射击一枪

  • Take a spaceship with a single weapon slot;

    乘坐带有单个武器槽的太空船;
  • Equip weapon;

    装备武器;
  • Shoot;

    射击;
  • Check that spaceship’s round contains at least one shot;

    检查飞船的回合至少包含一发子弹;
unity 单元测试_使用Unity测试工具以光速进行单元测试

Test with test stub created manually.

使用手动创建的测试存根进行测试。

FunctionalWeaponStub implements IWeapon interface but return hardcoded value.

FunctionalWeaponStub实现IWeapon接口,但返回硬编码值。

unity 单元测试_使用Unity测试工具以光速进行单元测试

Unlike Dummy object that does nothing, Stub contains hardcoded values and is intended to return them as this values result in specific execution path.

与不执行任何操作的Dummy对象不同,Stub包含硬编码的值,并打算将其返回,因为此值会导致特定的执行路径。

unity 单元测试_使用Unity测试工具以光速进行单元测试

It is possible to create the same stub using NSubstitute:

可以使用NSubstitute创建相同的存根:

The object created by Substitute.For implements IWeapon interface and is returned as IWeapon type. But it is actually a proxy object whose behaviour could be changed. Returns is an extension method of NSubstitute library that changes the behavior of this proxy. When Shoot method is called, the value specified in Returns() method is returned. It is also possible to provide a sequence of values or a delegate.

Substitute.For创建的对象实现IWeapon接口,并以IWeapon类型返回。 但这实际上是可以更改其行为的代理对象。 Returns是NSubstitute库的扩展方法,该方法可以更改此代理的行为。 调用Shoot方法时,将Returns()方法中指定的值。 也可以提供一个值序列或一个委托。

unity 单元测试_使用Unity测试工具以光速进行单元测试

A sequence of values would be returned In the example below.

在下面的示例中,将返回一个值序列。

First call to randomNumberService.Range() with any parameters will result in 0. Next call will return 2 and so on.

第一次使用任何参数对randomNumberService.Range()调用将导致0。下一次调用将返回2,依此类推。

Other useful function of NSubstitute is the ability to match arguments by template.

NSubstitute的另一个有用功能是能够通过模板匹配参数。

In the example below the default behavior would be overridden making any call to Range(10,100) return 80. For more details read the NSubstitute manual.

在下面的示例中,默认行为将被覆盖,使对Range(10,100)的任何调用均返回80。有关更多详细信息,请阅读NSubstitute 手册

The approach with random number generator stub is highly effective in testing random events, because it is possible to emulate required sequence of random numbers.

带有随机数生成器存根的方法在测试随机事件方面非常有效,因为可以模拟所需的随机数序列。

Making stubs manually is easy and naming them correctly leads to creation of clean and readable tests. NSubstitute can decrease the amount of code and provide additional flexibility.

手动制作存根很容易,正确命名它们可以创建干净且可读的测试。 NSubstitute可以减少代码量并提供额外的灵活性。

Test Spy

测试间谍

But what if it is needed to log the behavior of an object? How many hits has an opponent received? If the test double has logging functionality, then it is called a Test Spy.

但是,如果需要记录对象的行为怎么办? 对手收到了多少击球? 如果测试双精度具有日志记录功能,则称为测试间谍

Encounter means meeting someone in space en route to a star system. The player can choose an action to take in an encounter. For example, the police could be ignored and a pirate attacked. Test scenario: make sure that opponent gets hit in an encounter

意味着在太空中遇到某人到达星系。 玩家可以选择一个动作来进行相遇。 例如,警察可能会被忽略,海盗会遭到袭击。 测试场景:确保对手在遭遇中被击中

  • Take an opponent;

    带对手
  • Take a spaceship with two weapons slots;

    乘坐带有两个武器槽的太空船;
  • Equip two weapons;

    装备两种武器;
  • Create an encounter;

    创建一个相遇;
  • Choose attack action in the encounter;

    在遭遇中选择攻击动作;
  • Check that opponent got hits;

    检查对手是否命中;
unity 单元测试_使用Unity测试工具以光速进行单元测试

Encounter requires two spaceships and a random number generator to calculate probable outcomes. AlwaysMaxRandomNumber is passed which will always return maximum number and this will leave no chances for the opponent to evade the hit. The test is following a very specific execution path.

count需要两个太空飞船和一个随机数生成器来计算可能的结果。 AlwaysMaxRandomNumber被传递,它将始终返回最大数,这将使对手没有任何机会逃避命中。 该测试遵循非常具体的执行路径。

The player spaceship is real in this encounter, but the opponent is a spy. The spy combines stub and logging functionality. It logs the hits that would be checked later in the test.

玩家飞船在这次遭遇中是真实的,但对手是间谍。 间谍程序结合了存根和日志记录功能。 它记录了在测试中稍后将检查的匹配。

unity 单元测试_使用Unity测试工具以光速进行单元测试

It is possible to assemble a test spy using NSubstitute’s Arg.Do<> and When… Do… constructs.

可以使用NSubstitute的Arg.Do <>When…Do…结构来组装测试间谍。

unity 单元测试_使用Unity测试工具以光速进行单元测试

NSubstitute uses special argument matcher Arg.Do to execute a delegate when argument to it passes to the substitute. Which means that hitCount+=x.Count() would be executed on each method call.

NSubstitute使用特殊的参数匹配器Arg.Do在将其参数传递给替代对象时执行委托。 这意味着hitCount+=x.Count()将在每个方法调用上执行。

Mock Object

模拟对象

The spy only logs the data, leaving verification to the programmer. What will happen if a spy is given the right to verify the data?

间谍仅记录数据,而将验证留给程序员。 如果间谍被赋予验证数据的权利,将会发生什么?

Test Spy with verification capability is called Mock object. (Note: This article shows non strict mocks.)

具有验证功能的测试间谍称为模拟对象 。 (注意:本文显示了非严格的模拟 。)

Test scenario: make sure that each weapon shoots when spaceship shoot method is called

测试场景:确保在调用太空飞船射击方法时射击每种武器

  • Take a spaceship with two weapon slots;

    乘坐带有两个武器槽的太空船;
  • Equip two weapons;

    装备两种武器;
  • Shoot;

    射击;
  • Check that each weapon got call to Shoot method.

    检查每个武器是否都调用了Shoot方法。
unity 单元测试_使用Unity测试工具以光速进行单元测试

This test case is special, not just because it uses Mock object pattern, but because it verifies the behaviour of the spaceship instead of the state. (Learn more about London and Chicago schools of TDD)

这个测试用例很特殊,不仅因为它使用了Mock对象模式,而且还因为它验证了飞船的行为而不是状态。 (了解有关TDD的伦敦和芝加哥学校的更多信息)

The test does not care about returning values of test doubles, it makes sure that certain methods with certain parameters were called. We use an assumption that if a spaceship has called correct methods with correct parameters on external systems, then it works correctly.

该测试并不关心测试双精度值的返回值,它可以确保调用具有某些参数的某些方法。 我们假设,如果一艘太空船在外部系统上调用了具有正确参数的正确方法,那么它将正常工作。

Making mocks manually might be rather time consuming. Using NSubstitute framework to get mock objects for cheap is a good choice.

手动进行模拟可能会非常耗时。 使用NSubstitute框架廉价获取模拟对象是一个不错的选择。

NSubstitute can also verify if methods were called in a specific sequence.

NSubstitute还可以验证是否按特定顺序调用了方法。

unity 单元测试_使用Unity测试工具以光速进行单元测试

A mock object not only logs the calls but also verifies them. It is a test spy with built-in assertions.

模拟对象不仅记录调用,还验证它们。 它是带有内置断言的测试间谍。

Fake object

假物件

But what if a test double needs some logic?

但是,如果双重测试需要一些逻辑怎么办?

A test double that contains logic is called a Fake object and it is a dangerous beast. It is the only test double that contains logic and emulates a real system component. As the Fake itself is complex, it is used to substitute real system components which could not be substituted by stubs. The most common example is the substitution of a real database with an in-memory database or using fake web service instead of a real one.

包含逻辑的测试double称为Fake对象 ,它是危险的野兽。 它是唯一包含逻辑并模拟真实系统组件的测试双。 由于Fake本身很复杂,因此它用于替代无法用存根替代的实际系统组件。 最常见的示例是用内存数据库代替真实数据库,或者使用伪造的Web服务代替真实的数据库服务。

The best solution is to avoid fake objects when possible, as they know too much about the behaviour of the component they substitute and contain logic that should be tested and is a subject to change as well.

最好的解决方案是在可能的情况下避免使用伪造的对象,因为它们对所替代组件的行为了解得太多,并且包含应测试的逻辑,并且该逻辑也可能会发生变化。

Summary

摘要

As a brief conclusion:

作为一个简短的结论:

  • Making stubs, dummies and spies manually is not complex and giving them proper names will make code readable and clean.

    手动制作存根,假人和间谍并不复杂,给它们取适当的名称将使代码可读性强。
  • NSubstitutes is designed for mocking and using it is preferable to designing mock objects by hand.

    NSubstitutes是为模拟而设计的,使用它比手动设计模拟对象更好。
  • Checking the behaviour and state based testing are both powerful. Use the one that makes the test simpler.

    检查行为和基于状态的测试都很强大。 使用使测试更简单的方法。
  • Avoid fakes (logic) in test doubles when possible.

    尽可能避免在双打测试中出现假冒(逻辑)。

Happy unit testing !

快乐的单元测试!

Examples are on the GitHub.

示例在GitHub上

P.S.:

PS:

Different books use different terminology see terminology in books NSubstitute is very powerful and taking a look at the documentation is a good idea. Good principles to follow. If you still don’t feel the difference between mocks and stubs read “Mocks aren’t Stubs”. Arrange Act Assert (AAA) pattern was used to structure unit tests in the article. There is an open source implementation of a Space Trader game worth playing with.

不同的书籍使用不同的术语,请参见书籍中的术语 。NSubstitute非常强大,并且查看文档是一个好主意。 要遵循的良好原则 。 如果您仍然感觉不到模拟和存根之间的区别,请阅读“模拟不是存根”本文中使用了“排列行为声明”(AAA)模式来构建单元测试。 有一个值得玩的Space Trader游戏的开源实现

翻译自: https://blogs.unity3d.com/2014/07/28/unit-testing-at-the-speed-of-light-with-unity-test-tools/

unity 单元测试