Nodejs test drive for learning notes

  • 2020-05-30 19:26:01
  • OfStack

Share chapter 2 on test drive. The tests here focus on testing the Web backend -- why you write test cases (i.e. whether it's a waste of time to improve test cases), how to improve your test cases, how the code design simplifies test case writing, and some later ideas.

1. Why do you write test cases

This habit is often referred to as a development delay, where you spend almost the same amount of time developing your test cases as you do developing your code. However, in the development process, manual testing is usually done after 1 piece of code has been developed if the responsibility is not to leave the problem entirely to the tester to find. Such as:

Execute some methods in your code to see if the output values match your expectations.
Modify the database/cache, and then execute some method to see if the database changes as expected.
Use a tool to simulate the request for some interface and see if the return value of the interface/the change value of the database will be as expected.
If there is a front-end page, it will also involve front-end coupling, that is, through front-end interaction on the front-end page, check whether the front-end feedback meets the expectation, to indirectly verify the correctness of the back-end code.
Modern testing tools try to abstract these manual testing behaviors into blocks of code as much as possible, and when you consciously do manual testing, you are already trying to test the behavior of the case. Why do you need to code tests when you can do them manually?

The code can be reused or simply refactored to achieve more functionality, but when you choose to manually, you need to start over every time.
A mature workflow should include a code review process, which can be done in a variety of ways, reading your code sentence by sentence, or checking the completeness and correctness of your test code, and then running your test cases. The latter is simpler.
When code changes, such as fixing Bug, it is difficult to guarantee that your changes will affect other parts of your code that depend on you. In the era of manual testing there is a regression test where you retest your system once you fix Bug. But if you already have a good test case, just execute the command.
When you refactor code, ditto.

2. How do you refine your test cases

Before moving into the refinement phase, let's talk about how you will implement the test cases.


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 code above comes from minitest of Ruby. before contains blocks of code to do before the following test cases are executed, and usually supports a corresponding method that is executed after the test case is executed. Each use case makes a small judgment.

Paragraph 1 mentions a number of tests that are often used in manual testing. Here are two and three of them. When performing a database-related test, you need to insert a test data in before and delete the test data in after. In the intermediate test case, verify the correctness of the code by executing the corresponding method after execution: checking for data changes/checking for expected exceptions/returning the expected results. If it is an interface, it is through the code to initiate the corresponding request, and then check whether the returned content is returned to the expected, if necessary, to check whether the data in the database is consistent with the expected change.

Now that you have a test case, you still need to consider one special case. Now that I have written a relatively complete test case for a function, I have run all the PASS, and I find that the error of that function is still in the online log. Check that a branch of the function has not been tested before, but some situation on the line has run into the branch, and a very insignificant syntax error has been reported. Is there any way to make sure that all the code has been tested? What needs to be introduced here is a concept called test case coverage, where basically every language has a responsive implementation. Test case coverage quantifies whether your test case has run through all of the code in the x file, and all you need to do is keep your coverage as close to 100% as possible.

In a sense, test cases and test coverage are tools to increase developers' confidence in their code. But they are not omnipotent. There is always the possibility of missing some parameters in the test case. Of course, there is no code written for this possibility in your code. In the end, the test case coverage can only tell you that the code you write has been tested for you. So write as strict a code as possible, such as javascript using === instead of == whenever possible, using strongly typed programming specifications, etc., to reduce the potential risk of accepting a wide range of parameters.

3. How does code design simplify test case writing

The entire Web (and not limited to web) usually consists of three layers of code -- pure data processing and operations, involving databases, and involving specific network protocols. Among them, pure data operation is used to process functions or other codes that are mainly ordinary operations. When it comes to database, it is M in MVC in the traditional sense, and when it comes to specific network protocols, it is C in the corresponding sense. The three tests correspond to the first three of the regular tests in section 1.

Because the C layer often involves rendering the page and simulating the corresponding protocols, it is often possible to reduce the complexity of the test case code by focusing on functions and database-related code, which requires as little Controller code as possible. Some current recommendations for more complex applications:

Base data validation on the M layer. If developed using Ruby, ActiveRecord and Mongoid both provide convenient validation functionality.
Try using Pub/Sub mode in your code with some of the hooks (hook) provided in ORM to communicate between Model. For example, when A is created, it posts a message. B listens for the message and modifies one of its own property values.
Use Command mode to pull some business-neutral functions out of the system, such as mail delivery.
For reference, please refer to Laravel wisper resque

Idea of 4.

All of the above have avoided the test cases that need to be coordinated between the front and rear ends. The following content is mainly aimed at this. There are already some elegant implementations of Ruby in this direction, and those interested can simply enjoy the Capybara first.

With the popularity of 1 family of browser drivers including Selenium Phantomjs and Watir based on Selenium, using code to control the browser is no longer a complex matter. Based on this capability, try to divide the front-end based test into four steps:

Waiting for a signature element to appear (for example, waiting for a page to load to play, or for a piece of content to load asynchronously)
Simulate user actions that include and are not limited to user clicks and user input
Wait for the signature element to appear in the feedback (such as so-and-so input box)
Judge the content and whether it meets the expectation
Based on this process, most of the front-end testing can be solved. But relying on this process alone is not enough, as there may be impeding elements such as captcha in the page, which you can try to fetch through the database/cache without changing the code. Also, as with the test interface, it involves inserting test data into the database before testing, seriously changing the data in the database after the test case is executed, and deleting the test data after all the tests are completed. As a result, the implementation of this test case code requires an understanding of both the front and back ends. It is also considering designing a more general scheme based on Capybara.

Finally, a piece of Capybara code is pasted to end this content:


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


Related articles: