Unit Test Study Note 4

Source: Internet
Author: User

Chapter 4 high-quality test code

4.1Test code must also have high quality

Some developers think that the test code is not the product code delivered to the end user, but used for internal testing. Therefore, the quality is lower than the product code. We think this is a wrong idea.

First, although the test code will not be delivered to the end user, it is also the delivery code, it will be delivered to the next generation of maintenance programmers, and these maintenance programmers are likely to be ourselves. therefore, in order to make our lives easier for ourselves and other programmers in the future, we should meet the highest standards when writing test code.

On the other hand, the test code must also be quality code. the trouble caused by poor test code is far greater than the trouble caused by no test code. therefore, either we do not write the test code, or we must write the "good" test code.

Therefore, we hope to establish the idea that the test code is not a second-class citizen and must be tested according to the standard of the product code, because the test code is also the code to be delivered.

With this idea, the next question is: how can we write high-quality test code? In fact, any method or principle that prompts us to write high-quality product code is also suitable for prompting us to write high-quality test code. therefore, as long as we follow these principles and methods, we can write high-quality test code. these methods and principles include:

  • Principle of no broken window
  • Dry Principle
  • Single Responsibility Principle
  • Minimum Coupling Principle
  • Improve code readability

We will take a look at how to implement these principles and methods in the following sections.

On the other hand, like the product code, the test code should have its own independent source code tree to facilitate management. Therefore, in the last section of this chapter, we will introduce how to manage the source code tree of the test code.

4.2Principle of no broken window

"Broken Windows" in Software refers to bugs. for testing code, the principle of leaving no broken windows is particularly important. this is because such a bug is hard to be detected if a bug occurs in the test code. when the unit test fails, programmers often find bugs from the tested product code until they cannot find bugs in the product code, this is a waste of time and morale. therefore, when writing test code, make sure that the test code is correctly written for the first time. when a bug occurs in the test code, the system immediately fixes the bug and leaves no broken windows!

4.3 dryPrinciples

The dry principle requires that each item of information in the software be expressed in a single, unambiguous, and authoritative manner. when there is a violation of the dry principle in our test code, do not hesitate to refactor it immediately to remove repeated representation of the same information. the dry principle is also embodied in the testing code for code reuse. code reuse in test code is mainly in the following three forms.

4.3.1Use helper Method

This is a common form. It extracts the common code into a helper method and allows other test codes to call the Helper method. in fact, the setup () and teardown () methods in test fixture are typical applications in this form.

4.3.2Use parameterized tests

Parameterization tests are often used to perform a black box test on a method under test, that is, to give different inputs to the method under test, and then check whether the method can generate correct results for these inputs. let's take a look at how to perform parametric tests in C ++, C #, and Java respectively.

4.3.2.1 Google UTFSupport for parametric Testing

The following table lists the parameters of the gtest framework (version 1.5.

Gtest support for parametric Testing

Declare test fixture

# Ifndef class_under_test_test_h
# Define class_under_test_test_h
// In [classundertest] test. h file.
# Include <gtest/gtest. h>
// Test fixture declaration.
Class [classundertest] test: public testing: testwithparam <param_type>
{
Protected:
// Optional setup () and teardown ().
Virtual void setup ();
Virtual void teardown ();
};
# Endif

Define test method

// In [classundertest] test. cpp file.
# Include <gtest/gtest. h>
# Include # Include "[classundertest] test. H"
// Optional setup () and teardown ().
Void [classundertest] test: setup ()
{
...
}
Void [classundertest] test: teardown ()
{
...
}
// Test method definition.
Instantiate_test_case_p (prefix, [classundertest] test, param_value_generator );
Test_p ([classundertest] test, [feature] _ [scenario] _ [expectedbehavior])
{
Classundertest OBJ;
Param_type Param = getparam ();
Result_type result = obj. singleparam_methodundertest (PARAM );
Assert_xx (result );
}
// Other test method definition.
Test_f ([classundertest] test, [feature] _ [scenario] _ [expectedbehavior])
{
...
}

Parameter Value generator supported by gtest

Testing: range (begin, end [, step])

The value range is [begin, end). The step size is step. The default value is 1.

Testing: values (val1, val2 ,...)

The value range is (val1, val2 ,...).

Testing: valuesin (array)
Testing: valuesin (container)

The range is specified by an array or STL container.

Testing: bool ()

The value range is [false, true].

It can be seen that the support of gtest (version 1.5) for parametric testing still has many limitations, as shown in:

  • This function can only be used when the tested function is a single-Parameter Function.
  • Once the parameter type is specified, the method to be tested for other parameter types cannot be used.
  • The expected return value corresponding to each input parameter value cannot be specified.

We hope that the version will be enhanced in the future.

4.3.2.2 nunitSupport for parametric Testing

Nunit provides excellent support for parametric testing. Its usage is shown in the following table.

Nunit support for parametric Testing

Test Fixture and test method

Using nunit. Framework;
Using Rhino. mocks;
[Testfixture]
Public class [classundertest] Test
{
[Setup]
Public void setup ()
{
...
}
[Teardown]
Public void teardown ()
{
...
}
[Test]
Public void [feature] _ [scenario] _ [expectedresult] ()
{
...
}
[Rowtest]
[Row (param1_1, param2_1, param3_1, expectedresult_1)]
[Row (param1_2, param2_2, param3_2, expectedresult_2)]
[Row (param1_3, param2_3, param3_3, expectedresult_3)]
[...]
Public void [feature] _ [scenario] _ [expectedresult] (param1_type param1, param2_type param2, param3_type param3, result_type expectedresult)
{
Assert. XXX (param1, param2, param3, expectedresult );
}
}

4.3.2.3 JUnitSupport for parametric Testing

The usage of JUnit for parametric testing is as follows.

JUnit's support for parametric Testing

Test Fixture and test method

Import org. JUnit .*;
Import org. JUnit. Rules .*;
Import org. JUnit. Runner .*;
Import org. JUnit. Runners .*;
Import static org. JUnit. Assert;
Import static org. easymock. easymock .*;
// Test fixture must be annotated with @ runwith.
@ Runwith (parameterized. Class)
Public class [classundertest] Test
{
Private param1_type param1;
Private param2_type param2;
...
Private result_type expectedresult;
// The data-preparation method must be annotated with @ parameters, be public static, have no arguments, and return collection.
@ Parameters
Public static list <object []> preparedata ()
{
Object [] [] DATA = new object [] [] {
{Param1_1, param2_1,..., expectedresult_1 },
{Param1_2, param2_2,..., expectedresult_2 },
{...},
};
Return arrays. aslist (data );
}
// Test fixture must have a constructor.
Public [classundertest] Test (param1_type param1, param2_type param2,..., result_type expectedresult)
{
This. param1 = param1;
This. param2 = param2;
...
This. expectedresult = expectedresult;
}
@ Test
Public void [feature] _ [scenario] _ [expectedresult] ()
{
Assertxxx (param1, param2,..., expectedresult );
}
}

It can be seen that currently JUnit (version 4.8) does not support parametric testing as well as nunit, and its limitations are reflected in:

  • The test class must introduce constructors to receive parameters passed by test runner.
  • The number of parameters of the test class is fixed and determined by its constructor.

We hope that the future version will be enhanced.

4.3.3Introduce class inheritance system

Introducing the inheritance system is also a common form of code reuse. The common code is put into the base class, and the special code is put into the derived class, as shown in.

4.4Single Responsibility Principle

For a class, we require it "has only one reason for change"; for a method, we require it "does one thing and does it well"; similarly, for a test method, we require it to "test one thing only ". to achieve this, you can start from the following aspects.

4.4.1Use only one asserted statement in one test method

When multiple asserted statements appear in a test method, the test method is actually testing multiple tasks. At this time, we can solve the problem in two ways:

  • Split a test method into multiple test methods. If the splitting process violates the dry principle, you can use the technology mentioned in the previous section to reconstruct the method.
  • If the multiple asserted statements are actually testing multiple aspects of the same thing, you can consider combining these asserted statements into one asserted statement.

4.4.2Use only one mock object in a test method

When more than one mock object is used in a test method, it means that this test method is not just a test.

4.5Minimum Coupling Principle

The minimum coupling principle is reflected in unit test code, which requires that each test method be completely isolated and independent from each other. This requires:

  • There should be no time coupling between test methods, that is, their relative execution sequence will not affect the test results. The passing of a test method should not depend on whether other test methods have been executed.
  • Test methods cannot be called each other.
  • The status and information cannot be shared between test methods.

4.6Test code readability

This section describes some guidelines for testing code readability.

4.6.1Keep the logic of the test code simple

Avoid introducing the logic in the test code. specifically, the test code should not contain statements such as if, switch-case, for/foreach, while, and try-catch. the test code should be just sequential Execution Code.

4.6.2Use naming rules for test code

The use of neat and consistent naming rules can make our test code more understandable to other programmers. The following are the naming rules we recommend.

Test Project)

[Projectundertest] Test

Test class

[Classundertest] Test

Test Method

[Feature] _ [scenario] _ [expectedresult]

Test subclass

Testing [classundertest]

Stub collaborators

Stub [collaboratorclass]

Mock collaborators

Mock [collaboratorclass]

4.6.3Use the setup () method in a maintained manner

The following describes how to use the setup () method:

  • Only setup () is used to initialize the objects used by "all" test methods. if an object is only used for "some" instead of "all" testing methods, it should be initialized separately in the testing method it is used.
  • The setup () method should be simple and does not contain complex logic.
  • If the inheritance system is used to reuse the setup () method, we recommend that you explicitly call the setup () method of the base class in the derived class to make the test code more readable.

4.6.4Keep steps of the test method easy to differentiate

A test method consists of the following four steps: Create, verify CT, trigger, and verify. Ensure that these four steps are easy to differentiate and do not complete multiple steps simultaneously in the same statement.

4.6.5Multiple test classes can be used to drill the same tested class at the same time.

People often think that each tested class can have only one corresponding test class. All tests on the behavior of the tested class must be placed in this unique test class. This is incorrect. We don't have to use a dozen tests to fill up a test class. If a test class is filled with too many testing methods, the readability and maintainability of the test class will be greatly reduced. In this case, we should split the test class to improve the readability of the test code.

4.7Manage the source code tree of the test code

In terms of the physical storage path of the source file, we hope to separate the product code from the test code. That is to say, we should establish their own source code tree for the product code and the Test Code respectively. For example, it is the recommended file storage path.

In addition, with the features of the programming language, we can better separate the product code from the test code, as shown in the table below.

Programming Language

Techniques

C ++

Put the product code into the production namespace, and put the unit test code into the unit_test namespace.

C #

Put the product code into the production namespace, and put the unit test code into the unit_test namespace.

Java

Put the product code into the production package, and put the unit test code into the unit_test package.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.