1import unittest
 2
 3import solution
 4
 5
 6class TestSanity(unittest.TestCase):
 7    """Check if all of the classes are present."""
 8
 9    def test_tone(self):
10        self.assertIn('Tone', dir(solution), 'Убеди се, че класът "Tone" е наличен с точно това име.')
11        self.assertTrue(isinstance(solution.Tone, type), 'Убеди се, че "Tone" е клас.')
12
13    def test_interval(self):
14        self.assertIn('Interval', dir(solution), 'Убеди се, че класът "Interval" е наличен с точно това име.')
15        self.assertTrue(isinstance(solution.Interval, type), 'Убеди се, че "Interval" е клас.')
16
17    def test_chord(self):
18        self.assertIn('Chord', dir(solution), 'Убеди се, че класът "Chord" е наличен с точно това име.')
19        self.assertTrue(isinstance(solution.Chord, type), 'Убеди се, че "Chord" е клас.')
20
21
22if __name__ == '__main__':
23    unittest.main()
  1import unittest
  2
  3from solution import *
  4
  5
  6CANNOT_CHORD_THIS_CHORD = "Cannot have a chord made of only 1 unique tone"
  7CANNOT_REMOVE_TONE = "Cannot remove tone {0} from chord {1}"
  8INVALID_OPERATION = "Invalid operation"
  9
 10
 11class TestBasicToneFunctionality(unittest.TestCase):
 12    """Test the basic functionality of the Tone class (operations excluded)."""
 13    TONES = "CDEFGAB"
 14    SHARPS = (f"{tone}#" for tone in "CDFGA")
 15
 16    def test_tone_str(self):
 17        for tone in self.TONES:
 18            self.assertEqual(str(Tone(tone)), tone)
 19        for sharp_letter in self.SHARPS:
 20            self.assertEqual(str(Tone(sharp_letter)), sharp_letter)
 21
 22
 23class TestBasicIntervalFunctionality(unittest.TestCase):
 24    """Test the basic functionality of the Interval class (operations excluded)."""
 25    INTERVALS = (
 26        "unison",
 27        "minor 2nd",
 28        "major 2nd",
 29        "minor 3rd",
 30        "major 3rd",
 31        "perfect 4th",
 32        "diminished 5th",
 33        "perfect 5th",
 34        "minor 6th",
 35        "major 6th",
 36        "minor 7th",
 37        "major 7th"
 38    )
 39
 40    def test_interval_str(self):
 41        for index, interval in enumerate(self.INTERVALS):
 42            self.assertEqual(str(Interval(index)), interval)
 43
 44    def test_interval_overflow(self):
 45        major_7th = Interval(11 + 12**2)
 46        self.assertEqual(str(major_7th), "major 7th")
 47
 48    def test_interval_negative(self):
 49        major_7th = Interval(11)
 50        minor_2nd = -major_7th
 51        self.assertEqual(str(minor_2nd), "minor 2nd")
 52
 53
 54class TestBasicChordFunctionality(unittest.TestCase):
 55    """Test the basic functionality of the Chord class (operations excluded)."""
 56
 57    def test_chord_str(self):
 58        a_major_tones = (Tone(tone) for tone in ["A", "C#", "E"])
 59        a_major_chord = Chord(*a_major_tones)
 60        self.assertEqual(str(a_major_chord), "A-C#-E")
 61
 62    def test_chord_ordering(self):
 63        f_major_seventh_tones = (Tone(tone) for tone in "FCEA")
 64        f_sixth_ninth_chord = Chord(*f_major_seventh_tones)
 65        self.assertEqual(str(f_sixth_ninth_chord), "F-A-C-E")
 66
 67        f_sixth_ninth_tones = (Tone(tone) for tone in "FDAG")
 68        f_sixth_ninth_chord = Chord(*f_sixth_ninth_tones)
 69        self.assertEqual(str(f_sixth_ninth_chord), "F-G-A-D")
 70
 71    def test_chord_tone_repetition(self):
 72        a_minor_tones = (Tone(tone) for tone in "AAAAAAACCCCCCCCCCCCCCEEEEEEEEEEEEEEEEEE")
 73        a_minor_chord = Chord(*a_minor_tones)
 74        self.assertEqual(str(a_minor_chord), "A-C-E")
 75
 76    def test_chord_not_enough_tones(self):
 77        a_sharp = Tone("A#")
 78        with self.assertRaises(TypeError) as err:
 79            Chord(a_sharp)
 80        self.assertEqual(str(err.exception), CANNOT_CHORD_THIS_CHORD)
 81
 82        tones = (Tone(tone) for tone in ["A#", "A#", "A#"])
 83        with self.assertRaises(TypeError) as err:
 84            Chord(*tones)
 85        self.assertEqual(str(err.exception), CANNOT_CHORD_THIS_CHORD)
 86
 87    def test_is_minor(self):
 88        a_minor_tones = (Tone(tone) for tone in "AEC")
 89        a_minor_chord = Chord(*a_minor_tones)
 90        self.assertTrue(a_minor_chord.is_minor())
 91
 92        a_major_tones = (Tone(tone) for tone in ["A", "C#", "E"])
 93        a_major_chord = Chord(*a_major_tones)
 94        self.assertFalse(a_major_chord.is_minor())
 95
 96        a_kind_of_power_chord_tones = (Tone(tone) for tone in ["A", "B", "D", "E"])
 97        a_kind_of_power_chord = Chord(*a_kind_of_power_chord_tones)
 98        self.assertFalse(a_kind_of_power_chord.is_minor())
 99
100    def test_is_major(self):
101        a_minor_tones = (Tone(tone) for tone in "AEC")
102        a_minor_chord = Chord(*a_minor_tones)
103        self.assertFalse(a_minor_chord.is_major())
104
105        a_major_tones = (Tone(tone) for tone in ["A", "C#", "E"])
106        a_major_chord = Chord(*a_major_tones)
107        self.assertTrue(a_major_chord.is_major())
108
109        a_kind_of_power_chord_tones = (Tone(tone) for tone in ["A", "B", "D", "E"])
110        a_kind_of_power_chord = Chord(*a_kind_of_power_chord_tones)
111        self.assertFalse(a_kind_of_power_chord.is_major())
112
113    def test_is_power_chord(self):
114        a_minor_tones = (Tone(tone) for tone in "AEC")
115        a_minor_chord = Chord(*a_minor_tones)
116        self.assertFalse(a_minor_chord.is_power_chord())
117
118        a_major_tones = (Tone(tone) for tone in ["A", "C#", "E"])
119        a_major_chord = Chord(*a_major_tones)
120        self.assertFalse(a_major_chord.is_power_chord())
121
122        a_kind_of_power_chord_tones = (Tone(tone) for tone in ["A", "B", "D", "E"])
123        a_kind_of_power_chord = Chord(*a_kind_of_power_chord_tones)
124        self.assertTrue(a_kind_of_power_chord.is_power_chord())
125
126    def test_transposed(self):
127        d_major_tones = (Tone(tone) for tone in ["D", "F#", "A"])
128        d_major_chord = Chord(*d_major_tones)
129        result_chord = d_major_chord.transposed(Interval(2))
130        self.assertIsInstance(result_chord, Chord)
131        self.assertEqual(str(result_chord), "E-G#-B")
132
133    def test_transposed_negative(self):
134        e_minor_tones = (Tone(tone) for tone in "EGB")
135        e_minor_chord = Chord(*e_minor_tones)
136        result_chord = e_minor_chord.transposed(-Interval(2))
137        self.assertIsInstance(result_chord, Chord)
138        self.assertEqual(str(result_chord), "D-F-A")
139
140    def test_transposed_overflow(self):
141        d_major_tones = (Tone(tone) for tone in ["D", "F#", "A"])
142        d_major_chord = Chord(*d_major_tones)
143        result_chord = d_major_chord.transposed(Interval(8))
144        self.assertIsInstance(result_chord, Chord)
145        self.assertEqual(str(result_chord), "A#-D-F")
146
147    def test_transposed_negative_overflow(self):
148        e_minor_tones = (Tone(tone) for tone in "EGB")
149        e_minor_chord = Chord(*e_minor_tones)
150        result_chord = e_minor_chord.transposed(-Interval(8))
151        self.assertIsInstance(result_chord, Chord)
152        self.assertEqual(str(result_chord), "G#-B-D#")
153
154
155class TestOperations(unittest.TestCase):
156    """Test the operations between the different musical primitives."""
157
158    def test_tone_addition_different_tones(self):
159        g, f = Tone("F"), Tone("G")
160        result_chord = g + f
161        self.assertIsInstance(result_chord, Chord)
162        self.assertEqual(str(result_chord), "F-G")
163
164    def test_tone_addition_same_tone(self):
165        c, another_c = Tone("C"), Tone("C")
166        with self.assertRaises(TypeError) as err:
167            c + another_c
168        self.assertEqual(str(err.exception), CANNOT_CHORD_THIS_CHORD)
169
170    def test_tone_subtraction(self):
171        g, c = Tone("G"), Tone("C")
172        perfect_5th = g - c
173        self.assertEqual(str(perfect_5th), "perfect 5th")
174
175    def test_tone_subtraction_inverse(self):
176        c, g = Tone("C"), Tone("G")
177        perfect_4th = c - g
178        self.assertEqual(str(perfect_4th), "perfect 4th")
179
180    def test_tone_subtraction_same_tone(self):
181        c, another_c = Tone("C"), Tone("C")
182        unison = c - another_c
183        self.assertEqual(str(unison), "unison")
184
185    def test_add_interval_to_tone(self):
186        g = Tone("G")
187        g_sharp = g + Interval(1)
188        self.assertIsInstance(g_sharp, Tone)
189        self.assertEqual(str(g_sharp), "G#")
190
191    def test_add_interval_to_tone_same_tone(self):
192        g = Tone("G")
193        still_g = g + Interval(0)
194        self.assertIsInstance(still_g, Tone)
195        self.assertEqual(str(still_g), "G")
196
197    def test_add_interval_to_tone_overflow(self):
198        g = Tone("G")
199        a = g + Interval(2)
200        self.assertIsInstance(a, Tone)
201        self.assertEqual(str(a), "A")
202
203        a = g + Interval(2 + 12**2)
204        self.assertIsInstance(a, Tone)
205        self.assertEqual(str(a), "A")
206
207    def test_add_interval_to_tone_left_side_error(self):
208        g = Tone("G")
209        with self.assertRaises(TypeError) as err:
210            Interval(2) + g
211        self.assertEqual(str(err.exception), INVALID_OPERATION)
212
213    def test_subtract_interval_from_tone(self):
214        g = Tone("G")
215        f_sharp = g - Interval(1)
216        self.assertIsInstance(f_sharp, Tone)
217        self.assertEqual(str(f_sharp), "F#")
218
219    def test_subtract_interval_from_tone_same_tone(self):
220        g = Tone("G")
221        still_g = g - Interval(0)
222        self.assertIsInstance(still_g, Tone)
223        self.assertEqual(str(still_g), "G")
224
225    def test_subtract_interval_from_tone_oveflow(self):
226        c = Tone("C")
227        g = c - Interval(5)
228        self.assertIsInstance(g, Tone)
229        self.assertEqual(str(g), "G")
230
231    def test_subtract_interval_from_tone_left_side_error(self):
232        g = Tone("G")
233        with self.assertRaises(TypeError) as err:
234            Interval(2) - g
235        self.assertEqual(str(err.exception), INVALID_OPERATION)
236
237    def test_interval_addition(self):
238        minor_2nd, minor_3rd = Interval(1), Interval(3)
239        major_3rd = minor_2nd + minor_3rd
240        self.assertIsInstance(major_3rd, Interval)
241        self.assertEqual(str(major_3rd), "major 3rd")
242
243    def test_interval_addition_overflow(self):
244        perfect_4th, perfect_5th = Interval(5), Interval(7)
245        unison = perfect_4th + perfect_5th
246        self.assertIsInstance(unison, Interval)
247        self.assertEqual(str(unison), "unison")
248
249    def test_add_tone_to_chord(self):
250        f_major_tones = (Tone(tone) for tone in "FAC")
251        f_major_chord = Chord(*f_major_tones)
252        result_chord = f_major_chord + Tone("D")
253        self.assertIsInstance(result_chord, Chord)
254        self.assertEqual(str(result_chord), "F-A-C-D")
255
256    def test_add_tone_to_chord_order(self):
257        f_major_tones = (Tone(tone) for tone in "FAC")
258        f_major_chord = Chord(*f_major_tones)
259        result_chord = f_major_chord + Tone("G")
260        self.assertIsInstance(result_chord, Chord)
261        self.assertEqual(str(result_chord), "F-G-A-C")
262
263    def test_add_tone_to_chord_existing_tone(self):
264        f_minor_tones = (Tone(tone) for tone in ["F", "G#", "C"])
265        f_minor_chord = Chord(*f_minor_tones)
266        result_chord = f_minor_chord + Tone("F")
267        result_chord = result_chord + Tone("G#")
268        result_chord = result_chord + Tone("C")
269        self.assertIsInstance(result_chord, Chord)
270        self.assertEqual(str(result_chord), "F-G#-C")
271
272    def test_subtract_tone_from_chord(self):
273        f_minor_tones = (Tone(tone) for tone in ["F", "G#", "C"])
274        f_minor_chord = Chord(*f_minor_tones)
275        result_chord = f_minor_chord - Tone("C")
276        self.assertIsInstance(result_chord, Chord)
277        self.assertEqual(str(result_chord), "F-G#")
278
279    def test_subtract_tone_from_chord_error(self):
280        c5_chord_tones = Tone("C"), Tone("G")
281        c5_chord = Chord(*c5_chord_tones)
282        with self.assertRaises(TypeError) as err:
283            c5_chord - Tone("G")
284        self.assertEqual(str(err.exception), CANNOT_CHORD_THIS_CHORD)
285
286    def test_subtract_tone_from_chord_error(self):
287        c5_chord_tones = Tone("C"), Tone("G")
288        c5_chord = Chord(*c5_chord_tones)
289        a = Tone("A")
290        expected_message = CANNOT_REMOVE_TONE.format(str(a), str(c5_chord))
291        with self.assertRaises(TypeError) as err:
292            c5_chord - a
293        self.assertEqual(str(err.exception), expected_message)
294
295    def test_add_chords(self):
296        c5_chord = Chord(Tone("C"), Tone("G"))
297        this_other_chord = Chord(Tone("A"), Tone("B"))
298        result_chord = c5_chord + this_other_chord
299        self.assertEqual(str(result_chord), "C-G-A-B")
300
301    def test_add_chords_repeating_notes(self):
302        c5_chord = Chord(Tone("C"), Tone("G"))
303        inverted_c5_chord = Chord(Tone("G"), Tone("C"))
304        result_chord = c5_chord + inverted_c5_chord
305        self.assertEqual(str(result_chord), "C-G")
306
307
308if __name__ == "__main__":
309    unittest.main()
|  | 
|   
        Георги Кунчев
         06.11.2024 17:31Привет!  
Поради големия брой решения, влезли в последните няколко часа, ще довършим проверката и ще публикуваме резултатите утре.  
Не се проверяват лесно 60 решения с по 150 реда (9000 реда код) :tired_face: | 
|   
        Георги Кунчев
         04.11.2024 14:59@Даниел_Стефанов, както Виктор каза, не сме говорили още за тестове, но тъй като пишеш, а ми направи впечатление - ще споделя нещо за твое удобство.  
Вместо да закоментираш тест, който не ти трябва за момента, можеш да го декорираш с декоратор, който `unittest` дава наготово:  
```
@unittest.skip('No need to test this right now.')
def test_something(self):
    self.assertTrue('something')
``` | 
|   
        Виктор Бечев
         04.11.2024 14:55Супер е, че си написал тестове и тестовете ти реално тестват немалко неща.  
Проблемът, обаче, е, че са зависими от твоята собствена имплементация. Ето пример:
```
for index, tone in enumerate(c_minor_chord.tones):
            self.assertEqual(str(tone), str(expected_tones[index]))
```
Ако колега, на когото имплементацията му не разчита на `Chord.tones` - атрибутът се казва по различен начин или пък изобщо не използва такъв атрибут - тестът фейлва.
Същото важи и за `Interval.length` и прочие.
Освен това имаш дефинирани тестове и за поведение, което не изискваме от вас - например:
```
    def test_add_with_invalid_second_param_bool(self):
        with self.assertRaises(TypeError) as context:
            self.c + True
        self.assertIn(INVALID_OPERATION_ERROR_MESSAGE, str(context.exception))
```
Точката е поощрителна, защото още не сме говорили за тестове, а си се постарал, а вече по-натам - ще поощряваме само валидни такива.
Но е важно за всички останали да знаят, че ако някои от тестовете ти не минават - **това не е непременно проблем с тяхното решение**. | 
|   
        Даниел Стефанов
         04.11.2024 14:33Още тестове: https://pastebin.com/cjMUqPpV      фикснете импорта и сменете HOMEWORK_FILENAME във файла | 
|   
        Виктор Бечев
         03.11.2024 22:07След дискусия в едно от решенията, едно уточнение (новият текст в **bold**):
**Забележка:** Искаме това да работи **само** когато тонът е отляво **на знакът за събиране**. Когато тонът е отдясно, го приемаме за недефинирано поведение и искаме да се възбуди `TypeError` с текст `"Invalid operation"`. Семпло, но няма да ви вгорчаваме живота... Повече.
Говорим за отляво или отдясно спрямо операцията, а не някаква релативност спрямо скалата. | 
|   
        Виктор Бечев
         03.11.2024 21:21@Стефан_Шиваров - реално това ще работи без да правиш нищо. Не защото ще е "static", а защото това е разликата между функция и bound метод. `Chord.is_minor(chord)` ще предаде експлицитно `chord` като `self` на `is_minor(self)`, в случая с инстанцията - просто е имплицитно.
П.П. Пробвай го. :) | 
| 
        Стефан Шиваров
         03.11.2024 21:07За методите Chord.is\_minor , Chord.is\_major и т.н. , очаква ли се да правим и static версия, тоест освен през инстанцията chord.is\_minor(), да могат да се викат като Chord.is\_minor(chord) ? | 
|   
        Никола Георгиев
         03.11.2024 12:58``` 
import unittest
class TestMusicTheory(unittest.TestCase):
    def test_tone_initialization(self):
        c_sharp = Tone("C#")
        self.assertEqual(str(c_sharp), "C#")
    def test_interval_initialization(self):
        minor_third = Interval(3)
        self.assertEqual(str(minor_third), "minor 3rd")
    def test_chord_initialization(self):
        c_minor_chord = Chord(Tone("C"), Tone("D#"), Tone("G"))
        self.assertEqual(str(c_minor_chord), "C-D#-G")
    def test_chord_unique_tones(self):
        c = Tone("C")
        another_c = Tone("C")
        with self.assertRaises(TypeError) as context:
            Chord(c, another_c)
        self.assertEqual(str(context.exception), "Cannot have a chord made of only 1 unique tone")
    def test_csus4_chord(self):
        c = Tone("C")
        another_c = Tone("C")
        f = Tone("F")
        csus4_chord = Chord(c, f, another_c)
        self.assertEqual(str(csus4_chord), "C-F")
    def test_f_sixth_ninth_chord(self):
        f_sixth_ninth_chord = Chord(Tone("F"), Tone("C"), Tone("D"), Tone("A"), Tone("G"))
        self.assertEqual(str(f_sixth_ninth_chord), "F-G-A-C-D")
    def test_c_minor_chord_is_minor(self):
        c_minor_chord = Chord(Tone("C"), Tone("D#"), Tone("G"))
        self.assertTrue(c_minor_chord.is_minor())
    def test_c_not_minor_chord(self):
        c_not_minor_chord = Chord(Tone("C"), Tone("D"), Tone("G"))
        self.assertFalse(c_not_minor_chord.is_minor())
    def test_c_major_chord_is_major(self):
        c_major_chord = Chord(Tone("C"), Tone("E"), Tone("G"))
        self.assertTrue(c_major_chord.is_major())
    def test_c_not_major_chord(self):
        c_not_major_chord = Chord(Tone("C"), Tone("D"), Tone("G"))
        self.assertFalse(c_not_major_chord.is_major())
    def test_c_power_chord(self):
        c_power_chord = Chord(Tone("C"), Tone("F"), Tone("G"))
        self.assertTrue(c_power_chord.is_power_chord())
    def test_c_not_power_chord(self):
        c_not_power_chord = Chord(Tone("C"), Tone("E"), Tone("G"))
        self.assertFalse(c_not_power_chord.is_power_chord())
    def test_add_tones(self):
        c = Tone("C")
        g = Tone("G")
        result_chord = c + g
        self.assertEqual(str(result_chord), "C-G")
    def test_subtract_tones(self):
        c = Tone("C")
        g = Tone("G")
        result_interval = g - c
        self.assertEqual(str(result_interval), "perfect 5th")
    def test_add_interval_to_tone(self):
        c = Tone("C")
        perfect_fifth = Interval(7)
        result_tone = c + perfect_fifth
        self.assertEqual(str(result_tone), "G")
    def test_add_full_octave_to_tone(self):
        c = Tone("C")
        result_tone = c + Interval(12)
        self.assertEqual(str(result_tone), "C")
    def test_add_interval_to_tone_g(self):
        g = Tone("G")
        perfect_fifth = Interval(7)
        result_tone = g + perfect_fifth
        self.assertEqual(str(result_tone), "D")
    def test_subtract_interval_from_tone(self):
        c = Tone("C")
        perfect_fifth = Interval(7)
        result_tone = c - perfect_fifth
        self.assertEqual(str(result_tone), "F")
    def test_add_intervals(self):
        perfect_fifth = Interval(7)
        minor_third = Interval(3)
        result_interval = perfect_fifth + minor_third
        self.assertEqual(str(result_interval), "minor 7th")
    def test_add_chord_and_tone(self):
        c5_chord = Chord(Tone("C"), Tone("G"))
        result_chord = c5_chord + Tone("E")
        self.assertEqual(str(result_chord), "C-E-G")
    def test_subtract_from_major_chord(self):
        c_major_chord = Chord(Tone("C"), Tone("E"), Tone("G"))
        result_chord = c_major_chord - Tone("E")
        self.assertEqual(str(result_chord), "C-G")
    def test_c_power_chord_subtract_g(self):
        c_power_chord = Chord(Tone("C"), Tone("G"))
        with self.assertRaises(TypeError) as context:
            c_power_chord - Tone("G")
        self.assertEqual(str(context.exception), "Cannot have a chord made of only 1 unique tone")
    def test_c_power_chord_subtract_e(self):
        c_power_chord = Chord(Tone("C"), Tone("G"))
        with self.assertRaises(TypeError) as context:
            c_power_chord - Tone("E")
        self.assertEqual(str(context.exception), "Cannot remove tone E from chord C-G")
    def test_add_two_chords(self):
        c5_chord = Chord(Tone("C"), Tone("G"))
        this_other_chord = Chord(Tone("A"), Tone("B"))
        result_chord = c5_chord + this_other_chord
        self.assertEqual(str(result_chord), "C-G-A-B")
    def test_transpose_minor_chord(self):
        c_minor_chord = Chord(Tone("C"), Tone("D#"), Tone("G"))
        d_minor_chord = c_minor_chord.transposed(Interval(2))
        self.assertEqual(str(d_minor_chord), "D-F-A")
    def test_transpose_a_sharp_minor_chord(self):
        d_minor_chord = Chord(Tone("D"), Tone("F"), Tone("A"))
        a_sharp_minor_chord = d_minor_chord.transposed(-Interval(4))
        self.assertEqual(str(a_sharp_minor_chord), "A#-C#-F")
if __name__ == '__main__':
    unittest.main()
``` | 
|   
        Георги Кунчев
         03.11.2024 09:33@Дейвид_Барух, Павел зададе този въпрос и дадохме отговор. Следващият тон става главен. |