How to ensure the correctness of the test code

Source: Internet
Author: User

This article only applies to unit testing. Although it refers to testing, it aims to drive development, but not to test-driven development, it is more like understanding and thinking about how to ensure the correctness of the test code in the test first process of test-driven development. Of course there are some ideas that I think are common, whether or not testing is preferred. At present, I have the most exposure to Java unit tests, so I will focus on java. The examples are related to Java.

In addition, I saw a post asking such a question a few days ago. I thought that I had a similar problem when I first came into contact with the JUnit unit test. Now I have some experience, so I wrote it down, it is both a summary of your own experience and hope that someone can discuss and improve each other.

First, I think there are several key points to test the code correctness:

I. Test first

2. Write only the requirement test (note that it is the target code requirement. The user of this Code is of course the user, ^_^)

3. Do not test it for testing. (This Is What Kent back reminded him when he talked to a friend that he was talking to Kent back, here I am not sure whether the understanding of this sentence is correct or not, because the context is also critical to understanding a sentence, I am not very familiar with the specific content and process of my friend's conversation with Kent back, but here I will talk about my thoughts as a key point)

4. Write a test (atomic level) each time.

5. Clean code that works (including the test code, ^ _ ^)

6. For an application framework, it is best to write a test framework for this framework (this is actually a very specific content, but now Java is used a lot in web, testing is also relatively difficult, so there is this)

7. Always remind yourself of the purpose of test first. (Our purpose is to drive development, not to test. Haha, this is the same as the first and second points. The reason why we need to separate columns is just to remind you again, so I will not elaborate on this in detail later)

8. Laziness is a common problem for programmers. (I have the following point of view: the level of programmer can be seen from the degree of laziness ...... ^_^)

Before I start to talk about the above points, I want to write an example. I just think that I should write an example, ^_^. My literary talents are not very good, so I feel it.

Example:

I have a class specifically used to parse the database operation result set (resultset) into a DOM Document Object. This class can be used to define a node (declare node) in the given xml configuration template) the child node (column node) set to parse the resultset to generate a Document Object. Each column node defines the attributes of a field to be obtained from the resultset, including the field name, whether the field can be modified (editable), whether there is a pattern attribute used to format the data of the field, and so on; in order to be more general, you can also generate a column Node Based on the resultsetmetadata object returned by the resultset, and then obtain the data body from the column node. Of course, this operation has great limitations, for example, the attributes of the column node such as pattern and editable are not too flexible. In this design, the maximum flexibility is put on the xml configuration template, the data is obtained by the template definition, and the data display attribute is defined, however, when the column node is automatically generated based on the given resultset, the flexibility is greatly reduced. Although in most applications, the resultset is not used for automatic generation, however, this is required when the columns cannot be defined at the beginning, especially when the number of fields output by resultset changes. The problem has finally come out. Recently, an application is like this. First, you must use resultset to get the definition node (Column). Then, after all the code is completed, it is found that there are redundant fields in the given resultset. At this time, there is no way to modify the program to adapt to it.

After encountering this problem and determining that you must modify your code (to be honest, the first reaction is to let people modify SQL statements to remove redundant fields, because the original design simply did not have redundant data, however, it is really difficult for everyone to understand. SQL cannot be changed, because the database implementation uses a stable public implementation. Fortunately, the redundant fields are the same ), the first thought in my head is to add an event listener to listen to the code that generates data in this application (for the sake of convenience, let's call it "databuilder, once the data is generated, the resultset parsing event is triggered. Then, I can write the listener to process the parsed result set (of course, the redundant data is cut off ), in this way, other similar situations will occur. I can add a new listener to filter data without having to change the original code. Sweat, it seems that this idea is still feasible. At least it looks much simpler than writing a subclass, and it will be much easier to modify later. (In fact, we should pay attention to the environment in which this incident occurs. First, the code was okay, and then we suddenly found this problem, which needs to be modified immediately, so I don't have much time to think about it. I always make such a mistake .)

At this time, I have two options: write code directly, and write a test first. Of course, I chose to write a test first. The reason why these two tests are for comparison is clear. First, if I write code first, then I directly enter the role of the target function, because now it seems that the goal is very clear, and I want to add a function for databuilder to process the listener, after a document is parsed using the resultset parser, A parsing completion event is triggered to notify all registered listeners and pass the parsed results to the listener through the event object for processing. In the past, I immediately thought about how to add events, how to handle event lists, and two necessary interfaces, one is the listener interface, the other is the event interface. After some brief ideas, you don't have to spend much time doing this and then start debugging.

As mentioned above, it is mainly for comparison. Now I write the test first. Of course, the goal mentioned above seems to be very clear now, so no matter how you write a test class, after writing the necessary to do list, I started to think that if the code is finished now, how can I use it. Oh, by the way, before testing this, you need to write a listener for testing. This implementation can be very simple, and the resolution result document has been killed. Haha, the verification result is easier, remove all of its nodes, ^ _ ^. (Note: The main function is not yet tested here, but the listener is tested first.) However, at this time, I suddenly felt that the current design still seems a little troublesome (laziness is a common problem for programmers, sweat, every time you want to get lazy, you need to write the listener and let databuilder handle the listener list trigger event. Although the data filtering operation can be added independently, the register of the listener can also be completed through the xml configuration file, but it still seems redundant. It seems that the current requirement only requires a decorator mode and writes a modifier class to modify the resultset parser, new functions will be required in the future. You can change the decorator, or you can nest them to complete multiple functions. In this way, you do not need to move too many hands and feet on databuilder. Sweat, but I'm not busy with it myself (in any case, testing gives me a better idea of my design and goals, so I abandoned my original ideas without hesitation ). The new goal appeared. It seems that I need a decorator class implemented by the parser interface (Note: This resultset parser class was originally implemented by the resultsetparser interface ), I can write an abstract class of the extended parser interface to wrap it. The subsequent decorator implementations will all inherit from this abstract class and directly implement the modifier. (actually I always think, in the decorator mode, all the decorator implementation classes have a parent abstract class that inherits the interface of the Self-modified target class. The main purpose of the Implementation class function of the decorator is to make it clearer, in fact, this abstract class has very few items to wrap, and these items can be moved to the subclass. In this way, the subclass directly implements the interface for modifying the target class, with the same effect, so I think there is an abstract class that is inherited by all the modifier classes. What's more, it is declared that an implementation class that modifies the target class interface is a modifier class, this can achieve good results in reading programs and using this API .). OK, that's it. (It's always easy for me to decide, ^ _ ^ ).

So how can I test it now? Of course, I also need a decorator class implementation for testing (or remove the child node of the document according to the original plan ), after preparing the test data, check the result set. However, the problem arises again. I should not only check whether the column node is correctly filtered, but also check whether the data body is correct. In this case, if I use hard-coded assertions for testing, the results must be a bunch of assertions. Of course, I can first write an XML file for the correct result set, then read it and compare it with the processing result (alas, the document object does not implement equals), but this is not the most troublesome. the most troublesome thing is that such a test will depend on the database environment, in this way, I have to save the database environment to ensure that I can reproduce my tests under any circumstances, which is undoubtedly the most troublesome. To be honest, in the past, I did not write tests (even now I do not write them, although I know the advantages of writing tests ), the main reason is that it is difficult to establish the database environment at any time (I have tried dbunit, it is not very good, and there are a lot of code to build the environment), I am dizzy (think It's hard to be lazy. Isn't it possible to secretly skip the test ?).

Writing and testing is a very strange process. The brain's way of thinking is somewhat different from writing application code directly. Well, I didn't figure it out. I think this is the difference between us and masters, I have always insisted that people who really understand can interpret vague things as clear concepts. No matter how I plan to start writing this painful test code, I have a new idea. Why don't I get redundant data from the beginning, although the decorator mode is very attractive in the application here, because it is simple and clear enough, but at the same time, considering some characteristics of the document object itself, first it is a large object (haha, I feel that all DOM objects are big, but I have not really understood how big it is). Secondly, if it is to filter data, it is necessary to traverse the document node, in this case, it seems that the processing efficiency is not very good. This is not as simple as getting redundant data from the resultset at the beginning. As a result, the automatic generation of column nodes is still very weak. You can use the builder mode to reconstruct it. In this way, you can implement filtering by replacing the column node builder, it is also true that redundant data needs to be filtered only when column nodes are automatically generated. Therefore, you do not need to consider defining columns nodes by the xml configuration template; in the past, the default generation method can be extracted immediately as the default builder. In this way, few codes are actually added. I 'd like to have a little less code, because the document needs to be traversed when processing redundant data, but now you only need to identify the field name obtained from resultsetmetadata, it is OK if the field to be filtered does not generate a column node, when new changes occur, you only need to replace and implement the builder interface again. Of course, the original resultset parser has a test class and related tests. After extracting the code of the original column generated as the default builder, you can run the previous test, ah, Hahahaha, this is almost effortless. The refactoring function of eclipse can easily extract the column generated code into a method, and then upgrade this method into an interface. Then, this is the case (needless to say) after a default builder implementation is completed, the parser adds a builder Class Attribute and generates its set method. After all Ko operations, the parser runs the test (here the tool does more automatically, this step is more complex, but there is always no harm in running multiple tests. It may take a few seconds to run one test. It may not be necessary, ^_^ ), A green light. After the Refactoring is completed, sweat (note that, for now, I did not write the test, but used the original test code, and most of the Code is automatically generated. The most important thing is that the builder mode is used here, and its interface has been tested. Therefore, the builder implementation added later does not take this into account, but is only a complete functional requirement test, the functional requirements can always be very simple, and the test code is naturally simplified ).

Now I am back to the beginning, and my goal has changed. Haha, the new goal is to implement a builder interface to filter redundant fields. How to test it? A resultset is necessary for input parameters. No redundant data is required for output, and the field names to be filtered in actual applications are fixed (haha, don't forget my original purpose, filters redundant fields that are generated when SQL queries use public interfaces .), I can select an empty result set, and its fields are all the field names that I want to filter (here, as mentioned above, the data body is not what I want to care about, because the data body is generated based on the column node, I only need to check whether the column node is correct, which also simplifies my test code). Of course, previously, the builder object of the resultset parser was reset to the target code class to be implemented, and soon all the Code was completed, run the test, error, modify, and run the test, the test mode of Kent back was so simple, and finally it was a green light. Call ...........

Well, of course you don't forget the last step, clean code that works. I have a metaphor for refining the original code in this status: Close the door and beat the dog. At this time, the reconstruction really can be said to be stable, the worst is just back to the original place, but the general results are always unexpected. In any case, start refactoring. There are several key restructures: first, the default builder can use a singleton mode. Of course, the builder that implements filtering redundant fields is also, this is because the code for generating column nodes is not affected by multithreading. Then, the builder that filters redundant fields can actually be a default builder decorator. Check in code.

This is my experience. We can see that the previous test code also plays a role, so as long as the code is still running, the test code will never be redundant. To maintain a clear test code, we need not only clear requirement analysis, design, and good original target code, but also a little bit of laziness. In demand analysis and design, testing gives priority to an irresistible force that allows you to gain insight into your needs, especially when you are a user of your target code. I just wanted to mention this example. I didn't expect to write so much, sweat.

OK. Next we will discuss it one by one:

I. Test first

The core of test first is not "test", but "first ". The purpose of test-driven development is to develop, while the test takes precedence over the process. From the Test start, the test ends, and a small increment of iterative development is in the middle, test code and development code are synchronized (Note: In fact, I think the test code is also the development code, but to distinguish the two, the development code mentioned below does not include the test code; in addition, this sentence is not comprehensive, but a test-driven development ).

Test first is not required to ensure the correctness of the test code, but test first can ensure the correctness of the test code to the maximum extent. (The Code itself should be called back here. Whether it is testing code or developing code, it is nothing more than code in a development language. To ensure the correctness of the Code, I think it is mainly a good design, a clear structure, and a reasonable use of the language. Keeping the code structure clear is the easiest and simple indentation, the unified encoding style is basically qualified. The same is true for the test code .) If you do not write the test code first, you must have the target code (such as ^ _ ^), which is bound to be limited when writing the test code, don't say that you can ignore the written target code. (I think it's hard for 90% of the people to do it. They have all been written and changed. Once an interface is published, you can't change it if you want to.) When writing a test, you can't change it. What should you do, of course, the test is written according to the idea of writing the development code. (haha, it seems that if the development code is first written and then the test code is written, the purpose of testing code is to verify the correctness of the development code. At least I used to do this before.) So your thinking is banned, the quality of the test code (here the bad is not to say that it cannot be used, ^ _ ^) is controlled by the development code. Back to my example, if we did not carefully consider how to modify the design because of a sudden change in time and demand at the beginning (in fact, I think there are too many such situations, the users I contact have definitely put forward a certain requirement. In the end, the demonstration users totally reject this requirement, which leads to a complete reversal. But what I want to say is, there is really no way to deal with the users I face. The users who need to change people are larger than the users who originally raised the need. Sweat, don't change, don't drag the time, let's not elaborate on the reasons ), what kind of situation does it take to write the target code first from the initial idea? First, the time has been spent (haha, there is actually not much time for this example ), when the development code is complete and the test is to be written, the purpose of writing the test is to verify whether the code is correct or not. In this case, it is not necessary to prepare the input data and verify the output data, which one cares so much? (I think this is the case. Once the goal is determined, it is still a real goal. When we start something, we have already planned it in our head, so there is no room to think about other things, let alone consider doing another thing ). The results and processes of using the test-first method are also detailed in the example. It is easy to write and test without the target code, because there is nothing at the moment, what should I do if I want to make the test code clear, simple, and clear?

Therefore, test first is a good way to improve the correctness of the test code. Here we first write the development code and then write the test code. To put it bluntly, it is the third point I want to talk about, it is really easy to become the code for testing (in my memory, when I did this, I also tested it for testing, Haha, I don't know what other friends will do ).

2. Write only the requirement test (note that it is the target code requirement. The user of this Code is of course the user, ^_^)

As mentioned above, to ensure the correctness of the test code, it is also a truth to ensure that the Code itself is clear, but it should be put here, the main reason is that I often forget this point when writing tests. For example, when a test involves multiple classes, it is actually quite clear that unit testing is okay if you test your target class. However, everyone has different understandings and the level of writing test is different, so be cautious, when I write a test for the target class, I think that although class A does this, I also add an assertion to check if Class A is correct, I would like to add one more sentence, just in case (too careful, in fact, I don't know when I pass the world ). Don't underestimate a line of code. My feeling is that a method with more than 20 lines (blank line is not counted, sweat) will come back after you finish writing this method for a few months. It is very likely that you will be dizzy, at least immediately understand that the internal structure of this method is basically impossible. If the target class is associated with A, B, C, D, and E, I am afraid it will not be one or two lines of code, and you will be careful multiple times, for example, in a test case (the test case I refer to is not the testcase class, but a test method in the class) your assertion includes a test of the result of an operation on a. If you still use this operation on a in another test case, I am sure you will write this assertion, because the first time you care is not accidental. People said, Be careful to make the ship more than ten years old. However, it is not enough to worry about it. When writing a test, it is very easy to make such a mistake (alas, it is actually very easy for me to make such a mistake at the beginning, sweat ).

Therefore, the responsibility is distinct. You only need to write the test you need. Don't doubt it in the first place. Be careful and step by step. How do we use our code, so we write such a test. Now I use the test code as an example and write a public module. Besides the documentation, it is recommended that you check how to use the test code if you have trouble reading the documentation, or you simply copy my test code and use it again.

3. Do not test it for testing. (This Is What Kent back reminded him when he talked to a friend that he was talking to Kent back, here I am not sure whether the understanding of this sentence is correct or not, because the context is also critical to understanding a sentence, I am not very familiar with the specific content and process of my friend's conversation with Kent back, but here I will talk about my thoughts as a key point)

Do not test for testing. ^ _ ^. Although you want to write tests, all of them are developed. If the purpose of your testing code is testing, I will still say that you are not professional enough. (This sentence is a bit familiar, Star: Please, although everyone is Chinese, if you copy my lines, you will still be told to plagiarize it)

In fact, at first, I heard this sentence from my friends, and I was not very familiar with it, because after all, it was just a sentence without a head or tail. However, I think there is some truth (^ _ ^, I don't know if my idea is the intention of Kent back to say this ). To put it bluntly, the developer's test code should not be well targeted at the test target code, so as to better ensure the correctness of the test code.

It should be clarified here that it is not to say that the test code should not be used for testing. Haha, the results of the test code (whether or not testing first) are actually the same and can be automatically (or semi-automatically, ^ _ ^) the purpose of the test is to write the test. Because of your different purposes, the intermediate process is quite different. For developers, code development is the ultimate goal (what needs analysis, system design, and so on). It is not for users to run the code that meets their needs. Oh, of course, it is the ultimate goal, we still rely on this to support our family, sweat), so the unit test code in the development process is only used to assist us in Development (this seems to have been said many times, sweat, don't drop my bad eggs), so the ultimate goal of these test code is to develop code, which is essentially different from the test code written by the tester (if the tester visits this article, hey, sorry. It's not for testing ).

This is a good detour. However, only by knowing this, the first two points will be able to stand up. If you test the code at the beginning, you just need to test the function of the target code, test first is a joke. Even the target is not likely to be tested. Write the test as needed, regardless of the internal logic and boundary conditions, the tester must have said that I am lying off. Can this be used for testing?

Therefore, to ensure the correctness of the test code, do not test for the purpose of testing. Only when testing is not for the purpose of testing can you truly achieve the first and second points. (It seems that this should be set to the first place, sweat, but when it comes, it should be settled)

4. Write a test (atomic level) each time.

This is actually a small incremental iteration in xp. Although testing-Driven Development emphasizes that testing is preferred, the interaction between testing and development in parallel is crucial.

A very general test case, both testing and testing, will inevitably be complicated (the accumulation of quantity brings about a qualitative change), so it is best to write a test case for only one requirement, A few times. Add a test, run failed, modify the code, run the test until the test succeeds, then refactor the code, and finally pass the test. This is a small iteration cycle of test-driven development, of course, the control of iteration cycles varies from person to person.

The less tests you write, the less problems or needs you pay attention to. The more concentrated you are, the less code you test, major problems can be solved by splitting them into multiple small problems. If you think the test code is too complicated, naturally, you can reduce the complexity by splitting complex test code into several relatively simple test code segments.

5. Clean code that works (including the test code, ^ _ ^)

The best design can't hold your messy code, the simplest, and code without a good indent format. It looks like a nightmare. Now the tools are so good that they can be automated, however, I can still see a lot of unformatted code at work, sigh, but the minimum requirement is not enough. Let alone Let him refactor the code. It is estimated that writing and testing is also useless, test first is even more free.

So don't look at this sentence as easy as you can understand it. (Here I want to talk about test first again. As mentioned above, I actually started to write the development code first and then write the test. Every time I hesitated, did I write the test first, in many cases, the design that has been "molded" in my head cannot be shelved. I need to write the development code and test it immediately. I always think that the same will happen later, however, after I forced myself to write a test for multiple times, my experience was completely different. Test first was not as simple as I thought. Therefore, although I have mentioned a bunch of its advantages here, I still hope that there will not be so many friends who have never experienced it. I will love it first,, to really use it everywhere, we still need to improve the level of writing test code, because the test code is not that simple, especially after connecting with the database and the Web ).

Well, I don't have to go into details about this. In fact, you may have a good idea about it. As for how to do this, hey, let's take a look at test-driven development. You may have some experience, and I am out of scope here. I also can hold on to this point, I don't want to post it here because the good words of the masters are more incisive and persuasive.

6. For an application framework, it is best to write a test framework for this framework (this is actually a very specific content, but now Java is used a lot in web, testing is also relatively difficult, so there is this)

This is about the application. ^ _ ^. In fact, it may be something that some friends want to talk about, because I didn't really want to write it when I started learning the test, but I really don't know how to write it. For a complex application, such as the front-end web and backend database, as long as they are associated with the two, it is also a lot of code to simply write and test the requirements, some do not even know how to test. I am still afraid of database-side testing. I hope my friends can discuss this.

Here, my point of view is very clear. An application framework should be supported by a testing framework for this framework, so that the written testing code can be clear to ensure its correctness. The purpose of the test framework is to ensure that the test environment is correctly set up. Here we will talk about the problem and test environment. I think it is the key to testing and the most difficult aspect of unit testing. For an Interface Test (pure interface, not an implementation class), you may need to write an implementation class that extends this interface to assist in the test (in my example, when I started to consider using decorator, I first considered the Interface Test after this mode is applied. In this way, I can test the implementation class separately when testing the specific decorator implementation, when the builder mode is used for column node processing, the default builder is used for this test.) This is the second point I have mentioned; the application framework is usually composed of an interface architecture that abstracts the specific application interface, and a specific application implements the function by extending this interface, such as the common Web framework structs, its action is such an interface. To test it well, testing the framework itself is essential, but the specific application has little to do with the Framework, you only need to make the action that meets the requirements. In this way, testing the action is generally considered by application programmers. To test the action, a simple method is to isolate the code to be tested in the action from the structs framework, so that you do not need to access the structs framework for Direct Testing. However, this is actually not complete (but it also has the advantage of forcing the logic code to be written elsewhere rather than in the action. Of course, the action itself is not used to write this, however, as I can see, many programmers are actually used to writing business logic Code directly in actions. The advantage is that they only need to write a few lines of code, but they will not be able to see it in the future, this is my seventh point, the difference between laziness and laziness.) The best thing is to test the action class completely, starting from calling it. But in this case, preparing a test environment is very troublesome. Preparing an HTTP request object is not just a new one, which can be daunting. If you only write one or two such tests, there are actually hundreds of tests, so we need a testing framework at this time (Haha, I suddenly thought that some people may misunderstand the testing framework, that is, the code for testing the application framework itself, of course, such tests are indispensable. However, the testing framework mentioned here is the testcase framework written by the pointer during application framework development. It is a good choice for httpunit for Web testing, however, it is still relatively low-level (Ha, the same as the language here, Java is a high-level language, not to say it is more advanced than assembly ), write a test framework for the structs framework (which is now open-source) to minimize the setup of the test environment, in this way, only the test code that requires the test can be written, and the test code itself does not look so huge.

7. Laziness is a common problem for programmers. (I have the following point of view: the level of programmer can be seen from the degree of laziness ...... ^_^)

This topic has been introduced in the sixth point. In fact, it is just a joke about your own ideas. It is not very formal, because its nature has nothing to do with testing.

I am very lazy, otherwise I will not choose this profession, because the program can help me automatically, so that I can just look at it easily, ^ _ ^.

However, there are many types of laziness. There is one of the most direct ones, just looking at the laziness in front of you. For example, it is very casual to write code without formatting. It seems that you have done a lot less, however, when people look at the code, they will pay a higher price when they look at it themselves (especially when looking at the error). I think, Whether you look back at the code next time, it takes less time to understand it than to select a menu to automatically format the code. Designed for changes, it is precisely for future laziness. There is a point of view, and now I cannot remember where I saw it. It seems that there are everything (refactoring, XP, test-driven development, etc ), it is to refactor the existing code before adding a new function to an existing module, so that the existing code is suitable for adding this new function (as if this is the first time we see this book "refactoring ), refactoring does not mean to make the Code look better. Its purpose is to adapt to changes, and changes are inherently great. However, we can improve the existing code through refactoring, so that the existing code is suitable for adding new changes, the complexity of changes is reduced.

So I said, little laziness will be free, don't affect the overall situation, the code is clear, the necessary refactoring, not to do this is just a little laziness, they are at the price that may be paid more in the future. The test code is just code.

Well, I finally finished writing the code, and it is inevitable that some similarities (clean code that works seems to be able to cover all of them). I 'd like to sum up and think about it. I 'd like to continue with this sentence: clean code that works. The words of the Master are brilliant. However, if you think about the points mentioned above, it seems that you did not say how to do it. However, to do this, you can ensure the correctness of the test code. However, these key points are as follows, to learn how to do this, I think you should read more books. Test-driven development, refactoring, and extreme programming-Embracing Change are rare books. I think, compared with other books, these three keys do not lie in their profound theories or technologies, but they all guide programmers to practice, how to develop programs from the ground up, and develop good programming habits, it is more like the masters who teach us how to write programs by themselves. What is rare is that the writing is still very good and the description is easy to understand.

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.