[PYTHON] Test Driven Development with Django Part 3

Test Driven Development with Django Part 3

This is a learning note to help you understand Test Driven Development (TDD) in Django.

References are [** Test-Driven Development with Python: Obey the Testing Goat: Using Django, Selenium, and JavaScript (English Edition) 2nd Edition **](https://www.amazon.co.jp/dp/B074HXXXLS We will proceed with learning based on / ref = dp-kindle-redirect? _ Encoding = UTF8 & btkr = 1).

In this book, we are conducting functional tests using Django 1.1 series and FireFox, but this time we will carry out functional tests on Djagno 3 series and Google Chrome. I've also made some personal modifications (such as changing the Project name to Config), but there are no major changes.

⇒⇒ Click here for Part 1 --Chapter 1 ⇒⇒ Click here for Part 2-Chapter 2

Part1. The Basics of TDD and Django

Capter3. Testing as Simple Home Page with Unit Tests

In Chapter 2, we wrote a functional test using ʻunittest.TestCase` and tested if there was a" To-Do "in the page title. This time we will actually start the application and do TDD.

Our First Django App, and Our First Unit Test

Django takes the form of building multiple applications under one project. Let's create a Django application right away. Here we will create an application named ** lists **.

$ python manage.py startapp lists

Unit Tests, and How They Differ from Functional Tests

Functional Tests test your application from the outside (from the user's perspective) to see if it's working correctly. Unit Tests test whether your application is working from the inside (from a developer's point of view). TDD is required to cover functional tests and unit tests, and the development procedure is as follows.

** step1. ** Write a functional test (explaining new features from the user's perspective).

** step2. ** If the functional test fails, think about how to write the code to pass the test (do not write it suddenly). Add unit tests to define how you want your code to behave.

** step3. ** If the unit test fails, write the minimum application code that the unit test will pass.

** step4. ** Repeat step2 and step3 to finally check if the functional test passes.

Unit Testing in Django

I will write the test of the view of the homepage in lists / tests.py. Let's check here first.

# lists/tests.py

from django.test import TestCase

# Create your tests here.

Looking at this, I found that I could write unit tests using the TestCase class provided by Django. djagno.test.TestCase is an extension of ʻunittest.TestCase`, the standard module used in functional tests. Let's write a unit test as a trial.

# lists/tests.py

from django.test import TestCase


class SmokeTest(TestCase):

    def test_bad_maths(self):
        self.assertEqual(1 + 1, 3)

Django has a test runner feature that looks for and runs tests for each application. Let's start Django's test runner.

$ python manage.py test

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_bad_maths (lists.tests.SmokeTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\--your_pass--\django-TDD\lists\tests.py", line 9, in test_bad_maths
    self.assertEqual(1 + 1, 3)
AssertionError: 2 != 3

----------------------------------------------------------------------
Ran 1 test in 0.005s

FAILED (failures=1)
Destroying test database for alias 'default'...

I was able to confirm that lists / tests.py was executed and failed. I'll commit here.

$ git status
$ git add lists
$ git commit -m "Add app for lists, with deliberately failing unit test"

Django's MVC, URLs, and View Functions

Django needs to define what to do with a particular URL. The Django workflow looks like this:

  1. HTTP / request comes to a specific URL

  2. Since the rule has been decided which View should be executed for HTTP / request, execute the view according to the rule.

  3. View processes the request and returns HTTP / response.

Therefore, we have two things to do:

  1. Is it possible to associate the URL with the View (resolve the URL)?

  2. Can View change the HTML that can pass the functional test?

Now let's open lists / tests.py and write a small test.

# lists/tests.py

from django.urls import resolve  #add to
from django.test import TestCase
from lists.views import home_page  #add to

class HomePageTest(TestCase):

    def test_root_url_resolve_to_home_page_view(self):
        found = resolve('/')
        self.assertEqual(found.func, home_page)

django.urls.resolve is a module for resolving URLs that django uses internally. from lists.views import home_page is the view I will write next. You can see that this is described below. Let's test it.

$ python manage.py test

System check identified no issues (0 silenced).
E
======================================================================
ERROR: lists.tests (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: lists.tests
Traceback (most recent call last):
  File "C:\--your_user_name--\AppData\Local\Programs\Python\Python37\lib\unittest\loader.py", line 436, in _find_test_path
    module = self._get_module_from_name(name)
  File "C:\--your_user_name--\AppData\Local\Programs\Python\Python37\lib\unittest\loader.py", line 377, in _get_module_from_name
    __import__(name)
  File "C:\--your_path--\django-TDD\lists\tests.py", line 5, in <module>
    from lists.views import home_page
ImportError: cannot import name 'home_page' from 'lists.views' (C:\Users\--your_path--\django-TDD\lists\views.py)


----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

ʻImport Errorhas appeared. Looking at the contents, it tells me thathome_page cannot be imported from lists.views. Now let's write home_page in lists.views`.

# lists/views.py

from django.shortcuts import render

home_page = None

It sounds like a joke, but this should solve the ʻImport Error`. Recall that TDD feels like writing the smallest code to resolve an error.

Let's test it again.

$ python manage.py test

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
E
======================================================================
ERROR: test_root_url_resolve_to_home_page_view (lists.tests.SmokeTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\--your_path--\django-TDD\lists\tests.py", line 10, in test_root_url_resolve_to_home_page_view
    found = resolve('/')
  File "C:\--your_path--\django-TDD\venv-tdd\lib\site-packages\django\urls\base.py", line 25, in resolve
    return get_resolver(urlconf).resolve(path)
  File "C:\--your_path--\django-TDD\venv-tdd\lib\site-packages\django\urls\resolvers.py", line 575, in resolve
    raise Resolver404({'tried': tried, 'path': new_path})
django.urls.exceptions.Resolver404: {'tried': [[<URLResolver <URLPattern list> (admin:admin) 'admin/'>]], 'path': ''}

----------------------------------------------------------------------
Ran 1 test in 0.005s

FAILED (errors=1)
Destroying test database for alias 'default'...

Sure, the ʻImport Errorwas resolved, but the test failed again. If you check Traceback, you'll see that Django returns a 404 error even thoughresolve resolves'/'. This means that Django hasn't been able to resolve '/'`.

urls.py

Django has urls.py that maps URLs to Views. config / urls.py will be the main urls.py. Let's check here.

# config/urls.py

"""config URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]

If you check the contents, you will find how to write a URL mapping called " config URL Configuration ", so refer to it. This time, let's add the mapping between " / " and home_page to ʻurl patternsby writing Funciton views. Also, ʻadmin /is not used yet, so comment it out.

# config/urls.py

from django.contrib import admin
from django.urls import path
from lists import views


urlpatterns = [
    # path('admin/', admin.site.urls),  #Comment out
    path('', views.home_page, name='home')
]

The mapping is done. Do a test.

$ python manage.py test
[...]
TypeError: view must be a callable or a list/tuple in the case of include().

I added the URL mapping so the 404 error was resolved, but I got a TypeError. This is probably because when I called home_page from lists.view, it didn't return anything with home_page = None. Let's fix this by editing lists / views.py.

# lists/views.py

from django.shortcuts import render

def home_page():
    pass

I will test it.

$ python manage.py test

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.003s

OK
Destroying test database for alias 'default'...

I was able to pass the unit test. I'll commit here.

$ git add .
$ git status
$ git commit -m "First unit test and url mapping, dummy view"

We'll rewrite lists / tests.py so that we can test if the current lists / views.py is actually returning HTML.

# lists/tests.py

from django.urls import resolve
from django.test import TestCase
from django.http import HttpRequest

from lists.views import home_page


class HomePageTest(TestCase):

    def test_root_url_resolve_to_home_page_view(self):
        found = resolve('/')
        self.assertEqual(found.func, home_page)

    def test_home_page_returns_current_html(self):  #add to
        request = HttpRequest()
        response = home_page(request)
        html = response.content.decode('utf8')
        self.assertTrue(html.startswith.('<html>'))
        self.assertIn('<title>To-Do lists</title>', html)
        self.assertTrue(html.endwith('</html>'))

test_root_url_resolve_to_home_page_view checks to see if the URL mapping is correct, I'm checking if the correct HTML is returned with test_home_page_returns_current_html. We've added a new unit test, so let's test it right away.

$ python manage.py test

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
E.
======================================================================
ERROR: test_home_page_returns_current_html (lists.tests.SmokeTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\--your_path--\django-TDD\lists\tests.py", line 18, in test_home_page_returns_current_html
    response = home_page(request)
TypeError: home_page() takes 0 positional arguments but 1 was given

----------------------------------------------------------------------
Ran 2 tests in 0.005s

FAILED (errors=1)
Destroying test database for alias 'default'...

I got a TypeError. When I check the contents, it says home_page () takes 0 positional arguments but 1 was given. You can see that the definition of home_page () does not specify any arguments ( 0 positional arguments), but it is strange with the arguments given ( 1 was given).

So I would like to rewrite lists / views.py.

# lists/views.py

from django.shortcuts import render


def home_page(request):  #Change
    pass

Added the argument request to thehome_page ()function. Let's test with this.

$ python manage.py test

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
E.
======================================================================
ERROR: test_home_page_returns_current_html (lists.tests.SmokeTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\--your_path--\django-TDD\lists\tests.py", line 19, in test_home_page_returns_current_html
    html = response.content.decode('utf8')
AttributeError: 'NoneType' object has no attribute 'content'

----------------------------------------------------------------------
Ran 2 tests in 0.005s

FAILED (errors=1)
Destroying test database for alias 'default'...

The TypeError has been resolved, but the next time I get a ʻAttributeError. Since it says 'NoneType' object has no attribute'content', it seems that the cause is that the return value of home_page (request)isNone`. Modify lists / views.py.

# lists/views.py

from django.shortcuts import render
from django.http import HttpResponse  #add to


def home_page(request):
    return HttpResponse()

Fixed to return django.http.HttpResponse. Let's test it.

$ python manage.py test

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F.
======================================================================
FAIL: test_home_page_returns_current_html (lists.tests.SmokeTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\--your_path--\django-TDD\lists\tests.py", line 20, in test_home_page_returns_current_html
    self.assertTrue(html.startswith('<html>'))
AssertionError: False is not true

----------------------------------------------------------------------
Ran 2 tests in 0.005s

FAILED (failures=1)
Destroying test database for alias 'default'...

ʻAttribute Error has been resolved and ʻAssertion Error has occurred. You can see that the message False is not ture is displayed because html.startwith ('<html>') is False. Modify lists / views.py.

# lists/views.py

from django.shortcuts import render
from django.http import HttpResponse


def home_page(request):
    return HttpResponse('<html>')  #Change
$ python manage.py test

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F.
======================================================================
FAIL: test_home_page_returns_current_html (lists.tests.SmokeTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\--your_path--\django-TDD\lists\tests.py", line 21, in test_home_page_returns_current_html
    self.assertIn('<title>To-Do lists</title>', html)
AssertionError: '<title>To-Do lists</title>' not found in '<html>'

----------------------------------------------------------------------
Ran 2 tests in 0.005s

FAILED (failures=1)
Destroying test database for alias 'default'...

It is also ʻAssertion Error. It is said that ' To-Do lists </ title>'` cannot be found. Modify lists / views.py.</p> <pre><code class="language-python"># lists/views.py from django.shortcuts import render from django.http import HttpResponse def home_page(request): return HttpResponse('<html><title>To-Do lists</title>') #Change </code></pre> <pre><code class="language-sh">$ python manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). F. ====================================================================== FAIL: test_home_page_returns_current_html (lists.tests.SmokeTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\--your_path--\django-TDD\lists\tests.py", line 22, in test_home_page_returns_current_html self.assertTrue(html.endswith('</html>')) AssertionError: False is not true ---------------------------------------------------------------------- Ran 2 tests in 0.013s FAILED (failures=1) Destroying test database for alias 'default'... </code></pre> <p>It is also ʻAssertion Error<code>. I heard that </code>'</ html>'` cannot be found. Modify lists / views.py.</p> <pre><code class="language-python"># lists/views.py from django.shortcuts import render from django.http import HttpResponse def home_page(request): return HttpResponse('<html><title>To-Do lists</title></html>') #Change </code></pre> <p>This should finally work.</p> <pre><code class="language-sh">$ python manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). .. ---------------------------------------------------------------------- Ran 2 tests in 0.004s OK Destroying test database for alias 'default'... </code></pre> <p>It went well. Now that the unit tests have passed, let's start the development server and then run the functional tests.</p> <pre><code class="language-sh">#Launching the development server $ python manage.py runserver #Launch another cmd and run a functional test $ python functional_tests.py DevTools listening on ws://127.0.0.1:51108/devtools/browser/9d1c6c55-8391-491b-9b14-6130c3314bba F ====================================================================== FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "functional_tests.py", line 21, in test_can_start_a_list_and_retrieve_it_later self.fail('Finish the test!') AssertionError: Finish the test! ---------------------------------------------------------------------- Ran 1 test in 7.244s FAILED (failures=1) </code></pre> <p>The functional test is FAILED, because I used <code>.fail</code> in ʻunittest.TestCase` to make sure that an error occurs even if the test passes. Therefore, you can see that the functional test was successful!</p> <p>Let's commit.</p> <pre><code class="language-sh">$ git add . $ git commit -m "Basic view now return minimal HTML" </code></pre> <h4>Chapter3 Summary</h4> <p>Make sure you've covered it so far.</p> <p>--I started the Django application.</p> <p>--I used Django's unit test runner.</p> <p>――I understand the difference between functional tests and unit tests.</p> <p>--I created a view using Django's request and response objects</p> <p>--Returned basic HTML.</p> <p>I was able to confirm the process of creating functions by rotating unit tests and code addition / correction cycles.</p> <!-- ENDDDDDDDDDDDDDDDDDDDDDDDDDDDDD --> <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> <!-- post_new_ui_horiz --> <ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-5469278205356604" data-ad-slot="4209814965" data-ad-format="auto" data-full-width-responsive="true"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> <div style="margin-top: 30px;"> <div class="link-top" style="margin-top: 1px;"></div> <p> <font size="4">Recommended Posts</font> <!-- BEGIN LINK ************************* --> <div style="margin-top: 10px;"> <a href="/en/0057b55c8ba763bda6ca">Test Driven Development with Django Part 3</a> </div> <div style="margin-top: 10px;"> <a href="/en/148b42f070be478f6e23">Test Driven Development with Django Part 4</a> </div> <div style="margin-top: 10px;"> <a href="/en/25668d8d53f0c099388f">Test Driven Development with Django Part 6</a> </div> <div style="margin-top: 10px;"> <a href="/en/58dc509e8f681da73199">Test Driven Development with Django Part 2</a> </div> <div style="margin-top: 10px;"> <a href="/en/dd7abd24961250208c7c">Test Driven Development with Django Part 1</a> </div> <div style="margin-top: 10px;"> <a href="/en/f431c13531ced5b3d2f3">Test Driven Development with Django Part 5</a> </div> <div style="margin-top: 10px;"> <a href="/en/52335515344b181bed31">Test Driven Development Startup with PySide & Pytest</a> </div> <div style="margin-top: 10px;"> <a href="/en/0e15f6c150caa13ca34c">Development digest with Django</a> </div> <div style="margin-top: 10px;"> <a href="/en/3b3265d745aef36f26ff">Django test</a> </div> <div style="margin-top: 10px;"> <a href="/en/ae41a2c1f49bb920cf58">[Test Driven Development (TDD)] Chapter 21 Summary</a> </div> <div style="margin-top: 10px;"> <a href="/en/f7d11978da13a5f52c25">How to authenticate with Django Part 2</a> </div> <div style="margin-top: 10px;"> <a href="/en/fb00eaf69ac91f367710">How to authenticate with Django Part 3</a> </div> <div style="margin-top: 10px;"> <a href="/en/96bedd0b9d9a0ed9b01b">Application development using SQLite with Django (PTVS)</a> </div> <div style="margin-top: 10px;"> <a href="/en/218b4a4918cf4fb2079d">Django begins part 1</a> </div> <div style="margin-top: 10px;"> <a href="/en/344666a0d77503b05b1b">Experience Part I "Multinational Currencies" in the book "Test Driven Development" in Python</a> </div> <div style="margin-top: 10px;"> <a href="/en/4f59f154b7a4b111fe0d">Internationalization with django</a> </div> <div style="margin-top: 10px;"> <a href="/en/5a4495e4df24d395bec4">Django begins part 4</a> </div> <div style="margin-top: 10px;"> <a href="/en/ab7f0eeeaec0e60d6b92">CRUD with Django</a> </div> <div style="margin-top: 10px;"> <a href="/en/d616a74f931babae4566">First Django development</a> </div> <div style="margin-top: 10px;"> <a href="/en/1ab36e3d5365e8ca2942">Create test data like that with Python (Part 1)</a> </div> <div style="margin-top: 10px;"> <a href="/en/1cb250a27bbbbaa5c719">Build Django + NGINX + PostgreSQL development environment with Docker</a> </div> <div style="margin-top: 10px;"> <a href="/en/217f3015a8b974638e17">Build the fastest Django development environment with docker-compose</a> </div> <div style="margin-top: 10px;"> <a href="/en/60bc24dba80688d5d03a">[Python] Build a Django development environment with Docker</a> </div> <div style="margin-top: 10px;"> <a href="/en/932b14dee0ec2efbf1a7">Django Getting Started Part 2 with eclipse Plugin (PyDev)</a> </div> <div style="margin-top: 10px;"> <a href="/en/943093d15db9a55e7b4c">Build a Django development environment with Doker Toolbox</a> </div> <div style="margin-top: 10px;"> <a href="/en/005109fa4d9ad9736eb1">Authenticate Google with Django</a> </div> <div style="margin-top: 10px;"> <a href="/en/03c5bd710221a8145dbf">Django 1.11 started with Python3.6</a> </div> <div style="margin-top: 10px;"> <a href="/en/03c6ddf42e6edd584a07">Primality test with Python</a> </div> <div style="margin-top: 10px;"> <a href="/en/06c52ecfd3c40cbbe522">Upload files with Django</a> </div> <div style="margin-top: 10px;"> <a href="/en/1613b2ac40caad14d62f">Strengthen with code test ⑦</a> </div> <div style="margin-top: 10px;"> <a href="/en/201d278ee52e81cf4eee">Output PDF with Django</a> </div> <div style="margin-top: 10px;"> <a href="/en/20c4b459b9fc52aef062">Strengthen with code test ⑨</a> </div> <div style="margin-top: 10px;"> <a href="/en/262590c2d1b727052aea">Strengthen with code test ③</a> </div> <div style="margin-top: 10px;"> <a href="/en/271b38ef2c6610f40929">Markdown output with Django</a> </div> <div style="margin-top: 10px;"> <a href="/en/42d6029e8f8eec510118">Use Gentelella with django</a> </div> <div style="margin-top: 10px;"> <a href="/en/4c9edec8fbefb3116bf6">Twitter OAuth with Django</a> </div> <div style="margin-top: 10px;"> <a href="/en/58052e820a384d6dcb47">Strengthen with code test ⑤</a> </div> <div style="margin-top: 10px;"> <a href="/en/5df4bde4435d9d0b2a1a">Strengthen with code test ④</a> </div> <div style="margin-top: 10px;"> <a href="/en/6e044550435796ef09c5">Getting Started with Django 1</a> </div> <div style="margin-top: 10px;"> <a href="/en/715cdd6c2830e2aca0f6">Primality test with python</a> </div> <div style="margin-top: 10px;"> <a href="/en/82454eaedbf94273c4f1">Send email with Django</a> </div> <div style="margin-top: 10px;"> <a href="/en/897221c051bd673471cb">sandbox with neo4j part 10</a> </div> <div style="margin-top: 10px;"> <a href="/en/8da746e150d1e0947107">Web application made with Python3.4 + Django (Part.1 Environment construction)</a> </div> <div style="margin-top: 10px;"> <a href="/en/8f94095a1e58e2d5cb21">File upload with django</a> </div> <div style="margin-top: 10px;"> <a href="/en/9d3e3db6706dd69407c6">Strengthen with code test ②</a> </div> <div style="margin-top: 10px;"> <a href="/en/abaf2a920ce60339a87f">Use LESS with Django</a> </div> <div style="margin-top: 10px;"> <a href="/en/b98ee1d1aa59fc12866e">Pooling mechanize with Django</a> </div> <div style="margin-top: 10px;"> <a href="/en/c9dbc320a0e0e84093da">Use MySQL with Django</a> </div> <div style="margin-top: 10px;"> <a href="/en/d14cc2b97ea595a13045">Strengthen with code test ①</a> </div> <div style="margin-top: 10px;"> <a href="/en/dde20a965e212ab449b2">Build a development environment with Poetry Django Docker Pycharm</a> </div> <div style="margin-top: 10px;"> <a href="/en/ecae39fe75b412d44e89">[Memo] Django development environment</a> </div> <div style="margin-top: 10px;"> <a href="/en/ed64190a12a4498b9446">Articles that enable system development with Django (Python) _Introduction</a> </div> <div style="margin-top: 10px;"> <a href="/en/edb561ceae1c4abdd16a">Start today with Django</a> </div> <div style="margin-top: 10px;"> <a href="/en/f696723ee7ac9c3796cd">Getting Started with Django 2</a> </div> <div style="margin-top: 10px;"> <a href="/en/fbf614250f13b50dac7e">Strengthen with code test ⑧</a> </div> <div style="margin-top: 10px;"> <a href="/en/fdd86d6166533c1070ef">Strengthen with code test ⑨</a> </div> <div style="margin-top: 10px;"> <a href="/en/1665099685f9a05820ce">Tutorial for doing Test Driven Development (TDD) in Flask-2 Decorators</a> </div> <div style="margin-top: 10px;"> <a href="/en/242367a83c313a5e46bf">Build a Django development environment with Docker! (Docker-compose / Django / postgreSQL / nginx)</a> </div> <div style="margin-top: 10px;"> <a href="/en/371881a6b8ecfa768606">[Memo] Build a development environment for Django + Nuxt.js with Docker</a> </div> <div style="margin-top: 10px;"> <a href="/en/82678e1cd59c925846b4">[Django] Build a Django container (Docker) development environment quickly with PyCharm</a> </div> <div style="margin-top: 10px;"> <a href="/en/8b55d1e134f29b8a8dcd">Build a bulletin board app from scratch with Django. (Part 2)</a> </div> <!-- END LINK ************************* --> </p> </div> </div> </div> <div class="footer text-center" style="margin-top: 40px;"> <!-- <p> Licensed under cc by-sa 3.0 with attribution required. </p> --> </div> <script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/js/bootstrap.min.js"></script> <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.1.2/build/highlight.min.js"></script> <script> $(document).ready(function() { var cfg_post_height = 60; var cfg_per = 0.51; var ads_obj = $('<ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-5469278205356604" data-ad-slot="7950405964"></ins>'); $('pre code').each(function(i, e) {hljs.highlightBlock(e)}); function getDocumentOffsetPosition( el ) { var _x = 0; var _y = 0; while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) { _x += el.offsetLeft - el.scrollLeft; _y += el.offsetTop - el.scrollTop; el = el.offsetParent; } return { top: _y, left: _x }; } if ( $( "#article202011" ).length ) { var h1_pos = getDocumentOffsetPosition($('h1')[0]); var footer_pos = getDocumentOffsetPosition($('.link-top')[0]); var post_distance = footer_pos.top - h1_pos.top; // console.log('h1_pos: '+ h1_pos.top); // console.log(cfg_post_height) if((post_distance/h1_pos.top)>=cfg_post_height) { // console.log('tesssssssssssssssssssssssssssssssss'); $( ".container p" ).each(function( index ) { var p_tag_pos = $(this).position().top; var dis = p_tag_pos - h1_pos.top; var per = dis/post_distance; if(per>cfg_per) { ads_obj.insertAfter($(this)); (adsbygoogle = window.adsbygoogle || []).push({}); console.log( index + ": " + $( this ).text() ); return false; } }); } } }); </script> <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> <!-- ads --> <script data-ad-client="ca-pub-5469278205356604" async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js" type="d7540fe192d881abe59fcf57-text/javascript"></script> <!-- end ads --> </body> </html><script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="e001492e9c51c79b29e87ba9-|49" defer></script>