コード例 #1
0
    def test_import_scad(self):
        include_file = self.expand_scad_path("examples/scad_to_include.scad")
        mod = import_scad(include_file)
        a = mod.steps(3)
        actual = scad_render(a)

        abs_path = a._get_include_path(include_file)
        expected = f"use <{abs_path}>\n\n\nsteps(howmany = 3);"
        self.assertEqual(expected, actual)

        # Make sure this plays nicely with `scad_render()`'s `file_header` arg
        header = '$fn = 24;'
        actual = scad_render(a, file_header=header)
        expected = f"{header}\nuse <{abs_path}>\n\n\nsteps(howmany = 3);"
        self.assertEqual(expected, actual)

        # Confirm that we can leave out even non-default arguments in OpenSCAD
        a = mod.optional_nondefault_arg();
        actual = scad_render(a)
        expected = f'use <{abs_path}>\n\n\noptional_nondefault_arg();'
        self.assertEqual(expected, actual);

        # Make sure we throw ValueError on nonexistent imports
        self.assertRaises(ValueError, import_scad, 'path/doesnt/exist.scad')

        # Test that we recursively import directories correctly
        examples = import_scad(include_file.parent)
        self.assertTrue(hasattr(examples, 'scad_to_include'))
        self.assertTrue(hasattr(examples.scad_to_include, 'steps'))
コード例 #2
0
    def test_import_scad(self):
        include_file = self.expand_scad_path("examples/scad_to_include.scad")
        mod = import_scad(include_file)
        a = mod.steps(3)
        actual = scad_render(a)

        abs_path = a._get_include_path(include_file)
        expected = f"use <{abs_path}>\n\n\nsteps(howmany = 3);"
        self.assertEqual(expected, actual)

        # Make sure this plays nicely with `scad_render()`'s `file_header` arg
        header = '$fn = 24;'
        actual = scad_render(a, file_header=header)
        expected = f"{header}\nuse <{abs_path}>\n\n\nsteps(howmany = 3);"
        self.assertEqual(expected, actual)
コード例 #3
0
 def test_thread(self):
     actual_obj = thread(outline_pts=self.outline,
                         inner_rad=20,
                         pitch=self.tooth_height,
                         length=0.75 * self.tooth_height,
                         segments_per_rot=SEGMENTS,
                         neck_in_degrees=45,
                         neck_out_degrees=45)
     actual = scad_render(actual_obj)
     expected = '\n\nrender() {\n' \
                '\tintersection() {\n' \
                '\t\tpolyhedron(faces = [[0, 1, 3], [1, 4, 3], [1, 2, 4], [2, 5, 4], [0, 5, 2], [0, 3, 5], [3, 4, 6], ' \
                '[4, 7, 6], [4, 5, 7], [5, 8, 7], [3, 8, 5], [3, 6, 8], [6, 7, 9], [7, 10, 9], [7, 8, 10], [8, 11, 10], ' \
                '[6, 11, 8], [6, 9, 11], [9, 10, 12], [10, 13, 12], [10, 11, 13], [11, 14, 13], [9, 14, 11], [9, 12, 14], ' \
                '[12, 13, 15], [13, 16, 15], [13, 14, 16], [14, 17, 16], [12, 17, 14], [12, 15, 17], [15, 16, 18], ' \
                '[16, 19, 18], [16, 17, 19], [17, 20, 19], [15, 20, 17], [15, 18, 20], [0, 2, 1], [18, 19, 20]], ' \
                'points = [[14.9900000000, 0.0000000000, -5.0000000000], [19.9900000000, 0.0000000000, 0.0000000000], ' \
                '[14.9900000000, 0.0000000000, 5.0000000000], [14.1421356237, 14.1421356237, -3.7500000000], ' \
                '[17.6776695297, 17.6776695297, 1.2500000000], [14.1421356237, 14.1421356237, 6.2500000000], ' \
                '[0.0000000000, 20.0000000000, -2.5000000000], [0.0000000000, 25.0000000000, 2.5000000000], ' \
                '[0.0000000000, 20.0000000000, 7.5000000000], [-14.1421356237, 14.1421356237, -1.2500000000], ' \
                '[-17.6776695297, 17.6776695297, 3.7500000000], [-14.1421356237, 14.1421356237, 8.7500000000], ' \
                '[-20.0000000000, 0.0000000000, 0.0000000000], [-25.0000000000, 0.0000000000, 5.0000000000], ' \
                '[-20.0000000000, 0.0000000000, 10.0000000000], [-14.1421356237, -14.1421356237, 1.2500000000], ' \
                '[-17.6776695297, -17.6776695297, 6.2500000000], [-14.1421356237, -14.1421356237, 11.2500000000], ' \
                '[-0.0000000000, -14.9900000000, 2.5000000000], [-0.0000000000, -19.9900000000, 7.5000000000], ' \
                '[-0.0000000000, -14.9900000000, 12.5000000000]]);\n' \
                '\t\tdifference() {\n' \
                '\t\t\tcylinder($fn = 8, h = 7.5000000000, r = 25.0100000000);\n' \
                '\t\t\tcylinder($fn = 8, h = 7.5000000000, r = 20);\n' \
                '\t\t}\n' \
                '\t}\n' \
                '}'
     self.assertEqual(expected, actual)
コード例 #4
0
    def test_extra_args_to_included_scad(self):
        include_file = self.expand_scad_path("examples/scad_to_include.scad")
        mod = import_scad(include_file)
        a = mod.steps(3, external_var=True)
        actual = scad_render(a)

        abs_path = a._get_include_path(include_file)
        expected = f"use <{abs_path}>\n\n\nsteps(external_var = true, howmany = 3);"
        self.assertEqual(expected, actual)
コード例 #5
0
    def test_use(self):
        include_file = self.expand_scad_path("examples/scad_to_include.scad")
        use(include_file)
        a = steps(3)
        actual = scad_render(a)

        abs_path = a._get_include_path(include_file)
        expected = f"use <{abs_path}>\n\n\nsteps(howmany = 3);"
        self.assertEqual(expected, actual)
コード例 #6
0
 def test_numpy_type(self):
     try:
         import numpy
         numpy_cube = cube(size=numpy.array([1, 2, 3]))
         expected = '\n\ncube(size = [1,2,3]);'
         actual = scad_render(numpy_cube)
         self.assertEqual(expected, actual, 'Numpy SolidPython not rendered correctly')
     except ImportError:
         pass
コード例 #7
0
 def test_imported_scad_arguments(self):
     include_file = self.expand_scad_path("examples/scad_to_include.scad")
     mod = import_scad(include_file)
     points = mod.scad_points();
     poly = polygon(points);
     actual = scad_render(poly);
     abs_path = points._get_include_path(include_file)
     expected = f'use <{abs_path}>\n\n\npolygon(points = scad_points());'
     self.assertEqual(expected, actual)
コード例 #8
0
    def test_include(self):
        include_file = self.expand_scad_path("examples/scad_to_include.scad")
        self.assertIsNotNone(include_file, 'examples/scad_to_include.scad not found')
        include(include_file)
        a = steps(3) # type: ignore

        actual = scad_render(a)
        abs_path = a._get_include_path(include_file)
        expected = f"include <{abs_path}>\n\n\nsteps(howmany = 3);"
        self.assertEqual(expected, actual)
コード例 #9
0
    def test_use_reserved_words(self):
        scad_str = '''module reserved_word_arg(or=3){\n\tcube(or);\n}\nmodule or(arg=3){\n\tcube(arg);\n}\n'''

        fd, path = tempfile.mkstemp(text=True)
        try:
            os.close(fd)
            with open(path, "w") as f:
                f.write(scad_str)

            use(path)
            a = reserved_word_arg(or_=5)
            actual = scad_render(a)
            expected = f"use <{path}>\n\n\nreserved_word_arg(or = 5);"
            self.assertEqual(expected, actual)

            b = or_(arg=5)
            actual = scad_render(b)
            expected = f"use <{path}>\n\n\nor(arg = 5);"
            self.assertEqual(expected, actual)
        finally:
            os.remove(path)
コード例 #10
0
    def test(self):
        call_str = cls + "("
        for k, v in args.items():
            call_str += f"{k}={v}, "
        for k, v in kwargs.items():
            call_str += f"{k}={v}, "
        call_str += ')'

        scad_obj = eval(call_str)
        actual = scad_render(scad_obj)

        self.assertEqual(expected, actual)
コード例 #11
0
    def test_hole_transform_propagation(self):
        # earlier versions of holes had problems where a hole
        # that was used a couple places wouldn't propagate correctly.
        # Confirm that's still happening as it's supposed to
        h = hole()(rotate(a=90, v=[0, 1, 0])(cylinder(2, 20, center=True)))

        h_vert = rotate(a=-90, v=[0, 1, 0])(h)

        a = cube(10, center=True) + h + h_vert
        expected = '\n\ndifference(){\n\tunion() {\n\t\tcube(center = true, size = 10);\n\t\trotate(a = -90, v = [0, 1, 0]) {\n\t\t}\n\t}\n\t/* Holes Below*/\n\tunion(){\n\t\trotate(a = 90, v = [0, 1, 0]) {\n\t\t\tcylinder(center = true, h = 20, r = 2);\n\t\t}\n\t\trotate(a = -90, v = [0, 1, 0]){\n\t\t\trotate(a = 90, v = [0, 1, 0]) {\n\t\t\t\tcylinder(center = true, h = 20, r = 2);\n\t\t\t}\n\t\t}\n\t} /* End Holes */ \n}'
        actual = scad_render(a)
        self.assertEqual(expected, actual)
コード例 #12
0
    def test_color(self):
        all_args = [
            {'c': [1, 0, 0]},
            {'c': [1, 0, 0], 'alpha': 0.5},
            {'c': "#66F"},
            {'c': "Teal", 'alpha': 0.5},
        ]

        expecteds = [
            '\n\ncolor(alpha = 1.0000000000, c = [1, 0, 0]);',
            '\n\ncolor(alpha = 0.5000000000, c = [1, 0, 0]);',
            '\n\ncolor(alpha = 1.0000000000, c = "#66F");',
            '\n\ncolor(alpha = 0.5000000000, c = "Teal");',
        ]
        for args, expected in zip(all_args, expecteds):
            col = color(**args)
            actual = scad_render(col)
            self.assertEqual(expected, actual)
コード例 #13
0
 def test_thread_internal(self):
     actual_obj = thread(outline_pts=self.outline,
                         inner_rad=20,
                         pitch=2 * self.tooth_height,
                         length=2 * self.tooth_height,
                         segments_per_rot=SEGMENTS,
                         neck_in_degrees=45,
                         neck_out_degrees=45,
                         external=False)
     actual = scad_render(actual_obj)
     expected = '''intersection() {
         polyhedron(
             convexity=2,
             faces = [[0, 1, 3], [1, 4, 3], [1, 2, 4], [2, 5, 4], [0, 5, 2], [0, 3, 5], [3, 4, 6], [4, 7, 6], [4, 5, 7], [5, 8, 7], [3, 8, 5], [3, 6, 8], [6, 7, 9], [7, 10, 9], [7, 8, 10], [8, 11, 10], [6, 11, 8], [6, 9, 11], [9, 10, 12], [10, 13, 12], [10, 11, 13], [11, 14, 13], [9, 14, 11], [9, 12, 14], [0, 2, 1], [12, 13, 14]], 
             points = [[25.0100000000, 0.0000000000, 5.0000000000], [20.0100000000, 0.0000000000, 0.0000000000], [25.0100000000, 0.0000000000, -5.0000000000], [0.0000000000, 20.0000000000, 10.0000000000], [0.0000000000, 15.0000000000, 5.0000000000], [0.0000000000, 20.0000000000, 0.0000000000], [-20.0000000000, 0.0000000000, 15.0000000000], [-15.0000000000, 0.0000000000, 10.0000000000], [-20.0000000000, 0.0000000000, 5.0000000000], [-0.0000000000, -20.0000000000, 20.0000000000], [-0.0000000000, -15.0000000000, 15.0000000000], [-0.0000000000, -20.0000000000, 10.0000000000], [25.0100000000, -0.0000000000, 25.0000000000], [20.0100000000, -0.0000000000, 20.0000000000], [25.0100000000, -0.0000000000, 15.0000000000]]
         );
         cylinder($fn = 4, h = 20, r1 = 20, r2 = 20);
     }'''
     self.assertEqualNoWhitespace(expected, actual)
コード例 #14
0
    def test_custom_iterables(self):
        from euclid3 import Vector3

        class CustomIterable:
            def __iter__(self):
                return iter([1, 2, 3])

        expected = '\n\ncube(size = [1, 2, 3]);'
        iterables = [
            [1, 2, 3],
            (1, 2, 3),
            Vector3(1, 2, 3),
            CustomIterable(),
        ]

        for iterable in iterables:
            name = type(iterable).__name__
            actual = scad_render(cube(size=iterable))
            self.assertEqual(expected, actual, f'{name} SolidPython not rendered correctly')
コード例 #15
0
 def test_conical_thread_internal(self):
     actual_obj = thread(outline_pts=self.outline,
                         inner_rad=20,
                         rad_2=40,
                         pitch=self.tooth_height,
                         length=0.75 * self.tooth_height,
                         segments_per_rot=SEGMENTS,
                         neck_in_degrees=45,
                         neck_out_degrees=45,
                         external=False)
     actual = scad_render(actual_obj)
     expected = '''intersection(){
         polyhedron(
             convexity=2,
             faces=[[0,1,3],[1,4,3],[1,2,4],[2,5,4],[0,5,2],[0,3,5],[3,4,6],[4,7,6],[4,5,7],[5,8,7],[3,8,5],[3,6,8],[6,7,9],[7,10,9],[7,8,10],[8,11,10],[6,11,8],[6,9,11],[0,2,1],[9,10,11]],
             points=[[34.0549376635,0.0000000000,1.7556172079],[27.6176745677,0.0000000000,4.6816458878],[24.6916458878,0.0000000000,-1.7556172079],[0.0000000000,31.3483125545,4.2556172079],[0.0000000000,24.9110494587,7.1816458878],[0.0000000000,21.9850207788,0.7443827921],[-38.0149792212,0.0000000000,6.7556172079],[-31.5777161254,0.0000000000,9.6816458878],[-28.6516874455,0.0000000000,3.2443827921],[-0.0000000000,-54.0549376635,9.2556172079],[-0.0000000000,-47.6176745677,12.1816458878],[-0.0000000000,-44.6916458878,5.7443827921]]
         );
         cylinder($fn=4,h=7.5000000000,r1=20,r2=40);
     }'''
     self.assertEqualNoWhitespace(expected, actual)
コード例 #16
0
 def test_conical_thread_external(self):
     actual_obj = thread(outline_pts=self.outline,
                         inner_rad=20,
                         rad_2=40,
                         pitch=self.tooth_height,
                         length=0.75 * self.tooth_height,
                         segments_per_rot=SEGMENTS,
                         neck_in_degrees=45,
                         neck_out_degrees=45,
                         external=True)
     actual = scad_render(actual_obj)
     expected = '''intersection(){
         polyhedron(convexity=2,
             faces=[[0,1,3],[1,4,3],[1,2,4],[2,5,4],[0,5,2],[0,3,5],[3,4,6],[4,7,6],[4,5,7],[5,8,7],[3,8,5],[3,6,8],[6,7,9],[7,10,9],[7,8,10],[8,11,10],[6,11,8],[6,9,11],[0,2,1],[9,10,11]],
             points=[[5.9450623365,0.0000000000,-1.7556172079],[12.3823254323,0.0000000000,-4.6816458878],[15.3083541122,0.0000000000,1.7556172079],[0.0000000000,21.9850207788,0.7443827921],[0.0000000000,28.4222838746,-2.1816458878],[0.0000000000,31.3483125545,4.2556172079],[-28.6516874455,0.0000000000,3.2443827921],[-35.0889505413,0.0000000000,0.3183541122],[-38.0149792212,0.0000000000,6.7556172079],[-0.0000000000,-25.9450623365,5.7443827921],[-0.0000000000,-32.3823254323,2.8183541122],[-0.0000000000,-35.3083541122,9.2556172079]]
         );
         difference(){
             cylinder($fn=4,h=7.5000000000,r1=29.3732917757,r2=49.3732917757);
             cylinder($fn=4,h=7.5000000000,r1=20,r2=40);
         }
     }'''
     self.assertEqualNoWhitespace(expected, actual)
コード例 #17
0
 def test_neck_in_out_degrees(self):
     # Non-specified neck_in_degrees and neck_out_degrees would crash prior
     # to the fix for https://github.com/SolidCode/SolidPython/issues/92
     actual_obj = thread(outline_pts=self.outline,
                         inner_rad=20,
                         pitch=self.tooth_height,
                         length=0.75 * self.tooth_height,
                         segments_per_rot=SEGMENTS,
                         neck_in_degrees=45,
                         neck_out_degrees=0)
     actual = scad_render(actual_obj)
     expected = '''intersection(){
         polyhedron(
             convexity=2,
             faces=[[0,1,3],[1,4,3],[1,2,4],[2,5,4],[0,5,2],[0,3,5],[3,4,6],[4,7,6],[4,5,7],[5,8,7],[3,8,5],[3,6,8],[6,7,9],[7,10,9],[7,8,10],[8,11,10],[6,11,8],[6,9,11],[0,2,1],[9,10,11]],
             points=[[14.9900000000,0.0000000000,-5.0000000000],[19.9900000000,0.0000000000,0.0000000000],[14.9900000000,0.0000000000,5.0000000000],[0.0000000000,20.0000000000,-2.5000000000],[0.0000000000,25.0000000000,2.5000000000],[0.0000000000,20.0000000000,7.5000000000],[-20.0000000000,0.0000000000,0.0000000000],[-25.0000000000,0.0000000000,5.0000000000],[-20.0000000000,0.0000000000,10.0000000000],[-0.0000000000,-20.0000000000,2.5000000000],[-0.0000000000,-25.0000000000,7.5000000000],[-0.0000000000,-20.0000000000,12.5000000000]]
         );
         difference(){
             cylinder($fn=4,h=7.5000000000,r1=25.0100000000,r2=25.0100000000);
             cylinder($fn=4,h=7.5000000000,r1=20,r2=20);
         }
     }'''
     self.assertEqualNoWhitespace(expected, actual)
コード例 #18
0
 def test_thread_internal(self):
     actual_obj = thread(outline_pts=self.outline,
                         inner_rad=20,
                         pitch=2 * self.tooth_height,
                         length=2 * self.tooth_height,
                         segments_per_rot=SEGMENTS,
                         neck_in_degrees=45,
                         neck_out_degrees=45,
                         external=False)
     actual = scad_render(actual_obj)
     expected = '\n\nrender() {\n' \
                '\tintersection() {\n' \
                '\t\tpolyhedron(faces = [[0, 1, 3], [1, 4, 3], [1, 2, 4], [2, 5, 4], [0, 5, 2], [0, 3, 5], ' \
                '[3, 4, 6], [4, 7, 6], [4, 5, 7], [5, 8, 7], [3, 8, 5], [3, 6, 8], [6, 7, 9], [7, 10, 9], [7, 8, 10], ' \
                '[8, 11, 10], [6, 11, 8], [6, 9, 11], [9, 10, 12], [10, 13, 12], [10, 11, 13], [11, 14, 13], ' \
                '[9, 14, 11], [9, 12, 14], [12, 13, 15], [13, 16, 15], [13, 14, 16], [14, 17, 16], [12, 17, 14], ' \
                '[12, 15, 17], [15, 16, 18], [16, 19, 18], [16, 17, 19], [17, 20, 19], [15, 20, 17], [15, 18, 20], ' \
                '[18, 19, 21], [19, 22, 21], [19, 20, 22], [20, 23, 22], [18, 23, 20], [18, 21, 23], [21, 22, 24], ' \
                '[22, 25, 24], [22, 23, 25], [23, 26, 25], [21, 26, 23], [21, 24, 26], [0, 2, 1], [24, 25, 26]], ' \
                'points = [[25.0100000000, 0.0000000000, -5.0000000000], [20.0100000000, 0.0000000000, 0.0000000000], ' \
                '[25.0100000000, 0.0000000000, 5.0000000000], [14.1421356237, 14.1421356237, -2.5000000000], ' \
                '[10.6066017178, 10.6066017178, 2.5000000000], [14.1421356237, 14.1421356237, 7.5000000000], ' \
                '[0.0000000000, 20.0000000000, 0.0000000000], [0.0000000000, 15.0000000000, 5.0000000000], ' \
                '[0.0000000000, 20.0000000000, 10.0000000000], [-14.1421356237, 14.1421356237, 2.5000000000], ' \
                '[-10.6066017178, 10.6066017178, 7.5000000000], [-14.1421356237, 14.1421356237, 12.5000000000],' \
                ' [-20.0000000000, 0.0000000000, 5.0000000000], [-15.0000000000, 0.0000000000, 10.0000000000], ' \
                '[-20.0000000000, 0.0000000000, 15.0000000000], [-14.1421356237, -14.1421356237, 7.5000000000],' \
                ' [-10.6066017178, -10.6066017178, 12.5000000000], [-14.1421356237, -14.1421356237, 17.5000000000], ' \
                '[-0.0000000000, -20.0000000000, 10.0000000000], [-0.0000000000, -15.0000000000, 15.0000000000],' \
                ' [-0.0000000000, -20.0000000000, 20.0000000000], [14.1421356237, -14.1421356237, 12.5000000000],' \
                ' [10.6066017178, -10.6066017178, 17.5000000000], [14.1421356237, -14.1421356237, 22.5000000000],' \
                ' [25.0100000000, -0.0000000000, 15.0000000000], [20.0100000000, -0.0000000000, 20.0000000000], ' \
                '[25.0100000000, -0.0000000000, 25.0000000000]]);\n' \
                '\t\tcylinder($fn = 8, h = 20, r = 20);\n' \
                '\t}\n' \
                '}'
     self.assertEqual(expected, actual)
コード例 #19
0
    def test_separate_part_hole(self):
        # Make two parts, a block with hole, and a cylinder that
        # fits inside it.  Make them separate parts, meaning
        # holes will be defined at the level of the part_root node,
        # not the overall node.  This allows us to preserve holes as
        # first class space, but then to actually fill them in with
        # the parts intended to fit in them.
        b = cube(10, center=True)
        c = cylinder(r=2, h=12, center=True)
        p1 = b - hole()(c)

        # Mark this cube-with-hole as a separate part from the cylinder
        p1 = part()(p1)

        # This fits in the hole.  If p1 is set as a part_root, it will all appear.
        # If not, the portion of the cylinder inside the cube will not appear,
        # since it would have been removed by the hole in p1
        p2 = cylinder(r=1.5, h=14, center=True)

        a = p1 + p2

        expected = '\n\nunion() {\n\tdifference(){\n\t\tdifference() {\n\t\t\tcube(center = true, size = 10);\n\t\t}\n\t\t/* Holes Below*/\n\t\tunion(){\n\t\t\tcylinder(center = true, h = 12, r = 2);\n\t\t} /* End Holes */ \n\t}\n\tcylinder(center = true, h = 14, r = 1.5000000000);\n}'
        actual = scad_render(a)
        self.assertEqual(expected, actual)
コード例 #20
0
 def test_infix_difference(self):
     a = cube(2)
     b = sphere(2)
     expected = '\n\ndifference() {\n\tcube(size = 2);\n\tsphere(r = 2);\n}'
     actual = scad_render(a - b)
     self.assertEqual(expected, actual)
コード例 #21
0
 def test_infix_intersection(self):
     a = cube(2)
     b = sphere(2)
     expected = '\n\nintersection() {\n\tcube(size = 2);\n\tsphere(r = 2);\n}'
     actual = scad_render(a * b)
     self.assertEqual(expected, actual)
コード例 #22
0
 def test_background(self):
     a = cube(10)
     expected = '\n\n%cube(size = 10);'
     actual = scad_render(background(a))
     self.assertEqual(expected, actual)
コード例 #23
0
 def test_explicit_hole(self):
     a = cube(10, center=True) + hole()(cylinder(2, 20, center=True))
     expected = '\n\ndifference(){\n\tunion() {\n\t\tcube(center = true, size = 10);\n\t}\n\t/* Holes Below*/\n\tunion(){\n\t\tcylinder(center = true, h = 20, r = 2);\n\t} /* End Holes */ \n}'
     actual = scad_render(a)
     self.assertEqual(expected, actual)
コード例 #24
0
 def test_root(self):
     a = cube(10)
     expected = '\n\n!cube(size = 10);'
     actual = scad_render(root(a))
     self.assertEqual(expected, actual)
コード例 #25
0
 def test_disable(self):
     a = cube(10)
     expected = '\n\n*cube(size = 10);'
     actual = scad_render(disable(a))
     self.assertEqual(expected, actual)
コード例 #26
0
 def test_debug(self):
     a = cube(10)
     expected = '\n\n#cube(size = 10);'
     actual = scad_render(debug(a))
     self.assertEqual(expected, actual)