Especially until you can build and deploy the app you are developing and see it on the screen It takes 5 minutes and 10 minutes to spare, but in that case, The operation of writing business logic but testing it only manually produces only pain.
In this article, I know it's better to write it somehow, but honestly I don't know how to write it with the current structure, To the developers who wouldn't feel like spending that much because they don't know what they're happy about. It provides a design method for effectively writing unit tests with xUnit.
Basically java8 and JUnit4, but the content is not so language-specific.
Unit tests can only check the behavior of a function by assertions.
Manual testing is the most efficient way to find unknown defects.
Rather than improving the quality, it is possible to automatically perform the minimum checks so that the development speed is slowed down by boring and unexpected rework does not occur later.
Obviously, this article is aimed at people who don't usually write unit tests, so just in case.
As in the above example, it takes 5 minutes and 10 minutes to be able to check from the screen, but in that case, For complex business logic, it is more efficient to generate rework before testing from the screen.
Unit tests generally allow you to see the results faster than you can start the server and see it on the screen. Also, as the name of the unit (unit) test suggests, it is possible to verify at the level of each function before connecting all the functions. Another reason is that the implementation => validation time lag is smaller.
In other words
--Since verification can be performed at a stage before the state where it can be connected to the screen, rework can be found before the implementation procedure. --The verification itself takes less time than tapping on the screen after starting the server.
In these two points, rework can be detected quickly.
Often mentioned as a merit of TDD, Unit tests are usually written in the form of assertions (such as the return value of function XX should be ZZ when the argument is YY). This allows subsequent developers to ** successfully write ** by reading the test code for unit tests. You will be able to know most clearly that "this function has such specifications".
(Writing well is very important, It's rather confusing if it doesn't have both completeness as a test case and readability as a code.)
Not only can developers understand it, of course. If a developer who doesn't understand the function makes a bad fix, As long as the CI turns properly It is possible to automatically detect that "the behavior has changed from the specifications that should be" in the unit test.
Proper maintenance of unit tests is This leads to the ability to automatically detect defects that would otherwise be noticed only at the integration test stage.
This is also often cited as an advantage of TDD, The test code will be the first user of the (planned) public function you implement.
Especially in Java, I personally think that signature design determines various factors such as extensibility and risk of modification. By actually calling the function from the test code, it is often possible to regain the usability of the function.
If you notice this after it is called by various classes, the risk of modification and the range of influence will be enormous.
When performing a combination test, there is a prohibition. The point is that values that cannot actually be entered from the screen are excluded from the test case as a prohibition. In this case, the judgment that "you cannot enter from the screen" is unreliable.
For example, even if the front end is validated, It doesn't make sense if the value is rewritten by a malicious user at the moment of sending.
Unit tests do not have the restriction that they can be entered or not entered from the screen. Even for input patterns that are prohibited cases It is possible to test.
It would be nice to be able to write a test that expects an exception to be thrown.
Well, I gave various merits, As you read this article, you haven't written a unit test as mentioned above, I am fighting daily with large-scale application development work.
Why I haven't written it, why my seniors around me Considering that no one wrote as far as the eye can see Maybe actually In the first place, it may be because the structure is such that it is difficult to write unit tests on the product code side.
In this section, "Why can't I write unit tests?" I will explain the features when the structure makes it difficult to write unit tests.
Here, as a common function in your workplace, Imagine something named "complete" that controls the completion process of something.
public Map<String,Object> complete(BigBean bigBean) {
// ...
SomeKindOfConfig config = dao.loadConfig(someId);
this.computeSomeValue(bigBean);
if(config.getSomeAttribute() == 1) {
// ...
} else {
// ...
}
// ...
for(DtlBean dtl : bigBean.getManyDtls()) {
// ...
}
if(bigBean.getOtherAttribute() == 0) {
// ...
}
dao.updateSomething(bigBean);
return res;
}
When doing something completion I think that it is common to make a functional design that is convenient because various processes can be performed with the touch of a button. It is a pattern in which they are all written in one function.
Such processing is often done I have to try this setting later and branch the process! Because it can be a difficult situation like DB access is also chaos, such as entering ad hoc on the way.
For business logic that feels like this legacy, Unit tests are very difficult to write.
There are various problems other than being too long, If you often do it with just one function, It is difficult to write in the form of an assertion that "the output should be like this for the input" It is a big feature. Since there are many branches and the combination explodes, the amount of test class description becomes huge, which is inefficient.
private void computeSomeValue(BigBean bigBean) {
//...
bigBean.setSomeValue(someValue);
//...
}
If there is a void method whose argument content can change On the other hand Since it becomes impossible to clearly write the assertion that "when there is such an input, it should be as an output". I think you should avoid it as much as possible.
If you really want to meet the above requirements, The signature itself,
private SomeValueClass computeSomeValue(BigBean bigBean) {
//...
return someValue;
}
It is better to write like this.
Depending on the unit test operation, If something like a test environment isn't ready for a quick refresh For example, it is not easy to test a DB value-dependent one with a unit test.
In the case of the above process I am going to get the setting-like value from the DB on the way, This will result in a complete method assertion for the same argument Depending on the state of the DB, it will succeed or fail.
In the first place, for subsequent developers I think it's a bad implementation to go to see the settings suddenly on the way.
In this case, mock Dao with Mockito on the unit test side, etc. There is a way to deal with it.
A function of complexity that does not explode the combination of branches, If it is implemented separately in a loosely coupled state, You can implement and run unit tests very effectively.
For example, let's say you have the ability to pay rent for multiple months one day. We will implement a preview function that allows you to see how much rent you will pay for the month.
As an argument,
--The date when to pay. --An array in which the target month is in the state of YYYYMM.
There will be.
As an output,
--The date is displayed in YYYY / MM / DD. --The target month is displayed in the format of MM month. --When there are multiple target months, MM month --MM month is displayed from the first month to the last month. --Each target month is displayed as "before" if it is a year before the payment date, and "next" if it is the next year.
Suppose that the specification is.
If you try to connect everything from the screen and hit it normally,
--When there are multiple? When singular? --When it's a double-digit month? When it's a single digit month? ――When before? Next time?
There are various things, and it tends to increase as a combination.
However, since these are a set of simple specifications if divided, If you write a unit test of "a function that just formats a singular month" You can see that you can trust the moon format to some extent, If you write a unit of "function to sort multiple months", There is no need to relentlessly test the order of "functions that output by connecting the beginning to the end of multiple months with hyphens". And, "Compare the date and the year and month, and if the year and month are earlier," before ", If there is a unit test of "a function that returns" next "if the year and month are later", The unit test of the final preview display function You only need to test the representative value that they are properly combined.
If you implement each as a set with a unit test that is small and guarantees a return value for the argument, "At least it's working properly so far, isn't it?" There is also the advantage that you can develop with peace of mind because you can proceed while checking.
An expression is said to be referentially transparent if it can be replaced with its corresponding value without changing the program's behavior. As a result, evaluating a referentially transparent function gives the same value for same arguments.
https://en.wikipedia.org/wiki/Referential_transparency
"A function is referentially transparent, which means that replacing that function with its return value does not change the behavior of the program. As a result, when you execute a function that is referentially transparent, ** always returns the same return value for the same argument **. "
Write the business logic you care about in referential transparency. Personally, I think it's nice to have a clear separation between a class that has a state and a function that returns an output for an input.
Not only is it easy to write unit tests, but it's also easy to write unit tests if it always returns the same return value for the same argument. It makes the unit test effective in that it "reproduces the movement guaranteed by the unit test in any state".
I think there are some rules here.
When writing a unit test as a specification description, However, it may be counterproductive to make an assertion by passing the value that came up to the dark cloud.
First of all, the minimum
It is effective to grab some of the methods that are commonly known as test methods.
Rather, in the case of a function that can not be put into the case with these two Since the test itself in the unit test may be unsuitable, You may want to consider validation with other methods.
@Runwith(Theories.class)
Or
@Runwith(Parameterized.class)
You can use to test by passing multiple "sets of arguments and expected values" to one test code. In short, you can make the inputs and outputs comprehensive and easy to write.
If the number of test classes increases Create a Suite class that bundles it for each package, Let's be able to re-run all the test classes under it at once at any time.
If there is any change If you turn it all over immediately, As long as it is asserted in the unit test, it will work with the specifications so far, You can see immediately.
For projects that operate CI tools such as Jenkins and travis I think it's a good idea to turn it around again when the changes are pushed to the codebase.
--Rework is on the front --Specifications can be described --Easy to understand the usability of public functions --Can test kinsoku
--The function is too long and the expected return value is unclear --The argument has been changed --Depends on something other than arguments
--Appropriate division --Referential transparency
That was all.
Recommended Posts