Домашни > Bang the ranga!


Bang the ranga!
Краен срок: 30.05.2026 14:00
Точки: 3

Ще започнем с един любопитен факт. Освен [песента на Skrillex](https://www.youtube.com/watch?v=YJVmu6yttiw), Bangarang се споменава за първи път в поп културата още през 1991 във филма [Hook](https://www.youtube.com/watch?v=G87uIh9ISWU). 25 години по-късно му добавяме едно "a" и получаваме [Bangaranga](https://i.redd.it/jp7b25ekmbmg1.jpeg)! За да не останем назад със събитията, решихме да нахвърляме едно последно домашно за вас, да не ви е скучно покрай празника. И ако някой се е чудил къде е Коцето... ![Коце, къде си?!](/media/resources/challenge-4.png) #### Задачата За да не ви е за сефте на проектите, ще искаме от вас да напишете малко тестове. Ние пишем функционалността, вие пишете тестове, използвайки преподадения до момента материал _(testing framework)_. Ние ще подготвим функцията `does_the_banga_rang`, за която имаме следните изисквания: - Приема име на файл. - Чете файла. - Проверява дали в него се съдържат цели "думи", които да могат да сформират _(сформират ще рече - конкатенират изцяло)_ низа `"bangaranga"`. Т.е. ако в съдържанието на файла имаме `"The banga is doing some ranga!"` - това съдържа `"banga" + "ranga" = "bangaranga"`. Ако имаме `"Does the banga rang?"` - нямаме такива думи. - "Думи" в този контекст са низове от латински букви, които са оградени от `r"\b"`, както в регулярните изрази, т.е. `r"\b[a-z]+\b"`. Алгоритъмът е case-insensitive. - Думите трябва да са в точната последователност из съдържанието на файла. Т.е. `"banga is ranga"` е валидно, но `"ranga is banga"` - не. - Функцията връща число, равно на **минималния** брой отделни думи, които сформират `"bangaranga"`. Например: ``` # 2.txt banga the ranga ``` ``` # 3.txt bang a small ranga ``` ``` # multiple.txt bang a banga ranga bangaranga ``` ``` # питонски файл или REPL print(does_the_banga_rang("2.txt")) # 2 print(does_the_banga_rang("3.txt")) # 3 print(does_the_banga_rang("multiple.txt")) # 1 - bangaranga ``` - Ако файлът не съдържа думи, които да удовлетворяват изискването за `"bangaranga"` - функцията връща `0` - `does_the_banga_rang("some_file_without_bangaranga.txt") # 0`. - Ако има проблем с четенето от файла - `OSError` или `IOError` - искаме да се хвърли грешката `TheBangaDoesNotRangError`, дефинирана в същия файл, както функцията. И отново - **вие няма да пишете `does_the_banga_rang`, това ще го напишем ние. Вашата задача е, да напишете тестове за въпросната функция**. #### Тестовете Очевидно не знаете каква ще е имплементацията на функцията `does_the_banga_rang`, така че тестовете ви ще трябва да са black-box (както е редно за unit тестове). В тестовете си импортирайте така: ``` from bangaranga import does_the_banga_rang, TheBangaDoesNotRangError ``` ##### Какво се очаква от вашите тестове? - Вашите тестове трябва да проверяват публичното поведение на функцията според описаните по-горе изисквания. - За да бъде един test suite надежден, той трябва да минава успешно срещу коректната имплементация. - И също така да се проваля срещу некоректни имплементации, когато те нарушават изискванията. - Не пишете повече от 12 теста. Дори и по-малко биха покрили целия набор от изисквания, който сме дефинирали по-горе - Очакваме не просто тестовете ви да минат (или фейлнат, в зависимост от обстоятелствата), а да спазите добрите практики и насоки, които сме обяснили в лекциите за тестване и проведените live coding workshop-и - атомарност и автономност на тестовете, липса на странични ефекти и т.н. Тази точка ще оценяваме ръчно с допълнителни или наказателни точки, отвъд резултата от автоматизираните тестове. Помислете какви входове биха доказали, че функцията спазва описаните изисквания. Добрият test suite обикновено проверява: - нормални случаи; - гранични случаи; - случаи, в които няма валидно решение; - случаи, в които очакваме грешка. С други думи - не е достатъчно да тествате само примерите от условието.
 1import unittest
 2
 3import solution
 4
 5
 6class TestSanity(unittest.TestCase):
 7    """Check if all data is present."""
 8
 9    def test_requirements(self):
10        pass
11
12
13if __name__ == "__main__":
14    unittest.main()
  1import contextlib
  2import importlib
  3import io
  4import sys
  5import textwrap
  6import unittest
  7
  8import bangaranga
  9
 10
 11STUDENT_MODULE_NAME = "solution"
 12CORRECT_IMPLEMENTATION = "correct"
 13
 14
 15def format_student_output(output):
 16    return textwrap.indent(f"Output:\n{output}", "      | ", lambda line: True)
 17
 18
 19def iter_tests(suite):
 20    for item in suite:
 21        if isinstance(item, unittest.TestSuite):
 22            yield from iter_tests(item)
 23        else:
 24            yield item
 25
 26
 27class RecordingTextTestResult(unittest.TextTestResult):
 28    def startTestRun(self):
 29        super().startTestRun()
 30        self.successful_test_ids = set()
 31        self.failed_test_ids = set()
 32
 33    def addSuccess(self, test):
 34        super().addSuccess(test)
 35        self.successful_test_ids.add(test.id())
 36
 37    def addFailure(self, test, err):
 38        super().addFailure(test, err)
 39        self.failed_test_ids.add(test.id())
 40
 41    def addError(self, test, err):
 42        super().addError(test, err)
 43        self.failed_test_ids.add(test.id())
 44
 45
 46class RecordingTextTestRunner(unittest.TextTestRunner):
 47    resultclass = RecordingTextTestResult
 48
 49
 50def run_student_tests_against(implementation_name, only_test_ids=None):
 51    bangaranga._select_implementation(implementation_name)
 52    sys.modules.pop(STUDENT_MODULE_NAME, None)
 53    importlib.invalidate_caches()
 54    output = io.StringIO()
 55    try:
 56        with contextlib.redirect_stdout(output), contextlib.redirect_stderr(output):
 57            student_module = importlib.import_module(STUDENT_MODULE_NAME)
 58            suite = unittest.defaultTestLoader.loadTestsFromModule(student_module)
 59            if only_test_ids is not None:
 60                suite = unittest.TestSuite(test for test in iter_tests(suite) if test.id() in only_test_ids)
 61            result = RecordingTextTestRunner(stream=output, verbosity=0).run(suite)
 62    except BaseException as error:
 63        return {
 64            "was_successful": False,
 65            "successful_test_ids": set(),
 66            "failed_test_ids": {"<import-or-discovery-error>"},
 67            "output": f"{output.getvalue()}\n{type(error).__name__}: {error}",
 68        }
 69    finally:
 70        bangaranga._select_implementation(CORRECT_IMPLEMENTATION)
 71    return {
 72        "was_successful": result.wasSuccessful(),
 73        "successful_test_ids": result.successful_test_ids,
 74        "failed_test_ids": result.failed_test_ids,
 75        "output": output.getvalue(),
 76    }
 77
 78
 79class StudentTestSuiteEvaluationTests(unittest.TestCase):
 80    def test_student_tests_pass_against_correct_implementation(self):
 81        result = run_student_tests_against(CORRECT_IMPLEMENTATION)
 82        student_output = format_student_output(result["output"])
 83        self.assertTrue(
 84            result["was_successful"], f"Student tests should pass against the correct implementation.\n{student_output}"
 85        )
 86
 87
 88def make_bad_implementation_test(implementation_name):
 89    def test(self):
 90        correct_result = run_student_tests_against(CORRECT_IMPLEMENTATION)
 91        valid_test_ids = correct_result["successful_test_ids"]
 92        self.assertTrue(
 93            valid_test_ids,
 94            f"No student tests passed against the correct implementation.\n{format_student_output(correct_result['output'])}",
 95        )
 96
 97        bad_result = run_student_tests_against(implementation_name, only_test_ids=valid_test_ids)
 98        self.assertTrue(
 99            bad_result["failed_test_ids"],
100            f"Student tests passed against incorrect implementation: {implementation_name!r}.\n{format_student_output(bad_result['output'])}",
101        )
102
103    test.__name__ = f"test_student_tests_catch_{implementation_name}"
104    return test
105
106
107for implementation_name in bangaranga._IMPLEMENTATIONS:
108    if implementation_name == CORRECT_IMPLEMENTATION:
109        continue
110    test_name = f"test_student_tests_catch_{implementation_name}"
111    setattr(StudentTestSuiteEvaluationTests, test_name, make_bad_implementation_test(implementation_name))
112
113
114if __name__ == "__main__":
115    unittest.main()
Дискусия