When speaking at the Internet Software Testing conference in Hangzhou last Saturday, the topic of testability was mentioned. Let's give an example: "Class B has a dependence on Class, class A is a singleton class. Class B uses a function in Class A that needs to be mock. How can this problem be solved?" Originally, this isCodeA typical model mentioned in art, which is described in detail in this book. But today I received an email from an audience asking me how to answer this question.
I haven't written a blog for a long time, so I wrote a simple code to illustrate this problem.
Problem:
The Singleton class is a single-piece class that implements a function named GetUserName (). The GetUserName function needs to connect to the database and retrieve the user name from the database. The dependsonsingleton class is the tested class. The sayhello function in this class uses the GetUserName function of the singleton class. When we perform a unit test on the dependsonsingleton class, we obviously do not want to set up a real database, therefore, the ideal situation is to use mock to obtain the GetUserName function of a singleton class that can be controlled. If the singleton class is not a single-piece class, this is very simple. Use the interface extraction method to extract the interface from the singleton class, and then you can use the mock technology. However, because Singleton is a single-piece class, this is tricky: Singleton. getinstance returns a singleton object determined by the getinstance logic, so that it cannot be directly inserted into a mock object.
Code:
Code 1 Public Class Singleton {
2 Static Private Singleton _ instance;
3 Public Final String Username = " Dennis " ;
4
5 Private Singleton (){
6 }
7
8 Static Singleton getinstance (){
9 If ( Null = _ Instance ){
10 _ Instance = New Singleton ();
11 }
12 Return _ Instance;
13 }
14
15 String GetUserName () Throws Exception {
16 // Connect to DB and return data from DB
17 // Throw exception instead
18 Throw New Exception ( " There's no dB exist " );
19 }
20 }
1 Public Class Dependsonsingleton {
2
3 ...
4 Public String sayhello () Throws Exception {
5 Return " Hi, " + Singleton. getinstance (). GetUserName ();
6 }
7 }
Solution:
In fact, the idea of solving this problem is mainly to solve the issue of "separation" according to the book "Art of code modification. In Singleton, The getinstance function is not controlled, so the simplest way is to add a settestinginstance function for the singleton class and use this function to inject a controlled instance.
Modified singleton:
Code 1 Public Class Singleton {
2 Static Private Singleton _ instance;
3 Public Final String Username = " Dennis " ;
4
5 Private Singleton (){
6 }
7
8 Public Static Singleton getinstance (){
9 If ( Null = _ Instance ){
10 _ Instance = New Singleton ();
11 }
12 Return _ Instance;
13 }
14
15 Public Static Void Settestinginstance (Singleton instance ){
16 _ Instance = Instance;
17 }
18
19 Public String GetUserName () Throws Exception {
20 // Connect to DB and return data from DB
21 // Throw exception instead
22 Throw New Exception ( " There's no dB exist " );
23 }
24 }
Test code:
Code 1 Import JUnit. Framework. Assert;
2 Import JUnit. Framework. testcase;
3
4 Import Org. easymock. easymock;
5
6 Public Class Testdependsonsingleton Extends Testcase {
7 Public Void Testsayhello () Throws Exception {
8 Singleton depend = Easymock. createmock (Singleton. Class );
9 Singleton. settestinginstance (depend );
10
11 Easymock. Exact CT (depend. GetUserName (). andreturn ( " Dennis " );
12 Easymock. Replay (depend );
13
14 Dependsonsingleton ITU = New Dependsonsingleton ();
15 Assert. assertequals ( " Hi, Dennis " , ITU. sayhello ());
16 Easymock. Verify (depend );
17 }
18 }