Home >Web Front-end >JS Tutorial >Nodejs study notes test drive_node.js
Share the second chapter, about test driving. The tests here are mainly for Web back-end testing - why you should write test cases (that is, whether improving test cases is a waste of time), how to improve your test cases, how code design can simplify the writing of test cases, and some later ideas .
1. Why should you write test cases
This habit is usually considered to be a behavior that delays development progress. You need to spend almost the same time developing your code to gradually improve your test cases. However, during the development process, after developing a piece of code, if you are responsible and do not leave the problem to the tester to find it, you will usually do some manual testing at this time. For example:
Execute certain 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 as expected.
Use tools to simulate requesting certain interfaces to see whether the return value of the interface/changed value in the database meets expectations.
If there is a front-end page, it will also involve front-end and back-end joint debugging, that is, through front-end interaction on the front-end page, check whether the front-end feedback meets expectations to indirectly verify the correctness of the back-end code.
Modern testing tools are trying their best to abstract these manual testing behaviors into code blocks. When you consciously perform manual testing, you have actually begun to try the behavior of the test case. Since testing can be done manually, why do you need to use code to implement testing?
The code can be reused or can achieve more functions after simple refactoring, but when you choose to do it manually, you need to start over every time.
A mature workflow should include a code review process. There are many ways to review code. Read your code sentence by sentence, or check the completeness and correctness of your test code, and then run your test cases. The latter is simpler.
When code is changed, such as fixing a bug, it is difficult to guarantee whether your changes will affect other parts that depend on your code. In the era of manual testing, there is something called regression testing, which is to retest your system after you fix the bug. But if you already have a complete test case, just execute the command directly.
When you refactor your code, ditto.
2. How to improve your test cases
Before entering the refinement stage, first talk about how you will implement the test case.
describe Meme do before do @meme = Meme.new 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 block contained in before is what needs to be done before executing the following test case. It usually also supports a corresponding method to be executed after the test case is executed. Some small judgments are made in each use case.
The first paragraph mentioned some test contents that are often involved in manual testing. Here are 2 and 3 of them for explanation. When conducting database-related tests, you need to insert a piece of test data in before and delete the test data in after. In the middle test case, the correctness of the code is confirmed by executing the corresponding method. After the execution is completed: check the data changes/check whether there are expected exceptions/whether the expected results are returned. If it is an interface, it is to initiate the corresponding request through the code, and then check whether the returned content returns the expected results. If necessary, check whether the data in the database meets the expected changes.
You now have a test case, but there is still a special case that needs to be considered. I have now written a relatively complete test case for a function. After running it, I passed the test, but I found that there are still errors for that function in the online logs. After checking, I found that a certain branch of the function was not tested during the test before. It happened that something online happened to run to this branch. As a result, a very obscure syntax error was reported. Is there any way to ensure that all codes are correct? Tested? What needs to be introduced here is a concept called test case coverage. Basically every language will have a corresponding implementation. Through test case coverage, we can quantitatively tell you whether your test case has run through all the code in a certain file. What you need to do is to ensure that your coverage remains at 100% as much as possible.
In a sense, test cases and test coverage are tools used to increase developers' confidence in their own code. However, they are not omnipotent. There is always the possibility of missing some parameters in the test cases. Of course, your code has not been written for this possibility. In the end, the test case coverage can only tell you the code you wrote. We have tested it for you. I have tested it, but there is nothing you can do about the possibility that you have not considered. Therefore, write strict code as much as possible. For example, use === instead of == in JavaScript as much as possible, use strong-type programming standards, etc., to reduce the potential risks caused by accepting too large a parameter range.
3. How code design simplifies test case writing
The entire Web (not limited to the web) usually includes three levels of code - pure data processing and calculations, involving databases, and involving specific network protocols. Among them, pure data operations are mainly functions or other codes that deal with ordinary operations. When it comes to databases, it is the M in MVC in the traditional sense. 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 usually involves the rendering of pages and the simulation of corresponding protocols, usually focusing the test on functions and database-related codes can reduce the complexity of the test case code, which requires the Controller code As little as possible. Some current recommendations for more complex applications:
Place the basic verification of data in the M layer. If developed using Ruby, ActiveRecord and Mongoid both provide very convenient validation functions.
Try to use the Pub/Sub mode in the code with some hooks provided in the ORM to achieve communication between Models. For example, when A publishes a message when it is created, B modifies one of its own attribute values after listening to the message.
Use Command mode to extract some non-business functions from the system, such as email sending.
Reference for the above suggestions: Laravel wisper resque
4. Idea
The above content avoids the test cases that require joint debugging of the front and back ends. The following content is mainly focused on this area. Ruby already has some elegant implementations in this direction. If you are interested, you can go directly to Capybara first.
With the popularity of a series of browser drivers including Selenium Phantomjs and Watir based on the former, controlling the browser using code is no longer a complicated matter. Based on this capability, you can try to divide front-end-based testing into four steps:
等待某标志性元素出现(例如等待页面载入玩,或者某个内容异步加载出现)
模拟用户操作,这里的操作包括且不局限于用户点击、用户输入
等待反馈中标志性元素出现(例如某某输入框出现)
判断内容,是否符合预期
基于这个流程,可以解决绝大多数的前端测试。但是单纯依靠这个流程任然不够,因为页面中可能出现例如验证码这样的阻碍元素,在不修改代码的前提下,可以尝试通过数据库/缓存来取到这些内容。同样,和测试接口相同,这里也涉及到在测试前数据库中插入测试数据,测试用例执行后严重数据库里面数据变化,以及全部测试完毕后删除测试数据的内容。最终导致这块测试用例代码的实现需要同时对前端后端有一定的了解。目前还在考虑在借鉴 Capybara 的基础上,设计出更加通用的方案。
最后贴一段 Capybara 的代码结束这段内容:
feature "Signing in" do background do User.make(:email => 'user@example.com', :password => 'caplin') end scenario "Signing in with correct credentials" do visit '/sessions/new' within("#session") do fill_in 'Email', :with => 'user@example.com' fill_in 'Password', :with => 'caplin' end click_button 'Sign in' expect(page).to have_content 'Success' end given(:other_user) { User.make(:email => 'other@example.com', :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