This chapter focuses on the principles of automated testing. The previous chapter introduces many testing ideas, and the things of ideas are a bit imaginary. This chapter tells you what to do when you encounter specific problems. The author consulted many developers and testers, and also communicated with Martin Fowler on some principles of automated testing. Some of them are obvious, while others are uncertain. Therefore, this chapter mainly discusses the following issues:
- Test first or last?
- Tests or examples?
- Test-by-test or test all-at-once?
- Outside-in or inside-out?
- State or behavior verification?
- Fixture Design upfront or test-by-test?
Test first or last?
Yes, you should first writeCodeOr write test cases first? The author believes that the test case should be written first and then the code should be written. This is also an important principle of test-driven development and agile testing. There are many reasons for doing so, such:
- Writing a test case for a completed or old code is much more difficult than writing a test case before the code is completed. (In the face of a large finished system, it is impossible to start)
- Writing Test Cases can greatly enhance the code testability. The code written later is inherently testable, because the test case has been written earlier than it.
- Write a test case first, which can constrain the subsequent encoding to avoid adding some bloated functions that are not used at all during encoding, making the code look more streamlined.
Personal feelings:
Writing Test cases first and then writing code does have many advantages. However, there are very few people who actually do this. On the one hand, it is indeed difficult for traditional software development companies to make some changes. On the one hand, writing test cases first does not bring immediate benefits. Many people give up after trying it. Therefore, continuous practice and persistence are required. (I have to work hard)
Tests or examples?
To be honest, I did not understand what was being discussed. The test case is relative to the document. At the same time, I also proposed a term: EDD (example-driven development), but later I mentioned the EDD framework, such as rspec and jbehave, which made me confused. As far as I know, rspec, jbehave should be the BDD framework.
The author's final point: tests are examples.
Test-by-test or test all-at-once?
Should I write a test case and code, or should I write all the test cases first and then code? This is a very practical issue. In incremental development, there is one sentence: test a bit, code a bit, test a bit more. Of course, this approach is ideal, because it can more accurately locate code problems. However, the author mentioned that a better way is to first outline the functions in the test case, and the implementation in it is empty. Then, fill in a test case each time and write a piece of code.
My point of view:
Same as the author. For example, before writing a class, assume that you are the code caller, list the test cases in test fixture, and then complete the test cases one by one. Each time you write a test case, You can implement the corresponding code.
Outside-in or inside-out?
Normally, there are some dependencies and hierarchies between modules. Should we start to write the case from the outermost calling module or from the innermost layer? The author's point of view is from the outside to the inside.
Let's take a look at the situation from the inside out:
The development process from the inside out is more like the traditional development process, which is easy to understand and simple to implement. However, there is a disadvantage in this sequence, that is, the upper-layer SUT must depend on the implemented bottom-layer SUT. If two modules are developed by different people, the development of upper-level modules must wait for the development and writing of the lower-level modules to complete the work. At the same time, if the underlying SUT is implemented first, it may be overly designed to design some features that are not used by upper-layer modules at all. Eventually make the entireProgramReduced testability. Therefore, the process from the external is better:
First, write the test case at the outermost layer. You can use the test double object to replace the called underlying module, so that SUT can be tested (dependency injection ). At the same time, because "thinking from the user or caller's Perspective" is maintained at all times, the SUT object can achieve a clearer goal and more streamlined implementation, thus avoiding excessive design.
State or behavior verification?
The question is, should I use status-based verification or behavior-based verification? Status-based verification means that after the SUT is called, only the SUT status is checked, such as the return value, such as a summation function. Finally, check whether the summation result is correct. Behavior-based verification usually means that after a SUT is called, it not only changes the SUT status, but also has other effects. For example, a user-registered function must check whether the returned value is successfully registered and whether new user records are written in the database. BDD (behavior-driven development) is a behavior-based verification method. The author concluded that he mainly uses state-based verification, but sometimes uses behavior-based verification to pursue code coverage.
My understanding:
It is sufficient to use State verification for code with a single function, simplicity, and good design. However, the real system is often very complex, and modules call each other. The functions of a single function may not be so single. Behavior-based testing is to verify the effects of various behaviors from the user's perspective.
Fixture Design upfront or test-by-test?
Fixture is a collection of some cases. One idea is that many cases share a fixture, and a new fixture instance is created during method execution of each test case, execute the setup method before the case. Another idea is that the previous practice will make the case seem less clear, and it is not easy to find the setup or teardown methods that a test case method will execute. Therefore, we propose to use custom minimal fixture in each test case method, instead of using a large, hard to find or understand fixture.
My feelings:
I also felt this. I also found that some test cases I wrote like to make many test classes inherit a base class fixture, which defines setup and tearndown. At the same time, in the subclass, you can also add additional methods to perform preparation and cleanup operations. In this way, my test case does not seem clear, because it is difficult to see from my test case function what I did in setup, and what setup operations have been performed.