Домашни > Another brick in the wall


Another brick in the wall
Краен срок: 26.11.2024 18:00
Точки: 10

#### Make America Great Again! Тъй като Доналд Тръмп спечели последните избори в САЩ, решихме да потренираме с построяване на стена. Не се знае дали няма да ни се наложи и ние да браним границите си. ![Let's build a wall!](/media/resources/build-a-wall.png) #### Загрявка Напишете 5 класа, които репрезентират материали, с които може да се строи стена: * `Concrete` * `Brick` * `Stone` * `Wood` * `Steel` Всеки от класове има идентична сигнатура. При инстанциране приема един позиционен аргумент от тип `int`, който указва масата на създадения материал. Всеки от класовете има дефинирана плътност. Тя е различна за всеки материал: * `Concrete` = 2500 * `Brick` = 2000 * `Stone` = 1600 * `Wood` = 600 * `Steel` = 7700 Всяка инстанция от класовете за материал трябва да има пропърти `volume` за изчисление на обема си. Обемът е равен на масата, разделена на плътността. Очаква се резултатът да е от тип `float`. ``` brick = Brick(2000) print(brick.volume) # 1.0 ``` Лесно, нали? Brace yourselves! #### Фабрика Напишете клас `Factory`, който репрезентира фабрика за производство на материали. Класът може да се инстанцира без аргументи, като резултат е една конкретна фабрика за материали. Можем да правим много фабрики. ``` factory = Factory() ``` Инстанциите от тип `Factory` трябва да могат да бъдат извиквани, т.е. да са callable. Извикването може да се случи по два начина - само с произволен брой именувани аргументи или само с произволен брой позиционни. Не можем да извикваме инстанцията със смесица от позиционни и именувани. Не можем да я извикаме и без аргумент. Ако се опитаме да приложим някой от тези два случая, очакваме да се върне `ValueError` с произволен текст. ``` factory = Factory() factory() # ValueError factory(obj1, name=obj2) # ValueError ``` #### Извикване с именувани аргументи Ако извикате инстанция на `Factory` с произволен брой именувани аргументи, очаква се имената им да съвпадат с един от класовете за материали дефинирани по-горе (или имена на динамично създадени материали, за които ще поговорим по-долу). Ако получите именуван аргумент с друго име - `ValueError` с произволен текст. Очакваната стойност зад името на всеки именуван аргумент е масата за съответния материал, т.е. `int`. Няма да тестваме с невалидни стойности зад имената. Очаква се да върнете като резултат `tuple`, който съдържа по една инстанция, създадена от съответния клас, имаща съответната маса. Редът в резултата трябва да съвпада с реда на подадените аргументи. Извикване със само един именуван аргумент е валидно - връщате `tuple` с един елемент. ``` factory = Factory() brick, wood = factory(Brick=1000, Wood=5000) print(brick.volume) # 0.5 print(wood.volume) # 8.(3) ``` #### Извикване с позиционни аргументи Ако извикате инстанция на `Factory` с произволен брой позиционни аргументи, очаква се аргументите да са инстанции на някой от дефинираните материали (било то петте по-горе, или динамично създадени, за които ще разберете на следващия ред). Очаква се да се създаде динамично нов клас, който репрезентира сплав от подадените материали (да, наясно сме, че не всичко може да се смеси в сплав, но думата е удобна, така че я използваме). Като резултат, трябва да върнете инстанция на новосъздадения клас. Новият клас трябва да има същата сигнатура като 5-те класа за материал от началото на задачата: * да очаква маса за инстанциране; * да има дефинирана плътност; * да имплементира `volume` пропърти. Плътността на новосъздадения клас трябва да бъде средно аритметично от плътностите на всеки от 5-те оригинални материала, които са използвани за създаването на класа. Виж [апендикса](#-6). Името на класа трябва да е конкатенирана версия на имената на тези от **петте класа от по-горе**, които са използвани като вход, в азбучен ред, отделени от долна черта. Масата на създадената инстанция, трябва да е сума от масите на обектите, които са подадени като вход. Ако инстанцията ви бъде извикана няколко пъти с обекти, чиито класове съвпадат (без значение от реда), не очакваме да правите нов клас, тъй като такъв вече сте направили. Просто връщате нова инстанция от вече съществуващия клас. Това важи и за вход от само един елемент (дори той да е един от 5-те в началото). Ако има клас с очакваното име, използвате него. Ако няма, правите нов. Приемете, че няма да тестваме с вход, който съдържа повече от едно срещане на някой от 5-те първоначални материали. Т.е. не очаквайте да получите примерно два материала `Wood`, или комбинация от типа `Concrete_Wood` и `Wood`. Всеки от 5-те материала може да се срещне само веднъж. ``` factory = Factory() brick1, wood1 = factory(Brick=10, Wood=5) brick2, wood2 = factory(Brick=20, Wood=15) factory(brick1, wood1) # Инстанция на новосъздаден клас с име "Brick_Wood". # Класът има плътност (2000 + 600) / 2 = 1300. # Инстанцията има маса 15 factory(brick2, wood2) # Инстанция на вече съществуващия клас "Brick_Wood". # Инстанцията има маса 35 ``` Динамично създадени класове, които генерирате при извикване на инстанциите с позиционни аргументи, трябва да са валиден инпут за последващи извиквания на инстанцията, било то в позиционен, или именуван аргумент. ``` factory = Factory() brick, wood = factory(Brick=10, Wood=5) concreate, = factory(Concrete=10) brick_wood = factory(brick, wood) another_brick_wood, = factory(Brick_Wood=10) # Валидно инстанциране на ново парче сплав с маса 10 и тип Brick_Wood, който е създаден на предишния ред brick_concrete_wood = factory(brick_wood, concreate) # Създава нов клас Brick_Concrete_Wood # Плътността на класа е (2000 + 2500 + 600) / 3 = 1700 # Масата на инстанцията е 25 ``` Логично е след като фабриката е използвала дадени инстанции на материали, подадени като позиционни аргументи, за създаване на нова инстанция, входните инстанции да станат неизползваеми. Нека всеки обект, който е подаден като позиционен на инстанция на `Factory`, да става невалиден. Той продължава да съществува и можете да видите типа му и масата му, но ако го подадете отново на фабрика, да се поражда `AssertionError` с произволен текст. `AssertionError` е вграден тип грешка - не е нужно да го дефинирате. ``` factory = Factory() brick, wood = factory(Brick=10, Wood=5) brick_wood = factory(brick, wood) brick_wood = factory(brick, wood) # AssertionError ``` #### А стената? Като за десерт, трябва да можем да проверим дали материалите, които са създадени от дадена фабрика, са достатъчно за изграждане на стена с конкретен обем. Имплементирайте метод `can_build`, който приема един позиционен аргумент от тип `int` и връща булева стойност, в зависимост от това дали материалите, създадени от конкретната фабрика, са достатъчно. Не ни интересуват обекти, които са създадени извън инстанцията, върху която извикваме `can_build`. Изчисленията включват както материали, създадени с позиционни аргументи, така и материали, създадени с именувани. Не включваме материали, които са преизползвани (подавани на фабрика като позиционни), защото те са невалидни. ``` factory = Factory() brick, wood = factory(Brick=2000, Wood=1200) print(brick.volume) # 1.0 print(wood.volume) # 2.0 print(factory.can_build(3)) # True print(factory.can_build(4)) # False ``` #### Много фабрики? Да, можем да създаваме много фабрики. Ако даден материал е направен с една фабрика, той може да се подава на друга фабрика. Ако дадена сплав е създадена от една фабрика, другите фабрики трябва да са наясно за този технологичен скок и да преизползват вече създадения клас за тази сплав. ``` factory1 = Factory() factory2 = Factory() brick1, wood1 = factory1(Brick=1, Wood=2) brick2, wood2 = factory2(Brick=1, Wood=2) brick_wood1 = factory1(brick1, wood1) # Създава нов клас "Brick_Wood" и връща инстанция от него brick_wood2 = factory2(brick2, wood2) # Използва съществуващия клас "Brick_Wood" и връща инстанция от него print(type(brick_wood1) is type(brick_wood2)) # True ``` Правилото, че не можем да преизползваме материали, ако сме направили от тях друг материал, важи и тук. ``` factory1 = Factory() factory2 = Factory() brick, wood = factory1(Brick=1, Wood=2) brick_wood = factory1(brick, wood) factory2(brick, wood) # AssertionError ``` #### Can build? И последно. Искаме да можем да видим дали материалите, създадени (и не преизползвани) от **всички** фабрики са достатъчно за изграждане на стена. Оценката се случва спрямо обемите на въпросните материали. Ако методът `can_build` е извикан на инстанция, той проверява само материалите направени с тази инстанция. Ако извикаме метод `can_build_together` на класа `Factory`, проверява всички материали създадени (и не преизползвани) от всички инстанции. ``` factory1 = Factory() brick1, wood1 = factory1(Brick=2000, Wood=1200) print(brick1.volume) # 1.0 print(wood1.volume) # 2.0 brick_wood1 = factory1(brick1, wood1) print(brick_wood1.volume) # 2.46... print(factory1.can_build(3)) # False factory2 = Factory() brick2, wood2 = factory2(Brick=2000, Wood=1200) print(brick2.volume) # 1.0 print(wood2.volume) # 2.0 brick_wood2 = factory2(brick2, wood2) print(brick_wood2.volume) # 2.46... print(factory2.can_build(3)) # False print(Factory.can_build_together(3)) # True ``` #### Апендикс за изчисляване на нова плътност Ако имате два материала, например `Wood` и `Concrete`, и ги обедините в сплав, плътността на новия материал е `(2500 + 600) / 2 = 1550`. Ако към тази сплав добавите и `Steel`, плътността не може да се изчисли като средно аритметично от `Concrete_Wood` и `Steel`, защото те имат различно съотношение. Такава сметка би дала резултат `(1550 + 7700) / 2 = 4625`. Това е грешно. Правилната сметка трябва да направите като използвате трите основни материала, които са използвани: `(density of Concrete + density of Steel + density of Wood) / 3.` `(2500 + 7700 + 600) / 3 = 3600.`
 1import unittest
 2
 3import solution
 4
 5
 6class TestSanity(unittest.TestCase):
 7    """Check if all of the classes are present."""
 8
 9    def test_concrete(self):
10        self.assertIn('Concrete', dir(solution), 'Убеди се, че класът "Concrete" е наличен с точно това име.')
11        self.assertTrue(isinstance(solution.Concrete, type), 'Убеди се, че "Concrete" е клас.')
12
13    def test_brick(self):
14        self.assertIn('Brick', dir(solution), 'Убеди се, че класът "Brick" е наличен с точно това име.')
15        self.assertTrue(isinstance(solution.Brick, type), 'Убеди се, че "Brick" е клас.')
16
17    def test_stone(self):
18        self.assertIn('Stone', dir(solution), 'Убеди се, че класът "Stone" е наличен с точно това име.')
19        self.assertTrue(isinstance(solution.Stone, type), 'Убеди се, че "Stone" е клас.')
20
21    def test_wood(self):
22        self.assertIn('Wood', dir(solution), 'Убеди се, че класът "Wood" е наличен с точно това име.')
23        self.assertTrue(isinstance(solution.Wood, type), 'Убеди се, че "Wood" е клас.')
24
25    def test_steel(self):
26        self.assertIn('Steel', dir(solution), 'Убеди се, че класът "Steel" е наличен с точно това име.')
27        self.assertTrue(isinstance(solution.Steel, type), 'Убеди се, че "Steel" е клас.')
28
29    def test_factory(self):
30        self.assertIn('Factory', dir(solution), 'Убеди се, че класът "Factory" е наличен с точно това име.')
31        self.assertTrue(isinstance(solution.Factory, type), 'Убеди се, че "Factory" е клас.')
32
33
34if __name__ == '__main__':
35    unittest.main()
  1import unittest
  2from importlib import reload
  3
  4import solution
  5
  6
  7class TestMaterials(unittest.TestCase):
  8    """Test the material classes."""
  9
 10    def test_materials(self):
 11        """Test the material classes instances and properties."""
 12        # Material 1
 13        self.assertAlmostEqual(solution.Concrete(2500).volume, 1.0, places=1)
 14        self.assertAlmostEqual(solution.Concrete(3750).volume, 1.5, places=1)
 15        # Material 2
 16        self.assertAlmostEqual(solution.Brick(2000).volume, 1.0, places=1)
 17        self.assertAlmostEqual(solution.Brick(3000).volume, 1.5, places=1)
 18        # Material 3
 19        self.assertAlmostEqual(solution.Stone(1600).volume, 1.0, places=1)
 20        self.assertAlmostEqual(solution.Stone(2400).volume, 1.5, places=1)
 21        # Material 4
 22        self.assertAlmostEqual(solution.Wood(600).volume, 1.0, places=1)
 23        self.assertAlmostEqual(solution.Wood(900).volume, 1.5, places=1)
 24        # Material 5
 25        self.assertAlmostEqual(solution.Steel(7700).volume, 1.0, places=1)
 26        self.assertAlmostEqual(solution.Steel(11550).volume, 1.5, places=1)
 27
 28
 29class TestFactory(unittest.TestCase):
 30    """Test the factory class."""
 31
 32    def setUp(self):
 33        """Create isntances to be used by test cases."""
 34        self.factory1 = solution.Factory()
 35        self.factory2 = solution.Factory()
 36
 37    def tearDown(self):
 38        """Reload the Factory class."""
 39        del self.factory1
 40        del self.factory2
 41        reload(solution)
 42    
 43    def test_bad_input(self):
 44        """Test calling a factory using bad input."""
 45        # Calling with no arguments
 46        with self.assertRaises(ValueError):
 47            self.factory1()
 48        # Calling with mixed arguments
 49        brick = solution.Brick(2000)
 50        with self.assertRaises(ValueError):
 51            self.factory1(brick, Brick=1)
 52        # Calling with invalid name for an argument
 53        with self.assertRaises(ValueError):
 54            self.factory1(NotExisting=1)
 55    
 56    def test_named_arguments(self):
 57        """Test calling a factory using named arguments."""
 58        # Calling with single argument
 59        result = self.factory1(Concrete=1)
 60        self.assertIs(type(result), tuple)
 61        self.assertIs(type(result[0]), solution.Concrete)
 62        # Calling with multiple arguments
 63        result = self.factory1(Concrete=1, Brick=2, Stone=3, Wood=4, Steel=5)
 64        self.assertIs(type(result), tuple)
 65        self.assertIs(type(result[0]), solution.Concrete)
 66        self.assertIs(type(result[1]), solution.Brick)
 67        self.assertIs(type(result[2]), solution.Stone)
 68        self.assertIs(type(result[3]), solution.Wood)
 69        self.assertIs(type(result[4]), solution.Steel)
 70        self.assertAlmostEqual(result[0].volume, 0.0004, places=4)
 71        self.assertAlmostEqual(result[1].volume, 0.0010, places=4)
 72        self.assertAlmostEqual(result[2].volume, 0.0019, places=4)
 73        self.assertAlmostEqual(result[3].volume, 0.0067, places=4)
 74        self.assertAlmostEqual(result[4].volume, 0.00065, places=5)
 75    
 76    def test_positional_arguments_single_argument(self):
 77        """Test calling a factory using a sigle positional argument."""
 78        concrete1, = self.factory1(Concrete=5000)
 79        concrete2 = self.factory1(concrete1)
 80        self.assertIs(type(concrete2), solution.Concrete)
 81        self.assertEqual(concrete2.volume, 2.0)
 82        # Ensure the material cannot be used again
 83        with self.assertRaises(AssertionError):
 84            self.factory1(concrete1)
 85    
 86    def test_positional_arguments_multiple_argument_from_initial_set(self):
 87        """Test calling a factory using multiple positional arguments.
 88        
 89        Only using the original 5 materials.
 90        """
 91        wood, concrete, brick = self.factory1(Wood=1200, Concrete=5000,
 92                                              Brick=4000)
 93        result = self.factory1(wood, brick, concrete)
 94        self.assertEqual(result.__class__.__name__, 'Brick_Concrete_Wood')
 95        self.assertEqual(result.volume, 6.0)  # Density=1700, mass=10200
 96        # Ensure the new class expects mass and calculates correct volume
 97        brick_concrete_wood = type(result)(1700)
 98        self.assertEqual(brick_concrete_wood.volume, 1.0)
 99        # Ensure the materials cannot be reused
100        steel, = self.factory1(Steel=1)
101        with self.assertRaises(AssertionError):
102            self.factory1(wood, steel)
103        steel, = self.factory1(Steel=1)
104        with self.assertRaises(AssertionError):
105            self.factory1(concrete, steel)
106        steel, = self.factory1(Steel=1)
107        with self.assertRaises(AssertionError):
108            self.factory1(brick, steel)
109        # Ensure that attempting to create an alloy with only one
110        # expended material does not affect the other ones
111        wood, concrete, brick = self.factory1(Wood=9001, Concrete=5000,
112                                              Brick=1200)
113        _ = self.factory1(brick)
114        with self.assertRaises(AssertionError):
115            _ = self.factory1(wood, concrete, brick) # Brick is expended
116        concrete_wood = self.factory1(concrete, wood)
117
118    def test_positional_arguments_multiple_argument_with_dynamics(self):
119        """Test calling a factory using multiple positional arguments.
120
121        Using dynamicaly created classes.
122        """
123        materials = self.factory1(Stone=1600, Concrete=5000, Brick=4000)
124        brick_concrete_stone = self.factory1(*materials)
125        self.assertAlmostEqual(brick_concrete_stone.volume, 5.2131, places=4)
126        steel, wood = self.factory1(Steel=7700, Wood=1200)
127        mega_material = self.factory1(steel, brick_concrete_stone, wood)
128        self.assertEqual(mega_material.__class__.__name__,
129                         'Brick_Concrete_Steel_Stone_Wood')
130        self.assertAlmostEqual(mega_material.volume, 6.77, places=2)  # mass=19500, density=2880
131
132    def test_positional_arguments_singletons(self):
133        """Test dynamically created classes uniqueness."""
134        materials1 = self.factory1(Wood=1200, Concrete=5000, Brick=4000)
135        brick_concrete_wood1 = self.factory1(*materials1)
136        materials2 = self.factory1(Wood=1, Concrete=2, Brick=3)
137        brick_concrete_wood2 = self.factory1(*materials2)
138        self.assertIs(type(brick_concrete_wood1), type(brick_concrete_wood2))
139
140    def test_named_arguments_with_dynamically_created_classes(self):
141        """Test dynamically created classes uniqueness."""
142        materials = self.factory1(Wood=1200, Concrete=5000, Brick=4000,
143                                  Steel=7700, Stone=1600)
144        result1 = self.factory1(*materials)
145        result2, = self.factory1(Brick_Concrete_Steel_Stone_Wood=2)
146        self.assertEqual(result1.__class__.__name__,
147                         'Brick_Concrete_Steel_Stone_Wood')
148        self.assertIs(type(result1), type(result2))
149
150    def test_materials_between_factories(self):
151        """Test materials sharing."""
152        # Test sharing a new class
153        materials = self.factory1(Wood=1200, Concrete=5000, Brick=4000,
154                                  Steel=7700, Stone=1600)
155        result1 = self.factory1(*materials)
156        result2, = self.factory2(Brick_Concrete_Steel_Stone_Wood=2)
157        self.assertIs(type(result1), type(result2))
158        # Test sharing reusing status
159        wood, = self.factory1(Wood=1200)
160        self.factory1(wood)
161        with self.assertRaises(AssertionError):
162            self.factory2(wood)
163
164    def test_can_build(self):
165        """Test can_build methods."""
166        # Create volume=2 from factory1, reusing materials to ensure
167        # the obsolete status is taken into account.
168        concrete, steel = self.factory1(Concrete=2500, Steel=7700)
169        # Create volume=4 from factory2, reusing materials to ensure
170        # the obsolete status is taken into account.
171        brick, wood = self.factory2(Wood=1200, Brick=4000)
172        brick_wood = self.factory2(brick, wood)
173        self.assertFalse(self.factory1.can_build(2.000001))
174        self.assertTrue(self.factory1.can_build(2.0))
175        self.assertFalse(self.factory2.can_build(4.0001))
176        self.assertTrue(self.factory2.can_build(4.0))
177        self.assertFalse(solution.Factory.can_build_together(6.0001))
178        self.assertTrue(self.factory2.can_build_together(6.0))
179
180
181if __name__ == '__main__':
182    unittest.main()
Дискусия
Ивана Димитрова
24.11.2024 18:30

Здравейте. Необходимо ли е да се правят валидации и проверки, когато подаваме число, отговарящо за масата на материал, в инициализатора и дали въобще подаваме число от тип int, и ако е int, трябва ли да проверяваме дали е положително?
Нишка
Виктор Бечев
24.11.2024 18:42

Не е необходимо, разчитайте, че винаги ще е `int > 0`.
Стефан Шиваров
21.11.2024 14:41

Ако от някое factory1 се пробваме да направим сплав от вече създаден от това factory1 материал и друг материал създаден от друго factory2, трябва ли да прибавяме нещо към обема за factory1? Пример: ```py factory1 = Factory() brick1, wood1 = factory1(Brick=2000, Wood=1000) factory2 = Factory() steel2 = factory2(Steel=1000) brick_steel = factory1(brick1, steel2) ``` В случая brick1 ще се води като преизползван материал за factory1, но steel2 не e използван от factory1. Да разбирам, че понеже steel2 е използван вече от друго фактори, то тогава и двата ги считам за преизползвани, следователно не променям сумата на volume за факторито? Тогава при сплавите няма смисъл да се добавя нищо към общия обем?
Нишка
Георги Кунчев
21.11.2024 15:05

"Да разбирам, че понеже steel2 е използван вече от друго фактори, то тогава и двата ги считам за преизползвани?" -> Да "следователно не променям сумата на volume за факторито" -> Това си зависи от твоята имплементация, но за този конкретен случай очаквам `factory1` да има обема на `brick_steel`, а `factory2` да няма никакъв обем, защото всичко, което той е произвел (в случая само `steel2`), е преизползвано и е вече невалидно. "Тогава при сплавите няма смисъл да се добавя нищо към общия обем?" - Отново не мога да отговоря конкретно. Зависи как си пазиш данните. От примера ти е видно, че може обект, който е направен от една фабрика, да се подаде на друга фабрика, което реално измества произведения обем от първата към втората. Ти решаваш как ще пазиш информацията си и дали това е специален случай. В моята имплементация не беше нищо особено, но в твоята може и да е. Има много възможни решения на задачата.
Павел Петков
21.11.2024 10:23

``` factory2 = Factory() brick2, wood2 = factory2(Brick=2000, Wood=1200) print(brick2.volume) # 1.0 print(wood2.volume) # 2.0 brick_wood2 = factory2(brick2, wood2) print(brick_wood2.volume) # 2.46 ``` Заради този пример направих така че да се форматира отговора до 2 десетичен знак, защото реалният отговор е 2.4615384615384617. Написах го като коментар, че не съм сигурен дали трябва, тоест този print трябва да изкара 2.4615384615384617 или 2.46 или няма значение, защото използваме self.assertAlmostEqual, и ако ползвам self.assertAlmostEqual трябва да кажа колко знака след десетичната запетая искам, защото в противен случай пак няма да работи коректно.
Нишка
Георги Кунчев
21.11.2024 10:53

Добавих едно многоточие в края на 2.46, за да е ясно, че това само визуализира, а не че трябва да се закръглява.
Ивайло Кънчев
21.11.2024 01:45

Ако на фактори подадем един валиден и един невалиден обект, след хвърляне на грешка валидният обект си остава валиден, нали? И също така форматирането на обема до 2рия знак след запетайката ли трябва да е?
Нишка
Виктор Бечев
21.11.2024 03:11

Да, обектите се използват само ако се стигне до крайния резултат. За точността на обема не сме поставили условие, така че каквото число излезе - такова, ако е с 10 символа след запетаята - so be it.
Йоан Байчев
21.11.2024 00:21

Предвид уточнението, че инстанции на новосъздадените сплави могат да се създават само чрез обекта Factory и не са достъпни глобално, все още ли е валиден граничният случай за "Използване на материали между различни фабрики"?
Нишка
Виктор Бечев
21.11.2024 03:15

Не съм сигурен дали правилно разбирам въпроса, но създадените сплави от една фабрика са достъпни от всички фабрики, чрез използването на името им. Говоря за този пример: ``` factory = Factory() brick, wood = factory(Brick=10, Wood=5) concreate, = factory(Concrete=10) brick_wood = factory(brick, wood) another_brick_wood, = factory(Brick_Wood=10) # Валидно инстанциране на ново парче сплав с маса 10 и тип Brick_Wood, който е създаден на предишния ред ```
Василена Станойска
20.11.2024 18:05

Здравейте. Когато изчисляваме новата плътност, трябва ли да връщаме цяло число, или може да бъде и float?
Нишка
Николай Стоянов
20.11.2024 17:51

Здравейте! Когато създадем нова сплав, очаква ли се да можем да създадем инстанция от новия клас в глобалния scope или е приемливо това да става само от Factory обект?
Нишка
Георги Кунчев
20.11.2024 19:03

Може и само през `Factory` обект. Да ви караме да ги сетвате на `globals()` би било лицемерно от наша страна (и лоша практика).
Павел Петков
20.11.2024 15:36

Направих лека промяна в тестовете, за да не прави проблеми при десетичните запетаи ``` import unittest from fourth_homework import * class BaseMaterialsTests(unittest.TestCase): """ in these tests expected_density is mass and all check if expected_density/mass == 1.0 also they test decimal places for volume """ def setUp(self) -> None: self.factory = Factory() def test_density_for_concrete(self): expected_density = 2500 concrete, = self.factory(Concrete=expected_density) self.assertEqual(concrete.volume, 1.0) def test_density_for_brick(self): expected_density = 2000 brick, = self.factory(Brick=expected_density) self.assertEqual(brick.volume, 1.0) def test_density_for_stone(self): expected_density = 1600 stone, = self.factory(Stone=expected_density) self.assertEqual(stone.volume, 1.0) def test_density_for_wood(self): expected_density = 600 wood, = self.factory(Wood=expected_density) self.assertEqual(wood.volume, 1.0) def test_density_for_steel(self): expected_density = 7700 steel, = self.factory(Steel=expected_density) self.assertEqual(steel.volume, 1.0) def test_for_volume_decimal_places_if_one_decimal_places(self): expected_density = 7700 * 7 steel, = self.factory(Steel=expected_density) self.assertEqual(steel.volume, 7.0) self.assertIsInstance(steel.volume, float) def test_for_volume_decimal_places_if_more_than_places_up(self): # от задачата разбирам че просто трябва резултата да се форматира до 2рия знак # ако не е така да ме поправи някой :) # 4453/7700 = 0.5783116883116883 => 0.58 expected_density = 4453 steel, = self.factory(Steel=expected_density) self.assertEqual(steel.volume, 0.5783116883116883) self.assertIsInstance(steel.volume, float) def test_for_volume_decimal_places_if_more_than_places_down(self): # 4453/600 = 7.421666666666667 => 7.42 expected_density = 4453 steel, = self.factory(Wood=expected_density) self.assertAlmostEqual(steel.volume, 7.42, places=2) self.assertIsInstance(steel.volume, float) class FactoryTests(unittest.TestCase): def setUp(self): self.factory1 = Factory() self.factory2 = Factory() self.wood1, self.steel1, self.brick1, self.stone1, self.concrete1 = self.factory1(Wood=5, Steel=15, Brick=60, Stone=300, Concrete=20) self.wood2, self.steel2, self.brick2, self.stone2, self.concrete2 = self.factory2(Wood=5, Steel=30, Brick=300, Stone=600, Concrete=2000) def test_factory_invalid_call_with_empy_call(self): with self.assertRaises(ValueError): self.factory1() def test_factory_invalid_call_with_args_and_kwargs(self): with self.assertRaises(ValueError): self.factory1(self.wood1, Wood=5) def test_factory_return_type(self): self.assertIsInstance(self.factory2(Wood=66), tuple) def test_two_factories_with_same_passed_arguments_produce_different_objects_of_same_type(self): self.assertEqual(type(self.wood1), type(self.wood2)) self.assertEqual(self.wood1.volume, self.wood2.volume) self.assertNotEqual(self.wood1, self.wood2) def test_factory_call_with_kwargs_with_not_existing_class(self): with self.assertRaises(ValueError): self.factory1(Baba=1) def test_volume_for_all_instances_returned_from_factory_call_kwargs(self): wood, steel, stone = self.factory1(Wood=600, Steel=100, Stone=3200) self.assertAlmostEqual(wood.volume, 1.0, ) self.assertAlmostEqual(steel.volume, 0.012987012987012988) self.assertAlmostEqual(stone.volume, 2.0) def test_for_recreating_already_existing_dynamic_class(self): wooden_steel = self.factory1(self.wood1, self.steel1) self.assertEqual(wooden_steel.__class__.__name__, "Steel_Wood") another_wooden_steel = self.factory1(self.wood2, self.steel2) self.assertEqual(type(wooden_steel), type(another_wooden_steel)) def test_for_reusing_same_materials_for_creation(self): self.factory1(self.wood1, self.steel2) with self.assertRaises(AssertionError): self.factory1(self.wood1) def test_new_generated_class_name_is_sorted_in_ascending_order(self): result = self.factory1(self.wood1, self.steel1, self.brick1) self.assertEqual(result.__class__.__name__, "Brick_Steel_Wood") result = self.factory1(self.stone1, self.steel2, self.wood2, ) self.assertEqual(result.__class__.__name__, "Steel_Stone_Wood") def test_new_generated_class_name_is_sorted_in_ascending_order2(self): result = self.factory1(self.wood1, self.steel2, self.stone2, self.brick1, self.concrete1) self.assertEqual(result.__class__.__name__, "Brick_Concrete_Steel_Stone_Wood") def test_volume_and_density_for_dynamically_created_materials(self): # density = (2500 + 7700 + 600) / 3 = 3600. # mass = 5 + 30 + 2000 = 2035 # volume = 2035 / 3600 = 0.5652777777777778 = 0.57 result = self.factory1(self.wood1, self.steel2, self.concrete2) self.assertAlmostEqual(result.volume, 0.5652777777777778) def test_different_factories_called_with_same_kwargs_return_materials_of_same_type(self): steel_stone = self.factory1(Stone=1, Steel=1) another_steel_stone = self.factory2(Steel=1, Stone=1) self.assertEqual(type(steel_stone), type(another_steel_stone)) def test_one_factory_use_material_and_another_tries_to_use_it_and_fails(self): steel_stone = self.factory1(self.steel2, self.stone1) self.factory1(steel_stone) with self.assertRaises(AssertionError): self.factory2(self.wood1, steel_stone) def test_build_method_returns_true_for_greater_than_volume(self): factory = Factory() factory(Brick(2500)) self.assertTrue(factory.can_build(1)) def test_build_method_returns_true_for_equal_volume(self): factory = Factory() factory(Brick(2000)) self.assertTrue(factory.can_build(1)) def test_build_method_returns_false(self): factory = Factory() factory(Brick(1500)) self.assertFalse(factory.can_build(1)) def test_build_method_with_many_materials_returns_false(self): factory1 = Factory() brick1, wood1 = factory1(Brick=2000, Wood=1200) brick_wood1 = factory1(brick1, wood1) self.assertEqual(factory1.can_build(3), False) def test_build_method_with_many_materials_returns_true(self): # density = (2000+ 600+7700) / 3 = 3433.3333333333335 # mass = 2000 + 1000 + 70000 = 73000 # volume = 73000 / 3433.3333333333335 = 21.262135922330096 = 21.2 factory1 = Factory() brick1, wood1, steel = factory1(Brick=2000, Wood=1000, Steel=70000) factory1(brick1, wood1, steel) self.assertTrue(factory1.can_build(21.26)) self.assertEqual(factory1.can_build(21.27), False) def test_string_after_combination(self): result1 = self.factory1(self.wood1, self.brick1) result2 = self.factory2(self.concrete2, self.stone1) result = self.factory1(result1, result2) self.assertEqual(result.__class__.__name__, "Brick_Concrete_Stone_Wood") if __name__ == "__main__": unittest.main() ```
Нишка
Виктор Бечев
21.11.2024 03:09

Единственият проблем, който намирам с тестовете ти, се корени в решението ти да вземеш произволен толеранс при закръгляване на обема. Може и да се окаже, че недоглеждам нещо, но не забелязвам някъде да сме поставили условие за това с каква точност да се изчислява той, докато в твоите тестове го има като презумпция. Разбирам защо си го направил, иначе пък сравнението на числа с плаваща запетая става ненадеждно. Затова използваме `assertAlmostEqual`. Точката ти я оставям, защото всичко друго работи, но това е за дисклеймър на колегите, както и тема за размисъл за теб.