środa, 24 lipca 2013

[Visual Studio] Unit Testy

Pisząc Unit Testy należy pamiętać o zasadzie 3xA: Arrange Act Assert. Oznacza ona, że metody testujące powinny składać się z pewnego szablonu: na początku przypisujemy zmienne i oczekiwane wyjście, później wykonujemy test, a na końcu sprawdzamy, czy wynik jest poprawny. Wygodnym Frameworkiem do pisania Unit Testów w VS jest NUnitFramework. Instalujemy go za pomocą NuGeta.


Przykładowa metoda do przetestowania:

public class StringHelper
{
    public static int CountLetters(string word, char letter)
    {
        return word.Count(c => c == letter);
    }
}

...i odpowiadający mu UnitTest.

[TestFixture]
public class StringHelperTest
{
    [Test]
    public void CountLettersTest()
    {
        //Arrange
        var @string = "copacabana";
        var letter = 'a';
        int expected = 4;

        //Act
        int current = StringHelper.CountLetters(@string, letter);

        //Assert
        Assert.AreEqual(expected, current);
    }
}

Dla klas i metod obowiązuje pewna konwencja dodawania na końcu nazw słowa Test. Unit Testy można także bez problemu Debugować. Korzystając z dodatku Resharper można uruchamiać pojedyncze testy bezpośrednio w kodzie.


Przydatnymi atrybutami są SetUp i TearDown. Będą to metody wykonywane raz odpowiednio przed i po sesji testów.


[SetUp]
public void Init()
{
    Console.WriteLine("Initializing...");
}

[TearDown]
public void Clean()
{
    Console.WriteLine("Shutting down..."); 
}

Testy można także w razie potrzeby grupować w kategorie, również przez odpowiedni atrybut.

[Test]
[Category("another tests")]
public void CountLettersTest2()
{
    //Arrange
    var @string = "copACABana";
    var letter = 'a';
    int expected = 2;

    //Act
    int current = StringHelper.CountLetters(@string, letter);

    //Assert
    Assert.AreEqual(expected, current);
}

Dzięki temu z poziomu GUI możemy odpalać na przykład tylko testy z danej kategorii.


Ostatnia ciekawa funkcjonalność to timeouty. Możemy ustawić dla danej metody maksymalny czas wykonania poprzez atrybut MaxTime, np. [MaxTime(5000)].

wtorek, 23 lipca 2013

[WPF] Grafika trójwymiarowa

Jedną z ciekawszych funkcjonalności WPF jest wsparcie dla grafiki trójwymiarowej. Funkcjonalności 3D są dostępne zarówno z poziomu XAML jak i kodu proceduralnego. Poniższy kod przedstawia prosty sposób opisu sceny 3D w sposób deklaratywny:


<Window x:Class="_3dGraphics.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Viewport3D>
            <Viewport3D.Camera>
                <OrthographicCamera Position="5,5,5" LookDirection="-1,-1,-1" Width="5"/>
            </Viewport3D.Camera>
            <Viewport3D.Children>
                <ModelVisual3D x:Name="Light">
                    <ModelVisual3D.Content>
                        <AmbientLight/>
                    </ModelVisual3D.Content>
                </ModelVisual3D>
                <ModelVisual3D>
                    <ModelVisual3D.Content>
                        <Model3DGroup x:Name="House">
                            <GeometryModel3D x:Name="Roof">
                                <GeometryModel3D.Material>
                                    <DiffuseMaterial Brush="Blue"/>
                                </GeometryModel3D.Material>
                                <GeometryModel3D.Geometry>
                                    <MeshGeometry3D Positions="-1,1,1 0,2,1 0,2,-1 -1,1,-1 0,2,1 1,1,1
      1,1,-1 0,2,-1"
      TriangleIndices="0 1 2 0 2 3 4 5 6 4 6 7"/>
                                </GeometryModel3D.Geometry>
                            </GeometryModel3D>
                            <GeometryModel3D x:Name="Sides">
                                <GeometryModel3D.Material>
                                    <DiffuseMaterial Brush="Green"/>
                                </GeometryModel3D.Material>
                                <GeometryModel3D.Geometry>
                                    <MeshGeometry3D Positions="-1,1,1 -1,1,-1 -1,-1,-1 -1,-1,1 1,1,-1
       1,1,1 1,-1,1 1,-1,-1"
       TriangleIndices="0 1 2 0 2 3 4 5 6 4 6 7"/>
                                </GeometryModel3D.Geometry>
                            </GeometryModel3D>
                            <GeometryModel3D x:Name="Ends">
                                <GeometryModel3D.Material>
                                    <DiffuseMaterial Brush="Red"/>
                                </GeometryModel3D.Material>
                                <GeometryModel3D.Geometry>
                                    <MeshGeometry3D
       Positions="-0.25,0,1 -1,1,1 -1,-1,1 -0.25,-1,1 -0.25,0,1
       -1,-1,1 0.25,0,1 1,-1,1 1,1,1 0.25,0,1 0.25,-1,1 1,-1,1
       1,1,1 0,2,1 -1,1,1 -1,1,1 -0.25,0,1 0.25,0,1 1,1,1 1,1,-1
       1,-1,-1 -1,-1,-1 -1,1,-1 1,1,-1 -1,1,-1 0,2,-1"
       TriangleIndices="0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 15
       17 18 19 20 21 19 21 22 23 24 25"/>
                                </GeometryModel3D.Geometry>
                            </GeometryModel3D>
                        </Model3DGroup>
                    </ModelVisual3D.Content>
                </ModelVisual3D>
            </Viewport3D.Children>
        </Viewport3D>
    </Grid>
</Window>

Zastosowane powyżej elementy XAML służą do:

  • GeometryModel3D - reprezentuje kształt i materiał obiektu 3D, kształt w powyższym przypadku jest siatką, w której podaje się współrzędne trójkątów oraz ich indeksy.
  • ModelVisual3D - odpowiada za renderowanie contentu 3D
Położenie kamery oraz obiektów na scenie podaje się, pamiętając o układzie osi w kartezjańskim układzie współrzędnych.
Układ współrzędnych jest prawoskrętny, a pozycję podaje się ustawiając wartości na atrybucie Position.
Pozostałe ważne atrybuty to LookDirection (kierunek, w którym ma być obrócona kamera), oraz UpDirection decydujący o obrocie kamery wokół osi z. 

Rodzaje kamer:
  • PerspectiveCamera - odpowiada postrzeganiu przestrzeni przez ludzkie oko, obiekty znajdujące się daleko od kamery wydają się mniejsze, niż te bliższe
  • OrthographicCamera - obiekty pozostają tej samej wielkości niezależnie od odległości
Szerokość pola widzenia kontrolowana jest przez atrybut Width (dla drugiej kamery) i przez atrybut FieldOfView (dla pierwszej kamery).

Transformacje 3D:

Poniżej przedstawiono, jak wykonać trzy podstawowe transformacje z poziomu kodu proceduralnego


private void KeyDownHandler(object sender, KeyEventArgs e)
{
    Transform3DGroup group = new Transform3DGroup();
    switch (e.Key)
    {
        case Key.T:
            TranslateTransform3D transform3D = new TranslateTransform3D(0.1,0,0);
            group.Children.Add(transform3D);
            House.Transform = group;
            break;
        case Key.R:
            //45 - stopnie, nie radiany, new Vector3D(0, 1, 0) - wokol jakiej osi obrot
            RotateTransform3D rotate3d = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 1, 0), 45));
            group.Children.Add(rotate3d);
            House.Transform = group;
            break;
        case Key.S:
            ScaleTransform3D scale3d = new ScaleTransform3D(0.5,0.5,1);
            group.Children.Add(scale3d);
            House.Transform = group;
            break;
        default:
            break;
    }
}

Rodzaje świateł:

  • DirectionalLight - wysyła równoległe wiązki światła ze źródła umieszczonego nieskończenie daleko, odpowiada słońcu
  • PointLight - odpowiada żarówce, wysyła światło we wszystkich kierunkach, intensywność spada wraz ze wzrostem odległości od źródła
  • FlashLight - odpowiada działaniu latarki, emituje stożek światła, którego intensywność spada wraz ze wzrostem odległości od źródła
  • AmbientLight - światło rozproszone