Python Testing Tutorial 英文

微风

2019/03/24 发布于 技术 分类

文字内容
1. 目 录 致谢 Python Testing Tutorial Challenges Instructions for Trainers Exercise 1: Using a Mock Object Exercise 5: Import test data in multiple test packages Quotes py.test Introduction to the unittest Framework in Python Warming Up Unit Tests Fixtures Parameterized Tests Testing Command-Line Programs Test Suites Test Coverage Testing New Features Theme: Counting Words in Moby Dick Lesson Plan for a 45’ tutorial Lesson Plan for a 180’ tutorial Recap Puzzle 本文档使用 书栈(BookStack.CN) 构建 -1-
2. 致谢 致谢 当前文档 《Python Testing Tutorial(英文)》 由 进击的皇虫 使用 书栈 (BookStack.CN) 进行构建,生成于 2018-04-26。 书栈(BookStack.CN) 仅提供文档编写、整理、归类等功能,以及对文档内容的生成和导出工 具。 文档内容由网友们编写和整理,书栈(BookStack.CN) 难以确认文档内容知识点是否错漏。如 果您在阅读文档获取知识的时候,发现文档内容有不恰当的地方,请向我们反馈,让我们共同携手, 将知识准确、高效且有效地传递给每一个人。 同时,如果您在日常生活、工作和学习中遇到有价值有营养的知识文档,欢迎分享到 书栈 (BookStack.CN) ,为知识的传承献上您的一份力量! 如果当前文档生成时间太久,请到 书栈(BookStack.CN) 获取最新的文档,以跟上知识更新换 代的步伐。 文档地址:http://www.bookstack.cn/books/python_testing_tutorial'>http://www.bookstack.cn/books/python_testing_tutorial 书栈官网:http://www.bookstack.cn 书栈开源:https://github.com/TruthHun 分享,让知识传承更久远! 感谢知识的创造者,感谢知识的分享者,也感谢每一位阅读到此处的 读者,因为我们都将成为知识的传承者。 本文档使用 书栈(BookStack.CN) 构建 -2-
3. Python Testing Tutorial Python Testing Tutorial Python Testing Tutorial Overview Latest version of this book Copyright Contributors Python Testing Tutorial Overview This tutorial helps you to learn automated testing in Python 3 using the py.test framework. Latest version of this book Sources for this tutorial: github.com/krother/python_testing_tutorial. 本文档使用 书栈(BookStack.CN) 构建 -3-
4. Python Testing Tutorial PDF and EPUB versions: www.gitbook.io/book/krother/python-testingtutorial Copyright Feedback and comments are welcome at: krother@academis.eu © 2018 Magdalena & Kristian Rother Released under the conditions of a Creative Commons Attribution License 4.0. Contributors Kristian Rother, Magdalena Rother, Daniel Szoska 本文档使用 书栈(BookStack.CN) 构建 -4-
5. Challenges Challenges Challenges 1. Unit Tests 1.1 Test a Python function 1.2 Test proves if code is broken 1.3 Code proves if tests are broken 1.4 Test border cases Challenges 1. Unit Tests 1.1 Test a Python function The function main() in the module word_counter.py calculates the number of words in a text body. For instance, the following sentence contains three words: 1. Call me Ishmael Your task is to prove that the main() function calculates the number of words in the sentence correctly with three. Use the example test in test_1_1_unit_test.py. 1.2 Test proves if code is broken The test in the module test_failing_code.py fails, because there is a bug in the function word_counter.average_word_length(). In the sentence 1. Call me Ishmael The words are four, two, and seven characters long. This gives an average of: 1. >>> (4 + 2 + 7) / 3.0 2. 4.333333333333333 本文档使用 书栈(BookStack.CN) 构建 -5-
6. Challenges Your task is to fix the code, so that the test passes. Use the example in test_1_2_broken_code.py. 1.3 Code proves if tests are broken The test in the module test_failing_test.py fails, because there is a bug in the test file. Your task is to fix the test, so that the test passes. Use the example in test_1_3_broken_test.py. 1.4 Test border cases High quality tests cover many different situations. The most common situations for the program word_counter.py include: test case description expected output example input empty input is valid, but empty “” 0 minimal smallest reasonable input “whale” 1 typical representative input “whale eats captain” 3 invalid input is supposed to fail 777 Exception raised maximum largest reasonable input Melville’s entire book more than 200000 sanity program recycles its own output TextBody A created from another TextBody B A equals B nasty difficult example “That #~&%* program still doesn’t work!” 6 Your task is to make all tests in test_1_4_border_cases.py pass. 本文档使用 书栈(BookStack.CN) 构建 -6-
7. Instructions for Trainers Instructions for Trainers Instructions for Trainers Overview How to run a course using this toolkit Instructions for Trainers Overview This toolkit helps you to prepare training courses on automated testing in Python. It allows you to create courses with interchangeable testing frameworks background of participants course duration Our aim is to save you preparation time while leaving room for your own ideas. Most of all, we hope you have fun in your next course. How to run a course using this toolkit 1. Introduce the Moby Dick Theme to your trainees 2. Copy the code in code/mobydick and code/test_your_framework. 3. Set the PYTHONPATH environment variable, so that you can do import mobydick 4. Share the chapter “Challenges” with your trainees. 5. Share the chapter “Reference” on your test framework with your trainees. 6. Start coding! 本文档使用 书栈(BookStack.CN) 构建 -7-
8. Exercise 1: Using a Mock Object Exercise 1: Using a Mock Object Mock Objects Exercise 1: Using a Mock Object Mock Objects Exercise 1: Using a Mock Object The function word_report.get_top_words() requires an instance of the class TextBody. You need to test the function, excluding the possibility that the TextBody class is buggy. To do so, you need to replace the class by a Mock Object, a simple placeholder. Your task is to write a test for the function word_counter.get_top_words() that does not use the class TextBody. 本文档使用 书栈(BookStack.CN) 构建 -8-
9. Exercise 5: Import test data in multiple test packages Exercise 5: Import test data in multiple test packages Exercise 5: Import test data in multiple test packages Exercise 5: Import test data in multiple test packages In a big software project, your tests are distributed to two packages. Both test_first.py and test_second.py require the variable MOBYDICK_SUMMARY from the module test data.py. The package structure is like this: 1. testss/ 2. test_a/ 3. __init__.py 4. test_first.py 5. test_b/ 6. __init__.py 7. test_second.py 8. __init__.py 9. test_data.py 10. test_all.py Your task is to make sure that the variable MOBYDICK_SUMMARY is correctly imported to both test modules, so that the tests pass for all of: 1. tests/test_a/test_first.py 2. tests/test_b/test_second.py 3. tests/test_all.py 本文档使用 书栈(BookStack.CN) 构建 -9-
10. Quotes Quotes Quotes Quotes “Call me Ishmael” Herman Melville, Moby Dick 1851 “UNTESTED == BROKEN” Schlomo Shapiro, EuroPython 2014 “Code without tests is broken by design” Jacob Kaplan-Moss “Everyone knows that debugging is twice as hard as writing a program in the first place. So if you’re as clever as you can be when you write it, how will you ever debug it?” Brian Kernighan, “The Elements of Programming Style”, 2nd edition, chapter 2 “Pay attention to zeros. If there is a zero, someone will divide by it.” Cem Kaner “If you don’t care about quality, you can’t meet any other requirement” Gerald M. Weinberg “Testing shows the presence, not the absence of bugs.” Edsger W. Dijkstra “… we have as many testers as we have developers. And testers spend all their time testing, and developers spend half their time testing. We’re more of a testing, a quality software organization than we’re a software organization.” Bill Gates (Information Week, May 2002) 本文档使用 书栈(BookStack.CN) 构建 - 10 -
11. Quotes 本文档使用 书栈(BookStack.CN) 构建 - 11 -
12. py.test py.test py.test test fixtures and files test selection re-run failed tests py.test TO BE DONE test fixtures and files py.test tells you temp file paths if test fails. —> test file not deleted pytest.org/latest/tmpdir.html 1. def test_create_file(tempdir): 2. p = tmpdir.mkdir("sub").join("hello.txt"= 3. p.write("bla") 4. assert p.read() == "content" also see py.path.local test selection @slow decorator (see ‘patterns & examples’) re-run failed tests pyscaffold adds a py.test mode by default. 本文档使用 书栈(BookStack.CN) 构建 - 12 -
13. Introduction to the unittest Framework in Python Introduction to the unittest Framework in Python Introduction to the unittest Framework in Python Writing a test class Running the tests Testing command-line scripts Discovering tests Test data and fixtures Importing test data in multiple packages Introduction to the unittest Framework in Python unittest is a Python framework for writing Unit Tests, Integration Tests, and Acceptance Tests. It mainly provides a class TestCase and a main() method. unittest is typically imported with: 1. from unittest import TestCase, main Writing a test class Test classes should extend TestCase, and contain at least one method starting with test_ . Test methods contain assertions. TestCase offers many assertion methods (assertEqual, assertAlmostEqual, assertTrue etc.). 1. class AdditionTests(TestCase): 2. 3. 4. def test_add(self): self.assertEqual(add(3, 4), 7) Running the tests The unittest.main method will look for all classes derived from TestCase that have been imported. It runs all tests inside them and reports. Typically, you will find main() called in a separate code block: 本文档使用 书栈(BookStack.CN) 构建 - 13 -
14. Introduction to the unittest Framework in Python 1. if __name__ == '__main__': 2. main() You can run Python test files with unittest without calling main() 1. python -m unittest test_file Note: The name of the test module is spelled without .py Testing command-line scripts To test a command-line script call it using a shell command and redirect the output for further evaluation. The simplest way is to use os.system: 1. import os 2. os.system('python myprog.py > out.txt') Discovering tests 1. python -m unittest discover Test data and fixtures The methods setUp() and tearDown() can be used to prepare testing and clean up afterwards. Importing test data in multiple packages When you have many tests distributed to sub-packages, you may want to share test data among them. There are two ways to do so: Either set the PYTHONPATH variable to the directory with your tests. Alternatively, patch sys.path in a local module test_data.py in each of the sub-packages, so that they import ../test_data.* 本文档使用 书栈(BookStack.CN) 构建 - 14 -
15. Warming Up Warming Up Warming Up How many words are in the following sentence? How many words are in the next sentence? What is automated testing good for? Warming Up How many words are in the following sentence? 1. The program works perfectly? You will probably agree, that the sentence contains four words. How many words are in the next sentence? 1. That #§&%$* program still doesn't work!\nI already 2. de-bugged it 3 times, and still numpy.array 3. keeps raising AttributeErrors. What should I do? You may find the answer to this question less obvious. It depends on how precisely the special characters are interpreted. What is automated testing good for? Writing automated tests for your software helps you to: get clear on what you want the program to do. identify gaps in the requirements. prove the presence of bugs (not their absence!). help you during refactoring. 本文档使用 书栈(BookStack.CN) 构建 - 15 -
16. Unit Tests Unit Tests Unit Tests Exercise 1: Test a Python function Exercise 2: Test proves if code is broken Exercise 3: Code proves if tests are broken Exercise 4: Test border cases Unit Tests Exercise 1: Test a Python function The function main() in the module word_counter.py calculates the number of words in a text body. For instance, the following sentence contains three words: 1. Call me Ishmael Your task is to prove that the TextCorpus class calculates the number of words in the sentence correctly with three. Run the example test in test_unit_test.py with 1. pytest test_unit_test.py Exercise 2: Test proves if code is broken The test in the module test_failing_code.py fails, because there is a bug in the function word_counter.average_word_length(). In the sentence 1. Call me Ishmael The words are four, two, and seven characters long. This gives an average of: 1. >>> (4 + 2 + 7) / 3.0 2. 4.333333333333333 本文档使用 书栈(BookStack.CN) 构建 - 16 -
17. Unit Tests Fix the code in test_broken_code.py , so that the test passes. Exercise 3: Code proves if tests are broken The test in the module test_broken_test.py fails, because there is a bug in the test file. Your task is to fix the test, so that the test passes. Use the example in test_broken_test.py. Exercise 4: Test border cases High quality tests cover many different situations. The most common situations for the program word_counter.py include: test case description example input expected output empty input is valid, but empty “” 0 minimal smallest reasonable input “whale” 1 typical representative input “whale eats captain” 3 invalid input is supposed to fail 777 Exception raised maximum largest reasonable input Melville’s entire book more than 200000 sanity program recycles its own output TextBody A created from another TextBody B A equals B nasty difficult example “That #~&%* program still doesn’t work!” 6 Your task is to make all tests in test_border_cases.py pass. 本文档使用 书栈(BookStack.CN) 构建 - 17 -
18. Fixtures Fixtures Fixtures Exercise 1: A module for test data Exercise 2: Using the fixture Exercise 3: Create more fixtures Exercise 4: Fixtures from fixtures Fixtures Exercise 1: A module for test data Create a new module with a string variable that contains a conftest.py sentence with lots of special characters: 1. sample = """That #§&%$* program still doesn't work! 2. I already de-bugged it 3 times, and still numpy.array keeps raising AttributeErrors. What should I do?""" Create a function that returns a mobydick.TextCorpus object with the sample text above. Use the following as a header: 1. @pytest.fixture 2. def sample_corpus(): 3. ... Exercise 2: Using the fixture Now create a module test_sample.py with a function that uses the fixture: 1. def test_sample_text(sample_corpus): 2. assert sample_corpus.n_words == 77 Execute the module with conftest pytest . Note that you do not need to import . Pytest does that automatically. Exercise 3: Create more fixtures Create fixtures for the two text corpora in the files 本文档使用 书栈(BookStack.CN) 构建 mobydick_full.txt and - 18 -
19. Fixtures mobydick_summary.txt as well. Exercise 4: Fixtures from fixtures Create a fixture in conftest.py that uses another fixture: 1. from mobydick import WordCounter 2. 3. @pytest.fixture 4. def counter(mobydick_summary): 5. return WordCounter(mobydick_summary) Write a simple test that makes sure the fixture is not 本文档使用 书栈(BookStack.CN) 构建 None - 19 -
20. Parameterized Tests Parameterized Tests Parameterized Tests Exercise 1: Sets of example data Exercise 2: Write another parameterized test Parameterized Tests Exercise 1: Sets of example data You have a list of pairs (word, count) that apply to the text file mobydick_summary.txt : 1. PAIRS = [ 2. ('months', 1), 3. ('whale', 5), 4. ('captain', 4), 5. ('white', 2), 6. ('harpoon', 1), 7. ('goldfish', 0) 8. ] We will create six tests from these samples. Instead of creating six tests manually, we will use the test parametrization in pytest. Edit the file test_parameterized.py and add the following decorator to the test function: 1. @pytest.mark.parametrize('word, number', PAIRS) Add two arguments word and number to the function header and remove the assignment below. Run the test and make sure all six tests pass. Exercise 2: Write another parameterized test The function get_top_words() calculates the most frequent words in a text corpus. It should produce the following top five results for the book mobydick_full.txt: 本文档使用 书栈(BookStack.CN) 构建 - 20 -
21. Parameterized Tests position word 1. of 2. the 3. is 4. sea 5. ship Write one parameterized test that checks these five positions. 本文档使用 书栈(BookStack.CN) 构建 - 21 -
22. Testing Command-Line Programs Testing Command-Line Programs Testing Command-Line Programs Exercise 1: Test a command-line application Exercise 2: Test command-line options Exercise 3: User Acceptance Testing Command-Line Programs Exercise 1: Test a command-line application The program word_counter.py can be used from the command line to calculate the most frequent words with: 1. python word_counter.py mobydick_summary.txt Command-line applications need to be tested as well. You find tests in test_commandline.py. Your task is to make sure the command-line tests pass. Exercise 2: Test command-line options The program word_counter.py calculates most frequent words in a test file. It can be used from the command line to calculate the top five words: 1. python word_counter.py moby_dick_summary.txt 5 Your task is to develop a new test for the program. Exercise 3: User Acceptance The ultimate test for any software is whether your users are able to do what they need to get done. Your task is to manually use the program word_counter.py to find out whether Melville used ‘whale’ or ‘captain’ more frequently in the full text of the book “Moby Dick”. The User Acceptance test cannot be replaced by a machine. 本文档使用 书栈(BookStack.CN) 构建 - 22 -
23. Testing Command-Line Programs 本文档使用 书栈(BookStack.CN) 构建 - 23 -
24. Test Suites Test Suites Test Suites Exercise 1: Test collection Exercise 2: Options Exercise 3: Fixing tests Exercise 4: Test selection Test Suites Exercise 1: Test collection Run all tests written so far by simply typing 1. pytest Exercise 2: Options Try some options of pytest: 1. pytest -v # verbose output 2. 3. pytest -lf # re-run failed tests 4. 5. pytest -x # stop on first failing test Exercise 3: Fixing tests Fix the tests in test_suite.py Exercise 4: Test selection Run only one test class 1. pytest test_suite.py::TestAverageWordLength or a single test function: 1. pytest test_suite.py::TestAverageWordLength::test_average_words 本文档使用 书栈(BookStack.CN) 构建 - 24 -
25. Test Suites Your task is to run only the function test_word_counter.test_simple from the test suite in tests/. 本文档使用 书栈(BookStack.CN) 构建 - 25 -
26. Test Coverage Test Coverage Test Coverage Exercise 1: Calculate Test Coverage Exercise 2: Identify uncovered lines Exercise 3: Increase test coverage Test Coverage For the next exercises, you need to install a small plugin: 1. pip install pytest-cov Exercise 1: Calculate Test Coverage Calculate the percentage of code covered by automatic tests: 1. pytest --cov Exercise 2: Identify uncovered lines Find out which lines are not covered by tests. Execute 1. coverage html And open the resulting htmlcov/index.html in a web browser. Exercise 3: Increase test coverage Bring test coverage of word_counter.py to 100%. 本文档使用 书栈(BookStack.CN) 构建 - 26 -
27. Testing New Features Testing New Features Testing New Features Exercise 1: Add new feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature: special characters Exercise 2: Add new feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature: ignore case Exercise 3: Add new feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature: word separators Testing New Features Exercise 1: Add new feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature: special characters Add a new feature to the word_counter.py program. The program should remove special characters from the text before counting words. Your task is to prove that the new feature is working. Exercise 2: Add new feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature: ignore case Add a new feature to the word_counter.py program. The program should ignore the case of words, e.g. ‘captain’ and ‘Captain’ should be counted as the same word. Your task is to prove that the new feature is working. Exercise 3: Add new feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature:'>feature: word separators The program word_counter.py does separate words at spaces, but not tabulators. You need to change that. The following sentence should also contain four words: 1. The\tprogram\tworks\tperfectly. Your task is to add a test for this new situation and make it work. 本文档使用 书栈(BookStack.CN) 构建 - 27 -
28. Theme:'>Theme: Counting Words in Moby Dick Theme:'>Theme: Counting Words in Moby Dick Counting Words in Moby Dick Moby Dick:'>Dick: Plot synopsis Video Course Objective Why was this example selected? Counting Words in Moby Dick Moby Dick:'>Dick: Plot synopsis Captain Ahab was vicious because Moby Dick, the white whale, had bitten off his leg. So the captain set sail for a hunt. For months he was searching the sea for the white whale. The captain finally attacked the whale with a harpoon. Unimpressed, the whale devoured captain, crew and ship. The whale won. Video Moby Dick short synopsis on Youtube 本文档使用 书栈(BookStack.CN) 构建 - 28 -
29. Theme: Counting Words in Moby Dick Course Objective Herman Melville’s book “Moby Dick” describes the epic fight between the captain of a whaling ship and a whale. In the book, the whale wins by eating most of the other characters. But does he also win by being mentioned more often? In this course, you have a program that analyzes the text of Melville’s book. You will test whether the program work correctly? Why was this example selected? Three main reasons: The implementation is simple enough for beginners. Counting words easily yields different results (because of upper/lower case, special characters etc). Therefore the program needs to be thoroughly tested. You can easily change the theme to another book from Project Gutenberg. 本文档使用 书栈(BookStack.CN) 构建 - 29 -
30. Lesson Plan for a 45’ tutorial Lesson Plan for a 45’ tutorial Lesson Plan for a 45’ tutorial Target audience Learning Objective Lesson Plan Lesson Plan for a 45’ tutorial Target audience Programmers who have already written programs on their own but would like to learn about automated software testing. Learning Objective During the tutorial participants will implement automatic test functions that pass for the Moby Dick example. using the unittest module within 20’. Lesson Plan module topic time warm-up hello 1’ warm-up question: How do you know that your code works? 4’ motivation explain the benefit: You will be able to check in a few seconds that your program works. 1’ new content overview of the code example 1’ new content run the code example; collective analysis 15’ application write code using the task description 20’ wrap-up discuss pros and cons of testing 15’ wrap-up point to materials 2’ wrap-up goodbye 1’ 本文档使用 书栈(BookStack.CN) 构建 - 30 -
31. Lesson Plan for a 180’ tutorial Lesson Plan for a 180’ tutorial Lesson plan for a 180’ tutorial Lesson plan for a 180’ tutorial I used a very similar lesson plan to conduct a training at EuroPython 2014. The audience consisted of about 60 Python programmers, including beginners and seasoned developers. module topic time warm-up introduce the Moby Dick theme 5’ warm-up icebreaker activity 5’ warm-up announce training objectives 5’ part 1 Writing automatic tests in Python 45’ warm-up methods in the unittest module 5’ new content presentation: Unit Tests, Integration Tests, and Acceptance Tests 15’ application challenges 1.1 - 1.5 20’ wrap-up Q & A 5’ part 2 Integration and Acceptance Tests (45’) warm-up quiz on test strategies 10’ new content presentation on Test-Driven-Development 10’ application challenges 2.1 - 3.3 20’ wrap-up Q & A 5’ break 10’ part 3 Tests data and test suites (45’) warm-up multiple choice questions 10’ new content presentation on test suites 10’ application exercises 4, 5, 6 20’ wrap-up Q & A 5’ summary Benefits of testing (25’) 本文档使用 书栈(BookStack.CN) 构建 - 31 -
32. Lesson Plan for a 180’ tutorial transfer group discussion on benefits of testing 20’ finishing summary 4’ finishing goodbye 1’ 本文档使用 书栈(BookStack.CN) 构建 - 32 -
33. Recap Puzzle Recap Puzzle Recap Puzzle Recap Puzzle The rows in the table got messed up! Match the test strategies with the correct descriptions. test strategy description Unit Test files and examples that help with testing Acceptance Test collection of tests for a software package Mock relative amount of code tested Fixture tests a single module, class or function Test suite prepare tests and clean up afterwards Test data replaces a complex object to make testing simpler Test coverage tests functionality from the users point of view This exercise works better when each element from the table is printed on a paper card. 本文档使用 书栈(BookStack.CN) 构建 - 33 -