Jun 01, 2021 Article blog
As a programmer, unit tests should be heard of, but many students have not used them, and there may be some misunderstandings about unit tests, such as:
Try to answer these questions first.
Writing unit tests takes more time, which is not an accurate description. T
o be precise, writing unit tests takes more
"time
to write code", which is nothing to say, after all, to write more test code. B
ut a programmer, when doing a requirement, spends little time writing code. Y
ou have to take care of the logic of the previous code, design classes and methods, and then write the code, write the manual test, there may be
bug
to go to
debug
then fix, and then test. "
The
time it takes to actually write code is actually very small".
While unit testing may take up more time writing code, it can help you reduce other times,
"and will make you spend less time doing this."
Is there a bug for writing unit
bug
O
f course, we can't do a real
bug free
but unit tests are written to significantly reduce the number of
bug
B
ecause the cost of writing unit tests to discover
bug
is very low, it can detect
bug
during the development phase, and it can test the conditions of many boundaries. B
ut if you "have
misconceptions about demand and business",
which unit tests can't solve,
bug
will naturally arise. I
n other words, the benefits of unit testing go far beyond finding
bug
they also have the ability to
"code documentation"
and the existence of a
"refactored safety net".
It can even help you
"manage your needs"
and
"design code".
Changing the product code requires maintaining the corresponding test code, which does incur additional costs. H
owever, with the editor's
"refactoring function",
it is easier to modify the need to modify the place in bulk, in fact, the cost is not as great as imagined. A
nd after refactoring, run the unit test again to see which hangs up, can
double-check
you change the product code there is no problem.
If we look at unit tests as
"documentation of product code",
we are probably more receptive to the cost of this maintenance.
As mentioned earlier, unit testing has many functions. P
ersonally, I think unit testing is most useful for "code documentation" and "refactoring the safety net." A
fter all, the long process of software development, there is no need to repair and modify.
If there is not enough testing, change a piece of code is like in the discharge of mines, after the change of heart is always playing drums, before going online need to worship God in silence, for fear of triggering what
bug
But if there are enough tests (not just unit tests), you can run through the tests after you've changed the code to see which ones are hanging up, if they're caused by your own changes, and how to fix the tests. So there's a lot of bottom in my heart.
You know, the code is written for people to see. Testing is more friendly than product code because it's simple, straightforward, and described from the user's perspective, so if you want to understand what a piece of product code has, it's more intuitive and comfortable to see its unit tests.
Many teams do testing, but most of the testing work is done after development, with dedicated testing students taking charge of end-to-end testing or
API
testing. I
n fact, end-to-end testing costs are very large, especially for some boundary conditions, and constructing data and scenarios can be cumbersome.
And once a
bug
is discovered, it takes a lot of time to communicate, modify, commit, deploy.
The biggest advantage of unit testing is "low cost", want to test each branch of product code is relatively easy, and unit testing is generally the development students write their own, can use the smallest time to find
bug
with the lowest cost to modify
bug
Not all tests are unit tests, but they are divided into many kinds. The industry's more widely disseminated "test pyramids" describe their differences and relationships:
From the test pyramid model, the more underlying the test, the wider the coverage and lower the cost. Unit tests are at the lowest end of the test pyramid and are the basis of the entire test pyramid.
Of course, the test pyramid does not have to be only three layers, there may be other tests in the middle, such as "contract testing" and so on.
Unit tests, like its name, are small enough, fast enough, and dependency-free. U
nit tests measure only the logic of the part of the product code you want to test, and a unit test should measure only a simple business logic. I
n general, running a unit test is fast, basically between a few milliseconds and dozens of milliseconds.
If you have dependent classes, you can
mock
other classes to eliminate external dependencies.
Many students tend to confuse other tests with unit tests, most often by starting integration tests in the
Spring
context.
For example, using
@SpringBootTest
annotations can start
Spring
context, which tests the ability to rely on
Spring
features such as normal injection, but running it at once takes a lot of time (because you want to start the Spring context) and is not really "unit testing" because it relies on
Spring
framework.
So how exactly do you write unit tests? W
e have a methodology called
"TDD"
(test-driven development) in our industry. A
t the heart of
TDD
is the word "driver", whose philosophy is to drive product code from a test perspective.
In the test pyramid, unit testing is most relevant to developers, so "testing" here generally refers to unit testing.
TDD is roughly divided into these steps:
The first step is to clarify the requirements, because only by clarifying the requirements can we ensure that the code we drive with
TDD
is consistent with business expectations. T
he second step is the process of designing classes and methods, also known as
Task List
T
his step can design the relationship between the class and the class, and the method's arguments and in-references.
In fact, do not use
TDD
will also have the first two steps, but the use of
TDD
can help you better from a business perspective, first design the design of everything, avoid writing directly on the code, write half of the time feel wrong, and then change.
Steps 3-5 are actually a circular process. B ecause just started writing code may not pay too much attention to the format, style, performance of the code, write it more quickly, let the test pass. After the test passes, you can go back and refactor the previously written code, refactor it, and run through all the unit tests to see if there are pending unit tests to detect whether the refactoring has an impact on the expected input and output.
A complete unit test should be divided into four parts:
Java
case, there are several unit test frameworks, the most popular of which should be
JUnit
and
TestNG
T
he author uses
JUnit
a little more, and
JUnit
uses
@Test
annotations to declare a test in a method.
The latest version of
JUnit
is
JUnit 5
JUnit 5
has made a lot of improvements in parametric testing compared to the previous version, so that we don't have to write many highly similar test methods (for JUnit 5 parametric tests, you can view official documents, as well as corresponding Chinese translations, which are easy to read).
In general, method names need to be as readable as possible, which may be longer, but clearly state the intent of the test, such as:
@Test void shouldReturn5WhenCalculateSumGiven2And3() {}
@Test
void should_return_5_when_calculate_sum_given_2_and_3() {}
Whether to use hump nomenclase or underline, according to their own team's specifications, try to be consistent with all test styles. (Individuals prefer to underline it)
References are generally basic types or
POJO
objects, and some parameters can be drawn into variables that may be used later in the validation phase.
If the product code has an external dependency, you need to use
mock
to eliminate the external dependency.
Common
Mock
frameworks include
EasyMock
"Mockito"
and so on, so you can compare the differences between the
mock
frames and choose a suitable one.
Many students just started to write unit tests can not understand why the need for
mock
think
mock
is more troublesome, even a little more feeling of this. I
n fact, the meaning of
mock
is that you
"can guarantee that your test only tests the part of code that you want to test."
So if the test doesn't pass, you'll know that there must be a problem with the method you're testing, and it can't be an external dependency problem, so that you can make a real "unit" that guarantees that each test is small enough and pure enough.
When you are ready to enter the parameters and
mock
you explicitly call the method you want to measure, which is usually a simple line.
Finally, validation, verification is divided into several kinds, the most commonly used is to verify that the parameters are in line with their expectations. B
order conditions such as exceptions are also sometimes validated.
Test frameworks such as
JUnit
basically bring their own verification functions, but
API
are relatively simple, personal feeling is not particularly useful, recommended to use
"AssertJ",
powerful,
API
is more comfortable to use.
Let's take an example:
@Test void shouldReturnUserWithOrgInfoWhenLoginWithUserId() { String userId = "userId"; S tring orgId = "orgId"; U ser user = UserFactory.getUser(userId); O rg org = OrgFactory.getOrg(orgId); given(orgService.getOrgById(orgId)).willReturn(org);
UserInfo userInfo = userService.login(userId);
assertEquals(org, userInfo.getOrg());
}
Let's talk about some of the common problems with unit testing.
That's all right. A
lthough there is a saying that
TDD
recommends writing tests before writing implementations. B
ut many of the students who are just starting to write unit tests aren't used to it. W
riting tests first has the benefit of having you think from a business perspective when designing your code, rather than from a code implementation perspective.
You can try to write tests before you write implementations, and experience this feeling.
This has actually been discussed at the beginning of the article. W riting unit tests does take more time to "write code," but overall, it can shorten the entire requirements development cycle. So writing unit tests is a "good deal."
Unconfident code, logical complex code, important code.
For example, tool classes,
Service
layers for three-tier architectures, aggregate roots for
DDD
and domain services, these should all write enough unit tests.
It is cumbersome to construct a suitable parameter object, especially if some objects have a very large number of parameters, if each test has to be constructed from scratch, it will make the test code become very bloated, poor readability. F
actory classes can be used to mass produce objects at this time. T
his factory class is placed in the test catalog and does not affect the production code.
In the previous example,
UserFactory
is a factory class for a
User
object.
The return value is
void
indicating that the method does not have a parameter, and that there must be some behavior within the method, which may be
"changed the value of the internal property"
or
"the method that called an external class".
If you change an internal value, you can assert it by the object's
get
parameter. T
his is a problem with the domain model after
DDD
is used, because it is possible that the product code would not have exposed
get
method, but because of the testing needs, the
get
method of the internal properties was exposed. A
lthough you can also get the value of an internal property using reflection, it is not necessary.
It's better to weigh the pros and cons, or to expose the
get
approach to the domain model.
If you are calling an external method, you can use
verify
to verify that a method has been called, and
capture
to validate the arguments that called another method.
This also verifies that the product code is working as expected.
static
method is not good for
mock
and requires a special
mock
framework. L
ike
PowerMock
JMockit
I
n general, many of the methods of
Utils
class are
static
and we spend a lot of time in the class
LocalDateTime
getting the current time, which is also
static
This time you need to use a special
mock
frame to
mock
Multithreaded is also difficult to test.
If the program is simple, you can use multithreaded tool classes such as
Sleep
or
CountDownLatch
to aid the test, wait for all threads to run, and then verify uniformly.
If the program is relatively complex, you need to use a specialized multithreaded testing framework, such as
tempus-fugit
Thread Weaver
MultithreadedTC
and
OpenJDK
jcstress
project.
For specific framework use, you'll have time to write an introduction to a commonly used annotation later. I
n fact, the official documents are written, we write a few examples according to the official website will be. T
he more recommended base plan is
junit 5 + mockito + assertj
With regard
static
method and the multithreaded testing framework, it's okay to learn when you need it.
To learn more about testing, take a look at the tutorial:
Software testing: https://www.w3cschool.cn/software_testing/
Source: www.toutiao.com/a6856755990545891848/