Creating partial mocks with spies

Một phần của tài liệu Manning java testing with spock (Trang 270 - 275)

In this section you’ll see how to create partial mocks.8 Chapter 6 explained how Spock can create fake objects that are useful for testing and showed you mocks and stubs.

Spock supports a third type of “fake” object: spies.

Spies, shown in figure 8.5, work as partial mocks. They take over a Java object and mock only some of its methods. Method calls can either by stubbed (like mocks) or can pass through to the real object.

I purposely didn’t show you spies in chapter 6 because they’re a controversial tech- nique that implies problematic Java code. They can be useful in a narrow set of cases.

Their primary use is in creating unit tests for badly designed production code that can’t be refactored (a common scenario with legacy code).

8.3.1 A sample application with special requirements

Let's see an example that’s well-suited for writing a Spock test with spies instead of mocks/stubs. Say you’re tasked with the development of unit tests for an existing Java application. The Java application in question is a security utility that gets video feed from an external camera, and upon detecting intruders, deletes all files of the hard drive (to hide incriminating evidence).

The application code is implemented by two Java classes. The first class is responsi- ble for deleting the hard drive, and the second class implements the face-recognition algorithms that decide whether the person in front of the camera is a friend or enemy, as shown in the next listing.

public class CameraFeed { [...code redacted for brevity...]

public void setCurrentFrame(Image image){

[...code redacted for brevity...]

} }

8 And why you shouldn’t use them!

Listing 8.23 Java code with questionable design

Java class

Mock/

stub Spy

Partial fake Fake

Real object

Figure 8.5 A spy is a real class in which only a subset of methods are fake. The rest are the real methods.

Gets frames from video camera

public class HardDriveNuker { public void deleteHardDriveNow(){

[...code redacted for brevity...]

} }

public class SmartHardDriveNuker extends HardDriveNuker{

public void activate(CameraFeed cameraFeed){

[...code redacted for brevity...]

} }

You should instantly see the flawed design of this Java code. Figure 8.6 provides an overview.

The application doesn’t use dependency injection. Instead of splitting responsibil- ities into separate entities, the application contains both the logic of deletion and the face recognition in a single “object.”

You see this design flaw and start refactoring the application in order to write your unit tests. Unfortunately, your boss says that the binary application is digitally signed, and changing even the slightest thing in the source code will create an invalid signa- ture.9 Your boss adds that even if you successfully refactor the code, your department

9 My example is a bit extreme. Usually code can’t be changed for political reasons.

Responsible for hard disk deletion Immediately

deletes the hard drive

Contains complex image- recognition logic Calls deleteHardDriveNow() behind the scenes

Face detection from video

camera

Deletes hard drive SmartHardDriveNuker.java

CameraFeed

HardDriveNuker.java

Figure 8.6 Hard drive deletion logic is hidden inside the face-recognition logic

247 Creating partial mocks with spies

doesn’t have access to the digital certificate, so you couldn’t re-sign the binary after your change.

You need to write a unit test with the source code as is. You’re asked to examine the effectiveness of the face-recognition software by using images of both kinds (those that have a threat and those that don’t). This is one of the rare occasions that spies can be employed for unit testing.

8.3.2 Spies with Spock

You need to write a unit test that examines the activate() method of the SmartHard- DriveNuker class. You know that behind the scenes it calls the deleteHardDriveNow() method. It wouldn’t be realistic to delete your hard drive each time you write a unit test that triggers the face-recognition logic. You need to find a way to mock the dan- gerous method while the real method of the face-recognition logic is kept as is.

Spock supports the creation of spies, as shown in the next listing. A spy is a fake object that automatically calls the real methods of a class unless they’re explicitly mocked.

def "automatic deletion of hard disk when agents are here"() { given: "a camera feed"

CameraFeed cameraFeed = new CameraFeed() and: "the auto-nuker program"

SmartHardDriveNuker nuker = Spy(SmartHardDriveNuker)

nuker.deleteHardDriveNow() >> {println "Hard disk is cleared"}

when:"agents are knocking the door"

cameraFeed.setCurrentFrame(ImageIO.read(getClass().getResourceAsStream(

"agents.jpg")))

nuker.activate(cameraFeed);

then: "all files of hard drive should be deleted"

1 * nuker.deleteHardDriveNow() }

Here you create a spy of your class under test. By default, after creation, all methods are real and pass through to the real object.10 Then you specifically mock the method that deletes the hard drive. But the method that employs the face-recognition logic is still the real one.

When the activate() method is called, it runs its real code (so you can pass it dif- ferent images and test the effectiveness of the face-recognition code). In the case of an image that represents a “threat” and so triggers the hard drive deletion process, you know that the mocked method will be called (and thus your hard drive is safe).

Listing 8.24 Creating a spy with Spock

10Creating a spy without mocking any method is the same as using the object itself—not very exciting.

Creates a spy for the SmartHardDriveNuker class Mocks the

dangerous method—all other methods are real

Real face-recognition code runs

Examines the mocked method

This listing shows only one test, but in reality you’d need to write a parameterized test with multiple images that examines the behavior of the face-recognition code.

8.3.3 The need for spies shows a problematic code base

Spies are used for legacy code primarily because of the bad quality of legacy code.11 Well-designed code doesn’t ever need spies in the first place. Figure 8.7 shows a flow diagram of using spies that you should keep in your head at all times. The diagram isn’t specific to Spock. It applies to all testing frameworks (including Mockito).

In the example of the security utility, a spy is essential because the Java code doesn’t use dependency injection. This is just one of the code smells of badly designed code.

Java code that comes as a big ball of mud,12 breaks the SOLID principles,13 contains God14 objects, and generally suffers from big design flaws isn’t directly testable with mocks/stubs, and spies are needed.

In those cases, you should resist the temptation to write a Spock test with spies and instead refactor the code before writing your unit tests. You’ll find that in most cases (if not all), spies aren’t needed after the refactoring is complete.

8.3.4 Replacement of spies with mock

You use spies with the security utility because you can’t refactor the Java code first, as this would invalidate the digital signature of the binary. If that constraint didn’t hold,

11A universal fact: legacy code is always badly designed code.

12https://en.wikipedia.org/wiki/Big_ball_of_mud

13https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

14https://en.wikipedia.org/wiki/God_object

No Yes

Can you refactor

your Java code?

You think you need to use spies

Use spies Refactor it?

Use mocks/stubs You improved the design of your code!

Your Java code is not OOP

Figure 8.7 Spies can always be replaced with mocks in well-designed code.

249 Creating partial mocks with spies

you’d instead modify the Java code to properly use dependency injection. An obvious decoupling of dependencies is shown in the following listing.

public class SmartHardDriveNuker{

private final HardDriveNuker hardDriveNuker;

public SmartHardDriveNuker(final HardDriveNuker hardDriveNuker) {

this.hardDriveNuker = hardDriveNuker;

}

public void activate(CameraFeed cameraFeed) {

[...code redacted for brevity..]

hardDriveNuker.deleteHardDriveNow();

[...code redacted for brevity..]

} }

Here you refactor your Java code to use composition instead of inheritance. You also introduce the “dangerous” hard drive deletion code as an external dependency. After this refactoring, you can rewrite your unit test by using a normal mock, as shown in the next listing.

def "automatic deletion of hard disk when agents are here"() { given: "a camera feed and a fake nuker"

CameraFeed cameraFeed = new CameraFeed()

HardDriveNuker nuker = Mock(HardDriveNuker) and: "the auto-nuker program"

SmartHardDriveNuker smartNuker = new SmartHardDriveNuker(nuker) when:"agents are knocking the door"

cameraFeed.setCurrentFrame(ImageIO.read(getClass().getResourceAsStream(

"agents.jpg")))

smartNuker.activate(cameraFeed);

then: "all files of hard drive should be deleted"

1 * nuker.deleteHardDriveNow() }

If you’ve read chapter 6, the code in listing 8.26 should be easy to understand.

Because you’ve refactored the Java code and hard drive deletion is now an external dependency, you can mock that class and pass it the face-recognition code. This way,

Listing 8.25 Refactoring Java code to avoid spies

Listing 8.26 Using a mock instead of a spy

No inheritance is used.

Code reuse via composition

Gets hard drive nuker via constructor injection

Calls the dangerous method of the external dependency

Uses a mock instead of a spy Mock is passed

as a dependency

Calls the mocked nuker class behind the scenes Examines the interaction of the mock

your class under test—SmartHardDriveNuker—is a real one, and a mock is used for the collaborator class: HardDriveNuker.

The end result is that no spies are used. What you need to take away from this sec- tion of the book is that despite Spock support for spies, you should avoid using them, and instead spend time improving the design of your code so that spies aren’t needed.

And with that knowledge about spies, we conclude this book! You can now put it down and go write your own Spock tests!

Một phần của tài liệu Manning java testing with spock (Trang 270 - 275)

Tải bản đầy đủ (PDF)

(306 trang)