Share the second chapter, about test drivers. The tests here are mainly aimed at testing on the web backend - why do you want to write test cases (that is, is it a waste of time to improve the test cases), how to improve your test cases, how to simplify the writing of test cases, and some later ideas.
1. Why do you want to write test cases
This habit is often considered a behavior that delays development progress, and you need to spend almost the same time as developing the code to gradually improve your test cases. However, during the development process, if you are responsible and not completely handing over the problem to the tester after developing a piece of code, you usually do some manual tests at this time. For example:
Execute some methods in the code to see if the output value is as expected.
Modify the database/cache, and then execute certain methods to see if the database changes are in line with expectations.
Use the tool to simulate requesting certain interfaces to see if the interface's return value/database's change value will be as expected.
If there is a front-end page, it will also involve front-end joint debugging, that is, you need to use front-end interaction on the front-end page to check whether the feedback from the front-end meets expectations to indirectly verify the correctness of the back-end code.
Modern testing tools are abstracting these manual manual testing behaviors into code blocks as much as possible. When you consciously conduct manual testing, you are actually starting to try the behavior of test cases. Since it can be tested manually, why do you still need to use code to implement the test?
The code can be reused or can achieve more functions after a simple refactoring, but when you choose manual, you need to start over every time.
A mature workflow should include the code review process. There are many ways to review code. Read your code sentence by sentence, or check the perfection and correctness of your test code, and then run your test cases. The latter is simpler.
When code changes, such as fixing bugs, it is difficult to ensure that your changes will affect other parts that depend on your code. In the era of manual testing, there is a regression test, which means that when you fix a bug, you retest your system. But if you already have a complete test case, just execute the command to do it.
When you refactor the code, the same as above.
2. How to improve your test cases
Before entering the improvement stage, let’s talk about how you will implement the test cases.
describe Meme do before do @meme = end describe "when asked about cheeseburgers" do it "must respond positively" do @meme.i_can_has_cheezburger?.must_equal "OHAI!" end end describe "when asked about blending possibilities" do it "won't say no" do @meme.will_it_blend?.wont_match /^no/i end end end
The above code comes from Ruby's minitest. The code blocks included before are what you need to do before executing the following test cases. They usually support a corresponding method, which is executed after the test cases are executed. There are some small judgments in each use case.
The first paragraph mentions some test content that is often involved in manual testing, and here are 2 and 3 of them to explain. When performing database-related tests, you need to insert a test data in before and delete the test data in after. In the intermediate test case, by executing the corresponding method, after the execution is completed, check the data changes/check whether there are expected exceptions/whether the expected result is returned to confirm the correctness of the code. If it is an interface, it is to initiate the corresponding request through the code, and then check whether the returned content returns to expectations. If necessary, check whether the data in the database meets the expected changes.
Test cases are available now, but there is still a special case to consider. I have written a relatively complete test case for a function. After running PASS, I found that there is still an error in the online log. After checking, I found that a branch of the function was not tested before when testing it. It happened that some situation on the line ran to this branch. As a result, there was a very insignificant syntax error and reported an error. Is there a way to ensure that all the code has been tested? What needs to be introduced here is a concept called test case coverage, which basically every language will have a responsive implementation. Through the test case coverage, you can tell you in a quantitative way whether your test case has completed all the code in a certain file. What you need to do is to ensure that your coverage rate is 100% as much as possible.
In a sense, test cases and test coverage are tools used to increase developers' confidence in their code. But they are not omnipotent either. There may always be some possibility of missing parameters in the test cases. Of course, your code does not write code for this possibility. In the end, the coverage rate of the test case can only tell you that we have tested the code you wrote. It means that you have no choice but to do anything about the possibility you have not considered. So write strict code as much as possible, such as using === instead of == in javascript, using strongly typed programming specifications, etc., to reduce the potential risks caused by the large range of accepted parameters.
3. How code design simplifies writing test cases
The entire Web (not limited to the web) usually includes three levels of code - simple data processing and computing, involving databases, and specific network protocols. Among them, simple data operations are functions or other codes that process ordinary operations. When it comes to databases, it is M in MVC in the traditional sense, and when it comes to specific network protocols, it is the corresponding C. These three tests correspond to the first three items of the regular test content in the first section.
Because the C level may also involve page rendering and simulation of corresponding protocols, the focus of the test is usually placed in functions and database-related code to reduce the complexity of the test case code, which requires as little code as possible for Controller. Some current suggestions for more complex applications:
All basic verification of data is placed in the M-layer. If developed using Ruby, ActiveRecord and Mongoid provide very convenient validation functions.
Try to use Pub/Sub mode in your code to achieve communication between models (hooks) provided in some ORMs. For example, when A is created, a message is published, and B modifies a certain attribute value of his own after listening to the message.
Use Command mode to extract some business-independent functions from the system, such as email sending.
The above suggestions are referenced: Laravel wisper resque
4. Conceive
The above content avoids test cases that require joint debugging in the front and back ends, and the following content is mainly aimed at this area. Ruby has already had some elegant implementations in this direction. Those who are interested can directly appreciate Capybara.
With the popularity of a series of browser drivers including Selenium Phantomjs and Watir based on the former, using code to control browsers is no longer a complicated matter. Based on this capability, you can try to divide front-end-based testing into four steps:
Wait for a certain iconic element to appear (for example, waiting for the page to be loaded and played, or a content to be loaded asynchronously)
Simulate user operations, the operations here include and are not limited to user clicks and user inputs
Wait for the iconic element to appear in feedback (for example, a certain input box appears)
Determine whether the content meets expectations
Based on this process, most front-end tests can be solved. However, relying solely on this process is not enough, because obstructive elements such as verification codes may appear on the page. Without modifying the code, you can try to retrieve these contents through the database/cache. Similarly, like the test interface, it also involves inserting test data into the database before testing, serious data changes in the database after the test case is executed, and deleting the test data after all tests are completed. Ultimately, the implementation of this test case code requires a certain understanding of the front-end and back-end at the same time. Currently, we are still considering designing a more general solution based on reference to Capybara.
Finally, I will post a paragraph of Capybara's code to end this paragraph:
feature "Signing in" do background do (:email => 'user@', :password => 'caplin') end scenario "Signing in with correct credentials" do visit '/sessions/new' within("#session") do fill_in 'Email', :with => 'user@' fill_in 'Password', :with => 'caplin' end click_button 'Sign in' expect(page).to have_content 'Success' end given(:other_user) { (:email => 'other@', :password => 'rous') } scenario "Signing in as another user" do visit '/sessions/new' within("#session") do fill_in 'Email', :with => other_user.email fill_in 'Password', :with => other_user.password end click_button 'Sign in' expect(page).to have_content 'Invalid email or password' end end