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()
|  | 
|   
        Георги Кунчев
         01.11.2024 20:28@Даниел_Стефанов, главният тон на първия акорд е водещ, т.е. този, който стои отляво на '+'. | 
|   
        Даниел Стефанов
         01.11.2024 20:16При събиране на акорди как се определя кой е главния тон на получения акорд. Взимаме главния тон на първия операнд или взимаме този който е по-напред от двата главни тона като поредност? | 
|   
        Георги Кунчев
         01.11.2024 15:06@Ивайло_Кънчев, тъй като местата са разменени, посоката е различна. Разстоянието при C - G e 5, т.е "perfect 4th". | 
|   
        Ивайло Кънчев
         01.11.2024 13:58При изваждането на тонове, ако от C извадим G, какъв трябва да е резултатът?  Отново ли ще е Perfect 5? | 
|   
        Георги Кунчев
         31.10.2024 17:18@Даниел_Стефанов, да. Все още нямаме амбиция да позволим множество файлове. Много работа по сайта ще е. В повечето случаи това е достатъчно. Не пишем Java и е ок да имаме много класове в един файл. | 
|  | 
|   
        Виктор Бечев
         31.10.2024 10:491. Абсолютно си прав, корегираме!
2. Махането на root-а е валидно (било то и от музикална точка безумно) и ще се държи както си го описал - първия тон след основния става новия root.
3. Истината е, че просто не сме мислили да ви караме да имплементирате **и** такъв тип поведение, така че няма да тестваме с това. | 
|   
        Павел Петков
         31.10.2024 02:11Имам няколко неясни неща по домашното:
1. c_power_chord = Chord(Tone("C"), Tone("G"))
result_chord = c_power_chord - Tone("E")
TypeError: Cannot remove tone C from chord C-G
Предполагам , че е грешно и трябва да е " Cannot remove tone E from chord C-G"
2. Относно махането на тон от акорд, ако се опитаме да премахнем root на акорда валидна операция ли е. Ако е валидна операция би трябвало новият root да стане следващия тон
и всички останали тонове да се сортират спрямо него или поведението е разлчино.
3. Събирането на тон с акорд асоциативно ли е(тоест да прави едно и също без значение позицията на аргументите) или не трябва да имплементираме тон + акорд или
трябва тон + акорд да връща нов акорд, чийто root = тон и tones стават всички тонове на приетия акорд във функцията. |